Un flujo de n8n que llama a un modelo parece terminado cuando el camino feliz funciona una vez. La producción se rompe en la segunda entrega del mismo webhook, en el timeout que reintenta después de que la primera llamada ya tuvo éxito y en el borrador que se envió solo porque nadie era dueño del paso de aprobación.
Lo que sigue es la capa de endurecimiento para flujos con IA: idempotencia, política de reintentos, controles de aprobación humana y registros. Complementa tu primer agente de IA en n8n y los patrones de revisión en diseño de supervisión humana.
Habilitar el reintento en un nodo que quizá ya creó una nota en el CRM, envió un mensaje o encoló un correo puede duplicar efectos secundarios cuando el resultado es desconocido. Trata cada escritura externa como no repetible hasta que se demuestre el comportamiento de idempotencia o reconciliación del proveedor.
Por qué los pasos de IA necesitan un manejo de fallos distinto
Las llamadas HTTP y de modelo corrientes pueden fallar por códigos de estado, timeouts, respuestas mal formadas o estado de confirmación desconocido. Los pasos que incluyen un modelo añaden modos de fallo como:
- Timeouts en inferencia local lenta (endpoints locales compatibles con OpenAI).
- Fallos de parseo cuando el modelo devuelve prosa en lugar de JSON.
- Fallos blandos: JSON válido pero incorrecto.
- Éxito parcial: el modelo respondió, pero una escritura posterior con herramienta falló.
Un reintento ciego arregla algunos timeouts. Amplifica los demás. Separa reintentos de transporte (seguros si el servidor nunca confirmó el trabajo) de reintentos de negocio (solo seguros con una clave de idempotencia).
n8n permite a los operadores reintentar ejecuciones fallidas desde el historial de ejecuciones (documentación de ejecuciones de n8n). Esa función de operador no demuestra que un efecto secundario sea seguro de repetir; el flujo sigue necesitando los controles de reclamación, reconciliación y outbox que vienen a continuación.
La idempotencia empieza con una reclamación atómica
Elige una clave estable tan pronto como lo permita el desencadenante:
| Desencadenante | Clave candidata |
|---|---|
| Webhook de formulario/CRM | lead_id / ticket_id aguas arriba |
| Correo | Message-ID normalizado |
| Programación sobre una cola | (job_id, logical_period) o clave primaria de fila |
| Reejecución manual | La clave existente; una corrección o sustitución real es un evento de negocio nuevo y enlazado explícitamente |
No implementes un SELECT key seguido de un INSERT key, y no uses una fila de hoja de cálculo como bloqueo. Dos workers de n8n pueden ver ambos la clave como ausente y seguir adelante. Usa una restricción de unicidad de base de datos y una sola sentencia atómica; PostgreSQL documenta las restricciones únicas como el mecanismo que garantiza la unicidad de la clave (restricciones de PostgreSQL).
Forma mínima en PostgreSQL (adapta tipos, retención y migraciones a tu sistema):
CREATE TABLE workflow_runs (
idempotency_key text PRIMARY KEY,
state text NOT NULL CHECK (state IN (
'processing', 'awaiting_human', 'approved',
'completed', 'failed_retryable', 'failed_terminal'
)),
payload_hash text NOT NULL,
lease_owner uuid,
lease_expires_at timestamptz,
version bigint NOT NULL DEFAULT 0,
result jsonb,
updated_at timestamptz NOT NULL DEFAULT now()
);
Genera un UUID aleatorio de lease_owner por cada ejecución de n8n. Reclama una clave nueva, o recupera únicamente un lease explícitamente reintentable o caducado, en una sola sentencia:
INSERT INTO workflow_runs (
idempotency_key, state, payload_hash, lease_owner, lease_expires_at
)
VALUES ($1, 'processing', $2, $3, now() + interval '5 minutes')
ON CONFLICT (idempotency_key) DO UPDATE
SET lease_owner = EXCLUDED.lease_owner,
lease_expires_at = EXCLUDED.lease_expires_at,
state = 'processing',
version = workflow_runs.version + 1,
updated_at = now()
WHERE workflow_runs.payload_hash = EXCLUDED.payload_hash
AND (workflow_runs.state = 'failed_retryable'
OR (workflow_runs.state = 'processing'
AND workflow_runs.lease_expires_at < now()))
RETURNING idempotency_key, lease_owner, version;
Cero filas devueltas significa que otra ejecución es dueña de la clave o que la ejecución ya alcanzó un estado no reintentable: consulta su estado y no hagas nada o devuelve el resultado anterior. Si la misma clave llega con un payload_hash distinto, detente e investiga; tratar en silencio una entrada de negocio cambiada como el mismo evento oculta corrupción aguas arriba.
El lease debe estar acotado y renovarse solo por su dueño. Cada transición de estado usa comparar-y-fijar (CAS):
UPDATE workflow_runs
SET state = $4, version = version + 1, updated_at = now()
WHERE idempotency_key = $1
AND lease_owner = $2
AND version = $3
AND lease_expires_at > now()
RETURNING version;
Si no vuelve ninguna fila, esta ejecución perdió la propiedad y no debe actuar. Dimensiona el lease inicial a partir de la duración medida del trabajo, renuévalo antes de que caduque, limita su vida total y alerta ante robos de lease repetidos. Un lease evita que el trabajo abandonado bloquee para siempre; no hace segura una entrega externa no idempotente.
La entrega de webhooks y la ejecución de los workers son normalmente «al menos una vez». Una reclamación en base de datos hace determinista la propiedad concurrente. No crea efectos de correo, pago o CRM «exactamente una vez» a través de un límite de red; eso exige una clave de idempotencia aguas abajo o un outbox/despachador capaz de reconciliar un resultado desconocido.
Política de reintento para nodos de IA
Usa una matriz corta y codifícala en el flujo, no en la memoria tribal:
| Fallo | ¿Reintentar? | Notas |
|---|---|---|
| HTTP 429 / 503 del servidor del modelo | Normalmente, cuando la operación es segura de repetir | Respeta Retry-After cuando venga; usa backoff exponencial acotado con jitter y alerta ante presión sostenida |
| Timeout con confirmación desconocida | Solo si la llamada es de solo lectura o con clave | Prefiere consulta de estado a replay ciego |
| JSON inválido del modelo | Re-prompt limitado (1–2) | Luego enruta a humano con salida en bruto |
| Fallo de validación de negocio (enum malo, borrador vacío) | Sin bucle silencioso de reintento | Corrige prompt/esquema o escala |
| Conflicto 409 en CRM aguas abajo | Verifica antes de tratarlo como éxito | Consulta o reconcilia el recurso y confirma que ganaron la misma clave de idempotencia y el estado previsto |
| 500 en CRM tras incertidumbre de escritura | Investigar; no reenviar correo automáticamente |
Mantén iteraciones máximas finitas en nodos de agente. Un envoltorio de reintento alrededor de un agente que ya hace bucles con herramientas es la receta para que exploten las facturas de tokens y las llamadas duplicadas a herramientas.
Para endpoints locales, dimensiona timeouts según latencia medida; no apiles «reintentar tres veces a 60s cada una» en un webhook síncrono de cliente.
Controles de aprobación humana que bloquean efectos secundarios
Un control de aprobación humana no es un mensaje de Slack que dice «FYI». Es un estado en el que ninguna acción visible para el cliente o irreversible se ejecuta hasta recibir una señal explícita de aprobación.
Tres patrones que funcionan en n8n:
1. Aprobar antes de actuar
Nodo de IA → validar esquema → escribir borrador + clave en almacén → crear un reto de aprobación de un solo uso → solo una transacción de aprobación autenticada y no caducada puede encolar el envío.
2. Actuar con ventana
Encolar envío posterior con ventana de cancelación. Úsalo solo cuando la acción sea lo bastante reversible como para que una cancelación tardía tenga sentido.
3. Aprobar por excepción
Actúa automáticamente solo en casos estrechos y reversibles cuyas reglas deterministas de elegibilidad y evidencia de evaluación calibrada alcancen un umbral aprobado; muestréalos, vigílalos y escala o abstente ante la incertidumbre. La confianza autodeclarada de un modelo no es un control que se pueda imponer.
Ajusta la elección de la puerta de aprobación a la consecuencia — el mismo modelo de decisión que diseño de supervisión humana. El correo a clientes, los reembolsos, los cambios de cuenta o de CRM y las acciones financieras operativas ordinarias permanecen en aprobar-antes-de-actuar hasta que la evidencia medida y la política permitan otra cosa. El tratamiento médico, el asesoramiento legal, el asesoramiento financiero regulado, las decisiones de seguridad infantil y las decisiones estructurales o de construcción exigen un profesional cualificado; la automatización puede preparar o enrutar registros, pero no debe sustituir esa revisión.
Ejemplo de checklist de puerta en la tarjeta de aprobación:
- Clave de idempotencia
- Enlace al registro de origen
- Salida del modelo (borrador / etiqueta / puntuaciones)
- Errores de validación, si los hay
- Identidad del aprobador para el registro
- Tiempo de caducidad del estado pendiente
Los enlaces de aprobación son credenciales al portador
Nunca envíes https://n8n.example/webhook/approve?id=ticket-42&action=approve. Cualquiera que adivine, reenvíe, escanee o reproduzca esa URL puede actuar. Genera al menos 256 bits de material de token criptográficamente aleatorio, envía el token opaco solo por HTTPS y guarda únicamente su hash SHA-256 junto con:
- la clave de la ejecución y la decisión permitida;
- el aprobador o la audiencia prevista, o la política de SSO;
- una caducidad absoluta;
consumed_at, la decisión y la identidad del aprobador;- una restricción de un solo uso.
Un GET debería mostrar una página de confirmación, no mutar estado. Envía la decisión con POST tras autenticación y protección CSRF. Para casos de baja complejidad, los nodos actuales de n8n pueden pausar y solicitar aprobación; el propio n8n recomienda el nodo Wait para aprobaciones más complejas (operación de aprobación de Gmail en n8n). Verifica la semántica real de autenticación, caducidad, reenvío y auditoría del nodo y la versión que despliegues; un botón enviado por correo no es automáticamente apto para una aprobación de pago o legal.
Crea un registro de aprobación enlazado a la clave de idempotencia de negocio inmutable:
CREATE TABLE approvals (
approval_id uuid PRIMARY KEY,
idempotency_key text NOT NULL REFERENCES workflow_runs(idempotency_key),
token_hash bytea NOT NULL UNIQUE,
allowed_decisions text[] NOT NULL,
expires_at timestamptz NOT NULL,
consumed_at timestamptz,
decision text,
approver_subject text,
created_at timestamptz NOT NULL DEFAULT now()
);
Calcula el hash del token en bruto dentro de la aplicación y pasa solo el digest como $1. Consúmelo de forma atómica:
UPDATE approvals
SET consumed_at = now(), decision = $2, approver_subject = $3
WHERE token_hash = $1
AND consumed_at IS NULL
AND expires_at > now()
AND $2 = ANY (allowed_decisions)
RETURNING idempotency_key;
Cero filas devueltas significa caducado, inválido, ya usado o decisión incorrecta: no envíes. Ejecuta esta sentencia dentro de una transacción que después bloquee la fila correspondiente de workflow_runs, compruebe que sigue en awaiting_human, la actualice a approved e inserte la fila única del outbox. Revierte toda la transacción si falla cualquier paso. Para acciones de alta consecuencia, exige SSO con sesión iniciada más comprobaciones de rol y de segregación de funciones; poseer un enlace de correo no basta.
No dejes que el modelo elija
auto_replyy luego honres esa elección sin un umbral impuesto por el flujo. Los prompts sugieren; los nodos imponen.
Outbox transaccional para efectos externos
Consumir la aprobación, cambiar el estado de la ejecución y registrar el efecto externo previsto deberían ocurrir en una sola transacción de base de datos. No envíes desde dentro del webhook de aprobación. Restricción mínima de outbox:
CREATE TABLE effect_outbox (
effect_id uuid PRIMARY KEY,
idempotency_key text NOT NULL REFERENCES workflow_runs(idempotency_key),
effect_type text NOT NULL,
target text NOT NULL,
payload jsonb NOT NULL,
state text NOT NULL CHECK (state IN ('pending', 'sending', 'completed', 'unknown', 'failed')),
lease_owner uuid,
lease_expires_at timestamptz,
provider_id text,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (idempotency_key, effect_type, target)
);
Un worker de outbox reclama filas pendientes con un lease acotado (el FOR UPDATE SKIP LOCKED de PostgreSQL está pensado para consumidores tipo cola; consulta la documentación de la cláusula de bloqueo), llama al proveedor con la misma clave de idempotencia cuando lo admita, guarda el ID externo del proveedor y luego marca la fila como completada mediante CAS.
Si el worker agota su tiempo después de que el proveedor pueda haber aceptado una acción no idempotente, marca el efecto como unknown y reconcilia con el proveedor antes de reintentar. Un envío SMTP, por ejemplo, no puede convertirse en «exactamente una vez» mediante una transacción local de base de datos. El reenvío automático tras un resultado desconocido es la vía por la que aparecen los correos duplicados a clientes.
Registro que sobrevive a un incidente
El historial de ejecución de n8n es un comienzo. No es un archivo de cumplimiento por sí solo. Para pasos de IA, registra un evento estructurado por clave:
- Marca de tiempo y versión del flujo / id de commit si versionas flujos
- Clave de idempotencia y origen del desencadenante
- Hash de entrada con los datos sensibles ocultos, o campos permitidos (no secretos en bruto)
- Clase de proveedor/endpoint aprobada más la identidad del modelo y su revisión; evita exponer hosts internos o credenciales en registros de acceso amplio
- Campos de salida del modelo aprobados y minimizados, o un puntero controlado; capturar la salida en bruto exige su propia decisión de finalidad, acceso y retención
- Resultado de validación
- Decisión de puerta y actor
- Escrituras aguas abajo con ids externos
- Clase de error y recuento de reintentos
No almacenes volcados de cadena de pensamiento privados «para depurar» en un canal compartido. Almacena resúmenes de decisión y argumentos de herramientas que estarías dispuesto a auditar.
Los registros de ejecución suelen contener datos personales de tickets y correos. Define retención, acceso y redacción antes de habilitar registro verboso en nodos de IA de producción. Los modelos locales no te eximen de la responsabilidad al estilo del RGPD si tratas datos personales.
Cuando algo va mal, necesitas responder: ¿Procesamos esta clave? ¿Enviamos? ¿Quién aprobó? ¿Qué versión del modelo redactó?
Secuencia de referencia para un camino de lead o ticket
- El webhook recibe payload → validar esquema (puerta al estilo primer agente de IA en n8n).
- Calcular clave + hash del payload → reclamar atómicamente un lease
processingacotado. - Llamar nodo de IA / agente con contrato de salida estructurada.
- Validar JSON (enum, campos obligatorios, longitud máxima).
- Si sigue inválido tras reparación limitada →
failed_terminal+ alerta humana. - Si es válido y la acción es de alto riesgo → CAS a
awaiting_human; crear un reto de aprobación con hash, caducidad y un solo uso. - Al recibir un POST de aprobación autenticado → consumir el reto, actualizar el estado e insertar el efecto único del outbox, todo de forma atómica.
- El despachador toma el lease de la fila del outbox, llama al proveedor con la misma clave cuando se admita, guarda el ID del proveedor y marca por CAS tanto el efecto como la ejecución como completados.
- Al rechazar → marcar terminal con motivo; no encolar.
- En entrega duplicada → devolver el resultado anterior o informar del estado actual; nunca repetir en silencio el camino de modelo o de envío.
Opcional: delega en Hermes la redacción que requiere más criterio mediante su servidor API con autenticación Bearer, o utiliza deliberadamente el adaptador de webhooks independiente cuando su contrato de recepción de eventos y entrega configurada se ajuste al flujo de trabajo. En ambos casos, n8n o el sistema de negocio conserva las claves duraderas, los controles y los conectores. Consulta el diseño ilustrativo de transferencia mediante webhook de n8n a Hermes.
Reejecuciones forzadas sin romper la idempotencia
Los operadores reejecutarán ejecuciones fallidas desde la UI de n8n. Eso es sano — salvo que la reejecución cree silenciosamente una segunda nota en el CRM porque la clave sigue en completed tras un éxito parcial, o peor, reenvíe correo porque la clave nunca se escribió.
Define un protocolo explícito de reejecución:
- Recuperación reintentable — solo
failed_retryableo un leaseprocessingcaducado pueden reclamarse mediante la reclamación atómica mostrada arriba. Se conserva la misma clave de negocio. - Replay terminal o completado prohibido — las claves en
failed_terminal,awaiting_human,approvedycompleteddevuelven su estado anterior o actual y no vuelven a empezar. - Corrección o sustitución intencionada — crea un evento de negocio nuevo con su propia clave de idempotencia emitida aguas arriba, enlázalo con la clave original y el resultado externo, registra el operador y el motivo, y hazlo pasar por un camino nuevo de aprobación y outbox. No inventes un sufijo improvisado ni modifiques la ejecución original directamente.
Muestra el protocolo en la tarjeta de aprobación para que los operadores de turno de noche no inventen política bajo presión.
Métricas de observabilidad que merece la pena vigilar
No necesitas una plataforma de observabilidad completa el día uno. Haz seguimiento semanal:
- Tasa de webhooks duplicados (misma clave vista dos veces)
- Tiempo de espera en la puerta de aprobación (p50 / p95 — etiquetados como mediciones propias)
- Tasa de fallos de validación tras el nodo de IA
- Proporción de acciones automáticas frente a aprobadas por humanos
- Recuento de agotamiento de reintentos
Los picos en fallos de validación justifican investigar cambios en el modelo, el prompt, el esquema, la distribución de entradas o las integraciones. Los picos de duplicados justifican investigar reentregas aguas arriba, fallos de reclamación, replays o ambigüedad en el resultado del proveedor; la métrica por sí sola no diagnostica la causa.
Interruptor de emergencia y propiedad
Impón un interruptor de emergencia de denegación por defecto en el límite del efecto secundario o en el despachador, no solo en el primer nodo del flujo: AI_ACTIONS_ENABLED=false debe impedir todo envío externo incluso cuando una ejecución se reanuda a mitad de camino o se salta una rama temprana. Prueba el estado deshabilitado contra efectos en cola y en vuelo, define qué se sigue registrando y nombra a un responsable autorizado que pueda operar y verificar el control.
Define también:
- Quién puede aprobar
- Quién puede forzar una reejecución y cómo recibe el evento de sustitución una clave nueva emitida por el sistema de origen, vinculada a la original y sin sufijos improvisados
- Qué significa «hecho» para SLAs de soporte cuando la puerta está esperando
Checklist de publicación
- Clave de idempotencia elegida y persistida antes de la llamada de IA
- Diez entregas concurrentes de la misma clave producen exactamente un lease activo
- Probadas la recuperación de lease caducado y el rechazo por CAS de un dueño obsoleto
- Reglas de reintento documentadas por clase de fallo
- Probados el hash del token de aprobación, su caducidad, SSO/rol, POST/CSRF y el replay de un solo uso
- La puerta de aprobación humana inserta una fila de outbox; no puede llamar directamente al nodo de envío
- Un timeout del proveedor tras una posible aceptación pasa a
unknowny no reenvía automáticamente - Los registros estructurados incluyen clave, validación, aprobador, ids externos
- Interruptor de emergencia probado
- Retención de privacidad fijada en registros
Los nodos de IA merecen su sitio cuando son aburridos bajo fallo. La idempotencia evita que los reintentos mientan. Los controles de aprobación humana evitan que salidas incorrectas se conviertan en hechos para el cliente. El registro hace comprobables ambas afirmaciones.



