Primero, mide: ¿dónde se va el tiempo?
Cada comprobante pasa por etapas muy distintas:
| ETAPA | TIPO DE TRABAJO | SE ACELERA CON |
|---|---|---|
| Generar el XML | CPU, ligero | Rara vez es el problema |
| Firmar | CPU, pesado | Más núcleos, reutilizar el certificado cargado |
| Enviar a recepción | Red y espera | Concurrencia controlada |
| Esperar la autorización | Espera externa | Consultar sin bloquear, webhooks |
Si no mides cada etapa por separado, vas a optimizar la equivocada. Registra el tiempo de cada una y mira los percentiles p50, p95 y p99, no solo el promedio: los comprobantes lentos son los que alargan tu proceso.
Mide el costo de firmar en tu infraestructura
Este script mide solo la operación RSA, sin XAdES ni red. Te da una referencia de cuánto cuesta firmar en tu servidor:
import crypto from "node:crypto";
const { privateKey } = crypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
const xml = Buffer.from("<factura>...</factura>".repeat(50));
const N = 500;
const inicio = process.hrtime.bigint();
for (let i = 0; i < N; i++) {
crypto.sign("sha256", xml, privateKey);
}
const ms = Number(process.hrtime.bigint() - inicio) / 1e6;
console.log(`${(ms / N).toFixed(2)} ms por firma (solo la operación RSA, sin XAdES ni red)`);El número depende de tu hardware, así que mídelo tú. Si firmar cuesta una fracción pequeña del tiempo total, el cuello de botella está en otro lado.
Optimizaciones que sí valen la pena
- Carga el certificado una sola vez Abrir y descifrar el .p12 en cada firma es trabajo repetido. Cárgalo en memoria y reutilízalo, cuidando su almacenamiento seguro.
- Firma en paralelo, con tantos trabajadores como núcleos Firmar usa CPU, así que más trabajadores que núcleos no ayuda. En Node usa worker threads o procesos separados; en otros lenguajes, un pool de hilos.
- Limita la concurrencia hacia el SRI Enviar sin límite no es más rápido: satura el servicio y provoca más errores y reintentos. Usa una concurrencia controlada.
- Separa las etapas con colas Una cola para firmar, otra para enviar y otra para consultar la autorización. Cada etapa se escala por separado y una lentitud en una no frena a las demás.
- Guarda el XML firmado Si un envío falla, reintenta con el mismo XML firmado, no vuelvas a generarlo ni a firmarlo. Ahorras CPU y evitas generar un comprobante distinto.
- No esperes la autorización dentro del lote Envía, guarda la clave de acceso y consulta después, o mejor aún, recibe webhooks.
- Aplica contrapresión (backpressure) Si la cola de envío crece sin control, frena la etapa anterior en vez de acumular memoria.
Concurrencia controlada hacia el SRI
// Ejecuta `tarea` sobre cada elemento con, como máximo, `limite` ejecuciones a la vez
export async function conConcurrencia(items, limite, tarea) {
const resultados = new Array(items.length);
let siguiente = 0;
async function trabajador() {
while (true) {
const i = siguiente++;
if (i >= items.length) return;
resultados[i] = await tarea(items[i], i);
}
}
const n = Math.min(limite, items.length);
await Promise.all(Array.from({ length: n }, trabajador));
return resultados;
}Qué no optimizar sin medir
- Cambiar de algoritmo o de librería de firma por intuición El formato lo define el SRI.
- Subir la concurrencia al máximo Más solicitudes simultáneas pueden empeorar la latencia total.
- Reducir el tamaño del XML a costa de datos obligatorios El esquema es el que es.
Una arquitectura orientada a eventos
- Ventas Cada venta deja un comprobante por firmar.
- Cola: por firmar Acumula los comprobantes pendientes de firma.
- Trabajadores de firma Tantos como núcleos, con el certificado ya cargado.
- Cola: por enviar Guarda el XML firmado listo para enviar.
- Envío con concurrencia limitada Protege al SRI y evita reintentos innecesarios.
- SRI → estado en tu sistema Por webhook o por consulta de la clave de acceso.
Cada paso es un punto donde puedes medir, escalar y reintentar.
Qué pasa cuando usas una API
Si firmas y envías a través de una API como Flashpi, parte de este trabajo ya está resuelto del lado del servicio. Tu parte es no bloquear: encola, envía con concurrencia controlada y recibe el estado por webhooks.
Conclusión
Optimizar la latencia es, sobre todo, medir y separar etapas. Con el certificado cargado una vez, firma en paralelo, concurrencia limitada hacia el SRI y colas entre etapas, un lote grande deja de ser un problema.