Cómo leer la respuesta
Qué significa cada combinación de campos y qué tienes que hacer en cada caso.
Toda respuesta contesta tres preguntas distintas, y confundirlas es el error de integración más caro que existe: lleva a emitir dos veces un comprobante que ya estaba declarado.
| Campo | Pregunta que contesta |
|---|---|
success |
¿La API pudo trabajar? |
connection |
¿Se pudo hablar con SUNAT? |
sunat_success |
¿Qué dijo SUNAT? true · false · null |
Son independientes. success: true no significa aceptado: significa que no
hubo un fallo nuestro. El veredicto está en sunat_success, nunca en el código
HTTP ni en success.
sunat_success es el campo corto: true aceptado (con o sin observaciones),
false rechazado, null todavía sin pronunciarse. Ese null viaja siempre,
también cuando no hay veredicto: un campo ausente te obligaría a distinguir «no
llegó el dato» de «SUNAT no ha contestado», y son cosas distintas.
Si necesitas el detalle —en qué etapa está, no solo si terminó— está en
resolved y status_code (en la superficie de migración se llaman resuelto y state_type_id; son los mismos valores):
La tabla
Busca tu fila y haz lo que dice la última columna.
sunat_success |
resolved |
status_code |
Qué pasó | Qué haces |
|---|---|---|---|---|
null |
false |
01 |
Firmado. El envío está en cola | Nada. Consulta más tarde |
null |
false |
02 |
Firmado, esperando a que tú lo mandes | Mandarlo: compat o v1 |
null |
false |
03 |
Enviado. SUNAT todavía no contesta | Nada. No reenvíes |
null |
false |
04 |
Boleta esperando el resumen del día | Nada |
true |
true |
05 |
Aceptado | Nada. Guarda el CDR |
true |
true |
07 |
Aceptado con observaciones | Nada. Es válido y está declarado |
false |
true |
09 |
Rechazado: no existe para SUNAT | Corregir y emitir de nuevo |
Y si success es false, nada de lo anterior aplica: la API no pudo trabajar y
el motivo está en message y errors.
Los tres errores que cuestan dinero
«No está aceptado, lo emito otra vez». Mientras resolved sea false no hay
veredicto: el comprobante puede estar aceptándose en este momento. Emitirlo de
nuevo consume otro correlativo y te deja dos comprobantes.
if (!aceptado) reemitir() // MAL: también entra lo que sigue en curso
if (r.status === 'rejected') corregir() // BIEN
if (!r.resolved) esperar() // BIEN
«Observado es un fallo». 07 está aceptado. SUNAT lo declaró y anotó una
observación. Tratarlo como rechazo re-emite algo que ya existe.
«El HTTP dice 200, entonces salió bien». El HTTP dice si nuestra API pudo
trabajar. Un comprobante rechazado por SUNAT llega con 200 y status: "rejected".
Cuando no se sabe si llegó
sunat_success te dice el veredicto, pero no si el comprobante llegó. Para eso
está la señal de llegada, que viaja en las dos superficies — llego_a_sunat
suelto en /api/cpe/*, y reached_sunat dentro de result
en /v1—:
| Valor | Significa | Qué haces |
|---|---|---|
false |
No llegó | Se puede reintentar sin riesgo |
true |
Llegó y hay veredicto | Leer code y message |
null |
No se sabe | Consultar. Nunca reenviar a ciegas |
El null es el caso importante: pasa cuando SUNAT no responde a tiempo. El
comprobante ya salió, así que reenviarlo puede duplicarlo. Nosotros lo
reconciliamos solos consultando a SUNAT; tú solo tienes que no reenviar.
El campo estado, y por qué no deberías usarlo
En /api/cpe/* verás también un campo estado. No lo uses para decidir nada:
existe por compatibilidad y significa cosas distintas según el caso — el código de
etapa ("01", "03") mientras el comprobante está en curso, y el número 200
cuando ya se resolvió, que es un código HTTP repetido dentro del cuerpo y no un
estado.
Está ahí para no romper integraciones que ya lo leían. Para saber qué pasó, usa
resolved y status_code, que significan siempre lo mismo — y son los que
tiene también la API v1.