Tema
Idempotencia
Es el error número uno de un integrador nuevo, y el único que puede costar dinero real.
La regla
POST /api/v1/dte/documentos exige la cabecera Idempotency-Key. Sin ella:
json
{
"type": "https://api.factura.sv/errores/falta-idempotency-key",
"status": 400,
"detail": "La cabecera Idempotency-Key es obligatoria al emitir: evita que un reintento de red emita el documento dos veces."
}Lo mismo en POST /api/v1/gestion/ventas y en POST /api/v1/dte/lotes.
Por qué no es opcional
Tu sistema manda la petición. La red se corta antes de que llegue la respuesta. ¿Se emitió el documento o no?
No lo sabes. Y las dos salidas son malas:
- Reintentas y, si la primera sí llegó, acabas de emitir dos facturas por la misma venta. Dos correlativos consumidos, dos documentos con valor fiscal, y una nota de crédito que emitir para arreglarlo.
- No reintentas y, si la primera no llegó, cobraste sin facturar.
Con Idempotency-Key no hay que elegir: reintentas siempre, y el servidor sabe si esa petición ya se procesó.
Y el daño no es solo un documento de más. Un correlativo es una secuencia sin huecos que revisa un auditor: por cada número emitido hay que poder decir qué documento fue y qué pasó con él. Un duplicado se arregla con una nota de crédito y una explicación, no borrándolo.
Cómo se usa
Genera un identificador único por cada intento de emisión distinto y reutilízalo en todos los reintentos de ese mismo intento.
bash
CLAVE_IDEM=$(uuidgen)
curl -s -X POST "$API/api/v1/dte/documentos" \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: $CLAVE_IDEM" \
-H "Content-Type: application/json" \
-d "$CUERPO"Un UUID v4 sirve. Lo que no sirve:
| Clave | Por qué no |
|---|---|
| Un UUID nuevo en cada reintento | No es idempotencia: es emitir de nuevo. Es exactamente el fallo que se quiere evitar. |
| El número de tu venta, a secas | Si la venta se corrige y se reemite a propósito, la clave repetida devolvería el documento viejo. |
| Una marca de tiempo | Dos cajas pueden coincidir en el mismo milisegundo. |
Lo que sí sirve y es lo más práctico: guardar la clave en tu propia tabla de ventas cuando construyes la petición, y reutilizarla mientras esa venta siga sin documento. Así el reintento sobrevive incluso a que se reinicie tu proceso.
sql
ALTER TABLE mis_ventas ADD COLUMN clave_idempotencia uuid;Qué pasa exactamente si repites la clave
Responde 200 OK con el documento que ya se emitió, en vez de 202 Accepted.
Esa diferencia de código es deliberada: te dice sin ambigüedad que no se creó nada nuevo. Si tu integración distingue 201/202 de 200, tienes la señal sin comparar nada.
Primer envío: 202 Accepted → se emitió
Reintento: 200 OK → ya existía, este es el mismo documentoEl cuerpo es el mismo documento, con su código de generación y su número de control. Puedes tratar los dos casos igual y quedarte con el documento; el código solo importa si quieres registrar que hubo un reintento.
Cuánto dura
La clave queda asociada al documento de forma permanente, no con una ventana de expiración. Un reintento un mes después seguiría devolviendo el mismo documento.
Esto significa que la clave tiene que ser distinta para ventas distintas. Si usas un contador que se reinicia, acabarás recibiendo el documento de otra venta.
Ventas: el mismo mecanismo, dos efectos
En POST /api/v1/gestion/ventas la idempotencia protege dos cosas a la vez: que no se cobre dos veces y que no salga dos veces el inventario. Un reintento por red que descuadre el kardex es difícil de detectar y peor de arreglar.
Cómo probar que lo hiciste bien
Manda la misma petición dos veces con la misma clave y comprueba que el segundo codigoGeneracion es idéntico al primero:
bash
CLAVE_IDEM=$(uuidgen)
CUERPO='{"tipoDte":"01","items":[{"descripcion":"Prueba","cantidad":1,"uniMedida":59,"precioUni":1.13,"tipoItem":2}]}'
for i in 1 2; do
curl -s -X POST "$API/api/v1/dte/documentos" \
-H "Authorization: Bearer $CLAVE" \
-H "Idempotency-Key: $CLAVE_IDEM" \
-H "Content-Type: application/json" \
-d "$CUERPO" | grep -o '"codigoGeneracion":"[^"]*"'
doneLos dos códigos tienen que ser el mismo. Si son distintos, tu clave no se está reutilizando y en producción emitirás duplicados en cuanto haya un corte de red.