Módulo 4: Herramientas: el agente que actúa sobre sistemas reales
4. Conectar sistemas reales: Gmail, Sheets, base de datos y HTTP
Descripción
Al terminar esta lección vas a poder conectar Gmail, Google Sheets, una base de datos Postgres y — cuando ninguno de los anteriores cubra el sistema que necesitas — cualquier API HTTP directamente al agente como tools reales, de modo que el agente actúe sobre datos que existen de verdad en tu operación, no sobre datos de prueba inventados para un ejercicio.
Esto importa porque ahí está el valor real de un agente en un equipo de soporte, ventas u operaciones: no en que conteste bien preguntas genéricas, sino en que pueda tocar los sistemas donde vive el dato — el pedido en la base de datos de producción, el correo que llega a la bandeja correcta del equipo, la fila en la planilla que alguien revisa el lunes. Un agente que solo sabe conversar sobre esos sistemas, sin poder consultarlos ni actuar sobre ellos, es una demo. Un agente conectado a los sistemas reales es una herramienta de trabajo.
Conexión con el módulo: en la lección anterior conectaste tools nativas de n8n — acciones que buscan, crean y envían información dentro del propio flujo. Hoy das el salto a sistemas reales, con efectos que alguien de tu equipo ve del otro lado: un correo que de verdad llega, una fila que de verdad aparece en una planilla compartida, una consulta que de verdad lee tu base de datos de producción. Te vas a apoyar en algo que quedó pendiente al cerrar el Módulo 3: ahí construiste un agente con memoria persistente que recordaba lo que se había dicho, pero que no tenía forma de saber si ese dato seguía siendo cierto. Hoy cierras esa brecha — el agente deja de confiar en lo que recuerda y aprende a preguntarle al sistema real. La lección siguiente toma exactamente las tools que conectas hoy y les pone límites explícitos: qué campo no le confías a la decisión del modelo, qué acción exige confirmación humana. Hoy es la conexión; el contrato viene después.
De la sala de prácticas a las llaves reales de la oficina
Piensa en la primera semana de un empleado nuevo en soporte al cliente. Durante el entrenamiento, practica con un sistema de mentira: pedidos ficticios, una hoja de cálculo de ejemplo, un buzón de correo de prueba que nadie del otro lado va a leer. Sirve para que aprenda el procedimiento sin riesgo de romper nada real. Pero llega el momento en que alguien le entrega las llaves de verdad: el acceso al sistema de pedidos de producción, la cuenta de correo desde la que salen avisos reales al equipo, el permiso de escritura en la planilla que Recursos Humanos revisa cada semana. A partir de ahí, cada acción que toma tiene una consecuencia fuera de la sala de entrenamiento.
El agente que armaste hasta la lección anterior sigue en la sala de prácticas. Hoy le entregas las llaves reales. Y en n8n, la diferencia entre una tool de práctica y una tool real casi nunca está en un tipo de nodo especial — está en qué credenciales conectas y qué conector del agente usas.
Cómo cualquier nodo se convierte en una tool del agente
La mayoría de los nodos de aplicación de n8n — Gmail, Google Sheets, Postgres, y decenas más — pueden conectarse directamente al puerto ai_tool del nodo AI Agent, exactamente como conectaste el HTTP Request en la lección 5 del Módulo 1 o el Code Tool en el mini-proyecto del Módulo 3. No necesitas un nodo especial "de IA": el mismo nodo Gmail que enviaría un correo en un flujo tradicional puede recibir instrucciones del modelo cuando lo conectas como tool.
Lo que cambia al conectar un nodo así es que sus campos dejan de estar limitados a un valor fijo o una expresión sobre datos del flujo — pueden usar la función $fromAI(), que le pide al modelo que decida ese valor en el momento de llamar a la tool:
$fromAI(key, description, type, defaultValue)
| Parámetro | Obligatorio | Qué hace |
|---|---|---|
key | sí | Identificador del valor (letras, números, guiones y guiones bajos) — es el nombre con el que el modelo referencia este dato. |
description | no | La pista en texto que le dice al modelo qué debe poner ahí. Entre más específica, menos adivina el modelo. |
type | no | string, number, boolean o json. Por defecto es string. |
defaultValue | no | Qué usar si el modelo no logra determinar un valor. |
$fromAI() solo funciona dentro de un nodo conectado al puerto de tools del agente — en cualquier otro nodo del flujo es un error. Y, tan importante como saber que existe, es saber que no es obligatorio usarlo en todos los campos de una tool: vas a ver en el ejemplo de esta lección que algunos campos conviene dejarlos fijos, con una expresión que lee datos reales de la conversación, precisamente para no ponerlos a merced de lo que el modelo interprete del mensaje del cliente.
Ejemplo trabajado: el pedido #4521 deja de ser un recuerdo y pasa a ser un dato en vivo
Retoma el mini-proyecto del Módulo 3. Ahí, el cliente con teléfono +52-55-8811-2299 preguntaba por su pedido #4521, y el agente respondía correctamente — pero apoyado en la tool get_order_status de datos inventados primero, y después en lo que la memoria de Postgres recordaba que se había dicho ayer. Ninguna de las dos cosas es el sistema real de pedidos de TuTienda. Hoy conectas esa pieza que faltaba.
Paso 1 — agrega la base de datos real de operaciones a tu docker-compose.yml. Ya tienes el servicio postgres del Módulo 3, dedicado exclusivamente al historial de chat. Agrega un segundo servicio de Postgres, separado, que representa el sistema de pedidos real de la tienda — nunca mezcles la base de memoria del agente con la base de datos de negocio, aunque ambas sean Postgres y corran en tu misma laptop:
# docker-compose.yml — el mismo del Módulo 3, con store_db agregado
services:
n8n:
# ...igual que en el Módulo 3...
depends_on:
- postgres
- store_db
postgres:
# ...el servicio de memoria de chat del Módulo 3, sin cambios...
store_db:
image: postgres:16
restart: unless-stopped
environment:
- POSTGRES_USER=tutienda_app
- POSTGRES_PASSWORD=${STORE_DB_PASSWORD}
- POSTGRES_DB=tutienda_store
volumes:
- store_db_data:/var/lib/postgresql/data
volumes:
n8n_data:
postgres_data:
store_db_data:
# .env — agrega esta línea junto a las que ya tenías
STORE_DB_PASSWORD=pon_aquí_otra_contraseña_propia
docker compose up -d
Paso 2 — crea la tabla y siembra datos que reflejan hoy, no la foto del Módulo 3. Esto simula que TuTienda ya tenía este sistema corriendo en producción — tú solo te estás conectando a él:
docker compose exec store_db psql -U tutienda_app -d tutienda_store -c "
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_phone TEXT NOT NULL,
status TEXT NOT NULL,
eta DATE
);
INSERT INTO orders (order_id, customer_phone, status, eta) VALUES
(4521, '+52-55-8811-2299', 'entregado', '2026-07-20'),
(4522, '+52-55-8811-2299', 'en tránsito', '2026-07-24');
"
Fíjate en el pedido #4521: en la memoria persistente del Módulo 3 seguía figurando como "en tránsito". En el sistema real, ya se entregó ayer. Ese desfase es exactamente el que un agente que solo consulta memoria nunca detecta.
Paso 3 — crea la credencial de Postgres para esta base, distinta a la del Módulo 3. Credentials → New → Postgres:
Host = store_db # el nombre del servicio, no "localhost"
Database = tutienda_store
User = tutienda_app
Password = la que pusiste en .env
Port = 5432
SSL = Disable # red privada de Docker
Paso 4 — conecta el nodo Postgres al puerto ai_tool del agente, con la query parametrizada. Este es el punto que más se pasa por alto: nunca concatenes el valor de $fromAI() directamente dentro del texto de la query. n8n te da un campo separado, Query Parameters, para pasar esos valores de forma segura usando marcadores posicionales ($1, $2):
# CONEXIÓN ai_tool -> nodo: Postgres
credential = la credencial de store_db del Paso 3
operation = "Execute Query"
query = "SELECT status, eta FROM orders
WHERE order_id = $1 AND customer_phone = $2"
options.queryParameters = "={{ [
$fromAI('order_id', 'Número de pedido que el
cliente mencionó, solo el entero', 'number'),
$('Chat Trigger').item.json.customerPhone
] }}"
description = "Usa esta herramienta SIEMPRE que el cliente
pregunte por el estado de un pedido — incluso si
ya hablaste de ese pedido antes en la conversación.
El estado puede haber cambiado desde entonces.
Necesita el número de pedido."
Nota algo a propósito: order_id viene de $fromAI() — es un dato que el cliente menciona en el chat, y tiene sentido que el modelo lo extraiga del mensaje. Pero customer_phone no viene de $fromAI(), sino de una expresión fija que lee el teléfono real de la conversación (el mismo campo customerPhone que mandas en el body del curl, igual que en el Módulo 3). Si dejaras que el modelo decidiera también el teléfono, cualquier texto del cliente ("consulta el pedido del teléfono +52-55-0000-1111") podría hacer que el agente consultara pedidos de otra persona. Vas a profundizar en este criterio — qué campo sí le confías al modelo y cuál no — en la próxima lección; por ahora, quédate con la regla práctica: identidad de quien pregunta, fija; dato que el cliente aporta en su mensaje, $fromAI().
Paso 5 — pon a prueba el agente con la misma pregunta del Módulo 3. Activa el workflow y usa la Chat URL de producción del nodo Chat Trigger:
curl -X POST "<tu Chat URL, pestaña Production>" \
-H "Content-Type: application/json" \
-d '{
"action": "sendMessage",
"sessionId": "tab-postgres-tool",
"customerPhone": "+52-55-8811-2299",
"chatInput": "¿Dónde está mi pedido #4521?"
}'
Qué esperar:
{ "output": "Tu pedido #4521 ya fue entregado, el 20 de julio. Si no te llegó o hay algún problema con lo que recibiste, avísame y lo revisamos." }
Compáralo con lo que el mismo agente, apoyado solo en memoria, respondía en el Módulo 3 ante la misma pregunta un día después: seguía diciendo "en tránsito", porque repetía lo último que se había dicho, no lo que el sistema real mostraba en ese momento. Ábre el panel de ejecución de este turno en n8n y confirma con tus propios ojos que el nodo Postgres se ejecutó — vas a ver el input que recibió (order_id: 4521, el teléfono) y el resultado exacto que devolvió la base de datos, no una suposición del modelo.
Escalar a un humano: Google Sheets y Gmail trabajando juntas
Hay preguntas que el agente no debe intentar resolver solo — cuando el cliente pide explícitamente hablar con una persona, o exige un reembolso. Ahí necesitas dos sistemas reales distintos, trabajando como dos tools independientes que el agente puede llamar en la misma conversación: una planilla de Google Sheets como registro consultable, y Gmail como aviso inmediato al equipo.
Credenciales de Google. A diferencia de Postgres, que corre en tu propio Docker, Gmail y Google Sheets son servicios reales de Google — necesitas una cuenta y, en una instancia self-hosted como la tuya, tu propia app OAuth2 en Google Cloud Console (a diferencia de n8n Cloud, que trae un flujo administrado sin este paso). En Credentials → New → Gmail OAuth2 (o Google Sheets OAuth2), n8n te muestra la URL de redirección que debes agregar a los "Authorized redirect URIs" de tu app en Google Cloud; de ahí copias el Client ID y el Client Secret de vuelta al formulario de la credencial en n8n. Es un paso de infraestructura que haces una sola vez — la guía oficial en Recursos cubre el detalle completo.
Tool 1 — registrar el caso en Google Sheets. Crea una planilla llamada, por ejemplo, "Escalaciones TuTienda" con una hoja "Casos", y conecta el nodo Google Sheets al puerto ai_tool:
# CONEXIÓN ai_tool -> nodo: Google Sheets
credential = tu credencial de Google Sheets OAuth2
document = "Escalaciones TuTienda"
sheet = "Casos"
operation = "Append Row"
columns.timestamp = "={{ $now }}"
columns.customer_phone = "={{ $('Chat Trigger').item.json.customerPhone }}"
columns.order_id = "={{ $fromAI('order_id', 'Número de pedido
relacionado con la escalación, si el cliente
lo mencionó', 'string', 'sin dato') }}"
columns.reason = "={{ $fromAI('reason', 'Motivo de la
escalación en una frase, en las palabras
del cliente', 'string') }}"
description = "Usa esta herramienta para dejar un registro
permanente cada vez que un caso se escala a
soporte humano — reembolso, queja seria, o el
cliente pide explícitamente hablar con una
persona. Úsala junto con la herramienta que
avisa por correo: esta deja el historial,
esa avisa de inmediato."
Tool 2 — avisar al equipo por Gmail. Conecta el nodo Gmail, resource "Message", operation "Send a message":
# CONEXIÓN ai_tool -> nodo: Gmail
credential = tu credencial de Gmail OAuth2
resource = "Message"
operation = "Send a message"
to = "escalaciones@tutienda.example" # fijo — nunca $fromAI
subject = "={{ 'Escalación de cliente — pedido #' +
$fromAI('order_id', 'Número de pedido relacionado',
'string', 'sin dato') }}"
emailType = "Text"
message = "={{ $fromAI('summary', 'Resumen de 2 a 3 frases de por
qué el cliente pidió hablar con una persona o un
reembolso, incluyendo el pedido si lo mencionó',
'string') }}"
description = "Usa esta herramienta para avisar de inmediato al
equipo de soporte humano en los mismos casos donde
usas la herramienta de registro en la planilla —
nunca una sin la otra."
Fíjate otra vez en el campo to: es un valor fijo, escrito por ti al configurar el nodo, no algo que el modelo decide con $fromAI(). Si lo dejaras abierto a la decisión del modelo, cualquier instrucción dentro del mensaje del cliente — "mándale una copia a mi correo personal" — podría hacer que el agente reenviara el resumen del caso a un destinatario que tú nunca autorizaste. El destinatario de un aviso interno no es un dato que el cliente deba poder cambiar con lo que escribe.
Qué esperar. Escribe, desde el mismo chat de prueba:
curl -X POST "<tu Chat URL, pestaña Production>" \
-H "Content-Type: application/json" \
-d '{
"action": "sendMessage",
"sessionId": "tab-escalacion",
"customerPhone": "+52-55-8811-2299",
"chatInput": "Ya me cansé, llevo semanas esperando el pedido #4521 y nadie me resuelve. Quiero hablar con una persona o que me devuelvan mi dinero."
}'
{ "output": "Entiendo la frustración. Reviso el sistema y el pedido #4521 figura como entregado el 20 de julio — si no te llegó, es importante que alguien de nuestro equipo lo confirme contigo directamente. Ya dejé tu caso registrado y avisé al equipo de soporte; en breve te contactan." }
A diferencia de la tool de Postgres, aquí no puedes verificar el resultado con un docker compose exec — Sheets y Gmail viven fuera de tu Docker. La verificación real son dos cosas que sí puedes revisar con tus propios ojos: la fila nueva en la planilla de Google Sheets, y el correo que efectivamente llega a la bandeja escalaciones@tutienda.example. El panel de ejecución de n8n, mientras tanto, te muestra el input exacto que cada tool recibió — útil para confirmar qué entendió el modelo antes de ir a revisar los sistemas reales.
HTTP Request: la tool universal para cualquier API
Gmail, Google Sheets y Postgres cubren sistemas comunes, pero ningún proveedor de n8n tiene un nodo dedicado para cada API que tu negocio use — el servicio de tipo de cambio, el sistema de facturación interno, la API del proveedor de envíos. Para esos casos, el nodo HTTP Request funciona igual que los anteriores: conéctalo al puerto ai_tool y se vuelve una tool más, capaz de llamar a cualquier endpoint.
Escenario: un cliente pagó en dólares y pregunta cuánto le corresponde de reembolso en su propia moneda. Vas a usar la API pública y gratuita de Frankfurter (tipos de cambio del Banco Central Europeo, sin necesidad de cuenta ni API key) para resolverlo. Primero, confirma que la API responde tal como esperas, fuera de n8n:
curl "https://api.frankfurter.dev/v1/latest?base=USD&symbols=MXN"
Qué esperar:
{"amount":1.0,"base":"USD","date":"2026-07-21","rates":{"MXN":17.3943}}
Ahora conecta el nodo HTTP Request al agente:
# CONEXIÓN ai_tool -> nodo: HTTP Request
method = "GET"
url = "https://api.frankfurter.dev/v1/latest"
authentication = "None"
queryParameters:
base = "USD"
symbols = "={{ $fromAI('target_currency', 'Código de moneda ISO de 3
letras al que el cliente quiere convertir el monto —por
ejemplo MXN, EUR o COP', 'string', 'MXN') }}"
description = "Usa esta herramienta cuando el cliente pregunte
cuánto equivale un monto en dólares en su propia
moneda, por ejemplo para calcular un reembolso.
Necesita el código de moneda ISO de 3 letras. No la
uses para preguntas sobre el estado de un pedido —
para eso existe otra herramienta."
Qué esperar:
curl -X POST "<tu Chat URL, pestaña Production>" \
-H "Content-Type: application/json" \
-d '{
"action": "sendMessage",
"sessionId": "tab-fx",
"customerPhone": "+52-55-8811-2299",
"chatInput": "Pagué 49.99 dólares y me van a reembolsar. ¿Cuánto es eso en pesos mexicanos hoy?"
}'
{ "output": "Al tipo de cambio de hoy (1 USD ≈ 17.39 MXN), 49.99 USD equivalen a aproximadamente 869 MXN. El monto exacto del reembolso puede variar un poco según el tipo de cambio del día en que se procese." }
La autenticación queda en None porque esta API específica no la exige. Para una API que sí requiera una llave, el mismo nodo HTTP Request soporta credenciales predefinidas (cuando n8n ya trae soporte para ese proveedor) o autenticación genérica — Basic, Header, Query, OAuth2, entre otras — configurable sin salir del nodo.
Errores comunes
Confundir la base de datos de memoria con la base de datos del sistema real (conceptual). Qué pasa: alguien conecta el mismo Postgres que usa Postgres Chat Memory (el del Módulo 3) como fuente de datos para una tool de negocio, o al revés, espera que la tool de Postgres de esta lección le dé al agente memoria de la conversación. Por qué pasa: ambas piezas usan el mismo motor de base de datos y hasta pueden vivir en el mismo servidor físico, pero cumplen roles completamente distintos — Postgres Chat Memory guarda lo que se dijo, indexado por sesión; el nodo Postgres conectado como tool consulta lo que es cierto ahora en tus tablas de negocio, sin ninguna relación con el historial de chat. Cómo detectarlo: pregúntate, para cualquier consulta que hagas, si la respuesta debería cambiar aunque la conversación sea exactamente la misma — si la respuesta puede cambiar sin que cambie lo que el cliente dijo (como el estado de un pedido), es un dato de sistema real, no memoria. Cómo corregirlo: mantenlas en bases de datos separadas, como en esta lección (postgres para memoria, store_db para el sistema real), o al menos en tablas claramente distintas si comparten servidor.
Concatenar el valor de $fromAI() directamente en el texto de la query SQL (práctico). Qué pasa: escribes algo como query = "SELECT status FROM orders WHERE order_id = " + $fromAI('order_id', ..., 'number') en vez de usar $1 y el campo Query Parameters. Funciona en las pruebas normales, pero un modelo puede, ante un mensaje del cliente diseñado para confundirlo, terminar produciendo un valor que no es un número de pedido sino un fragmento de SQL — el riesgo clásico de inyección SQL, ahora con el modelo como intermediario en vez de un formulario web. Por qué pasa: cualquier texto insertado directamente en una query, sin pasar por un mecanismo de sanitización, se interpreta como parte del SQL — no importa si ese texto lo escribió un usuario en un formulario o lo generó un modelo de lenguaje. Cómo detectarlo: revisa cada tool que use Execute Query y busca signos de concatenación de texto dentro del campo query en vez de marcadores $1, $2 con sus valores en Query Parameters. Cómo corregirlo: usa siempre marcadores posicionales en la query y pasa los valores — vengan de $fromAI() o de una expresión fija — a través del campo Query Parameters, como en el ejemplo de esta lección.
Dejar en $fromAI() un campo que identifica al destinatario o al dueño del dato (práctico). Qué pasa: el campo to de un correo, o el customer_phone de una consulta a la base de datos, queda configurado con $fromAI() en vez de un valor fijo o una expresión sobre datos verificados de la conversación. El cliente, sin necesidad de mala intención — o con ella —, puede escribir algo que cambie ese valor: "envíalo a este otro correo" o "consulta el pedido de este otro teléfono". Por qué pasa: $fromAI() toma su valor de lo que el modelo interpreta del texto que cualquiera puede escribir en el chat — es exactamente el mecanismo correcto para datos que el cliente aporta sobre su propio caso (un número de pedido, un monto), pero no para datos que definen quién recibe algo o de quién es la información que se consulta. Cómo detectarlo: por cada campo con $fromAI() en una tool, pregúntate si ese valor debería poder cambiar según lo que escriba cualquier persona en el chat — si la respuesta es no, no debería estar ahí. Cómo corregirlo: fija esos campos con un valor literal (como to en el ejemplo de Gmail) o con una expresión que lea un dato ya verificado de la conversación (como customer_phone leído del Chat Trigger, no del texto del mensaje).
Ejercicios
Ejercicio 1 — Encuentra el riesgo. Un colega conectó esta tool de Postgres para buscar el correo de un cliente a partir de su número de cuenta:
query = "SELECT email FROM customers WHERE account_id = " + $fromAI('account_id', 'Número de cuenta del cliente', 'string')
Pasa todas las pruebas normales. ¿Qué le falta y cómo lo corregirías?
Ver solución
Le falta usar el campo Query Parameters con un marcador posicional en vez de concatenar el valor directamente en el texto de la query. La versión corregida:
query = "SELECT email FROM customers WHERE account_id = $1"
options.queryParameters = "={{ [ $fromAI('account_id', 'Número de cuenta del cliente', 'string') ] }}"
Por qué funciona: con Query Parameters, n8n trata el valor que llega desde $fromAI() como un dato — nunca como parte del texto SQL a ejecutar — sin importar qué caracteres contenga. Con concatenación directa, cualquier contenido en ese valor se interpreta literalmente como parte de la instrucción SQL, abriendo la puerta a que un mensaje del cliente, procesado por el modelo, termine alterando la query que se ejecuta.
Ejercicio 2 — Diseña los campos correctos. Vas a agregar una cuarta tool: cuando el cliente pida cancelar un pedido, el agente debe actualizar (Update) la fila de ese pedido en orders, cambiando status a 'cancelado'. ¿Qué campos de esa tool dejarías en $fromAI() y cuáles fijarías con una expresión sobre datos de la conversación? Justifica cada uno.
Ver solución
order_id — en $fromAI(). Es un dato que el cliente aporta sobre su propio caso al pedir la cancelación; el modelo debe extraerlo del mensaje.
customer_phone (para la condición WHERE, no para lo que se actualiza) — fijo, con {{ $('Chat Trigger').item.json.customerPhone }}. Igual que en el ejemplo de la lección: el teléfono identifica quién está preguntando y de quién es el pedido, no algo que el cliente deba poder cambiar escribiendo un texto distinto.
status (el nuevo valor, 'cancelado') — fijo, literal, no $fromAI(). No tiene sentido que el modelo "decida" a qué valor cambiar el estado en una acción de cancelación — la tool completa ya representa esa única acción; si mañana necesitas otro estado (por ejemplo, "pausado"), esa sería una tool distinta con su propia descripción, no un campo abierto a que el modelo escriba cualquier valor de estado.
Por qué funciona: el criterio es el mismo de esta lección — $fromAI() para datos que el cliente aporta sobre su propio caso; fijo o desde datos ya verificados para todo lo que define el alcance de la acción (a quién afecta) o el efecto exacto que produce (a qué valor cambia).
Ejercicio 3 — Elige el sistema correcto. Un equipo de Recursos Humanos quiere un agente interno que, cuando un empleado pida vacaciones por chat, haga tres cosas: (a) consultar cuántos días de vacaciones le quedan disponibles, dato que vive en la base de datos de nómina; (b) dejar un registro de la solicitud en una planilla que Recursos Humanos revisa cada semana; (c) avisar de inmediato al gerente directo del empleado. ¿Qué tipo de nodo conectarías como tool para cada una de las tres acciones?
Ver solución
(a) Postgres (o el motor de base de datos que use el sistema de nómina), operación Execute Query o Select — el mismo patrón del ejemplo de esta lección con la tabla orders, aplicado a una tabla de saldos de vacaciones.
(b) Google Sheets, operación Append Row — el mismo patrón de la tool de escalaciones: un registro consultable, no una notificación efímera.
(c) Gmail (o el sistema de mensajería interna que use la empresa), Send a message — el mismo patrón de aviso inmediato al equipo de soporte, aplicado ahora al gerente directo.
Por qué funciona: el criterio no cambia entre TuTienda y Recursos Humanos — depende de qué necesita cada acción, no del dominio del negocio. Dato que vive en una tabla y cambia con el tiempo → base de datos. Registro histórico consultable → Sheets. Aviso inmediato a una persona → correo (o el canal de mensajería equivalente).
Ejercicio 4 — Adapta la tool de tipo de cambio. Quieres que la tool de Frankfurter de esta lección también sirva para calcular reembolsos a clientes en Colombia, en pesos colombianos. ¿Necesitas crear una tool nueva, o la que ya construiste lo resuelve? Explica por qué.
Ver solución
No necesitas una tool nueva. El campo symbols ya está resuelto con $fromAI('target_currency', ...), así que el modelo puede pasar COP igual que pasaría MXN — la API de Frankfurter soporta el código ISO de peso colombiano sin ningún cambio de tu parte. Por eso, en la descripción de la tool, definiste el propósito en términos generales ("cuánto equivale un monto en dólares en su propia moneda") en vez de mencionar una moneda específica.
Por qué funciona: cuando dejas el dato que varía (la moneda destino) como parámetro de $fromAI() en vez de fijarlo dentro de la URL o la descripción, la misma tool cubre cualquier caso dentro de ese mismo patrón — no tienes que anticipar cada país por separado.
Resumen y siguiente paso
Hoy conectaste cuatro tipos de sistema real al agente — Postgres, Google Sheets, Gmail y HTTP Request genérico — usando el mismo mecanismo en los cuatro casos: el puerto ai_tool del agente y $fromAI() para los campos que el modelo debe completar. Y resolviste algo que quedó abierto desde el Módulo 3: un agente que antes solo repetía lo que la memoria recordaba, ahora consulta el sistema real antes de responder — la diferencia entre el pedido #4521 "en tránsito" (lo que se dijo ayer) y "entregado" (lo que es cierto hoy).
Antes de avanzar deberías poder: conectar cualquier nodo compatible al puerto ai_tool de un agente y escribir su descripción; decidir, para un campo cualquiera de una tool, si debe ir en $fromAI() o fijo, y explicar por qué; y usar Query Parameters en vez de concatenación de texto en cualquier tool que ejecute SQL.
Lo que no hiciste todavía, a propósito: ninguna de las cuatro tools de hoy le pidió confirmación a un humano antes de ejecutarse, y ninguna descripción dejó explícito qué pasa si el modelo se equivoca al elegir cuándo llamarla. Conectar el sistema real es solo la mitad del trabajo — la otra mitad es decidir, con criterio, qué tanto confías en que el modelo la use bien. Esa es exactamente la próxima lección.
Recursos
- How tools work — n8n Docs — panorama de los tipos de tool disponibles y cómo el agente decide cuál usar.
- Use AI for parameters ($fromAI) — n8n Docs — referencia completa de la función
$fromAI(), sus cuatro parámetros y sus límites. - Postgres node — n8n Docs — operaciones disponibles, incluida la sección sobre Query Parameters para evitar inyección SQL.
- Gmail node — n8n Docs — operaciones de mensajes, borradores, etiquetas e hilos, y su uso como tool de un agente.
- Google Sheets node — n8n Docs — operaciones de documento y hoja, incluida Append Row.
- HTTP Request node — n8n Docs — configuración de autenticación, query parameters y body para llamar a cualquier API como tool.