Módulo 4: Herramientas: el agente que actúa sobre sistemas reales

6. Sub-workflows como tools: encapsular lógica reutilizable

Descripción

Al terminar esta lección vas a poder tomar un proceso de varios pasos con lógica de negocio en el medio —consultar una base de datos, aplicar una regla condicional, calcular un resultado— y exponerlo como una sola tool que el agente puede llamar, sin que el agente tenga que orquestar esos pasos turno por turno ni conocer cómo funcionan por dentro.

Esto importa por una razón concreta: en la lección anterior definiste el contrato de una tool —qué entra, qué sale, qué no se le confía al agente—. Pero un contrato solo sirve si algo lo cumple, y no todas las reglas de negocio caben en un nodo nativo de Gmail, Sheets o HTTP como los que conectaste en la lección 4. Cuando el equipo de soporte de una empresa real te pida un agente que decida si un pedido califica para reembolso, esa decisión casi nunca es un solo paso —hay que consultar el pedido, aplicar una política con excepciones por categoría, calcular un monto—. Si describes todo eso en el prompt del agente y esperas que encadene tres tools nativas en el orden correcto cada vez, tarde o temprano se salta un paso o los ejecuta en desorden. Encapsular esa lógica en un sub-workflow —y exponer el sub-workflow completo como una sola tool— resuelve eso: el agente ve una interfaz simple, y la complejidad vive en un solo lugar que puedes probar, corregir y reutilizar sin tocar el agente.

Conexión con el módulo: la lección 5 te enseñó a describir bien una tool y sus límites de confianza. Esta lección resuelve la otra mitad de ese contrato: qué haces cuando la implementación no cabe en un nodo nativo. Todavía no vas a ver tools que viven fuera de tu instancia de n8n —eso, y el servidor MCP de tu propia instancia, es el tema completo de la próxima lección—.

Cuando una tool necesita ser un equipo, no una sola persona

Piensa en la diferencia entre pedirle algo a la persona de recepción y mandarlo al departamento correspondiente. Si le preguntas a recepción "¿dónde está el baño?", te responde ella misma —es una sola acción, no necesita ayuda de nadie más—. Pero si le preguntas "¿puedo devolver esta licuadora que compré hace tres semanas?", recepción no decide eso sola: revisa el pedido en el sistema, aplica la política de devoluciones —que tiene excepciones según qué compraste—, calcula cuánto te toca de reembolso, y recién ahí te da una respuesta. Tú, como cliente, no ves esos tres pasos internos; solo ves que preguntaste algo y te respondieron con una decisión.

Las tools nativas que conectaste en la lección 4 —un nodo de Gmail, un nodo de Sheets, una llamada HTTP— son como la pregunta del baño: una acción, un nodo, resuelta al instante. Pero cuando la respuesta del agente depende de encadenar varios pasos con lógica condicional en el medio, pedirle al agente que orqueste eso turno por turno —llamar la tool de consultar el pedido, después decidir con qué criterio, después llamar otra tool para calcular— es exactamente lo que la lección 5 te enseñó a no confiarle: decisiones de negocio con reglas que tú, no el modelo, debes fijar.

La solución de n8n es literal: conviertes esos pasos en un workflow separado —un sub-workflow—, y expones ese workflow completo como si fuera una sola tool. Dos piezas hacen que esto funcione:

  1. El nodo Execute Sub-workflow Trigger, puesto como punto de entrada del sub-workflow, donde declaras el esquema de entrada —los campos que la tool acepta, cada uno con nombre y tipo—. Esta es la mitad del contrato de la lección 5 que vive del lado de la implementación: qué datos necesita el sub-workflow para hacer su trabajo.
  2. El último nodo del sub-workflow, cuya salida es literalmente lo que se devuelve a quien llamó. No hay un nodo especial de "responder" —n8n toma la salida del nodo que quede al final de la cadena y esa es la respuesta. Si agregas un nodo después por error, la respuesta cambia sin que lo hayas pedido.

Del otro lado —en el workflow donde vive tu agente—, conectas un nodo llamado Call n8n Workflow Tool al puerto de tools del AI Agent. Ahí eliges qué workflow guardado llamar, escribes la descripción que le dice al agente cuándo usar esta tool —el mismo principio de la lección 5, aplicado a un sub-workflow en vez de a un nodo nativo—, y mapeas cada campo del esquema de entrada.

Ejemplo trabajado

TuTienda —la tienda en línea con la que ya trabajaste el caso del agente de soporte— necesita que su agente decida si un pedido califica para reembolso. La política real tiene una excepción que hace que esto no sea un solo paso: el plazo de devolución es de 30 días para mercancía general, pero de solo 14 días para electrónicos.

Paso 1 — Construyes el sub-workflow Check Refund Eligibility. Empieza con un Execute Sub-workflow Trigger donde declaras el único dato que este proceso necesita del exterior:

# Nodo: Execute Sub-workflow Trigger — inicio de "Check Refund Eligibility"
Input Source = "Define Using Fields Below"
Inputs:
  - Name: order_id
    Type: String

Con Define Using Fields Below —en vez de Accept All Data— cada campo queda declarado con nombre y tipo, y es justo esa lista la que va a aparecer del otro lado, en el nodo Call n8n Workflow Tool, cuando selecciones este sub-workflow. Dejar el trigger en Accept All Data funciona para probar rápido, pero no publica ningún campo mapeable — es la diferencia entre un contrato explícito y "mándame lo que sea".

Paso 2 — Un nodo Postgres consulta la tabla orders filtrando por order_id y entrega la fila del pedido: fecha de compra, categoría del producto y precio.

Paso 3 — Un nodo Code aplica la regla de negocio. Esta es la lógica condicional que no le vas a pedir al agente que reproduzca de memoria en cada turno:

// Code node — dentro de "Check Refund Eligibility"
// Aplica la política de devoluciones de TuTienda según categoría del producto
const order = $input.first().json;

const windowDays = order.category === 'electronics' ? 14 : 30;
const daysSincePurchase = Math.floor(
  (Date.now() - new Date(order.purchase_date).getTime()) / (1000 * 60 * 60 * 24)
);

const eligible = daysSincePurchase <= windowDays;

return [{
  json: {
    eligible,
    refund_amount: eligible ? order.price : 0,
    reason: eligible
      ? `Dentro de la ventana de ${windowDays} días para la categoría "${order.category}".`
      : `Fuera de la ventana de ${windowDays} días — pasaron ${daysSincePurchase} desde la compra.`,
  },
}];

Paso 4 — Un nodo Edit Fields (Set), el último de la cadena, deja la forma final que se va a devolver: { eligible, refund_amount, reason }. Que sea el último nodo no es cosmético — es literalmente lo que define qué recibe quien llamó a este sub-workflow.

Paso 5 — En el workflow del agente, agregas Call n8n Workflow Tool conectado al puerto de tools del AI Agent:

# Nodo: Call n8n Workflow Tool — conectado al AI Agent
Description = "Usa esta tool para determinar si un pedido es elegible para
               reembolso y por qué monto. Requiere el ID del pedido que
               menciona el cliente."
Source = "Database"
Workflow = "Check Refund Eligibility"

Workflow Inputs:
  order_id = {{ $fromAI('order_id', 'El ID del pedido que menciona el
               cliente, por ejemplo 4521', 'string') }}

$fromAI(key, description, type, defaultValue) es la función que deja que el modelo, no tú, decida qué valor va en ese campo en cada llamada: key es el identificador que el modelo va a asociar con el dato, description es la pista de qué buscar en el mensaje del cliente, y type fuerza que el valor llegue como string. Es el mismo mecanismo que ya viste con tools nativas en la lección 3 — aquí lo estás usando para alimentar el esquema de entrada de un sub-workflow en vez del parámetro de un nodo suelto.

Qué esperar. Un cliente escribe en el chat: "¿Me pueden reembolsar el pedido #4521? Lo compré hace 20 días, es una licuadora." El agente reconoce que necesita verificar elegibilidad, extrae order_id = "4521" con $fromAI(), y llama a Check Refund Eligibility. El sub-workflow consulta el pedido —categoría home_appliance, comprado hace 20 días— y el nodo Code calcula: ventana de 30 días (no es electrónico), 20 días transcurridos, elegible. El último nodo devuelve:

{
  "eligible": true,
  "refund_amount": 899,
  "reason": "Dentro de la ventana de 30 días para la categoría \"home_appliance\"."
}

Ese JSON es lo único que el agente recibe de vuelta —no ve la consulta SQL ni el cálculo—, y con eso responde: "Sí, tu pedido #4521 califica para reembolso completo de $899, porque compraste hace 20 días y la licuadora tiene una ventana de devolución de 30 días."

Reutilización entre agentes, y dónde termina esta lección

La razón por la que vale la pena encapsular esto —y no solo dejarlo repetido dentro del prompt de un único agente— es que el mismo sub-workflow Check Refund Eligibility lo puede llamar el agente de WhatsApp de soporte, un agente interno de Slack para el equipo de finanzas, y cualquier otro que TuTienda construya después. Si la política de devoluciones cambia —digamos, el plazo de electrónicos pasa de 14 a 21 días— corriges el nodo Code una sola vez, en un solo lugar, y todos los agentes que llaman a esa tool quedan actualizados sin que edites ni un prompt. Esa es la ganancia real frente a copiar la misma lógica condicional dentro de cada agente por separado.

n8n tiene además un atajo para construir estos sub-workflows a partir de algo que ya armaste: seleccionas los nodos en el lienzo, clic derecho, Convert to sub-workflow (disponible desde la versión 1.97.0). n8n arma automáticamente el Execute Sub-workflow Trigger y un nodo Edit Fields al final, etiquetado como Return. Lo que no hace por ti es fijar los tipos de cada campo de entrada y salida — eso, como viste en el Paso 1, sigue siendo una decisión tuya.

Una frontera vale la pena marcarla ahora: el sub-workflow que expones como tool puede tener toda la lógica determinista que quieras —consultas, condicionales, cálculos—, pero meter otro nodo AI Agent dentro de él ya no es "encapsular una regla de negocio" — es empezar a construir un sistema donde un agente le delega trabajo a otro agente. Esa idea tiene su propio espacio más adelante en la ruta; aquí, la tool que construiste es una caja determinista y predecible, y esa predictibilidad es justo el punto.

Errores comunes

Dejar el Execute Sub-workflow Trigger en Accept All Data y pensar que el esquema de entrada es "solo documentación" (conceptual). Qué pasa: alguien arma el sub-workflow, no define campos en el trigger porque "ya se entiende qué necesita" con solo leer los nodos de adentro, y al ir a conectar Call n8n Workflow Tool no aparece ningún campo mapeable — no hay dónde poner el $fromAI('order_id', ...). Por qué pasa: es fácil pensar el esquema de entrada como una anotación para humanos, cuando en realidad es la fuente de la que n8n lee qué campos mostrar del lado del agente. Sin esquema declarado, no hay contrato que el agente pueda cumplir — exactamente lo que la lección 5 definió como una tool mal descrita. Cómo detectarlo: si al seleccionar el sub-workflow en Call n8n Workflow Tool la sección Workflow Inputs aparece vacía, el trigger sigue en Accept All Data. Cómo corregirlo: cambia el Input Source a Define Using Fields Below y declara cada campo con su nombre y tipo.

Agregar un nodo después del que realmente calcula la respuesta (práctico). Qué pasa: el sub-workflow Check Refund Eligibility termina con el Edit Fields que arma { eligible, refund_amount, reason }, pero alguien agrega después un nodo de Slack que notifica al canal #refunds-log — y ahora ese es el último nodo de la cadena. El agente deja de recibir el JSON de elegibilidad y recibe en su lugar lo que devuelve el nodo de Slack —confirmación de que el mensaje se envió—, sin ningún campo eligible ni refund_amount que interpretar. Por qué pasa: n8n no tiene un nodo especial de "responder" — devuelve la salida del último nodo de la cadena, sea cual sea, y agregar un paso adicional al final cambia silenciosamente qué se retorna. Cómo detectarlo: el agente empieza a responder de forma incoherente o admite que no tiene la información, aunque el sub-workflow "funcione" si lo ejecutas manualmente y miras el log completo. Cómo corregirlo: pon el nodo de notificación en una rama aparte —no en la cadena principal que termina en la respuesta—, o muévelo antes del Edit Fields final.

Probar el sub-workflow ejecutándolo manualmente con datos de prueba, y asumir que eso prueba lo que el agente le va a mandar (práctico). Qué pasa: alguien ejecuta Check Refund Eligibility a mano con order_id: "4521" escrito directamente en el nodo de prueba, todo funciona, y da por cerrado el trabajo. Pero cuando el agente lo llama en producción, el cliente escribió "el pedido #4521" y $fromAI() extrajo "#4521" con el símbolo incluido — la consulta a Postgres no encuentra ninguna fila con ese valor. Por qué pasa: probar el sub-workflow de forma aislada valida la lógica interna, no el dato real que un modelo de lenguaje extrae de un mensaje ambiguo de un cliente. Cómo detectarlo: revisa la ejecución real del agente en el panel de ejecuciones —no la prueba manual— y compara el valor exacto que $fromAI() mandó contra lo que el nodo Postgres esperaba. Cómo corregirlo: agrega una normalización explícita al inicio del sub-workflow —por ejemplo, un Code node que limpie el valor con order_id.replace('#', '')— en vez de confiar en que el modelo siempre va a extraer el formato exacto.

Ejercicios

Ejercicio 1 — Diagnosticar una respuesta rota. El sub-workflow Check Refund Eligibility termina así: Postgres → Code (calcula elegibilidad) → Edit Fields (arma la respuesta) → Slack (notifica al canal interno). El agente empieza a responder a los clientes con mensajes sin sentido sobre reembolsos, aunque cada ejecución en el panel de n8n se ve "exitosa". ¿Qué está recibiendo realmente el agente, y cómo lo arreglas?

Ver solución

El agente recibe la salida del nodo Slack —típicamente algo como confirmación de que el mensaje se envió al canal, con campos como el ID del mensaje o el canal—, no el JSON { eligible, refund_amount, reason } que armó el Edit Fields. n8n devuelve la salida del último nodo de la cadena sin importar cuál sea su propósito, y el nodo de Slack quedó después del que realmente arma la respuesta. La corrección es mover el nodo de Slack a una rama separada que no forme parte de la cadena principal —o ponerlo antes del Edit Fields— para que el Edit Fields vuelva a ser el último nodo.

Por qué funciona: la ejecución se ve "exitosa" porque todos los nodos corrieron sin error — el problema no es que algo falle, es que el nodo equivocado quedó al final de la cadena, y eso no genera ningún error visible en el log.

Ejercicio 2 — Diseñar el contrato de entrada. TuTienda quiere una segunda tool sub-workflow, Check Discount Eligibility, que decide si un cliente puede recibir un descuento por lealtad combinando: su historial de compras (una consulta HTTP a un CRM externo) y una regla de negocio ("clientes con más de 5 compras en los últimos 12 meses califican para 10% de descuento"). Escribe la configuración del Execute Sub-workflow Trigger —Input Source y los campos declarados— y la Description que pondrías en el nodo Call n8n Workflow Tool.

Ver solución
Input Source = "Define Using Fields Below"
Inputs:
  - Name: customer_id
    Type: String

Description en Call n8n Workflow Tool: "Usa esta tool para determinar si un cliente califica para el descuento de lealtad del 10%. Requiere el ID del cliente, no su nombre ni su correo — si no tienes el ID, pídeselo antes de llamar esta tool."

Solo se necesita customer_id porque el resto de la lógica —consultar el CRM, contar compras, aplicar el umbral de 5— vive dentro del sub-workflow, no en lo que el agente tiene que decidir o mandar.

Por qué funciona: el esquema de entrada declara exactamente lo mínimo que el mundo exterior necesita proveer — un identificador — y deja toda la lógica de negocio encapsulada del lado de la implementación, que es justo el punto de convertir esto en una tool separada.

Ejercicio 3 — Elegir entre tool nativa y sub-workflow. TuTienda necesita dos tools nuevas: (a) "enviar por correo la confirmación estándar de que se recibió una solicitud de soporte" y (b) "decidir si un pedido califica para reembolso aplicando la política con excepciones por categoría". ¿Cuál construyes como tool nativa —como las que viste en la lección 4— y cuál como sub-workflow? Justifica con lo que aprendiste en esta lección.

Ver solución

(a) es una tool nativa: un solo nodo Gmail con una plantilla fija, un paso, sin ninguna decisión condicional en el medio — exactamente el tipo de acción que un nodo nativo resuelve solo, sin necesidad de encapsular nada.

(b) es un sub-workflow: encadena una consulta a datos (el pedido), una regla condicional con excepción por categoría, y un cálculo — varios pasos con lógica de negocio que no quieres que el agente reconstruya turno por turno, y que además puede necesitar corregirse cuando cambie la política, sin tocar el prompt del agente.

Por qué funciona: la pregunta que decide entre los dos no es "qué tan importante es la tarea" sino cuántos pasos con lógica condicional hacen falta para completarla — un paso, tool nativa; varios pasos con reglas en el medio, sub-workflow.

Resumen y siguiente paso

Ya sabes convertir un proceso de varios pasos en una tool reutilizable: declaras el contrato de entrada en el Execute Sub-workflow Trigger con Define Using Fields Below, construyes la lógica determinista adentro —consultas, condicionales, cálculos—, dejas el nodo que arma la respuesta final como el último de la cadena, y lo conectas al agente con Call n8n Workflow Tool, mapeando cada campo con $fromAI() para que el modelo decida qué valor mandar en cada llamada real.

Antes de avanzar deberías poder: explicar por qué el último nodo de un sub-workflow —y no un nodo especial de "responder"— es lo que determina la respuesta; decidir entre construir una tool nativa o un sub-workflow según cuántos pasos con lógica condicional hacen falta; y escribir un esquema de entrada que declare exactamente lo mínimo que el agente necesita proveer.

Lo que todavía no resolviste es qué hacer cuando la lógica que necesitas no vive en tu instancia de n8n en absoluto —un servicio de un proveedor externo que ya expone sus propias tools, o el caso inverso, dejar que Claude Desktop o Cursor construyan workflows dentro de tu n8n—. Eso es exactamente el tema de la próxima lección: MCP.

Recursos