El veredicto de SUNAT
Un comprobante nace pending: ya tiene número, está firmado y existe para Emitay. SUNAT
responde después: puede tardar segundos o minutos. La API no te hace esperar esa respuesta
salvo que se lo pidas.
Los estados
Sección titulada «Los estados»status |
Qué significa | ¿Vale el comprobante? |
|---|---|---|
pending |
SUNAT aún no responde. Emitay lo sigue enviando | Todavía no se sabe |
accepted |
SUNAT lo aceptó | Sí |
observed |
SUNAT lo aceptó con observaciones, que vienen en sunat.notes |
Sí. Conviene corregir la causa en los siguientes |
rejected |
SUNAT lo rechazó: no es un comprobante válido | No. Su número queda usado |
voided |
Fue aceptado y luego anulado | No |
sunat.code y sunat.description son el código y las palabras de SUNAT, tal cual.
Tres formas de enterarse
Sección titulada «Tres formas de enterarse»1. En la misma llamada
Sección titulada «1. En la misma llamada»Prefer: wait=10 retiene la respuesta hasta 10 segundos (el máximo es 30) mientras Emitay
espera a SUNAT:
curl https://api.emitay.com/v1/invoices \ -H "Authorization: Bearer $EMITAY_API_KEY" \ -H "Content-Type: application/json" \ -H "Prefer: wait=10" \ -d '{ "series": "F001", "customer": { "document_number": "20100066603", "name": "CLIENTE S.A." }, "items": [{ "description": "Servicio de consultoría", "quantity": 2, "unit_price": "50.00" }] }'Es una espera acotada, no otro camino: si SUNAT no contesta a tiempo, la respuesta llega
igual, con status: "pending", y el comprobante sigue su curso.
2. Consultando hasta que haya veredicto
Sección titulada «2. Consultando hasta que haya veredicto»GET /v1/documents/{id} devuelve el comprobante como está ahora. El SDK lo hace por ti, sin
escribir un bucle:
import { EmitayTimeoutError, outcomeOf } from "emitay";
const emitida = await emitay.invoices.create(body, { wait: 10 });
try { const factura = await emitay.documents.waitFor(emitida, { timeoutMs: 60_000 }); const sunat = outcomeOf(factura); if (sunat.valid) { console.log(`Aceptada: ${factura.links.pdf}`); // sunat.notes trae las observaciones } else { console.error(`Rechazada (${sunat.code}): ${sunat.description}`); }} catch (error) { if (!(error instanceof EmitayTimeoutError)) throw error; // Nada falló: SUNAT tarda. El comprobante sigue en camino, en error.document.}waitFor pregunta cada segundo y va espaciando hasta 5. Si agota su plazo lanza
EmitayTimeoutError. No es un fallo y no hay que emitir de nuevo: emitir otra vez
crearía otro comprobante.
3. Por webhook
Sección titulada «3. Por webhook»Es lo que usa un backend: Emitay llama a tu servidor cuando hay veredicto, aunque tarde minutos y aunque tu proceso se haya reiniciado entretanto.
| Evento | Cuándo |
|---|---|
document.accepted |
SUNAT aceptó |
document.observed |
SUNAT aceptó con observaciones |
document.rejected |
SUNAT rechazó, o venció el plazo sin respuesta |
Cómo recibirlos y verificarlos está en Webhooks.
Mientras está pendiente
Sección titulada «Mientras está pendiente»- Emitay reintenta solo. Si SUNAT no responde o falla, se reenvía el mismo archivo con una espera que crece de 15 segundos a una hora. No tienes que hacer nada.
last_errordice por qué falló el último intento. Es informativo: mientras el estado seapending, el comprobante sigue en camino.- Cada tipo tiene una ventana de envío que fija SUNAT: 3 días desde la emisión para una
factura y sus notas, 5 para una boleta y las suyas, 1 para una guía. Si la ventana se
cierra sin que SUNAT haya dado un veredicto, el comprobante termina
rejected, consunat: nully el motivo enlast_error.
Rechazado: qué hacer
Sección titulada «Rechazado: qué hacer»Un rechazo es definitivo para ese número: Emitay nunca regenera el XML de un número ya
enviado. Lee sunat.code y sunat.description, corrige la causa y emite un comprobante
nuevo, que tomará el siguiente número.
Lo que se puede saber antes, Emitay lo niega antes: la mayoría de los rechazos posibles se
responden como 422 en la petición, sin consumir número.
Encontrar un comprobante
Sección titulada «Encontrar un comprobante»const porId = await emitay.documents.get("doc_…");const porNumero = await emitay.documents.list({ series: "F001", number: 123 });
for await (const pagina of emitay.documents.pages({ status: "rejected" })) { for (const documento of pagina.data) console.log(documento.series, documento.number);}El listado filtra por status, type, series, number, issue_date_from e
issue_date_to. En la terminal: npx emitay document F001-123.