Fac-360

Ciclo de vida

Los catorce estados de un comprobante, qué los mueve y cuáles no tienen salida.

Un comprobante no se «envía»: se crea, se hace durable, y a partir de ahí una máquina de estados lo lleva hasta SUNAT. Esta página es esa máquina, entera.

La lista de estados y la tabla de transiciones que siguen son las del código (packages/domain/src/document-state.ts). No hay una segunda versión en otro sitio.

Los estados

EstadoQué significa
RECEIVEDAceptado y durable. Ya tiene correlativo y ya consumió cuota
VALIDATEDPasó las validaciones de negocio
XML_BUILTEl UBL está construido
SIGNEDFirmado con el certificado de la empresa
SUBMISSION_PENDINGA punto de salir hacia SUNAT
SUBMITTEDSUNAT recibió el envío
TICKET_PENDINGSUNAT dio un ticket y se está consultando el resultado
ACCEPTEDCDR de aceptación
OBSERVEDCDR con observaciones: aceptado, con reparos
REJECTEDCDR de rechazo
FAILED_RETRYABLEFalló algo transitorio; se reintentará
FAILED_FINALFalló algo que no mejora reintentando
AMBIGUOUSNo se puede establecer si el envío ocurrió. Ver abajo
CANCELLEDCerrado sin llegar a enviarse

Los seis últimos de la tabla anterior que no tienen movimientos salientes —ACCEPTED, OBSERVED, REJECTED, FAILED_FINAL, AMBIGUOUS y CANCELLED— son terminales. Nada los reabre.

Las transiciones

RECEIVED           → VALIDATED · FAILED_FINAL · CANCELLED
VALIDATED          → XML_BUILT · FAILED_RETRYABLE · FAILED_FINAL · CANCELLED
XML_BUILT          → SIGNED · FAILED_RETRYABLE · FAILED_FINAL
SIGNED             → SUBMISSION_PENDING · FAILED_RETRYABLE · FAILED_FINAL
SUBMISSION_PENDING → SUBMITTED · AMBIGUOUS · FAILED_RETRYABLE · FAILED_FINAL
SUBMITTED          → TICKET_PENDING · ACCEPTED · OBSERVED · REJECTED · AMBIGUOUS
TICKET_PENDING     → ACCEPTED · OBSERVED · REJECTED · FAILED_RETRYABLE · AMBIGUOUS
FAILED_RETRYABLE   → SUBMISSION_PENDING · TICKET_PENDING · FAILED_FINAL

Dos ausencias son deliberadas y conviene leerlas:

TICKET_PENDING → FAILED_FINAL no existe. En cuanto hay ticket, SUNAT ya aceptó el resumen para procesarlo. Ningún fallo local puede concluir después que el resumen no ocurrió. La única salida que no sea un CDR es AMBIGUOUS.

TICKET_PENDING → TICKET_PENDING tampoco. Consultar un ticket que sigue en proceso no es un cambio de estado. Si consultas y ves el mismo estado, no ha pasado nada: es lo correcto.

El camino síncrono y el asíncrono

La factura, la boleta y las notas usan sendBill: el envío es síncrono y el CDR llega en la misma respuesta, así que van de SUBMITTED directamente a ACCEPTED, OBSERVED o REJECTED.

El resumen diario y la comunicación de baja usan sendSummary: SUNAT contesta con un ticket, y el resultado se consulta después. Ése es el ramal SUBMITTED → TICKET_PENDING → ….

Cada consulta de ticket se clasifica así:

Respuesta de SUNATQué se hace
ProcesadoSe lee el CDR, y el CDR decide el resultado fiscal
En procesoSe espera y se vuelve a consultar, sin tocar el estado
Procesado con erroresTambién se lee el CDR: el rechazo se lee, no se deduce
Cualquier otra cosaAMBIGUOUS

Que «procesado con errores» termine leyendo el CDR y no marcando un rechazo directo no es un rodeo: el código de transporte dice que el proceso terminó, no qué decidió SUNAT. Eso está en el CDR.

AMBIGUOUS, que es el estado importante

AMBIGUOUS no significa «falló». Significa no sabemos si ocurrió. Se llega ahí cuando SUNAT no contestó, o contestó algo que no permite establecer el resultado.

Un comprobante en AMBIGUOUS nunca se reenvía automáticamente, y tú tampoco deberías reenviarlo. Si el envío sí llegó, el reenvío produce un duplicado ante SUNAT que solo una comunicación de baja aceptada retira.

Es una decisión explícita de la plataforma: ante la duda, se cerca el documento y se deja para reconciliación en vez de adivinar. Por eso AMBIGUOUS es terminal, y por eso tiene su propio evento de webhook en lugar de mezclarse con document.failed — un receptor que no distinga «esto no pasó» de «no sabemos si pasó» reintentará el segundo.

Cómo lo sigues

Dos formas, y no son excluyentes:

Consultando. GET /api/v2/documents/{id} te da el estado actual, y GET /api/v2/operations/{id} el de la operación que lo mueve. Las dos piden documents:read y operations:read.

Escuchando. Los webhooks te avisan de cada transición que puedes accionar, sin que tengas que preguntar. Es lo que querrás en cuanto tengas volumen: ver Webhooks.

Los estados intermedios —XML_BUILT, SIGNED, SUBMISSION_PENDING— existen para que un fallo sepa dónde reanudar, no para que los observes. Un integrador normal solo actúa sobre ACCEPTED, OBSERVED, REJECTED y AMBIGUOUS.

On this page