Módulo 8: Proyecto: sistema de atención al cliente multicanal
7. Depuración, monitoreo básico y control de costos
Descripción
Al terminar esta lección vas a tener los tres instrumentos que faltan para dar el sistema por terminado. Una validación de salida determinista que impide que el agente afirme lo que ninguna tool devolvió — el hueco que la lección 6 dejó abierto a propósito. Una bitácora propia que sobrevive a la purga de ejecuciones de n8n, instrumentada en los seis puntos donde el sistema toma decisiones de seguridad, con las consultas que detectan un incidente antes de que alguien reclame. Y una hoja de costo con un número concreto de dólares por conversación, obtenido de tu propio panel de ejecuciones y no de una estimación.
Ese último punto merece una advertencia. La pregunta "¿cuánto cuesta cada conversación?" aparece en toda conversación seria sobre un sistema de IA —con un cliente, con un jefe, en una entrevista— y la respuesta "depende" es una forma educada de decir que no lo mediste. Al terminar esta lección vas a poder decir un número, decir de dónde salió, y decir cuánto sube en el peor caso.
Esto importa por una razón que atraviesa todo el proyecto: las fallas de un agente dejan la ejecución en verde. Un workflow tradicional que se rompe te avisa. Un agente que fue secuestrado, que se excedió, o que inventó una fecha termina exitosamente y produce una respuesta perfectamente formada. Nadie se entera hasta que el daño aparece por otro lado: el estado de cuenta del mes, un cliente enojado, una auditoría. Los instrumentos de hoy son lo único que convierte "no pasó nada" en "no pasó nada, y puedo demostrarlo".
Conexión con el módulo: la lección 6 cerró las acciones y dejó abiertas las afirmaciones; la fase 1 de hoy cierra eso. Cada capa que montaste en las lecciones 4, 5 y 6 tiene aquí su punto de registro. Y las mediciones de hoy son la materia prima de la lección 8: la demo de tres minutos y las respuestas de entrevista se apoyan en estos números.
El medidor de la luz
Antes de que existiera un medidor en la pared, discutir sobre la factura de electricidad era discutir sobre intuiciones. Alguien decía que el problema era el aire acondicionado, otro que era la nevera vieja, y no había forma de saberlo. Se apagaban cosas al azar, la factura bajaba un poco o no, y nadie sabía cuál de las cosas apagadas lo hizo.
Un medidor cambia la conversación de tres formas.
Da un número absoluto. No "gastamos mucho", sino una cifra. Y una cifra se puede comparar contra otro mes, contra otra casa, contra lo que se puede pagar.
Permite atribuir. Apagas un aparato, miras el medidor, lo vuelves a encender. En treinta segundos sabes cuánto consume. Ninguna cantidad de razonamiento sobre las especificaciones del aparato reemplaza esa medición.
Y con un poco más de resolución, avisa. Un medidor que registra por hora muestra un consumo raro a las tres de la mañana, y eso lleva a descubrir un calentador que quedó encendido. Nadie iba a buscarlo; el dato lo encontró.
Tu sistema tiene exactamente esas tres necesidades. Necesita un número por conversación, necesita poder atribuir ese número a un agente concreto, y necesita avisar cuando algo se comporta distinto de lo normal.
Y hay una lección del medidor que se traduce sin cambios: mide antes de optimizar. La intuición sobre dónde se va el costo en un sistema multi-agente es notoriamente mala. Mucha gente jura que el problema son las delegaciones y al medir descubre que la mitad del gasto está en un system prompt de novecientas palabras que se reenvía en cada iteración de todos los agentes.
Fase 1 — La validación de salida
Empezamos cerrando el hueco. El ejercicio 2 de la lección 6 mostró un ataque que atraviesa el guardrail y la aprobación humana porque no pide ejecutar ninguna tool: solo pide que el agente diga algo. Ninguna de las capas anteriores protege las afirmaciones.
La defensa no es un filtro ni un permiso: es comparar, campo por campo, lo que el agente afirma contra lo que las tools devolvieron. Y esa comparación tiene que ser determinista — un nodo Code, no otro modelo.
Paso 1.1 — La salida estructurada del núcleo
Hasta ahora, core_output fijaba status y needs_human a mano. Ahora los produce el propio agente, con un Structured Output Parser conectado al triage_agent:
{
"message_to_customer": "Revisé tu pedido 4521: salió el 21 de julio y la entrega estimada es el 23. Te dejo el código de seguimiento por si quieres verlo en la web de la transportadora.",
"status": "resolved",
"needs_human": false,
"facts": {
"order_id": "4521",
"order_status": "in_transit",
"eta_date": "2026-07-23",
"tracking_code": "TR-99182",
"charge_id": null,
"dispute_id": null,
"refund_status": "not_requested",
"policy_days": null
},
"facts_source": ["order_specialist:lookup_order"]
}
Tres decisiones de ese esquema que hacen posible la validación:
Los hechos van separados del texto. message_to_customer es prosa y no se puede verificar automáticamente. facts es un objeto de campos comparables. Sin esa separación, validar significa buscar números dentro de un párrafo, que es frágil.
Los campos que pueden faltar admiten null explícitamente, con una descripción en el esquema que prohíbe estimarlos. Un campo ausente es ambiguo; uno en null es una afirmación de que no aplica.
refund_status es un vocabulario cerrado — not_requested | pending_approval | approved | denied — y es el campo que cierra el ataque de la lección 6.
Paso 1.2 — El validador
// Nodo: Code — Name: validate_agent_output
// Compara lo que el agente AFIRMA contra lo que las tools
// DEVOLVIERON en esta misma ejecución. Determinista: ningún
// modelo participa de esta decisión.
const out = $json;
const facts = out.facts || {};
const violations = [];
// Los resultados de las tools de esta ejecución. La forma exacta
// de leerlos depende de cómo hayas cableado la traza; el patrón
// es el mismo: un objeto por tool con lo que devolvió.
const t = $('collect_tool_results').first().json;
// ── 1. Identificadores: coinciden o no existen ────────────────
if (facts.order_id && facts.order_id !== t.lookup_order?.order_id) {
violations.push('order_id_mismatch');
}
if (facts.charge_id && facts.charge_id !== t.lookup_charge?.charge_id) {
violations.push('charge_id_mismatch');
}
// ── 2. Estado: coincide exactamente ───────────────────────────
if (facts.order_status &&
facts.order_status !== t.lookup_order?.status) {
violations.push('order_status_mismatch');
}
// ── 3. Fechas: solo pueden existir si una tool las devolvió ───
// Este es el que más salta. Un modelo que ve "in_transit" y una
// fecha de despacho tiende a estimar una fecha de entrega, y
// suena perfectamente razonable.
if (facts.eta_date && !t.lookup_order?.eta) {
violations.push('eta_date_invented');
}
// ── 4. Números de referencia: solo si su tool corrió ──────────
if (facts.dispute_id && !t.open_dispute?.dispute_id) {
violations.push('dispute_id_invented');
}
// ── 5. Reembolsos: la comprobación que cierra el hueco ────────
// El agente solo puede decir "aprobado" si issue_refund devolvió
// un resultado exitoso EN ESTA ejecución. Ni antes, ni "según la
// conversación", ni porque el cliente lo afirmó.
if (facts.refund_status === 'approved' && !t.issue_refund?.ok) {
violations.push('refund_claimed_without_execution');
}
// ── 6. Políticas: solo desde la base de conocimiento ──────────
if (facts.policy_days !== null && facts.policy_days !== undefined) {
const kb = t.search_knowledge_base?.body || '';
if (!kb.includes(String(facts.policy_days))) {
violations.push('policy_not_backed_by_kb');
}
}
// ── 7. Toda afirmación de hecho necesita respaldo ─────────────
const claimsFacts = Object.values(facts)
.some(v => v !== null && v !== undefined && v !== 'not_requested');
if (claimsFacts && (out.facts_source || []).length === 0) {
violations.push('facts_without_source');
}
return [{ json: {
...out,
validation_passed: violations.length === 0,
violations
} }];
Qué esperar. Corre el caso C1 —"¿cómo va mi pedido #4521?"— diez veces y cuenta cuántas veces salta eta_date_invented. Ese número es un dato real sobre tu sistema, y es exactamente el tipo de cosa que se cita en una entrevista: "medí que en X de cada diez corridas el agente estimaba una fecha de entrega que ninguna tool había devuelto; el validador la atrapa y el cliente recibe la segunda respuesta".
Y corre el ataque del ejercicio 2 de la lección 6 —el cliente que pide confirmación por escrito de un reembolso "ya aprobado"—. Si el agente pone refund_status: "approved", la comprobación 5 lo marca y la respuesta no sale. El hueco queda cerrado.
Paso 1.3 — Las tres salidas de fallo
Un validador que solo detecta no sirve de nada. Hay que decidir qué pasa cuando validation_passed es falso, y hay tres caminos:
# Nodo: IF — Name: output_is_valid
#
# [true] → Guardrails: output_guardrail → core_output
#
# [false] → Switch por tipo de violación:
#
# Reintento (UNA sola vez)
# Para violaciones de invención: eta_date_invented,
# dispute_id_invented, facts_without_source.
# Se vuelve a llamar al agente con el mensaje de error como
# contexto adicional: "Tu respuesta anterior afirmó una fecha
# de entrega que ninguna tool devolvió. Vuelve a responder sin
# estimar datos que no tengas."
# UNA vez. Si la segunda también falla, se degrada.
#
# Respuesta degradada (plantilla)
# Un texto fijo, escrito por ti, con los datos que SÍ
# verificaron: "Tu pedido 4521 está en tránsito. No tengo una
# fecha exacta de entrega; puedes seguirlo con el código
# TR-99182." Sin modelo de por medio, así que no puede
# inventar nada.
#
# Escalar
# Para refund_claimed_without_execution y para cualquier
# violación que se repita tras el reintento. Llama a
# escalate_to_human y responde con la plantilla de escalación.
El límite de un reintento no es negociable: un bucle de reintentos con un modelo que insiste en la misma alucinación multiplica el costo y la latencia sin converger.
Y el último nodo antes de core_output:
# Nodo: Guardrails — Name: output_guardrail
# Operation: Check Text for Violations
# Text To Check: {{ $json.message_to_customer }}
#
# PII — que no salga un correo o un teléfono completo
# de otro cliente en una respuesta
# Secret Keys — que no se filtre nada de una credencial en un
# mensaje de error mal manejado
# Keywords — los compromisos que TuTienda no hace por
# escrito: "garantizamos la entrega", "reembolso
# aprobado", "sin costo adicional"
#
# [Fail] → respuesta degradada + registro
Fase 2 — La bitácora
Aquí viene el problema que arruina las investigaciones reales, y no es técnico sino de calendario.
Las ejecuciones de n8n no viven para siempre. Por defecto la purga está activada, y una ejecución se borra cuando ocurre cualquiera de dos cosas: que hayan pasado más de EXECUTIONS_DATA_MAX_AGE horas desde que terminó —por defecto 336 horas, o sea 14 días— o que el total supere EXECUTIONS_DATA_PRUNE_MAX_COUNT, por defecto 10.000.
Haz la cuenta con TuTienda. Trescientas conversaciones diarias, y cada una genera dos ejecuciones —el adaptador de canal y el núcleo— más las de los sub-workflows de tools. Son fácilmente ochocientas ejecuciones al día. El tope de 10.000 se alcanza en menos de dos semanas.
Ahora piensa cuándo aparece un incidente. Un reembolso indebido se ve en la conciliación de fin de mes. Un cliente reclama por algo que el agente le dijo "hace como dos semanas". El momento en que necesitas la traza es sistemáticamente posterior al momento en que n8n la borró.
La solución es una bitácora propia: un registro que tú escribes, en un destino que tú controlas, con lo mínimo para reconstruir. No reemplaza a las ejecuciones —es mucho menos detallada—: es el índice que te dice que algo pasó y dónde buscar mientras la ejecución todavía existe.
CREATE TABLE agent_audit_log (
id BIGSERIAL PRIMARY KEY,
logged_at TIMESTAMPTZ NOT NULL DEFAULT now(),
execution_id TEXT NOT NULL, -- para ir a la ejecución
session_key TEXT, -- para reconstruir la conversación
workflow_name TEXT NOT NULL,
agent_name TEXT,
channel TEXT, -- web | whatsapp
customer_id TEXT,
verified_by TEXT, -- session | crm_phone | declared
event_type TEXT NOT NULL, -- guardrail_block | tool_call |
-- approval_request | approval_result |
-- validation_fail | final_response
tool_name TEXT,
tool_parameters JSONB,
outcome TEXT, -- ok | denied | blocked | invalid | timeout
notes TEXT
);
CREATE INDEX ON agent_audit_log (logged_at DESC);
CREATE INDEX ON agent_audit_log (customer_id, logged_at DESC);
CREATE INDEX ON agent_audit_log (tool_name, logged_at DESC);
CREATE INDEX ON agent_audit_log (event_type, logged_at DESC);
-- El agente escribe aquí, y solo inserta.
GRANT INSERT ON agent_audit_log TO n8n_agent_rw;
Y los seis puntos donde se instrumenta, uno por capa del proyecto:
# PUNTOS DE INSTRUMENTACIÓN
1. Rama Fail del input_guardrail (lección 6)
event_type "guardrail_block" · outcome "blocked"
notes: qué guardrail saltó. NO el texto completo.
2. Antes de cada tool L1 o L2 (lección 4)
event_type "tool_call"
tool_name · tool_parameters · outcome
3. Al pedir una aprobación (lección 6)
event_type "approval_request"
tool_parameters: monto, motivo, order_id
verified_by: cómo se identificó al cliente
4. Al resolverse la aprobación (lección 6)
event_type "approval_result"
outcome: ok | denied | timeout
5. Rama false del validador (fase 1)
event_type "validation_fail" · outcome "invalid"
notes: el array de violations
6. Respuesta final al cliente (siempre)
event_type "final_response" · outcome "ok"
notes: el texto enviado
# Nodo: Postgres — Name: audit_log_write
# Operation: Insert · Table: agent_audit_log
# Credential: n8n_agent_rw
#
# execution_id = {{ $execution.id }}
# session_key = {{ $('core_input').item.json.customer_id
# ? 'customer:' + ... : ... }}
# workflow_name = {{ $workflow.name }}
# channel = {{ $('core_input').item.json.channel }}
# customer_id = {{ $('core_input').item.json.customer_id }}
# verified_by = {{ $('core_input').item.json.verified_by }}
# event_type = "tool_call"
# tool_name = {{ $tool.name }}
# tool_parameters = {{ JSON.stringify($tool.parameters) }}
# outcome = "ok"
#
# NOTA: no escribas aquí el texto completo del cliente ni datos
# personales que no necesites. Una bitácora que guarda de más se
# convierte ella misma en una fuga. Guarda identificadores y
# decisiones; el detalle está en la ejecución mientras exista.
Esa nota final vale una historia real que se repite: alguien decide que "más registro es mejor", guarda la conversación entera de cada cliente, y a los dos meses tiene una tabla con datos personales de miles de personas en un lugar que nadie diseñó para protegerlos. La bitácora guarda decisiones, no contenidos.
Fase 3 — Las consultas de detección
Una bitácora que nadie consulta es una tabla que crece. Estas tres se escriben una vez y corren solas en un workflow aparte con Schedule Trigger diario, que avisa por el canal interno solo si devuelven filas.
-- ── 1. Reintentos después de un rechazo ───────────────────────
-- En un sistema sano esto devuelve cero filas SIEMPRE. Cualquier
-- fila es un agente que no respetó una negativa.
WITH denied AS (
SELECT execution_id, customer_id, tool_name, logged_at
FROM agent_audit_log
WHERE event_type = 'approval_result'
AND outcome IN ('denied', 'timeout')
AND logged_at > now() - interval '7 days'
)
SELECT d.execution_id, d.customer_id, d.tool_name,
count(a.id) AS retries_after_denial
FROM denied d
JOIN agent_audit_log a
ON a.execution_id = d.execution_id
AND a.event_type = 'tool_call'
AND a.tool_name = d.tool_name
AND a.logged_at > d.logged_at
GROUP BY d.execution_id, d.customer_id, d.tool_name;
-- ── 2. Concentración de acciones sensibles ────────────────────
-- Un cliente con tres o más acciones sensibles en una semana es
-- un patrón, no una casualidad.
SELECT customer_id, tool_name, count(*) AS n,
min(logged_at) AS first_seen, max(logged_at) AS last_seen
FROM agent_audit_log
WHERE event_type = 'tool_call'
AND tool_name IN ('issue_refund', 'open_dispute')
AND logged_at > now() - interval '7 days'
GROUP BY customer_id, tool_name
HAVING count(*) >= 3
ORDER BY n DESC;
-- ── 3. Salud de las capas ─────────────────────────────────────
-- La consulta menos obvia y una de las más valiosas: te dice si
-- tus defensas están vivas.
SELECT event_type, outcome, count(*) AS n
FROM agent_audit_log
WHERE logged_at > now() - interval '7 days'
GROUP BY event_type, outcome
ORDER BY n DESC;
Qué esperar de la tercera, después de correr tu batería de doce casos:
event_type | outcome | n
-------------------+---------+----
tool_call | ok | 31
final_response | ok | 12
guardrail_block | blocked | 2 ← C11, dos de cinco corridas
approval_request | denied | 3 ← los ataques que llegaron ahí
approval_request | ok | 1 ← el reembolso legítimo
validation_fail | invalid | 2 ← dos fechas inventadas en C1
Seis filas que cuentan la historia completa de tu sistema en una semana. Y la regla de lectura: si alguna capa aparece con cero, revísala. Un guardrail_block con cero filas en una semana casi nunca significa que nadie te atacó; significa que el nodo quedó mal cableado o con un umbral que no hace nada. Una defensa que nunca reporta es indistinguible de una defensa apagada.
Fase 4 — Medir el costo: el método
Ahora el número. Y el método importa tanto como el número, porque el tuyo va a ser distinto del mío.
Paso 1 — Activa la traza en todos los niveles. Return Intermediate Steps en el orquestador y en cada especialista. Sin esto no puedes contar nada.
Paso 2 — Cuenta las llamadas al modelo por nivel. En la traza, cada "llamada al modelo" es una iteración. Anota cuántas hizo el orquestador y cuántas cada especialista. Ese conteo es tu métrica de latencia, porque las llamadas son secuenciales.
Paso 3 — Lee el uso de tokens. Los nodos de modelo de chat de n8n reportan el uso de tokens de cada llamada en los datos de salida de la ejecución. Ábrelos en el panel y anota entrada y salida por nodo de modelo, no solo del primero. Aquí es donde vas a ver con tus propios ojos el fenómeno más importante: el contexto creciendo en cada iteración, porque cada iteración reenvía todo lo anterior.
Paso 4 — Lee el tiempo. El panel muestra la duración de la ejecución completa y de cada nodo.
Paso 5 — Multiplica por el precio vigente de tu proveedor, el día que lo midas.
Paso 6 — Arma la hoja con veinte o treinta ejecuciones representativas, y quédate con tres columnas: típico, p90 y peor observado. La que más se usa en la práctica no es la del caso típico: es la p90, el valor por debajo del cual queda el 90% de las conversaciones, porque los casos caros pesan más de lo que su frecuencia sugiere.
Ejemplo trabajado: la hoja de costo de TuTienda
Estos son los números medidos sobre el sistema de este proyecto. Los conteos de tokens son estables —dependen de tus prompts, y los tuyos se parecen mucho a estos si seguiste las lecciones—. Los precios cambian: los de abajo son un orden de magnitud de referencia al escribir esto (julio de 2026), con un modelo económico alrededor de $0.30 por millón de tokens de entrada y $2.50 de salida, y uno capaz alrededor de $3 y $15. Verifica los vigentes de tu proveedor y rehaz la multiplicación; la estructura no cambia.
# CONVERSACIÓN TÍPICA — un tema, una delegación
# Caso C1: "¿cómo va mi pedido #4521?"
Componente Llamadas Tokens in Tokens out Modelo
──────────────────────────────────────────────────────────────────
input_guardrail 1 800 20 económico
triage_agent 3 12,200 390 económico
(3,200 · 4,100 · 4,900 — el contexto crece cada vuelta)
order_specialist 3 5,600 310 capaz
(1,400 · 1,900 · 2,300)
──────────────────────────────────────────────────────────────────
TOTAL 7 18,600 720
Económico 13,000 in × $0.30/M = $0.0039
410 out × $2.50/M = $0.0010
Capaz 5,600 in × $3/M = $0.0168
310 out × $15/M = $0.0047
──────────────────────────────────────────────────────────────────
COSTO POR CONVERSACIÓN TÍPICA ≈ $0.026 USD
DURACIÓN ≈ 9 segundos
# CONVERSACIÓN p90 — dos temas, dos delegaciones
# Caso C4: cargo no reconocido + estado de pedido
Componente Llamadas Tokens in Tokens out Modelo
──────────────────────────────────────────────────────────────────
input_guardrail 1 800 20 económico
triage_agent 5 26,100 620 económico
billing_specialist 5 12,400 450 capaz
order_specialist 3 5,600 310 capaz
──────────────────────────────────────────────────────────────────
TOTAL 14 44,900 1,400
Económico 26,900 in + 640 out = $0.0097
Capaz 18,000 in + 760 out = $0.0654
──────────────────────────────────────────────────────────────────
COSTO p90 ≈ $0.075 USD
DURACIÓN ≈ 21 segundos
El número que buscabas: una conversación de TuTienda cuesta entre 2.6 y 7.5 centavos de dólar en modelo, según si el cliente trae uno o dos temas. Con 2,000 conversaciones al mes y una mezcla de 70% simples y 30% de dos temas:
1,400 × $0.026 = $36.40
600 × $0.075 = $45.00
────────────────────────
MODELO, AL MES ≈ $81 USD
Y ahora la otra mitad, que es donde este proyecto se diferencia de un ejercicio: el canal.
Chat web $0 (no hay costo de canal)
WhatsApp — conversaciones de servicio (las que inicia el
cliente y se responden dentro de la ventana de 24 h):
Meta ha tenido periodos en los que estas conversaciones
no se cobran, cobrando en cambio las plantillas que inicia
la empresa. El esquema ha cambiado varias veces y varía
por país. VERIFICA el vigente en la tabla de precios de
Meta antes de dar un número.
Servidor (n8n self-hosted + Postgres) ≈ $5-20 USD/mes
n8n Community $0
Ese hallazgo merece decirse en voz alta porque es contraintuitivo y es lo que un cliente pregunta primero: para un sistema de atención al cliente puro, el canal puede costar mucho menos de lo que la fama de WhatsApp sugiere, y el costo real está en el modelo. Lo que sí cuesta caro en WhatsApp son las plantillas que inicia la empresa —campañas, recordatorios, notificaciones proactivas— que este sistema no usa. Poder hacer esa distinción es la diferencia entre una estimación y una conversación informada.
Total defendible del sistema: alrededor de $90 a $100 dólares al mes para 2,000 conversaciones, con la advertencia de que el precio del modelo y el de WhatsApp hay que verificarlos el día que se ponga en producción.
Y la comparación que hace útil ese número: si esas 2,000 conversaciones las atendiera una persona a razón de cinco minutos cada una, son 166 horas al mes. Ese es el marco en el que $100 se discute.
Fase 5 — Las palancas, medidas
Con la hoja armada, aplica las palancas y vuelve a medir. Una por vez, corriendo la batería de doce casos entre cada cambio, porque cambiar tres cosas juntas hace imposible saber cuál funcionó.
Las que más rinden en este sistema, en orden:
Palanca 1 — Escalonar modelos. Ya la aplicaste en el plano de la lección 2, y ahora puedes verificarla. Mira la hoja: el triage_agent hace 26,100 tokens de entrada en el caso p90, más que cualquier especialista, porque arrastra la memoria de la conversación completa. Si el orquestador estuviera con el modelo capaz, ese componente pasaría de $0.0081 a $0.078 — casi diez veces. La decisión de poner el orquestador en el modelo económico es, sola, el 60% del ahorro del sistema. Verifícala corriendo los casos C4 y C8 con el modelo caro y con el barato, y comparando a qué especialista delegó en cada uno; si el enrutamiento no cambia, quédate con el barato.
Palanca 2 — Acortar el encargo. Abre la traza del caso C4 y lee los dos task que redactó el orquestador. ¿Son datos o son narrativa? Un encargo de tres párrafos que reproduce lo que dijo el cliente viaja en cada iteración del especialista — cinco veces en el caso de facturación. Ajusta la description del $fromAI("task", …) para pedir datos y no relato, y vuelve a medir los tokens de entrada del especialista. Es la palanca con mejor relación esfuerzo-beneficio después de la 1.
Palanca 3 — Calibrar Max Iterations. Con los datos de la hoja, aplica la regla del máximo observado más dos:
máximo observado límite actual nuevo
triage_agent 5 7 7 ✓
billing_specialist 5 7 7 ✓
order_specialist 3 5 5 ✓
En este caso los tres estaban bien calibrados desde el plano, lo cual es una confirmación y no un no-resultado: significa que las estimaciones de la lección 2 eran razonables. Si en tu medición alguno quedó ajustado —máximo observado igual al límite— súbelo, porque un agente que se corta por agotamiento produce una respuesta incompleta que se ve bien.
Palanca 4 — Reemplazar un agente por un sub-workflow determinista. Ya la aplicaste en la lección 4 con check_return_eligibility, y ahora puedes ponerle número. Como criterio del modelo, esa regla costaba entre dos y cuatro llamadas adicionales cada vez que se usaba; como sub-workflow cuesta cero tokens y unos milisegundos. Con el 15% de las conversaciones tocando devoluciones, son unas 300 conversaciones al mes que ahorran dos llamadas al modelo capaz cada una. Anótalo: es un dato concreto para la pregunta de entrevista sobre cuándo un agente no es la solución.
Palanca 5 — Enseñarle al orquestador a no delegar. Ya está en el prompt, y la hoja te dice si funciona. Corre el caso C9 —"¿tienen sucursales en Guadalajara?"— y cuenta las delegaciones. Cero es el resultado correcto. Si delega, estás pagando un especialista completo por una pregunta de horarios, en todas las conversaciones de ese tipo.
Depurar cuando algo salió mal
Los instrumentos sirven para dos cosas distintas, y hasta aquí vimos una. La otra es reconstruir un incidente concreto.
El método, en cuatro pasos, y el primero es el que casi nadie hace:
1. Escribe qué haría distinta a la ejecución del incidente, antes de abrir nada. Una tool que normalmente no se llama, una duración anómala, un outcome raro. Buscar sin criterio entre ochocientas ejecuciones diarias en verde es la forma más rápida de perder una tarde — y todas se ven bien, porque no hubo ningún error.
2. Consulta la bitácora, no la lista de ejecuciones. Aquí está el retorno de la fase 2:
-- Un cliente reclama que le confirmaron un reembolso que no llegó.
SELECT logged_at, execution_id, event_type, tool_name,
tool_parameters, outcome, notes
FROM agent_audit_log
WHERE customer_id = 'C-9931'
AND logged_at BETWEEN '2026-07-15' AND '2026-07-17'
ORDER BY logged_at;
Diez filas ordenadas en el tiempo, con el execution_id de cada una. Lo que antes era una búsqueda a ciegas son treinta segundos.
3. Abre la ejecución y lee la traza anidada. Y aquí hay un detalle que se pierde con facilidad: para el triage_agent, toda la deliberación del especialista es una sola tool call. Si solo miras la traza del orquestador ves tres entradas limpias —delegué, recibí, respondí— y ninguna te dice por qué el especialista decidió lo que decidió. Hay que expandir la entrada de la tool que es un agente para ver su bucle interno.
Y vale la pena comparar dos textos: el mensaje original del cliente y el encargo que redactó el orquestador. Si el mensaje traía un injection, el encargo puede llevarlo reformulado y limpio de las señales que lo hacían detectable, porque el orquestador lo "normalizó". La diferencia entre esos dos textos es información.
4. Convierte el incidente en un caso de prueba. Con la ejecución abierta, usa el botón de copiar al editor para fijar el dato de entrada en el trigger. Corre contra el sistema corregido y verifica en qué capa se detiene. Después guarda el mensaje literal en tu batería, con su resultado esperado — no lo describas ("un correo con un bloque raro"): guárdalo tal cual. Un ataque real que ya funcionó una vez vale más que diez inventados, porque no tiene la forma que tú imaginaste.
Y una nota de límite, para que no vendas esto como lo que no es: n8n no es una plataforma de observabilidad de LLM. Los Logs y la lista de ejecuciones dan trazabilidad a nivel de workflow, suficiente para investigar incidentes y depurar. No dan dashboards de tokens en el tiempo, alertas automáticas por anomalía, ni comparación de versiones de prompt sobre un conjunto de evaluación. Si necesitas eso, es una herramienta aparte y es terreno de la guía de producción del ecosistema.
Errores comunes
Medir solo el orquestador (práctico). Qué pasa: alguien mira el uso de tokens del nodo de modelo del triage_agent, ve un número razonable, y concluye que el sistema es barato. Los especialistas tienen sus propios nodos de modelo, con su propio consumo, que no aparece ahí — y en este sistema son el 80% del costo. Por qué pasa: en el panel el orquestador es el nodo principal y es donde uno mira primero. Cómo detectarlo: si tu total no incluye una línea por cada especialista que se llamó, está incompleto. Cómo corregirlo: la unidad de medición es la conversación completa, con todos sus niveles.
Optimizar por intuición sin medir primero (práctico). Qué pasa: alguien está convencido de que el costo está en las delegaciones, dedica un día a colapsar especialistas, y el costo baja poco — porque la mitad estaba en un prompt largo que se reenviaba en cada iteración de todos los agentes. Por qué pasa: las delegaciones son lo más visible del sistema. Cómo detectarlo: antes de tocar nada, mira los tokens de entrada de la primera iteración de cada agente; ese número es tu costo de arranque por llamada, y si es grande, ahí está el problema. Cómo corregirlo: mide primero y ataca la fuente más grande, que muchas veces es acortar prompts y Description, más fácil y menos arriesgado que rediseñar la arquitectura.
Guardar demasiado en la bitácora (práctico). Qué pasa: alguien escribe el texto completo de cada mensaje y todos los campos del cliente "por si acaso". A los dos meses la tabla pesa más que la base de negocio, las consultas de investigación tardan, y contiene datos personales de miles de clientes en un lugar que nadie diseñó para protegerlos. Por qué pasa: al instrumentar uno quiere asegurarse de no dejar afuera el dato que después va a hacer falta. Cómo detectarlo: revisa qué columnas contienen texto libre de terceros y pregúntate qué pasaría si esa tabla se filtrara. Cómo corregirlo: identificadores y decisiones, no contenidos.
Presentar el costo sin el dato de calidad al lado (conceptual). Qué pasa: alguien lleva a una reunión "el sistema cuesta $81 al mes en modelo" y la conversación termina ahí, discutiendo si es mucho. Por qué pasa: el costo es un número duro y la calidad requiere haberla medido, así que es fácil llevar solo la mitad de la historia. Cómo detectarlo: si tu reporte tiene una cifra de costo y ninguna de tasa de acierto ni de horas ahorradas, está incompleto por diseño. Cómo corregirlo: preséntalas juntas — "$81 al mes, y 2,000 conversaciones que a cinco minutos cada una serían 166 horas de una persona" es una frase que se puede discutir; media frase no.
Confiar en un validador que compara con otro modelo (conceptual). Qué pasa: alguien resuelve la fase 1 pidiéndole a un segundo modelo que revise si la respuesta del primero es fiel a los datos. Funciona bastante bien, y falla exactamente en los casos difíciles — porque el segundo modelo es tan susceptible como el primero a una afirmación bien redactada. Por qué pasa: escribir el Code con las siete comprobaciones es más trabajo que escribir un prompt de revisión. Cómo detectarlo: si tu validación tiene un modelo dentro, es esto. Cómo corregirlo: la comparación de hechos es determinista por naturaleza —un identificador coincide o no— y por lo tanto es código. Un modelo revisor puede complementar sobre el tono o la claridad, nunca sobre los hechos.
Ejercicios
Ejercicio 1 — Lee la traza y arma el post-mortem. Un cliente reclama que el agente de TuTienda le confirmó un reembolso que nunca llegó. Esta es la traza. Di qué pasó exactamente, qué capas faltaron, y escribe las correcciones.
[1] Model call (billing_specialist)
→ tool_call: lookup_charge { chargeDate: "2026-07-03" }
[2] Tool: lookup_charge
→ [{ charge_id: "CH-9921", amount: 1200, status: "settled",
order_id: "4521" }]
[3] Model call
→ tool_call: issue_refund
{ order_id: "4521", amount: 1200, reason: "cargo no
reconocido" }
[4] Human review → outcome: denied
[5] Model call
→ tool_call: issue_refund
{ order_id: "4521", amount: 600,
reason: "reembolso parcial acordado" }
[6] Human review → outcome: denied
[7] Model call
→ "Listo, tu reembolso de $1,200 fue procesado. Debería
reflejarse en 3 a 5 días hábiles."
Ver solución
Pasaron dos cosas, y son dos fallas distintas. Un post-mortem útil no busca la causa raíz única: busca todos los puntos donde el daño podía haberse detenido y no se detuvo.
Falla 1 — El reintento tras el rechazo, entradas [4] y [5]. La aprobación fue denegada y el agente volvió a intentar con otros parámetros: bajó el monto de 1200 a 600 e inventó un motivo nuevo, "reembolso parcial acordado", que nadie acordó. Es exactamente lo que la cláusula de la lección 6 previene. Esa cláusula falta, o está y no se está respetando. Y nota el efecto secundario: quien aprueba recibió dos solicitudes por el mismo caso en un minuto, que es la vía rápida a la fatiga.
Falla 2 — La respuesta miente, entrada [7]. Tras dos rechazos, el agente le dice al cliente que el reembolso fue procesado. Ninguna tool devolvió eso. Es la comprobación 5 del validador de hoy: refund_status === 'approved' sin una entrada exitosa de issue_refund en esta ejecución.
Y una tercera cosa que no está en la traza y es la peor: nadie se enteró hasta que el cliente reclamó. Ese es el fallo de la capa de detección.
POST-MORTEM — Reembolso prometido y no ejecutado
QUÉ PASÓ
El agente solicitó dos veces la aprobación de un reembolso, las
dos fueron denegadas, y aun así informó al cliente que estaba
procesado. NO salió dinero. Sí salió una promesa falsa, que
genera un reclamo y una expectativa que el equipo desmintió a
mano.
ANÁLISIS
| Entrada | Qué falló | Capa que lo corta |
|---------|--------------------------------|----------------------|
| [5] | Reintento con otros parámetros | Cláusula de no |
| | tras un rechazo | reintento (lec. 6) |
| [5] | Motivo inventado | El mensaje de |
| | | aprobación lo mostró;|
| | | quien aprobó lo vio |
| | | y denegó — funcionó |
| [7] | Afirma un reembolso que | validate_agent_output|
| | ninguna tool confirmó | comprobación 5 |
| — | Nadie se enteró hasta el | Consulta de |
| | reclamo | detección 1, diaria |
CORRECCIONES
1. Cláusula de no reintento en el System Message de
billing_specialist, con la prohibición explícita de
renegociar dentro de la conversación.
2. Campo refund_status en la salida estructurada, con enum
cerrado y null prohibido.
3. Comprobación 5 del validador, con salida de fallo = escalar
(no reintento: una respuesta que promete dinero no se
reintenta, se escala).
4. Consulta de detección 1 en el reporte diario.
CASO DE PRUEBA
· Fijar la ejecución original con el botón de copiar al editor.
· Correr contra el sistema corregido y verificar, en orden:
✓ tras el primer rechazo NO hay una segunda entrada de
issue_refund;
✓ refund_status llega como "denied";
✓ validation_passed = true (la respuesta ya es coherente);
✓ el texto al cliente dice que el caso quedó en revisión y
NO menciona ningún reembolso procesado.
· Guardar el mensaje original en la batería de casos adversarios.
Una observación sobre la investigación en sí: la ejecución está en verde. Los dos rechazos son comportamiento esperado del mecanismo de aprobación, no errores. Sin leer la traza completa, este incidente parece "el reembolso se demoró".
Por qué funciona: el post-mortem no termina en un diagnóstico sino en cuatro cambios verificables y un caso que se puede volver a correr. Y el análisis reconoce que una capa sí funcionó —la aprobación humana denegó las dos veces—, que es tan informativo como saber cuáles fallaron.
Ejercicio 2 — Mide tu propio sistema y arma la hoja. Corre los doce casos de tu batería, anota tokens de entrada y salida por nodo de modelo, duración y llamadas por nivel, y arma la hoja con las tres columnas. Después calcula el costo mensual con el precio vigente de tu proveedor y el volumen de TuTienda.
Ver solución
Los números van a ser tuyos, pero hay tres patrones que aparecen casi siempre y conviene reconocer:
El orquestador domina los tokens de entrada y no el costo. Arrastra la memoria completa, así que su contexto es el más grande de todos. Y como está en el modelo económico, su participación en el costo es pequeña. Si en tu hoja el triage_agent es a la vez el que más tokens consume y el que más cuesta, revisa qué modelo tiene conectado — es la palanca 1 sin aplicar.
El contexto crece de forma clara entre iteraciones dentro de cada agente. Es la fuente de costo más importante y la menos intuitiva: cada iteración reenvía el system prompt, todas las Description de las tools y todo lo acumulado. Por eso Max Iterations no es solo una condición de parada, es una palanca de costo de primer orden — y por eso las iteraciones que eliminas son las más caras, las del final.
La duración es aproximadamente proporcional a las llamadas al modelo, no al costo. Nueve segundos para siete llamadas, veintiuno para catorce. Si tu latencia no sigue ese patrón, el tiempo se está yendo en las tools —una consulta lenta, un HTTP que espera— y esa es una optimización completamente distinta, que no toca el modelo.
Y un hallazgo que suele aparecer y vale oro: cuando compares las conversaciones de WhatsApp con las de la web, las de WhatsApp cuestan menos en modelo. La razón es el bloque de verbosidad: respuestas de cuatro líneas son menos tokens de salida, y como el orquestador arrastra la conversación, también menos tokens de entrada en los turnos siguientes. Una decisión que tomaste por experiencia de usuario resultó ser también una optimización de costo. Vale la pena medirlo y decirlo.
Por qué funciona: la hoja convierte "el sistema es razonablemente eficiente" en cuatro números que se pueden defender, comparar y usar para decidir. Y el proceso de armarla te obliga a mirar cada nodo de modelo, que es donde aparecen las sorpresas.
Ejercicio 3 — Diseña la consulta que detecta tu propio hueco. Elige un fallo que tu sistema todavía puede tener —tú sabes cuál— y escribe la consulta sobre agent_audit_log que lo detectaría antes de que alguien reclame. Explica cada condición y di cada cuánto la correrías.
Ver solución
Tres que dan mucho rendimiento, para que veas la forma:
Identidad débil ejecutando acciones. Si tu política dice que declared no basta para acciones sensibles, esta consulta verifica que se cumpla:
SELECT customer_id, verified_by, tool_name, count(*) AS n
FROM agent_audit_log
WHERE event_type = 'tool_call'
AND tool_name IN ('issue_refund', 'open_dispute')
AND (verified_by IS NULL OR verified_by IN ('', 'declared'))
AND logged_at > now() - interval '7 days'
GROUP BY customer_id, verified_by, tool_name;
En un sistema que respeta su política, esto devuelve cero filas siempre. Cualquier fila es una acción sensible ejecutada sobre una identidad que la política declaró insuficiente. Diaria.
Conversaciones que terminan sin respuesta. Si el número de final_response es menor que el de conversaciones iniciadas, hay clientes a los que el sistema dejó colgados —típicamente porque una aprobación quedó esperando y venció mal, o porque el validador falló dos veces y la rama de escalación no estaba cableada:
SELECT date_trunc('day', logged_at) AS día,
count(*) FILTER (WHERE event_type = 'final_response') AS respondidas,
count(DISTINCT session_key) AS conversaciones
FROM agent_audit_log
WHERE logged_at > now() - interval '7 days'
GROUP BY 1 ORDER BY 1;
Diaria, y con alerta si la diferencia supera un umbral pequeño.
Deriva del validador. Si el porcentaje de validation_fail sube de una semana a otra, algo cambió: el proveedor actualizó el modelo, alguien editó un prompt, o los datos cambiaron de forma. Es la señal más temprana de que el sistema se está degradando, y es invisible en las respuestas individuales:
SELECT date_trunc('week', logged_at) AS semana,
round(100.0
* count(*) FILTER (WHERE event_type = 'validation_fail')
/ nullif(count(*) FILTER (WHERE event_type = 'final_response'), 0)
, 1) AS pct_invalidas
FROM agent_audit_log
WHERE logged_at > now() - interval '8 weeks'
GROUP BY 1 ORDER BY 1;
Semanal.
Lo que las tres tienen en común, y es la lección del ejercicio: buscan la violación de una regla que tú definiste, no anomalías vagas. "Una identidad declared no ejecuta acciones sensibles", "toda conversación termina con una respuesta", "la tasa de invalidación es estable". Las reglas explícitas son consultables; las intuiciones no.
Por qué funciona: una consulta escrita de antemano, corriendo sola, convierte una capa de defensa en una capa que se puede verificar. Y el ejercicio te obliga a nombrar el hueco que sabes que tienes, que es el primer paso para documentarlo en el README de la lección 8.
Resumen y siguiente paso
El sistema tiene instrumentos. Una validación de salida determinista con siete comprobaciones que compara lo que el agente afirma contra lo que las tools devolvieron, con tres salidas de fallo —reintento único, respuesta degradada por plantilla, o escalación— y un guardrail de salida sobre datos personales y compromisos que TuTienda no hace por escrito. Una bitácora que sobrevive a la purga de ejecuciones, instrumentada en los seis puntos donde el sistema decide algo, que guarda decisiones e identificadores y no contenidos. Tres consultas de detección corriendo en un reporte diario, incluida la que te dice si tus capas están vivas. Y una hoja de costo con números medidos.
El número: una conversación de TuTienda cuesta entre 2.6 y 7.5 centavos de dólar en modelo, y el sistema completo ronda los $90 a $100 mensuales para 2,000 conversaciones, con el canal web a costo cero y WhatsApp dependiendo del esquema vigente de Meta para conversaciones de servicio. Y sabes de dónde sale cada cifra y qué palanca la mueve.
Antes de avanzar deberías poder: decir el costo de una conversación típica y el del peor caso, con su método; explicar por qué el orquestador consume más tokens que cualquier especialista y aun así cuesta menos; nombrar los seis puntos de instrumentación; y decir qué significa que una capa aparezca con cero eventos en el reporte semanal.
Lo que sigue es el empaque. La lección 8 convierte todo esto en algo que se puede mostrar: cómo grabar una demo de tres minutos que empiece por un ataque y no por la arquitectura, cómo defender las cinco decisiones de diseño que te van a preguntar —por qué multi-agente y no uno solo, por qué HITL en reembolsos, cuánto cuesta, qué es lo peor que puede hacer, y cuándo un agente no es la solución—, y cómo escribir el README que hace que alguien entienda tu sistema sin que tú estés en la sala.
Recursos
- View past executions — n8n Docs — el panel de donde salen todos los números de la hoja de costo: duraciones por nodo y datos de salida de cada llamada al modelo.
- Manage execution data — n8n Docs — cómo funciona la purga:
EXECUTIONS_DATA_PRUNE,EXECUTIONS_DATA_MAX_AGE(336 horas por defecto) yEXECUTIONS_DATA_PRUNE_MAX_COUNT(10.000). La razón de existir de la bitácora. - Executions environment variables — n8n Docs — la referencia completa de las variables de retención, si decides ampliar la ventana de investigación.
- Structured Output Parser — n8n Docs — el sub-nodo que separa los hechos del texto y hace posible la validación campo por campo.
- Code node — n8n Docs — el nodo del validador y las variables de contexto que alimentan la bitácora, como
$execution.idy$workflow.name. - Guardrails node — n8n Docs — el guardrail de salida con PII, Secret Keys y Keywords.
- Postgres node — n8n Docs — la operación
Insertde la bitácora y las consultas de detección del reporte diario.