No emitas dentro de la venta
Si la venta espera la respuesta del SRI, cada lentitud se vuelve una venta lenta. En vez de eso, guarda el comprobante pendiente en la misma transacción que la venta y deja que un proceso aparte lo envíe. Este patrón se llama outbox.
CREATE TABLE comprobantes_pendientes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
documento_id TEXT NOT NULL UNIQUE, -- una venta = un comprobante
secuencial TEXT NOT NULL, -- asignado UNA vez, se reutiliza en cada reintento
clave_acceso TEXT, -- se guarda en cuanto se conoce
payload TEXT NOT NULL,
estado TEXT NOT NULL DEFAULT 'PENDIENTE',
intentos INTEGER NOT NULL DEFAULT 0,
proximo_intento TEXT
);La venta termina rápido. El comprobante se emite en segundo plano.
Asigna el secuencial una sola vez
Este es el error que más duplicados provoca. Si cada reintento genera un nuevo secuencial, un envío que sí llegó al SRI se vuelve dos comprobantes. Asigna el secuencial y la clave de acceso una vez, guárdalos y reutilízalos en cada reintento.
SRI 43 · Clave de acceso registrada SRI 45 · Secuencial registrado
Consulta antes de reenviar
Si perdiste la respuesta (un timeout, una caída de red), no sabes si el SRI recibió el comprobante. Antes de reenviar, consulta su estado por clave de acceso. Si existe, no lo envíes de nuevo.
Reintenta solo lo que tiene sentido
| SITUACIÓN | ¿REINTENTAR? | QUÉ HACER |
|---|---|---|
| Fallo de red o timeout | Sí | Consulta el estado y reintenta con espera creciente |
| Error del servidor (5xx) o límite de solicitudes (429) | Sí | Espera creciente con variación aleatoria |
| EN_PROCESAMIENTO | No reenvíes | Consulta el estado hasta que cambie |
| API key inválida (401) | No | Alerta: es una falla de configuración |
| Rechazo por datos o esquema (422, DEVUELTA) | No | Pasa a revisión: el mismo envío fallará igual |
| Clave de acceso ya registrada | No | Consulta el estado: el comprobante ya existe |
Un reintento a ciegas de un error de datos solo llena tu cola de basura.
Espera creciente con variación
Si mil comprobantes fallan a la vez y todos reintentan a los 5 segundos, vuelves a saturar el servicio. Usa espera exponencial con variación aleatoria (full jitter):
const dormir = (ms) => new Promise((r) => setTimeout(r, ms));
// Reintenta solo lo que tiene sentido reintentar
export const esReintentable = (err) =>
err.codigo === "RED" || err.status === 429 || err.status >= 500;
export async function conReintentos(
operacion,
{ max = 5, baseMs = 500, maxMs = 20000, reintentable = esReintentable } = {}
) {
let ultimoError;
for (let intento = 1; intento <= max; intento++) {
try {
return await operacion(intento);
} catch (err) {
ultimoError = err;
if (!reintentable(err) || intento === max) break;
const tope = Math.min(maxMs, baseMs * 2 ** (intento - 1));
await dormir(Math.random() * tope); // "full jitter": evita que todos reintenten a la vez
}
}
throw ultimoError;
}Dale un final a los que no avanzan
- Define un máximo de intentos.
- Al agotarlos, mueve el comprobante a una cola de revisión (dead-letter) y avisa a una persona.
- Nunca dejes un reintento infinito: oculta problemas reales.
Protege al SRI y a ti con un corte de circuito
Si detectas que muchos envíos seguidos fallan por caída del SRI, pausa los envíos unos minutos en vez de seguir golpeando. Reanuda con pocas solicitudes de prueba. Si la caída es prolongada, revisa el procedimiento de contingencia vigente.
Qué medir
| MÉTRICA | PARA QUÉ SIRVE |
|---|---|
| Comprobantes pendientes y su antigüedad | Detectar que la cola no avanza |
| Tasa de rechazo por tipo | Encontrar errores de datos sistemáticos |
| Intentos promedio hasta autorizar | Ver la salud del SRI y de tu red |
| Comprobantes en revisión | Saber qué necesita atención humana |
Para saber el estado sin consultar sin parar, combina esta cola con webhooks.
Conclusión
Una cola bien diseñada convierte un SRI lento en un problema invisible para tus usuarios. Los tres pilares son: desacoplar la venta, reutilizar el secuencial y consultar antes de reenviar.