SIFENDE
Guías

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 inconsistencia

Los 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 enviar

Estados 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

IntentoEspera antes de consultar
12s
23s
35s
48s
513s
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 Requests con backoff adicional si lo recibís
  • Guardá mensajeRechazo completo en tu DB cuando recibas RECHAZADO

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

On this page