Polling de Resultados SIFEN
Estrategia de polling para monitorear el estado de procesamiento de documentos en SIFEN, con intervalos, backoff y manejo de timeouts.
La emisión de documentos a SIFEN es asíncrona. Cuando hacés POST /documento-electronico, Sifende valida y firma el XML, lo envía a SIFEN en un lote, y la respuesta inicial es PENDIENTE. SIFEN procesa los lotes en su backend y devuelve el resultado segundos o minutos después.
Por qué es asíncrono
SIFEN procesa documentos en lotes. Esto permite:
- Throughput alto (miles de DE/minuto)
- Validaciones complejas de cada documento
- Resiliencia ante picos de carga
El precio: el resultado no es inmediato. Tu integración debe consultar el estado periódicamente hasta obtener un veredicto final (APROBADO o RECHAZADO).
Estados del DE
PENDIENTE → EN_LOTE → ENVIADO → APROBADO ← estado terminal exitoso
→ RECHAZADO ← estado terminal con error SIFEN
→ ERROR ← falla técnica de envío/procesamiento
APROBADO → CANCELADO ← después de un evento de cancelación
DESCONOCIDO ← caso raro de inconsistenciaLos estados del DE (enum exacto del backend): PENDIENTE, EN_LOTE, ENVIADO, APROBADO, RECHAZADO, ERROR, CANCELADO, DESCONOCIDO.
Estados del lote (interno)
Sifende agrupa los DE en lotes para enviarlos a SIFEN. El lote pasa por:
PREPARADO → INTENTANDO → ENVIADO → PROCESADO ← éxito
→ ERROR ← falló al procesar
→ FALLIDO ← falló al enviarEstados del lote (enum exacto): PREPARADO, INTENTANDO, ENVIADO, PROCESADO, ERROR, FALLIDO.
No necesitás pollear el lote: hacelo directamente sobre el DE por su CDC.
Flujo completo
Emitís el DE. La respuesta incluye id y cdc, con estado PENDIENTE.
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"cdc": "01800123451001001000000122026042710000000006",
"estado": "PENDIENTE"
}Persistí el id y el cdc en tu base de datos antes de empezar a hacer polling. Si tu proceso crashea, vas a poder retomar el polling desde donde quedó.
Consultás el estado con GET /api/v1/documento-electronico/status/:cdc repetidamente, con backoff.
Respuesta:
{
"cdc": "01800123451001001000000122026042710000000006",
"estado": "APROBADO",
"iTiDe": 1,
"numeroDocumento": 1,
"fechaCreacion": "2026-04-27T10:30:00",
"protocoloAutorizacion": "01202604150000123456",
"mensajeRechazo": null
}numeroDocumento es Long (entero). El formato "NNN-NNN-NNNNNNN" se devuelve como numeroFormateado en la respuesta de emisión: son campos separados, no los confundas.
Cortás el polling cuando el estado sea APROBADO, RECHAZADO, ERROR o CANCELADO (estados terminales).
Estrategia de backoff exponencial
| Intento | Espera antes de consultar |
|---|---|
| 1 | 2s |
| 2 | 3s |
| 3 | 5s |
| 4 | 8s |
| 5 | 13s |
| 6+ | 30s (tope) |
Timeout total recomendado: 10 minutos. Si después de 10 minutos seguís en PENDIENTE, contactá a soporte: algo está atascado en SIFEN.
Implementación TypeScript
type EstadoDE =
| "PENDIENTE"
| "EN_LOTE"
| "ENVIADO"
| "APROBADO"
| "RECHAZADO"
| "ERROR"
| "CANCELADO"
| "DESCONOCIDO";
interface EstadoResponse {
cdc: string;
estado: EstadoDE;
iTiDe: number;
numeroDocumento: number; // Long en backend
fechaCreacion: string;
protocoloAutorizacion: string | null;
mensajeRechazo: string | null;
}
async function consultarEstado(cdc: string): Promise<EstadoResponse> {
const response = await fetch(
`https://api.sifende.com.py/api/v1/documento-electronico/status/${cdc}`,
{
headers: { "Authorization": `Bearer ${process.env.SIFENDE_API_KEY}` },
}
);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
async function esperarResultado(
cdc: string,
timeoutMs = 10 * 60 * 1000
): Promise<EstadoResponse> {
const inicio = Date.now();
let intento = 0;
while (Date.now() - inicio < timeoutMs) {
const espera = Math.min(2000 * Math.pow(1.6, intento), 30000);
await new Promise((r) => setTimeout(r, espera));
const resultado = await consultarEstado(cdc);
if (resultado.estado === "APROBADO") return resultado;
if (resultado.estado === "RECHAZADO") {
throw new Error(`SIFEN rechazó el DE: ${resultado.mensajeRechazo}`);
}
if (resultado.estado === "ERROR") {
throw new Error(`Falla técnica al procesar el DE: ${resultado.mensajeRechazo ?? 'sin detalle'}`);
}
if (resultado.estado === "CANCELADO") return resultado;
intento++;
}
throw new Error(`Timeout esperando resultado para CDC ${cdc}`);
}
// Uso
try {
const resultado = await esperarResultado(cdc);
console.log("DE aprobado:", resultado.cdc);
} catch (err) {
console.error("Error:", err.message);
}Persistencia para resiliencia
Si tu proceso puede crashear durante el polling, guardá el estado del DE en tu DB y reintentá desde ahí:
// Pseudocódigo
async function emitirYPersistir(payload: any) {
const { id, cdc } = await emitirDE(payload);
await db.documentos.insert({ id, cdc, estado: "PENDIENTE" });
// Polling en background: incluso si el proceso muere, el DE queda persistido
pollearYActualizar(cdc).catch(console.error);
return { id, cdc };
}
// Worker independiente para retomar PENDIENTES después de un crash
async function reanudarPendientes() {
const pendientes = await db.documentos.where({ estado: "PENDIENTE" });
for (const doc of pendientes) {
pollearYActualizar(doc.cdc).catch(console.error);
}
}Webhooks: en el roadmap. Estamos trabajando en notificaciones por webhook para eliminar la necesidad de polling. Mientras tanto, el polling con backoff es la estrategia recomendada.
Tips de producción
- No uses intervalos fijos cortos (cada 1s); es ineficiente y genera carga innecesaria
- Hacé el polling en background, no bloquees la respuesta del usuario
- Loggeá el tiempo total hasta
APROBADO. Sirve para detectar degradaciones de SIFEN - Manejá
429 Too Many Requestscon backoff adicional si lo recibís - Guardá
mensajeRechazocompleto en tu DB cuando recibasRECHAZADO
Después de 48 horas, siResultLoteDE deja de devolver el lote. SIFEN responde con código 0364 (no encontrado) para consultas a nivel de lote pasadas las 48 h desde el envío. Si tu DE quedó sin resolver en ese plazo, fallback a la consulta por CDC individual (/documento-electronico/status/:cdc), que sigue funcionando indefinidamente. Sifende ya implementa este fallback en su scheduler interno.
Próximos pasos
- Reintentar Rechazados: qué hacer si recibís
RECHAZADO - Manejar Errores: patrón general de error handling
- Referencia: Consultar Estado