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
| Estado | Qué significa |
|---|---|
RECEIVED | Aceptado y durable. Ya tiene correlativo y ya consumió cuota |
VALIDATED | Pasó las validaciones de negocio |
XML_BUILT | El UBL está construido |
SIGNED | Firmado con el certificado de la empresa |
SUBMISSION_PENDING | A punto de salir hacia SUNAT |
SUBMITTED | SUNAT recibió el envío |
TICKET_PENDING | SUNAT dio un ticket y se está consultando el resultado |
ACCEPTED | CDR de aceptación |
OBSERVED | CDR con observaciones: aceptado, con reparos |
REJECTED | CDR de rechazo |
FAILED_RETRYABLE | Falló algo transitorio; se reintentará |
FAILED_FINAL | Falló algo que no mejora reintentando |
AMBIGUOUS | No se puede establecer si el envío ocurrió. Ver abajo |
CANCELLED | Cerrado 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_FINALDos 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 SUNAT | Qué se hace |
|---|---|
| Procesado | Se lee el CDR, y el CDR decide el resultado fiscal |
| En proceso | Se espera y se vuelve a consultar, sin tocar el estado |
| Procesado con errores | También se lee el CDR: el rechazo se lee, no se deduce |
| Cualquier otra cosa | AMBIGUOUS |
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.