El fallo que produce dos registros

Imagina que llega dos veces la misma solicitud comercial. El flujo pide guardar un borrador, el almacén lo guarda y la conexión se corta antes de confirmar. Si el flujo crea otra operación con un nuevo ID, puede generar un segundo registro. El problema no es solo el error de red: falta una forma de reconocer que ambas peticiones pertenecen al mismo trabajo.

Distingue un intento de la operación lógica. La tarea conserva su identidad aunque cambie el número de intento. La idempotencia busca que repetir una misma operación no produzca efectos adicionales sobre el estado acordado. No garantiza por sí sola que el resultado sea correcto ni resuelve la deduplicación comercial de solicitudes diferentes.

Clasifica el error antes de reintentar

Un error de formato no se arregla esperando. Una credencial inválida requiere revisar configuración. Un límite temporal de servicio puede justificar una espera. Un timeout después de una escritura requiere averiguar qué ocurrió, porque el destino pudo completar la operación.

Situación Respuesta propuesta Qué conservar
Entrada inválida Detener y devolver motivo Error de validación, ID y origen
Fallo transitorio antes de una acción Reintento limitado con espera Mismo ID lógico, intento distinto
Resultado de escritura desconocido Consultar destino o repetir con contrato idempotente Clave y contenido original
Misma clave con contenido distinto Rechazar y revisar conflicto Ambas huellas y motivo del cambio
Límite de intentos agotado Cola de excepción Todos los intentos y estado final

Los tiempos y límites deben fijarse por dependencia. Evita un bucle que reintente indefinidamente: puede multiplicar carga y ocultar errores persistentes. En una herramienta como n8n, su documentación de manejo de errores explica cómo derivar fallos a un workflow de error; eso no implementa por sí mismo la idempotencia del sistema de destino.

Diseña una identidad estable

Una clave que puedes utilizar es la combinación del sistema de origen y el ID original de solicitud. No generes una clave nueva dentro del bloque que se reintenta. Define cuánto tiempo se conserva y qué ocurre si cambia el contenido manteniendo la misma identidad.

Guarda también una huella del contenido normalizado y de la versión de la operación. No utilices solo el email: dos peticiones legítimas pueden venir de la misma persona. Para fusionar oportunidades comerciales hacen falta reglas de negocio diferentes a impedir que un mensaje repetido produzca dos borradores.

Stripe documenta su propio contrato de idempotencia, con reutilización de resultados y condiciones de conservación de claves. Es un ejemplo de API, no una garantía trasladable a cualquier CRM. Consulta el contrato real del destino antes de asumir que un encabezado tiene efecto.

Protege el almacén, no solo el workflow

Una comprobación «si no existe, inserta» puede fallar con dos peticiones simultáneas: ambas podrían leer ausencia antes de escribir. La unicidad debe estar protegida por el almacén. PostgreSQL permite una restricción única y una alternativa de inserción con ON CONFLICT.

Este ejemplo muestra cómo plantear el guardado en un almacén local. Adáptalo y pruébalo en tu entorno:

CREATE TABLE candidate (
  source text NOT NULL,
  request_id text NOT NULL,
  payload_hash text NOT NULL,
  payload jsonb NOT NULL,
  PRIMARY KEY (source, request_id)
);

INSERT INTO candidate (source, request_id, payload_hash, payload)
VALUES ($1, $2, $3, $4)
ON CONFLICT (source, request_id) DO NOTHING
RETURNING source, request_id;

Si la inserción no devuelve fila, recupera el registro existente y compara su huella. Si coincide, puedes recuperar el resultado previo; si difiere, deriva el conflicto a revisión. No interpretes «no se insertó» como éxito sin esa comprobación. Los parámetros representan valores enlazados, nunca concatenación de datos externos en SQL.

Este patrón solo protege el estado en esa tabla. Si después se escribe en un CRM remoto, necesitas coordinar ambos estados y verificar el contrato del destino. Una transacción local no convierte varias APIs en una única transacción distribuida.

Reproduce un fallo después de guardar

La práctica adjunta permite observar el problema con dos almacenes SQLite: uno conserva el estado del ejecutor y otro representa el destino. Se guarda el efecto en el destino y, justo después del commit, el programa provoca una excepción. El ejecutor todavía no ha registrado que terminó.

Descarga el laboratorio de reintentos, sus pruebas y el resultado observado. Descomprime el ZIP y abre una terminal dentro de reintentos-v1. Necesitas Python 3.10 o posterior con SQLite; no utiliza modelos, paquetes externos ni conexiones de red.

python3 agentic_lab.py harness --output salida-harness.json
python3 -m unittest -v test_agentic_lab.HarnessTests

La secuencia implementada es esta:

  1. Crea una operación con identidad estable y contenido sintético. Sin aprobación, la escritura queda detenida.
  2. Simula una aprobación en código. Esta práctica no autentica a una persona.
  3. Registra el inicio del intento, escribe el efecto junto a su huella en el destino y confirma esa transacción.
  4. Lanza una excepción antes de marcar la operación como completada en el ejecutor.
  5. Cierra ambas conexiones y vuelve a abrirlas. Consulta el recibo del destino con la misma identidad y compara la huella.
  6. Reconcilia el estado del ejecutor. Como el efecto ya existe y coincide, no vuelve a escribirlo.

Este es el fragmento del resultado observado el 19 de septiembre de 2026 con Python 3.14.4 y SQLite 3.51.3 en Linux:

{
  "before": "awaiting_approval",
  "after_restart": "completed",
  "effects": 1,
  "attempts": 1
}

after_restart es el nombre del campo del ejemplo: describe la reapertura de conexiones y del objeto ejecutor dentro del mismo proceso. No se ha matado ni reiniciado el proceso del sistema operativo.

El contador de intentos sigue en uno porque la reanudación solo consulta y reconcilia. El programa comprueba el número de filas del destino; no deduce la ausencia de duplicados a partir de un mensaje de éxito. En resultado.json, incluido en el ZIP, puedes seguir los eventos attempt_started y reconciled.

Las 11 pruebas de software del ejecutor pasaron en esta verificación. Cubren, entre otros casos, falta de aprobación, aprobación caducada, cambio de revisión, fallo antes y después del efecto, agotamiento de intentos y conflicto de huella. Son comprobaciones de este mecanismo local, no once pruebas de fiabilidad de un CRM.

Qué demuestra la práctica y qué debes ampliar

El ejemplo muestra que una identidad conservada, un recibo vinculado al contenido y una consulta de reconciliación permiten recuperar este fallo concreto. También explica por qué una aprobación caducada no obliga a repetir una acción: el recibo puede acreditar que el efecto se realizó antes. Una operación nueva sí necesitaría una autorización válida.

La demostración utiliza un único ejecutor y un destino diseñado para guardar recibo y efecto en la misma transacción. No cubre trabajadores concurrentes, caídas eléctricas, errores de disco ni una API remota. El ejemplo PostgreSQL anterior es una propuesta separada y no se ha ejecutado en esta práctica.

Si tu CRM no ofrece el mismo contrato, tendrás que diseñar cómo consultar una operación cuyo resultado quedó incierto. Mantener una tabla local de «enviado» no demuestra por sí solo qué llegó al destino. La política debe explicar qué hacer si el recibo no aparece o si la misma identidad corresponde a un contenido distinto.

Prueba el fallo en el lugar adecuado

El protocolo de CRM debe incluir una repetición exacta, dos entregas concurrentes y un corte después de guardar pero antes de confirmar. Conserva la misma clave en los reintentos y comprueba el número real de registros activos al terminar. Añade el caso de la misma clave con contenido diferente para verificar que no se sobrescribe silenciosamente.

Registra latencia, número de intentos, claves y estado final. Una traza que diga «éxito» no demuestra ausencia de duplicados; consulta el almacén. Los cinco casos del protocolo de CRM incluyen una solicitud repetida. Su ensayo con modelos se prepara para un equipo de laboratorio independiente; las comprobaciones ligeras de esta guía no sustituyen esa ejecución ni las pruebas pendientes de red y concurrencia.

Cuenta el coste y documenta la recuperación

En la calculadora, los intentos medios incluyen todos los reintentos. La revisión humana se cuenta por tarea revisada, con su tiempo total. Si una incidencia genera trabajo adicional de reparación, inclúyelo dentro del alcance temporal que declares.

Deja un procedimiento para consultar una operación, decidir si se puede repetir y resolver conflictos. No borres duplicados automáticamente para mejorar una métrica de éxito: conserva evidencia, determina el origen y define una corrección autorizada. Las fichas actuales proponen este contrato; ninguna acredita todavía una prueba de concurrencia o recuperación.

Para contrastar

Fuentes y versiones

Consulta la documentación original para entender cada herramienta y contrastar lo que se explica aquí.

  1. Stripe · Idempotent requests Consultada el 19 de septiembre de 2026 · Versión no fijada
  2. PostgreSQL · INSERT y ON CONFLICT Consultada el 19 de septiembre de 2026 · Documentación PostgreSQL 18; no ejecutada aquí
  3. n8n · Error handling Consultada el 19 de septiembre de 2026 · Versión no fijada
Los ejemplos están creados para practicar. Puedes consultar las fuentes y conocercómo preparamos y revisamos el contenido.