Módulo 6: Canales reales: chat web, WhatsApp, Telegram y voz

2. Chat Trigger y widget web embebido

Descripción

Al terminar esta lección vas a poder abrir el primer canal real de tu agente: configurar el nodo Chat Trigger en sus dos modos —el chat alojado por n8n y el chat embebido en tu propio sitio—, elegir con criterio entre sus tres modos de respuesta, proteger el acceso, y pegar el widget @n8n/chat en una página web de TuTienda con dos etiquetas de HTML. También vas a resolver, en este canal, la pregunta de identidad que sembró la lección 1: cómo hacer que el historial de conversación pertenezca al cliente y no a la pestaña del navegador.

Esto importa porque es la primera vez en toda la guía que alguien que no eres tú puede hablarle a tu agente. Hasta ahora todo pasó dentro de n8n: el panel de chat del canvas, las ejecuciones de prueba, la traza. Al final de esta lección va a existir una URL que puedes mandarle a otra persona, o un widget en una página, y el sistema del Módulo 5 —triage_agent, order_specialist, billing_specialist— va a atender del otro lado. Es también el canal más barato para equivocarte: es gratis, viene incluido en n8n, no depende de la aprobación de nadie, y si algo se rompe se arregla recargando la página.

Conexión con el módulo: esta es la lección donde la capa de canal y la capa de adaptador de la lección 1 dejan de ser un diagrama. El Chat Trigger es la capa de canal más amable que vas a conocer, precisamente porque n8n lo controla entero — no hay un tercero imponiéndote reglas. Por eso conviene aprender aquí las decisiones estructurales (modo de respuesta, autenticación, identidad de sesión) mientras nada más está fallando; en WhatsApp, en la lección 3, esas mismas decisiones reaparecen mezcladas con trámites de Meta y es mucho más difícil aislar qué está roto. Y todo lo que traes del Módulo 3 sobre sessionId y memoria persistente entra en juego en la última sección.

El intercomunicador y la recepción

Piensa en dos formas de dejar entrar gente a un edificio.

La primera es una recepción completa: un mostrador, alguien atendiendo, sillas, y un letrero con el nombre del edificio. Todo eso ya existe, no lo diseñaste tú, y funciona bien desde el primer día. Lo único que puedes hacer es cambiar el letrero, poner otras sillas y decidir a quién se le permite pasar. Es cómodo y es rápido.

La segunda es un intercomunicador en la puerta de tu propia oficina, dentro de un edificio que tú ya construiste. Nadie ve la recepción del edificio original: la gente entra por tu puerta, con tu letrero, con tus colores. Tú te encargas de la fachada; el intercomunicador solo se encarga de que el mensaje llegue a quien tiene que llegar.

El nodo Chat Trigger de n8n es exactamente esas dos cosas, y se elige con un selector. En modo Hosted Chat, n8n te da la recepción completa: una página de chat, alojada por n8n, con su URL propia, lista para usar. En modo Embedded Chat, n8n te da solo el intercomunicador: una URL de webhook a la que tu propia página web le habla, y la fachada la pones tú.

La distinción importa porque casi todo el mundo empieza en Hosted —es lo correcto para probar— y casi todo el mundo termina en Embedded, porque un negocio de verdad quiere el chat dentro de su sitio y no en un dominio de n8n. Vas a hacer las dos cosas en esta lección, en ese orden.

Anatomía del Chat Trigger

Antes de tocar nada, veamos qué es este nodo. El Chat Trigger —que en la lista de nodos aparece como "When chat message received"— es un nodo de disparo que hace tres cosas al mismo tiempo, y esa combinación es lo que lo hace distinto de un Webhook común:

  1. Publica un endpoint HTTP que recibe los mensajes del usuario.
  2. Genera y arrastra un sessionId, para que la memoria del agente pueda agrupar los turnos de una misma conversación sin que tú hagas nada.
  3. Opcionalmente, sirve una interfaz de chat completa en esa misma URL (eso es el modo Hosted).

Ya lo usaste en el Módulo 1 sin configurarle nada: lo arrastraste al canvas, apareció el panel de Chat abajo, y con eso probaste tu primer agente. Ese panel del canvas es la versión de prueba local. Lo que esta lección hace es convertirlo en un canal accesible desde afuera.

Cuando el trigger recibe un mensaje, la salida que entrega al siguiente nodo tiene esta forma:

{
  "sessionId": "a2f0c8b1e4d7...",
  "action": "sendMessage",
  "chatInput": "Hola, ¿cómo va mi pedido #4521?"
}

Tres campos y ninguno sobra. chatInput es el texto que escribió la persona — es el campo que el nodo AI Agent lee por defecto cuando su origen de prompt está en "Connected Chat Trigger Node". sessionId es la identidad de la conversación, la que la memoria usa como clave. Y action distingue un mensaje normal de una carga de historial previo, cosa que vas a ver más abajo.

Los nombres exactos de los campos y de las opciones pueden variar entre versiones menores de n8n. Cada vez que esta lección nombre un parámetro, ábrelo en el panel del nodo y confirma la etiqueta en tu versión antes de darlo por hecho. Es un hábito barato que ahorra media hora de confusión.

Paso a paso: el agente del Módulo 5 en modo Hosted

Vamos a conectar el sistema que ya tienes. Si tu triage_agent con sus dos especialistas está en un workflow, ábrelo; si prefieres empezar con un agente simple para no arriesgar el que funciona, también sirve — todo lo de esta lección aplica igual.

Paso 1 — El trigger. Si el workflow ya arranca con un Chat Trigger (viene así desde el Módulo 5), no agregues otro. Solo ábrelo: hasta ahora estaba con sus valores por defecto.

Paso 2 — El modo. El primer parámetro del nodo es Make Chat Publicly Available o equivalente, un interruptor que decide si la conversación vive solo en el panel del canvas o si además hay una URL pública. Actívalo. En cuanto lo hagas aparecen dos URLs, y la diferencia entre ellas es la fuente del error más común de esta lección:

Test Chat URL        → funciona SOLO mientras tienes el canvas abierto y
                       presionaste "Execute workflow". Es para probar.
Production Chat URL  → funciona siempre, pero SOLO si el workflow está
                       ACTIVO (el interruptor de arriba a la derecha).

Qué esperar. Copia la URL de producción, actívala pegándola en otra pestaña del navegador, y verás una página de chat con un cuadro de texto. Si en cambio ves un error 404 o un mensaje de que el webhook no está registrado, casi siempre es una de dos cosas: usaste la URL de test sin tener el canvas ejecutando, o usaste la de producción con el workflow inactivo. Está bien, le pasa a todo el mundo la primera vez.

Paso 3 — El modo de respuesta. Aquí está la primera decisión de diseño real. El parámetro Response Mode tiene tres valores y cada uno cambia cómo llega la respuesta a la pantalla:

  • When Last Node Finishes. El chat espera a que el workflow entero termine y entonces muestra la salida del último nodo. Es el comportamiento por defecto y el más simple: un mensaje entra, el agente razona, un mensaje sale. Para el sistema del Módulo 5 funciona perfecto.
  • Using Response Nodes. El chat no espera al último nodo: espera a que un nodo Chat (o un Respond to Webhook, en modo embebido) diga explícitamente qué responder. Sirve cuando quieres mandar más de un mensaje, o mandar algo antes de terminar el trabajo, o hacer una pausa esperando confirmación del usuario.
  • Streaming response. La respuesta aparece palabra por palabra mientras el modelo la genera, en vez de aparecer completa al final. Es lo que hace que un chat se sienta rápido aunque tarde lo mismo.

La decisión práctica es esta: empieza con When Last Node Finishes porque es el que menos partes tiene. Cambia a Using Response Nodes cuando necesites hablarle al usuario a mitad del proceso — el caso típico es un agente que va a tardar quince segundos y quieres avisarle antes de que se vaya. Y considera Streaming cuando el agente escriba respuestas largas y el silencio se vuelva incómodo. Una advertencia honesta sobre streaming: no todos los modelos ni todas las configuraciones lo soportan igual, y su comportamiento con sistemas multi-agente —donde hay razonamiento intermedio que no se debería mostrar— conviene probarlo antes de prometerlo. Verifica en tu versión qué combinación te funciona.

Paso 4 — La autenticación. El parámetro Authentication define quién puede abrir ese chat:

  • None. Cualquiera con la URL entra. Es lo correcto para un chat de atención al cliente en una web pública, y es lo que vas a usar. Pero ten presente lo que significa: cualquiera con la URL puede consumir tokens de tu cuenta de modelo. Sí, cualquiera.
  • Basic Auth. Pide usuario y contraseña, los mismos para todos. Útil para una demo interna o para un chat que solo debe ver tu equipo.
  • n8n User Auth. Solo entran usuarios con sesión iniciada en tu instancia de n8n. Es lo más restrictivo y sirve para herramientas internas.

Aquí viene un párrafo de honestidad. Un chat con Authentication: None y una URL que se puede compartir es, literalmente, un endpoint público conectado a un modelo que cobra por token. Si alguien encuentra esa URL, puede escribirle todo el día. Eso no es motivo para no publicarlo —es el modelo de negocio de cualquier chat de atención— pero sí es motivo para dos cosas: mantén la URL de producción fuera de repositorios públicos, y no dejes un chat abierto olvidado en una instancia de prueba. El control de gasto y las defensas de verdad son el Módulo 7; por ahora, que quede sembrada la conciencia.

Paso 5 — Las opciones de presentación. Bajo Options hay un conjunto de campos que solo afectan al modo Hosted, y que convierten una página genérica en algo que parece de TuTienda:

# Nodo: Chat Trigger — Options (modo Hosted Chat)

Title:              Atención TuTienda
Subtitle:           Estamos para ayudarte con tus pedidos y cobros
Input Placeholder:  Escribe tu mensaje…
Initial Message(s): ¡Hola! Soy el asistente de TuTienda.
                    Puedo ayudarte con el estado de un pedido, una
                    devolución o un cobro que no reconozcas.
Require Button Click to Start Chat:  activado
Allowed Origin (CORS):  https://tutienda.example
Load Previous Session:  Memory Connected to Agent

Vale la pena entender qué hace cada uno, porque tres de ellos no son cosméticos.

Initial Message(s) es el mensaje que la persona ve antes de escribir nada. No lo genera el modelo: es texto fijo que tú escribes, así que no cuesta tokens y es idéntico siempre. Esto es más importante de lo que parece. Un chat que abre en blanco recibe preguntas de todo tipo, muchas fuera del alcance del agente; un chat que abre diciendo "puedo ayudarte con pedidos, devoluciones y cobros" recibe muchas menos preguntas fuera de alcance, porque acabas de decirle a la persona qué esperar. Es la forma más barata de acotar una conversación: acotarla antes de que empiece.

Require Button Click to Start Chat hace que el widget muestre un botón de "Nueva conversación" en vez de abrir directo al cuadro de texto. Suena trivial y tiene un efecto real: evita disparos accidentales y, en el modo ventana embebida, da un punto de partida claro a la sesión.

Allowed Origin (CORS) es el campo que más confusión genera, así que vale la pena explicarlo con calma. CORS es una regla de los navegadores: cuando una página web en el dominio A intenta hacer una petición al dominio B, el navegador le pregunta a B si eso está permitido, y si B no contesta que sí, el navegador bloquea la petición. No es una defensa contra un atacante determinado —cualquiera puede llamar a tu webhook desde fuera de un navegador— pero sí evita que tu chat quede embebido en el sitio de otra persona. El valor por defecto suele ser *, que significa "cualquier origen". Ponerlo en https://tutienda.example significa "solo la página de TuTienda puede embeberme". Durante el desarrollo local vas a necesitar incluir también tu origen de pruebas, algo como http://localhost:8080, separado por coma.

Load Previous Session decide si, al reabrir el chat, la persona vuelve a ver los mensajes anteriores. Requiere que haya un nodo de memoria conectado al agente, porque de ahí es de donde salen esos mensajes. Sin él, cada recarga de la página se ve como una conversación en blanco — aunque el agente sí recuerde, porque la memoria y la pantalla son cosas distintas. Ese matiz es sutil y confunde: el agente puede recordar perfectamente y aun así la pantalla verse vacía, porque Load Previous Session controla lo que se pinta, no lo que el modelo tiene en contexto.

Paso 6 — Probar. Guarda, activa el workflow, abre la URL de producción y escribe: "Hola, ¿cómo va mi pedido #4521?"

Qué esperar. Después de unos segundos —y sí, van a ser varios segundos, porque el triage_agent está delegando— aparece la respuesta compuesta. Si abres la pestaña de ejecuciones de n8n vas a encontrar esa corrida completa, con su traza de delegaciones, igual que en el Módulo 5. Lo único que cambió es por dónde entró el mensaje.

Perfecto. Ya tienes un canal. Cualquier persona a la que le mandes esa URL puede hablar con tu agente.

Responder con el nodo Chat

Cuando eliges Response Mode: Using Response Nodes, el Chat Trigger deja de responder solo y espera a que alguien le diga qué mandar. Ese alguien es el nodo Chat (que en versiones anteriores se llamaba "Respond to Chat" — otro nombre que conviene verificar en tu panel).

El nodo tiene dos operaciones y la segunda es más interesante de lo que suena:

  • Send Message. Manda un mensaje al chat y el workflow sigue corriendo. Es el que usas para decir "dame un segundo que consulto el sistema" antes de arrancar la parte lenta.
  • Send and Wait for Response. Manda un mensaje y pausa la ejecución hasta que la persona conteste. Cuando contesta, el workflow continúa desde ahí con esa respuesta.

Esa segunda operación es un mecanismo de human-in-the-loop dentro del propio canal, y trae un parámetro Response Type con estas opciones:

  • Free Text: la persona escribe lo que quiera.
  • Approval: la persona ve botones y hace clic. Puedes configurar si aparece solo "aprobar" o "aprobar y rechazar", personalizar el texto de los botones, y activar Block User Input para que no pueda escribir texto libre mientras decide.

Veamos el caso de uso más claro para TuTienda:

# Flujo con confirmación antes de una acción sensible

Chat Trigger (Response Mode: Using Response Nodes)
  └─► AI Agent: triage_agent
        └─► (el agente determina que hay que cancelar el pedido)
              └─► Chat  ──  Operation: Send and Wait for Response
                            Message: "Confirmo: voy a cancelar el pedido
                                      #4521. El reembolso tarda de 5 a 7
                                      días hábiles. ¿Lo cancelo?"
                            Response Type: Approval
                            Approve Button Label:  Sí, cancelar
                            Disapprove Button Label: No, déjalo así
                            Block User Input: activado
                    │
                    ├── aprobado  →  ejecutar la cancelación
                    └── rechazado →  responder que no se hizo nada

Fíjate en lo que este patrón resuelve. Sin él, la confirmación tendría que ser conversacional: el agente pregunta "¿confirmas?", el cliente escribe "sí", y el agente tiene que interpretar ese "sí" — que también podría ser "sip", "dale", "sí pero espera", o "sí, y de paso cancela el otro también". Con botones, la confirmación es un dato, no una interpretación. Cuando la acción es destructiva, un dato vale mucho más que una interpretación.

Este mismo patrón, generalizado y con criterios de cuándo aplicarlo, es la lección de human-in-the-loop del Módulo 7. Aquí lo ves como una capacidad del canal; allá lo vas a ver como una política de seguridad.

Una limitación que conviene saber ahora: el nodo Chat funciona con el modo Hosted. En modo Embedded, la respuesta se maneja con el nodo Respond to Webhook, que es el equivalente genérico. Verifica el comportamiento en tu versión antes de diseñar un flujo que dependa de esto.

El modo embebido: el chat dentro de tutienda.example

Ahora el intercomunicador. Cambia el Chat Trigger a Embedded Chat y observa lo que pasa: las opciones de presentación desaparecen. No hay Title, no hay Subtitle, no hay Initial Messages. Tiene sentido — esas cosas son de la interfaz, y en modo embebido la interfaz es tuya. Lo que queda es la URL del webhook, el modo de respuesta, Allowed Origin (CORS) y Load Previous Session.

n8n publica un paquete que construye esa interfaz por ti: @n8n/chat. Es una librería de JavaScript que dibuja el widget de chat y le habla a tu webhook. Se puede instalar con npm en un proyecto, pero para una página normal la vía más simple es cargarla desde un CDN. Dos etiquetas:

<!-- Widget de chat de TuTienda — pegar antes de </body> -->

<!-- 1) Los estilos del widget -->
<link
  href="https://cdn.jsdelivr.net/npm/@n8n/chat/dist/style.css"
  rel="stylesheet"
/>

<!-- 2) El widget, con su configuración -->
<script type="module">
  import { createChat } from 'https://cdn.jsdelivr.net/npm/@n8n/chat/dist/chat.bundle.es.js';

  createChat({
    // La URL de PRODUCCIÓN del Chat Trigger en modo embebido.
    // Si pones la de test, funciona un rato y luego deja de funcionar
    // sin explicación aparente: la de test caduca al cerrar el canvas.
    webhookUrl: 'https://TU-INSTANCIA-N8N/webhook/xxxxxxxx/chat',

    // 'window' pone una burbuja flotante en la esquina.
    // 'fullscreen' llena el contenedor que le indiques en target.
    mode: 'window',

    // Mensajes de bienvenida. Van aquí, no en el nodo: en modo
    // embebido la interfaz es tuya, así que el texto también.
    initialMessages: [
      '¡Hola! Soy el asistente de TuTienda.',
      'Puedo ayudarte con pedidos, devoluciones y cobros.'
    ],

    // Textos de la interfaz.
    i18n: {
      en: {
        title: 'Atención TuTienda',
        subtitle: 'Respondemos al instante, todos los días.',
        inputPlaceholder: 'Escribe tu mensaje…',
        getStarted: 'Nueva conversación'
      }
    }
  });
</script>

Dos detalles de ese bloque que conviene no pasar por alto.

El primero es que la clave del objeto i18n es en aunque los textos estén en español. No es un descuido: en es el idioma por defecto del widget, y si defines tus textos ahí funcionan sin configurar nada más. Puedes agregar más idiomas y elegir cuál usar, pero para un sitio de un solo idioma esta es la forma más corta. Es exactamente el tipo de detalle que hace perder veinte minutos si nadie lo menciona.

El segundo es webhookUrl. Tiene que ser la URL de producción, y el workflow tiene que estar activo. Si el widget aparece pero cada mensaje falla en silencio, ese es el primer lugar donde mirar — abre la consola del navegador y vas a ver el error real, que suele ser un 404 (workflow inactivo) o un error de CORS (el origen de tu página no está en Allowed Origin).

Ejemplo trabajado: pasarle la identidad del cliente al agente

Aquí resolvemos la pregunta que quedó sembrada en la lección 1. El widget genera un sessionId aleatorio por navegador, así que la conversación pertenece a la pestaña, no a la persona. Si el cliente entra desde su teléfono, es otro sessionId y otra conversación.

Pero fíjate en la situación real de TuTienda: el chat está en una página donde el cliente ya inició sesión. Tu servidor sabe perfectamente quién es. Sería absurdo que el agente no lo sepa.

createChat acepta un objeto metadata cuyo contenido viaja con cada mensaje hasta el workflow. Ahí se pasa la identidad:

<script type="module">
  import { createChat } from 'https://cdn.jsdelivr.net/npm/@n8n/chat/dist/chat.bundle.es.js';

  createChat({
    webhookUrl: 'https://TU-INSTANCIA-N8N/webhook/xxxxxxxx/chat',
    mode: 'window',

    // Todo lo que pongas aquí llega al workflow junto con el mensaje.
    // Lo rellena TU servidor al renderizar la página, con los datos
    // de la sesión autenticada del cliente.
    metadata: {
      customer_id: 'C-9931',
      plan: 'premium',
      locale: 'es-MX'
    }
  });
</script>

Del lado de n8n, ese objeto llega dentro del payload del Chat Trigger. La ruta exacta hasta él es una de las cosas que conviene verificar en tu versión —abre una ejecución real y mira el JSON de salida del nodo antes de escribir la expresión—, pero típicamente se lee así:

# Nodo: Postgres Chat Memory (conectado al triage_agent)

Session ID:  Define below
Key:         {{ $('When chat message received').item.json.metadata.customer_id }}

Qué esperar. Con esto, la memoria deja de agruparse por pestaña y pasa a agruparse por cliente. El mismo C-9931 que conversó ayer desde su computadora reanuda el hilo hoy desde su teléfono, porque el sessionId ya no lo inventa el navegador — lo pone tu servidor. Es la credencial de empleado del Módulo 3, aplicada al canal web.

Y hay un segundo uso, más sutil, del mismo mecanismo. Con customer_id disponible desde el primer turno, el triage_agent ya no necesita preguntarle al cliente quién es. Puedes inyectar ese dato en el mensaje que recibe el agente, de modo que arranque la conversación sabiendo con quién habla. Compara los dos primeros turnos:

# SIN metadata
Cliente:  ¿cómo va mi pedido?
Agente:   ¡Hola! Claro, ¿me compartes tu número de pedido o el correo
          con el que compraste?

# CON metadata (customer_id inyectado)
Cliente:  ¿cómo va mi pedido?
Agente:   ¡Hola! Tu pedido más reciente, el #4521, va en camino y llega
          el jueves. ¿Es ese el que buscabas?

El segundo se siente diez veces mejor y no requiere un modelo mejor. Requiere que la capa de adaptación le pase al cerebro un dato que ya estaba disponible en la página. Ese es, en pequeño, todo el oficio de esta capa.

Una advertencia de confianza que sí importa. metadata viaja desde el navegador, y cualquier cosa que viaje desde el navegador la puede modificar quien controle ese navegador. Si tu chat es público y le pasas un customer_id por metadata, alguien puede cambiarlo por otro y ver la conversación de un tercero. Para un chat detrás de una sesión autenticada donde el dato lo escribe tu servidor en el HTML, el riesgo es bajo. Para cualquier cosa que toque datos sensibles, la identidad debe validarse del lado del servidor — típicamente pasando un token firmado en vez de un ID en claro, y verificándolo en n8n antes de usarlo. Los límites de confianza son el tema del Módulo 7; por ahora, que quede claro que este mecanismo es cómodo pero no es una autenticación.

Errores comunes

Usar la URL de test como si fuera permanente (práctico). Qué pasa: montas el widget, pruebas, funciona precioso. Cierras el canvas de n8n para irte a comer, vuelves, y el chat ya no responde — cada mensaje falla y no hay ningún error visible en la página. Por qué pasa: la URL de test solo está registrada mientras el editor está abierto y ejecutando; la de producción requiere que el workflow esté activo. Las dos se ven casi iguales y es facilísimo copiar la equivocada. Cómo detectarlo: abre la consola del navegador (F12) y mira la petición fallida; un 404 con un mensaje sobre webhook no registrado es exactamente esto. Cómo corregirlo: usa siempre la URL de producción para cualquier cosa embebida, y confirma que el interruptor de "Active" del workflow está encendido — es el mismo interruptor, arriba a la derecha, que a veces se apaga solo cuando duplicas un workflow.

Confundir "el agente no recuerda" con "la pantalla no muestra" (conceptual). Qué pasa: alguien recarga la página del chat, ve el hilo vacío, y concluye que la memoria no funciona. Cambia el nodo de memoria, ajusta el sessionId, prueba otro almacén — y nada cambia, porque la memoria nunca estuvo rota. Por qué pasa: Load Previous Session controla si el widget pinta los mensajes anteriores, y es una opción distinta del nodo de memoria que alimenta el contexto del modelo. Son dos cosas separadas que la intuición junta. Cómo detectarlo: después de recargar, escribe algo que dependa del contexto anterior —"¿y qué pasó con lo que te pregunté antes?"— y mira si el agente responde bien. Si responde bien con la pantalla vacía, la memoria está perfecta y lo que falta es la opción de presentación. Cómo corregirlo: activa Load Previous Session y confirma que hay un nodo de memoria conectado al agente, porque sin él la opción no tiene de dónde leer.

Dejar Allowed Origin (CORS) en * y descubrirlo tarde (práctico). Qué pasa: el widget funciona en todas partes durante el desarrollo porque el valor por defecto acepta cualquier origen. Pasa a producción así, y ahora cualquier página de internet puede embeber tu chat y consumir tus tokens. Por qué pasa: * es el valor que hace que todo funcione a la primera, y nada te avisa de que sigue ahí. Cómo detectarlo: revisa el campo en el nodo; si dice *, es esto. Cómo corregirlo: pon los orígenes reales separados por coma —tu dominio de producción y, si lo necesitas, tu origen local de desarrollo— y prueba desde los dos. Ten presente el alcance real de la medida: CORS lo aplica el navegador, así que protege contra la incrustación en otro sitio, no contra alguien llamando directamente a tu webhook con una herramienta de línea de comandos. Para eso hace falta autenticación de verdad.

Poner los mensajes de bienvenida en el nodo cuando el modo es embebido (práctico). Qué pasa: alguien configura Initial Message(s), Title y Subtitle en el Chat Trigger, embebe el widget, y no aparece ninguno de los tres. Revisa el nodo tres veces y todo está bien escrito. Por qué pasa: esas opciones pertenecen al modo Hosted, donde la interfaz la sirve n8n. En modo embebido la interfaz la construye @n8n/chat, así que los textos van en las opciones de createChat. Cómo detectarlo: si el modo del trigger es Embedded y esperabas ver textos configurados en el nodo, es esto. Cómo corregirlo: mueve los textos a initialMessages e i18n en el snippet de la página.

Olvidar que un chat público es un endpoint público (conceptual). Qué pasa: alguien publica el chat con Authentication: None, comparte la URL en una demo, y semanas después nota un consumo de tokens que no corresponde al tráfico real de su sitio. Por qué pasa: la URL es fácil de compartir, no caduca, y nada en n8n te recuerda que del otro lado hay un modelo que cobra. Cómo detectarlo: compara el número de ejecuciones del workflow con las conversaciones que esperabas; si no cuadran, alguien más está escribiendo. Cómo corregirlo: para demos y herramientas internas usa Basic Auth o n8n User Auth; para un chat de verdad público, mantén la URL fuera de repositorios y documentos compartidos, y trata el control de gasto y los guardrails como lo que son — un tema propio, que es el Módulo 7.

Ejercicios

Ejercicio 1 — Elige el modo de respuesta. Para cada uno de estos tres escenarios de TuTienda, di qué Response Mode usarías y por qué. (a) Un agente que responde preguntas frecuentes en dos segundos. (b) Un agente que consulta tres sistemas y tarda entre diez y veinte segundos, y quieres avisarle al cliente que espere. (c) Un agente que redacta respuestas largas de varios párrafos y quieres que se sienta ágil.

Ver solución

(a) When Last Node Finishes. Dos segundos no necesitan nada más. Agregar nodos de respuesta o streaming aquí es complejidad sin beneficio: nadie percibe la diferencia entre "apareció en dos segundos" y "apareció letra por letra durante dos segundos".

(b) Using Response Nodes. Es el caso para el que existe. Pones un nodo Chat con operación Send Message justo después del trigger, diciendo algo como "Dame un momento, estoy consultando tu pedido", y el workflow sigue trabajando. Cuando termina, un segundo nodo Chat manda la respuesta real. Sin esto, el cliente ve quince segundos de silencio, y quince segundos de silencio en un chat es mucho tiempo — la mitad de la gente escribe "¿hola?" antes de que llegue la respuesta, lo cual además dispara otra ejecución.

(c) Streaming response. Es exactamente su caso de uso: el texto empieza a aparecer casi de inmediato aunque la respuesta completa tarde. La percepción de velocidad cambia por completo sin que la latencia real cambie nada. La salvedad honesta: verifica que tu modelo y tu configuración de agente lo soporten en tu versión de n8n, y presta atención a qué se muestra cuando hay razonamiento intermedio o delegación entre agentes — no quieres que el cliente vea el JSON de billing_specialist apareciendo palabra por palabra.

Por qué funciona: los tres escenarios no se distinguen por preferencia estética sino por un número —cuánto tarda— y por si hay algo que decir a mitad de camino. Ese es el criterio real.

Ejercicio 2 — Diagnostica el widget mudo. Un compañero pegó el snippet en la página de TuTienda. El widget aparece, se ve bien, y al escribir un mensaje no pasa absolutamente nada: no hay respuesta ni error visible. Escribe los cinco lugares que revisarías, en orden, y qué esperarías ver en cada uno.

Ver solución

En este orden, de lo más probable a lo menos:

  1. La consola del navegador (F12). Es lo primero siempre, porque el error real está ahí y en la página no se ve. Un 404 apunta a URL equivocada o workflow inactivo; un mensaje que menciona CORS o Access-Control-Allow-Origin apunta al campo de orígenes; un 401 apunta a autenticación.
  2. El interruptor "Active" del workflow. Si está apagado, la URL de producción no existe. Es la causa número uno y toma dos segundos descartarla.
  3. La webhookUrl del snippet contra la del nodo. Que sea la de producción y no la de test, y que esté copiada completa — se corta con facilidad al copiar y pegar.
  4. Allowed Origin (CORS) en el nodo. Que incluya el origen exacto desde el que se sirve la página, con su esquema y su puerto. https://tutienda.example y http://localhost:8080 son orígenes distintos y hay que listar los dos si usas los dos.
  5. El modo del Chat Trigger. Si quedó en Hosted en vez de Embedded, la URL responde con una página de chat en vez de aceptar la petición del widget. El síntoma es raro y despista bastante.

Y un sexto lugar, si los cinco anteriores están bien: la pestaña de ejecuciones de n8n. Si ahí aparece una ejecución por cada mensaje que escribiste, el canal funciona y el problema está en el camino de vuelta —típicamente un Response Mode en Using Response Nodes sin ningún nodo que responda—. Si no aparece ninguna ejecución, el mensaje nunca llegó y el problema está en los cinco anteriores.

Por qué funciona: ese último desempate —¿hay ejecución o no?— parte el problema en dos mitades y es el paso que más tiempo ahorra. Sin él, uno revisa las diez causas posibles; con él, revisa cinco.

Ejercicio 3 — Diseña la identidad del chat de TuTienda. TuTienda tiene dos situaciones en su sitio: visitantes anónimos que están viendo el catálogo sin haber iniciado sesión, y clientes con sesión iniciada en su panel de cuenta. Quieres un solo widget que sirva para los dos. Escribe qué metadata mandas en cada caso y qué expresión pones en Session ID del nodo de memoria para que funcione en ambos.

Ver solución

La forma más limpia es que el servidor rellene metadata distinto según el estado de sesión, y que n8n use una expresión con respaldo.

Para un cliente autenticado, tu página renderiza:

metadata: {
  customer_id: 'C-9931',      // lo escribe tu servidor
  authenticated: true
}

Para un visitante anónimo, tu página no manda customer_id en absoluto:

metadata: {
  authenticated: false
}

Y en el nodo de memoria, una expresión que usa el customer_id si existe y cae en el sessionId del widget si no:

Session ID:  Define below
Key:  {{ $('When chat message received').item.json.metadata.customer_id
          || $('When chat message received').item.json.sessionId }}

El operador || de JavaScript devuelve el primer valor que no sea vacío. Si hay customer_id, la conversación se agrupa por cliente y persiste entre dispositivos. Si no lo hay, se agrupa por sesión de navegador — que para un visitante anónimo es exactamente lo correcto, porque no hay ninguna identidad mejor disponible.

Un matiz que vale la pena notar: el visitante anónimo que después inicia sesión cambia de clave a mitad de camino, y su conversación previa queda huérfana bajo la clave vieja. Hay dos posturas razonables. Aceptarlo —la conversación anónima rara vez vale la pena migrar— o, si el caso lo justifica, que el agente al detectar la autenticación resuma lo conversado y lo escriba en la nueva sesión. La primera es la correcta el 90% de las veces, y decirlo así en una entrevista, con la segunda opción nombrada como alternativa descartada a propósito, transmite bastante más criterio que implementar la segunda sin necesidad.

Y confirma siempre la ruta exacta hasta metadata en el JSON de una ejecución real de tu versión antes de dar la expresión por buena.

Por qué funciona: el ejercicio te obliga a resolver identidad con una sola configuración para dos poblaciones distintas, que es exactamente el problema que reaparece en la lección 7 cuando los canales son cuatro en vez de dos.

Resumen y siguiente paso

Ya tienes el primer canal real de tu agente, y con él las decisiones estructurales que se repiten en todos los demás. El Chat Trigger en modo Hosted te da una página lista para usar con URL propia; en modo Embedded te da un webhook al que tu sitio le habla con @n8n/chat y dos etiquetas de HTML. Elegiste modo de respuesta según cuánto tarda tu agente y si hay algo que decir a mitad de camino, protegiste el acceso con criterio, entendiste que Allowed Origin protege contra incrustación pero no es autenticación, y resolviste la identidad del cliente pasando metadata desde una página autenticada en vez de conformarte con el sessionId de la pestaña.

Antes de avanzar deberías poder: explicar la diferencia entre la URL de test y la de producción y por qué el widget deja de funcionar con la primera; nombrar los tres modos de respuesta y en qué caso usarías cada uno; y decir dónde se configuran los mensajes de bienvenida en cada uno de los dos modos, que no es el mismo lugar.

Lo que sigue es el canal que de verdad importa en LATAM y también el más incómodo de montar. La lección 3 va a WhatsApp Business API: qué piezas hay que crear en el panel de Meta, cuáles son las dos credenciales distintas que n8n necesita —una para recibir y otra para enviar—, por qué solo puedes tener un webhook por app y qué significa eso para tus pruebas, y la regla que cambia el diseño de todo: la ventana de 24 horas, con su factura correspondiente.

Recursos

  • Chat Trigger node — n8n Docs — la referencia completa del nodo: los dos modos, las tres opciones de autenticación, los tres modos de respuesta y la lista de opciones de presentación. Verifica ahí los nombres exactos de tu versión.
  • Chat node — n8n Docs — el nodo Send Message / Send and Wait for Response, con sus tipos de respuesta y los parámetros de los botones de aprobación.
  • @n8n/chat — npm — el paquete del widget embebido; ahí está la lista completa y actualizada de las opciones de createChat, incluidas las que esta lección no usó.
  • Respond to Webhook node — n8n Docs — el nodo con el que se responde en modo embebido cuando el modo de respuesta espera nodos de respuesta.
  • Memory in n8n — n8n Docs — para releer cómo el sessionId agrupa el historial, que es la pieza que metadata te permite sustituir por una identidad real.
  • Workflow activation — n8n Docs — qué significa exactamente activar un workflow y por qué la URL de producción depende de ello; el origen del error más común de esta lección.