MS
Marcio Sequeiros
[ FACTURACIÓN ELECTRÓNICA CRÍTICA & NORMATIVAS ] 7 MIN DE LECTURA · NOVIEMBRE 2026

Resiliencia ante caídas del SEE de SUNAT: Arquitectura de contingencia, Outbox Pattern y nuevas normativas de envío

Cómo diseñar plataformas de facturación masiva capaces de absorber caídas de endpoints SOAP de SUNAT/OSE, responder en menos de 120 ms al punto de venta y cumplir con la reducción del plazo de envío a 3 días o 24 horas sin perder comprobantes.

1. La realidad operativa: Caídas recurrentes del servicio SOAP y OSEs

En el ecosistema tributario peruano, el servicio web de recepción de comprobantes electrónicos de SUNAT (billService) y los Operadores de Servicios Electrónicos (OSE) sufren degradaciones frecuentes, especialmente entre los días 10 y 20 de cada mes durante las fechas límite de declaración.

HTTP 502 / 503 / 504

Saturación de gateways de balanceo en la sede electrónica de SUNAT.

Socket Read Timeout

La petición SOAP fue enviada pero la conexión cae antes de recibir la Constancia de Recepción (CDR).

Caídas de Clave SOL

Fallas de autenticación LDAP/OAuth central de SUNAT que rechazan credenciales válidas.

[ NORMATIVA TRIBUTARIA VIGENTE ]

2. El impacto crítico de la reducción de plazos de envío (R.S. 000150-2021 y R.S. 000003-2023)

Históricamente, los emisores contaban con hasta 7 días calendario para remitir las facturas electrónicas a SUNAT. Con las resoluciones de superintendencia emitidas por SUNAT:

  • Plazo límite de remisión: Se redujo drásticamente a hasta el día calendario siguiente al de emisión (o hasta un máximo de 3 días calendario en periodos de adecuación según perfil de contribuyente).
  • Rechazo automático e irreparable: Todo comprobante remitido con posterioridad al plazo legal es catalogado automáticamente como RECHAZADO (código de error 2324 o rechazo definitivo).
  • Consecuencia fiscal severa: Una factura rechazada por envío extemporáneo no tiene ninguna validez tributaria: el receptor no puede deducir crédito fiscal ni sustentar costo/gasto, y el emisor no puede reutilizar la serie y correlativo.

REGLA DE ORO DE ARQUITECTURA: El sistema no puede pausar los envíos a la espera de intervención manual. El pipeline de reintentos debe ser autónomo, idempotente y garantizar el envío dentro de la ventana reglamentaria.

3. Patrón Transaccional Outbox: Desacoplar el Punto de Venta

El mayor error en sistemas de facturación principiantes es acoplar la llamada SOAP a SUNAT dentro del ciclo sincrónico de venta del cliente en caja. Si SUNAT demora 15 segundos o se cae, la cola de personas en caja se detiene.

Nuestra arquitectura utiliza el Patrón Outbox:

  1. El POS/ERP solicita la factura. En menos de 90 ms, el microservicio calcula impuestos, genera el XML UBL 2.1, lo firma con el certificado digital X.509 (XAdES-BES) y guarda el registro con estado PENDIENTE_DESPACHO en la tabla Outbox.
  2. El cliente recibe su ticket impreso o PDF con su DigestValue (código hash) y código QR normativo, lo cual cumple la exigencia legal de entrega inmediata.
  3. Un pool de workers asíncronos procesa la tabla Outbox mediante un Circuit Breaker:
// Worker de despacho con Circuit Breaker y Reintento con Jitter
export class SunatDispatchWorker {
  private circuitState: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
  private consecutiveFailures = 0;
  private readonly FAILURE_THRESHOLD = 5;
  private readonly COOLDOWN_MS = 180_000; // 3 minutos de enfriamiento
  private lastStateChange = Date.now();

  async dispatchInvoice(invoice: OutboxInvoice): Promise<DispatchResult> {
    if (this.circuitState === 'OPEN') {
      if (Date.now() - this.lastStateChange > this.COOLDOWN_MS) {
        this.circuitState = 'HALF_OPEN';
      } else {
        // Circuito abierto: encolar y respetar el backoff sin saturar SUNAT
        return { status: 'DEFERRED_CIRCUIT_OPEN', retryAfterMs: this.calculateJitter(invoice.attempts) };
      }
    }

    try {
      const cdr = await this.soapClient.sendBill({
        fileName: invoice.fileName,
        contentZipBase64: invoice.zipBase64
      });

      this.consecutiveFailures = 0;
      this.circuitState = 'CLOSED';
      return { status: 'ACCEPTED', cdrZip: cdr };
    } catch (error: any) {
      if (this.isNetworkOrGatewayTimeout(error)) {
        this.consecutiveFailures++;
        if (this.consecutiveFailures >= this.FAILURE_THRESHOLD) {
          this.circuitState = 'OPEN';
          this.lastStateChange = Date.now();
        }
        // Ante timeout ambiguo: registrar estado IN_DOUBT para consulta de validez previa
        return { status: 'IN_DOUBT_VERIFY_FIRST', nextCheckMs: this.calculateJitter(invoice.attempts) };
      }
      throw error;
    }
  }

  // Backoff exponencial con Jitter: evita el 'Thundering Herd' al revivir SUNAT
  private calculateJitter(attempt: number): number {
    const baseMs = 5000; // 5 segundos base
    const maxMs = 300_000; // 5 minutos máximo
    const exponential = Math.min(maxMs, baseMs * Math.pow(2, attempt));
    const jitter = Math.floor(Math.random() * 2000); // 0 a 2000 ms aleatorio
    return exponential + jitter;
  }
}

4. Reintentos con Backoff Exponencial y Jitter (Evitar el Thundering Herd)

Cuando la plataforma de SUNAT se restablece tras una caída de 40 minutos, millones de servidores de empresas peruanas intentan reenviar simultáneamente sus comprobantes acumulados. Esto genera una nueva caída inmediata por saturación ("efecto avalancha" o Thundering Herd).

Para mitigar esto, cada reintento aplica un retardo exponencial con dispersión aleatoria:

t_espera = min(t_maximo, t_base * 2^intento) + aleatorio(0, 2000 ms)

Esta fluctuación dispersa la carga en el tiempo de forma homogénea, garantizando que el 100% de los comprobantes pendientes ingresen a la sede electrónica sin recibir códigos 503 por saturación.

5. El problema del "Comprobante en Limbo" (Socket Timeout)

¿Qué ocurre si el microservicio envía el ZIP con el comprobante a SUNAT, la llamada tarda 60 segundos y el socket se cierra por timeout?

PELIGRO: REENVÍO CIEGO

Si reenvías inmediatamente sendBill, y SUNAT ya había procesado el XML en su base de datos, responderá con error de duplicidad o rechazo inconsistente.

La solución arquitectónica es la desambiguación previa mediante la API de Consulta Integrada o getStatusCdr:

// Resolución idempotente de comprobantes en estado 'en duda' (Post-Timeout)
export async function resolveInDoubtInvoice(invoice: OutboxInvoice): Promise<InvoiceStatus> {
  // 1. Consultar la API REST de Validez Integrada de SUNAT o getStatusCdr
  const validationResult = await sunatQueryApi.consultarComprobante({
    numRuc: invoice.emisorRuc,
    codComp: invoice.tipoComprobante, // '01' Factura, '03' Boleta
    numeroSerie: invoice.serie,
    numero: invoice.correlativo,
    fechaEmision: invoice.fechaEmision,
    monto: invoice.totalVenta
  });

  if (validationResult.estadoComprobante === '1') {
    // El comprobante ya fue procesado y ACEPTADO por SUNAT durante el timeout
    const cdr = await sunatSoapClient.getStatusCdr({
      rucComprobante: invoice.emisorRuc,
      tipoComprobante: invoice.tipoComprobante,
      serieComprobante: invoice.serie,
      numeroComprobante: invoice.correlativo
    });
    await invoiceRepository.markAccepted(invoice.id, cdr);
    return 'ACCEPTED';
  }

  if (validationResult.estadoComprobante === '0') {
    // No existe en los registros de SUNAT: reenvío seguro con idéntico XML y firma
    return 'READY_FOR_RETRY';
  }

  return 'RETRY_LATER';
}

6. Pre-Auditoría en Memoria: Cero comprobantes inválidos en cola

Bajo el nuevo plazo legal estricto de envío, no hay margen para errores de cálculo o de catálogos. Si un comprobante mal formado se queda en la cola de contingencia durante una caída de SUNAT, al momento de restablecerse el servicio el comprobante será rechazado irrevocablemente fuera de plazo.

›
Validación Schematron y XSD local: Se compilan los esquemas XML oficiales de UBL 2.1 en memoria para validar la sintaxis antes del despacho.
›
Consistencia de Catálogo 07 (Afectación IGV): Se verifica que códigos gravados (10), exonerados (20), inafectos (30) o exportación (40) coincidan matemáticamente con la suma de bases imponibles y el impuesto general a las ventas.
›
Redondeo y bolsas plásticas (ICBPER): Precisión bancaria a dos decimales y suma exacta de impuestos a las bolsas plásticas por ítem conforme a la ley vigente.

7. Modo de Contingencia Normada por SUNAT (Caídas Prolongadas)

Cuando la contingencia no es una degradación de red momentánea sino una falla generalizada del sistema (corte de energía prolongado o indisponibilidad total declarada por SUNAT):

  • Emisión física de contingencia: Se emiten comprobantes impresos por imprenta autorizada que llevan impresa de fábrica la frase "Comprobante de Pago emitido en contingencia".
  • Regularización mediante Resumen de Contingencia: Una vez restablecido el sistema, se remite a SUNAT el Resumen de Comprobantes Impresos en Contingencia mediante el método SOAP sendSummary, obteniendo un ticket asíncrono para verificar su aceptación.

[ CONCLUSIONES DE PRODUCCIÓN PARA MÁS DE 30 EMPRESAS ]

La implementación conjunta de este patrón Outbox con Circuit Breaker, pre-auditoría en memoria y desambiguación idempotente permitió:

  • ✔ 0% de facturas bloqueadas o perdidas por caídas de SUNAT.
  • ✔ 100% de cumplimiento del plazo legal de remisión de 24 a 72 horas.
  • ✔ Tiempo de respuesta constante en punto de venta < 100 ms independientemente del estado de SUNAT.