# Integración con Genesys
Source: https://docs.jelou.ai/connect/panel-multi-agente/genesys
Conecta Jelou con Genesys para gestionar conversaciones y habilitar la interoperabilidad entre ambas plataformas.
Jelou integra los canales de comunicación con Genesys, permitiendo a los usuarios:
* Gestionar conversaciones desde Genesys utilizando los canales de comunicación conectados en Jelou.
* Transferir conversaciones y contexto de interacción entre Jelou y Genesys para garantizar una atención continua y eficiente.
* Escalar conversaciones hacia agentes de Genesys cuando sea necesario.
* Centralizar la gestión de conversaciones en un ecosistema integrado entre ambas plataformas.
## Requisitos previos
Para utilizar la integración con Genesys, necesitas:
* Una cuenta **Enterprise** activa en Jelou.
* El nodo de **Genesys** habilitado en la cuenta.
* Una cuenta activa de **Genesys Cloud** con permisos administrativos.
* Un cliente **OAuth** con permisos para consumir las APIs de Genesys Cloud.
* Los canales y flujos que participarán en la interoperabilidad previamente definidos.
***
## Instala la aplicación
Inicia sesión en Jelou. Dirígete al módulo **Brain** y selecciona la sección **Canvas**.
Dentro del Canvas, crea un nuevo flujo según tu necesidad. Para este ejemplo:
1. Agrega un **nodo de texto** y configura un mensaje de bienvenida para el canal.
2. Desde la barra lateral de nodos, arrastra el **nodo de Genesys** hacia el flujo.
Se recomienda agregar:
* Un mensaje de confirmación para transferencias exitosas.
* Un mensaje de manejo de errores en caso de falla de integración.
Antes de configurar la integración, valida que existan los roles necesarios para el cliente OAuth.
Ruta: **Menu > User Management > Roles and Permissions**
Los roles permitirán que el cliente OAuth tenga acceso a los recursos y APIs utilizados por la integración.
Selecciona el rol correspondiente para revisar los permisos habilitados.
Ruta: **Menu > User Management > Roles and Permissions > \[Rol] > Edit Role**
El rol **Developer** puede utilizarse con los permisos predeterminados de Genesys Cloud.
Dirígete a: **Menu > IT and Integrations > OAuth**
Crea un cliente OAuth que permita autenticar el middleware o servicio externo encargado de la interoperabilidad con Genesys Cloud.
Una vez creado, abre la aplicación para revisar su configuración.
Ruta: **Menu > IT and Integrations > OAuth > \[Cliente OAuth] > Edit Application**
Verifica la siguiente información:
* Nombre de la aplicación.
* Tipo de autenticación.
* Grant Type.
* Configuración general.
Selecciona la pestaña **Roles** del cliente OAuth.
Ruta: **Menu > IT and Integrations > OAuth > \[Cliente OAuth] > Roles**
Asigna los roles previamente configurados para que el cliente OAuth pueda consumir las APIs requeridas por la integración.
Verifica que las divisiones asociadas a los roles correspondan a todas las interacciones que utilizará el conector. Una configuración incorrecta puede generar errores de permisos durante la operación.
Dirígete a: **Menu > Digital and Telephony > Message > Platform Integrations**
Crea una integración de tipo **Open Messaging** para permitir el intercambio de mensajes entre Genesys Cloud y Jelou.
Abre la integración creada para revisar su configuración.
Una vez creada la integración Open Messaging, identifica el **Integration ID**.
Este identificador corresponde al valor único de la integración y será utilizado posteriormente para configurar el Trigger.
El **Integration ID** se encuentra al final de la URL cuando accedes al detalle de la integración Open Messaging.
Dirígete a: **Menu > Orchestration > Triggers**
Crea un Trigger que escuche los eventos asociados a las conversaciones de Open Messaging. Este Trigger será el encargado de ejecutar automáticamente el Workflow cuando ocurra el evento configurado.
Abre el Trigger creado y configura una condición utilizando el **Integration ID** obtenido anteriormente.
Esta condición garantiza que únicamente se ejecuten los eventos correspondientes a la integración configurada.
El Trigger debe ejecutar el Workflow de Architect encargado de notificar a Jelou que la atención en Genesys finalizó.
Dirígete a: **Menu > Orchestration > Architect > Flows**
Crea un Workflow que reciba la información enviada por el Trigger. Este Workflow será responsable de procesar los datos de la conversación y ejecutar el Data Action que notifica la finalización a Jelou.
Dentro del Workflow, configura las variables que recibirán la información enviada desde el Trigger.
Estas variables permitirán identificar la conversación y construir la solicitud hacia el Data Action.
Dentro del Workflow, agrega una tarea para ejecutar el **Data Action** encargado de notificar a Jelou que la atención en Genesys terminó.
Dirígete a: **Menu > IT and Integrations > Data Actions**
Crea un nuevo Data Action que será utilizado por el Workflow.
Abre el Data Action creado y configura la petición hacia el webhook de Jelou.
Este Data Action notifica a Jelou que la atención en Genesys Cloud terminó. Al recibirlo, Jelou retoma el control y el canal continúa atendiendo al usuario final, sin que este perciba el cambio.
Ruta: **Menu > IT and Integrations > Data Actions > \[Data Action] > Configuration**
**Método y endpoint**
| Campo | Valor |
| -------------------- | --------------------------------------------- |
| Método HTTP | `POST` |
| Request URL Template | `https://chatbot.jelou.ai/v1/genesys/webhook` |
**Headers**
| Header | Valor |
| -------------- | ------------------ |
| `Content-Type` | `application/json` |
**Body del request**
El webhook espera un evento de Open Messaging con la siguiente estructura:
```json theme={null}
{
"id": "EVENT_ID",
"type": "Event",
"direction": "Outbound",
"conversationId": "CONVERSATION_ID",
"channel": {
"from": { "id": "FROM_ID" },
"to": { "id": "CUSTOMER_ID", "idType": "Phone" }
},
"events": [
{ "eventType": "CustomerEnd" }
]
}
```
**Descripción de los campos**
| Campo | Tipo | Descripción |
| -------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | Identificador único del evento enviado. |
| `type` | string | Tipo de payload. Para notificar la finalización siempre es `Event`. |
| `direction` | string | Dirección del evento respecto a Genesys Cloud. Debe ser `Outbound` (sale de Genesys hacia Jelou). |
| `conversationId` | string | Identificador de la conversación en Genesys Cloud. Es el valor con el que Jelou identifica la transferencia activa. |
| `channel.from.id` | string | Identificador del participante de Genesys que emite el evento (por ejemplo, el agente o el flujo de Architect). |
| `channel.to.id` | string | Identificador del usuario final en el canal. En WhatsApp es el número de teléfono en formato E.164. |
| `channel.to.idType` | string | Tipo de identificador del usuario final. Para canales de WhatsApp usa `Phone`. |
| `events[].eventType` | string | Evento a notificar. Usa `CustomerEnd` para indicar que la atención en Genesys Cloud finalizó y el control vuelve al canal. |
El campo `direction` se nombra desde el punto de vista de Genesys Cloud: `Outbound` significa que el evento **sale de Genesys** hacia Jelou. El webhook rechaza los payloads marcados como `Inbound`.
**Mapeo de variables**
En la sección **Input Contract** declara las variables que recibirá el Data Action desde el Workflow y referéncialas en el body mediante la sintaxis `${input.nombreVariable}`. Los nombres del ejemplo son ilustrativos: usa los que hayas definido en tu Workflow.
```json theme={null}
{
"id": "${input.eventId}",
"type": "Event",
"direction": "Outbound",
"conversationId": "${input.conversationId}",
"channel": {
"from": { "id": "${input.fromId}" },
"to": { "id": "${input.customerId}", "idType": "Phone" }
},
"events": [
{ "eventType": "CustomerEnd" }
]
}
```
El `conversationId` debe ser el mismo que Genesys Cloud asignó a la conversación transferida desde Jelou. Si no corresponde a una transferencia activa, Jelou no podrá identificarla y el control no se devolverá al canal.
***
## Configura la aplicación
Después de completar la configuración en Genesys Cloud:
Regresa al módulo **Brain** y selecciona el nodo de **Genesys** dentro del flujo.
Completa la configuración del nodo con los parámetros correspondientes de la integración.
Verifica que la conexión con Genesys Cloud sea exitosa antes de continuar.
Cuando la configuración esté lista, **guarda y publica** el flujo.
***
## Usa la aplicación
Una vez configurada la integración:
* Las conversaciones podrán transferirse automáticamente entre Jelou y Genesys Cloud.
* Los canales creados en Jelou automatizarán la atención inicial de los clientes.
* Los agentes podrán continuar la conversación directamente desde Genesys Cloud.
* Cuando la atención finalice en Genesys Cloud, el Trigger y el Workflow notificarán a Jelou y el canal retomará la conversación con el usuario final.
* Los eventos de conversación serán procesados automáticamente mediante Open Messaging, Trigger, Workflow y Data Actions.
No se requieren acciones manuales adicionales una vez publicada la automatización.
# Integración con HubSpot
Source: https://docs.jelou.ai/connect/panel-multi-agente/hubspot
Conecta Jelou con HubSpot para gestionar conversaciones, automatizar flujos y centralizar la atención al cliente.
Jelou integra los canales de comunicación y automatizaciones conversacionales con HubSpot, permitiendo a los usuarios:
* Gestionar conversaciones desde HubSpot utilizando canales de comunicación conectados en Jelou.
* Automatizar flujos conversacionales mediante canales configurados desde el módulo Brain.
* Transferir conversaciones y atención de clientes hacia Inbox de HubSpot o canales personalizados.
* Centralizar la atención al cliente y automatizaciones en una única plataforma.
## Requisitos previos
Para utilizar la integración con HubSpot, necesitas:
* Una cuenta **Enterprise** activa en Jelou.
* El nodo de **HubSpot** habilitado en la cuenta.
***
## Instala la aplicación
Inicia sesión en Jelou. Dirígete al módulo **Brain** y selecciona la sección **Canvas**.
Dentro del Canvas, crea un nuevo flujo según tu necesidad. Para este ejemplo:
1. Agrega un **nodo de texto** y configura un **mensaje de bienvenida** para el canal de prueba.
2. Desde la barra lateral de nodos, arrastra el **nodo de HubSpot** hacia el flujo.
Se recomienda agregar:
* Un mensaje de confirmación para asignaciones exitosas.
* Un mensaje de manejo de error en caso de falla de integración.
Dentro del nodo de HubSpot, haz clic en **Continuar configuración en HubSpot**. La plataforma redireccionará automáticamente a HubSpot para completar la instalación y autorización de la aplicación.
HubSpot te pedirá seleccionar la cuenta que deseas conectar con Jelou. Elige la cuenta correspondiente y haz clic en **Elegir cuenta**.
A continuación, revisa los permisos que la aplicación solicita para enviar y recibir mensajes, gestionar conversaciones y acceder a la información de tu cuenta.
Haz clic en **Conectar aplicación**. Una vez finalizada la autorización, regresarás automáticamente a Jelou.
***
## Configura la aplicación
Después de instalar la integración:
Regresa al módulo **Brain** y selecciona el **nodo de HubSpot** dentro de tu flujo.
Configura el canal de comunicación que utilizarás:
* **Inbox de HubSpot** — para gestionar conversaciones directamente desde HubSpot.
* **Canal personalizado** — para canales configurados de forma independiente.
Configura las acciones deseadas para la atención de clientes y automatizaciones dentro del flujo.
Cuando la configuración esté lista, **guarda y publica** el flujo.
***
## Usa la aplicación
Una vez configurada la integración:
* Las conversaciones recibidas desde los canales conectados podrán ser transferidas y gestionadas desde HubSpot.
* Los canales creados en Jelou podrán automatizar la atención inicial de clientes.
* Los agentes podrán continuar la conversación directamente desde HubSpot Inbox o desde el canal configurado.
* Las sincronizaciones y transferencias de conversaciones se realizan automáticamente según la configuración establecida en el flujo.
No se requieren acciones manuales adicionales una vez publicada la automatización.
***
## Desinstala la aplicación
Para desinstalar la aplicación de Jelou desde HubSpot:
Inicia sesión en tu cuenta de HubSpot. Haz clic en el ícono de **configuración** ubicado en la barra de navegación superior.
En el menú lateral izquierdo, dirígete a **Integraciones > Aplicaciones conectadas**. Ubica la aplicación de **Jelou**.
Haz clic en **Acciones** y luego selecciona **Desinstalar**. En la ventana de confirmación, escribe `desinstalar` y haz clic en **Desinstalar** para confirmar la acción.
Una vez desinstalada la aplicación:
* Jelou dejará de tener acceso a la cuenta de HubSpot.
* Las automatizaciones y transferencias configuradas mediante el nodo de HubSpot dejarán de funcionar.
* Para eliminar completamente la integración del flujo, debes borrar el nodo de HubSpot dentro del módulo Brain en Jelou.
# Guía general para construir con IA
Source: https://docs.jelou.ai/guides/agentes-ia/index
Configura instrucciones, modelos, temperatura, bases de conocimiento, MCPs y herramientas para tus AI Agents
## Instrucción
La instrucción define el comportamiento base del agente: qué rol adopta, qué tono utiliza y qué pasos sigue antes de responder. Asegúrate de que sea breve, concreta y libre de ambigüedades. Si necesitas profundizar en la redacción, consulta la guía de buenas prácticas en `@prompting.mdx`.
[Ver recomendaciones y ejemplos de prompting](/guides/agentes-ia/prompting)
## Modelo
Escoge el modelo que mejor se ajuste al tipo de interacción que buscas. Considera latencia, costo y complejidad de las tareas antes de seleccionarlo.
### Modelos disponibles
* **Llama 4 Scout**: modelo ágil y de baja latencia, ideal para ideas rápidas e interacciones ligeras.
* **GPT 4.1 Mini**: variante simplificada de GPT-4.1 optimizada para respuestas rápidas con menor demanda de recursos.
* **Claude 3.5 Sonnet**: excelente para tareas complejas que requieren textos más elaborados y contextos extensos.
* **GPT 4-o (Azure)**: versión de GPT 4-o alojada en Azure, enfocada en estabilidad y rendimiento en entornos empresariales.
* **Llama 4 Maverick**: modelo de alto rendimiento diseñado para razonamientos exigentes y resolución multistep.
* **GPT 4.1**: evolución refinada de GPT-4, con mejor comprensión, razonamiento y precisión.
* **GPT 4-o Mini**: versión más rápida y ligera de GPT 4-o, orientada a casos donde prima la velocidad.
## Temperatura
La temperatura controla el nivel de creatividad del modelo. Utiliza valores entre `0` y `1`: en `0` obtendrás respuestas deterministas y consistentes; conforme te acerques a `1`, las respuestas serán más creativas y variadas. Ajusta este parámetro según necesites precisión o exploración.
## Bases de conocimiento
Refuerza al agente con documentación específica para que responda con información actualizada y verificable.
* **Archivos**: carga archivos de hasta 2 MB en formatos `.pdf` o `.csv`.
* **URLs**: añade enlaces cuya descarga no supere los 5 MB. Verifica que el contenido sea accesible públicamente.
## MCPs
Conecta Model Context Protocols (MCPs) para ampliar las capacidades del agente. Cada integración requiere:
* **URL del endpoint** al que se realizará la consulta.
* **Headers opcionales** con credenciales o metadatos necesarios para la autenticación.
* **Nombre identificador** para reconocer el MCP dentro del estudio.
* **Descripción** que resuma qué información o acciones ofrece.
## Tools
Las herramientas permiten que el agente ejecute acciones externas. Puedes configurarlas de dos formas:
* **Tools creadas en la plataforma**: defínelas directamente en Brain Studio para integraciones reutilizables y gestionadas.
* **Llamadas HTTP**: especifica un endpoint, método y esquema de entrada/salida para realizar peticiones puntuales a tus servicios.
Combina estos elementos para que el agente seleccione automáticamente la herramienta adecuada durante la conversación.
## Configuración avanzada
En la pestaña de configuración avanzada del nodo AI Agent encontrarás opciones adicionales que amplían las capacidades del agente para procesar diferentes tipos de contenido y mensajes interactivos.
### Soporte de lectura payload en mensajes entrantes
Habilita esta opción para permitir que el AI Agent procese payloads de respuesta rápida de mensajes interactivos. Cuando está activada, el agente puede interpretar y responder a las acciones del usuario en botones de respuesta rápida, mejorando la experiencia en flujos conversacionales que utilizan elementos interactivos.
**Cuándo habilitarla:**
* Cuando tu flujo incluye mensajes con botones de respuesta rápida (quick replies).
* Si necesitas que el agente procese la información enviada a través de payloads en lugar de solo el texto visible del botón.
* Para flujos que requieren capturar datos estructurados desde interacciones con botones.
### Soporte de documentos PDF
Activa esta configuración para que el AI Agent pueda leer y procesar documentos PDF enviados por los usuarios durante la conversación. El agente extraerá el contenido del documento y podrá responder preguntas o realizar acciones basadas en la información contenida.
**Nota importante:** Esta funcionalidad solo procesa el texto del PDF. Las imágenes contenidas dentro del documento no serán procesadas ni analizadas por el agente.
**Cuándo habilitarla:**
* Cuando esperas que los usuarios envíen documentos PDF como parte de la interacción.
* Si necesitas que el agente analice, resuma o extraiga información de archivos PDF.
* Para casos de uso como validación de documentos, extracción de datos o análisis de contenido.
### Leer imagen con URL y pie de foto
Esta configuración permite al AI Agent procesar imágenes que incluyen un pie de foto (caption). El agente podrá acceder tanto a la imagen como al texto descriptivo asociado, procesando ambos elementos de forma conjunta.
**Cuándo habilitarla:**
* Cuando los usuarios envían imágenes con descripciones textuales.
* Si necesitas que el agente procese tanto el contenido visual como el contexto textual de la imagen.
* Para flujos que requieren análisis combinado de imagen y texto.
## Buenas prácticas
* Se recomienda activar siempre la sección de Base de Conocimiento (Knowledge) para anclar las respuestas del agente a la documentación oficial de tu negocio o a la documentación básica que necesite el prompt. Esto reduce drásticamente el riesgo de alucinaciones, ya que el modelo priorizará la información de tus archivos sobre su conocimiento general. Para maximizar su efectividad, asigna descripciones claras a cada archivo cargado, permitiendo que el agente sepa exactamente cuándo debe buscar información en ellos.
# Prompting
Source: https://docs.jelou.ai/guides/agentes-ia/prompting
Buenas prácticas y ejemplos para redactar prompts efectivos en AI Agents
Esta guía se centra exclusivamente en el diseño y la redacción de prompts efectivos para AI Agents y AI Tasks. Para conceptos generales de agentes, estructura, herramientas y configuraciones, revisa la `Guía general de AI Agents`.
## Ejemplos de prompts para un AI Agent
### Preguntas Frecuentes
Cuando necesitemos darle contexto adicional sobre una empresa al AI Agent para que pueda responder a las consultas del usuario. Deben usar el nodo AI Agent.
#### Prompt
```text theme={null}
Eres un asesor de [nombre cliente], [descripción de cliente], que debe responder consultas al usuario.
Siempre responde a las consultas de manera [tono, personalidad]
Cuando el usuario te haga una consulta, debes siempre seguir el siguiente proceso:
1. Busca contexto sobre [nombre cliente] en los archivos que tienes disponible.
2. Determinar si la información que obtienes es relevante para la consulta del usuario. Se muy cuidadoso en este paso, ya que debes definir de manera clara si esta información es relevante o no.
3. Si y solo si el usuario expresa deseo de salir o terminar la interacción, ejecuta la función end_function con los siguientes parámetros: {"salir":true}
```
Incluso, se puede incluir guías para manejar la interacción, como por ejemplo:
```text theme={null}
Ten en cuenta las siguientes indicaciones para tu interacción:
- Si no encuentras información sobre un servicio, asume que [nombre comercio] no brinda ese servicio.
- Eres parte de [nombre cliente], por lo tanto puedes hablar en primera persona.
```
### Redireccionar al usuario
Cuando se necesite redireccionar al usuario a una parte del flujo según su intención.
Si es el único propósito (como un router) pueden usar un AI Task.
Si esto es parte de una interacción, como por ejemplo redireccionar al usuario a un flujo cuando lo solicita, pueden usar un AI Agent.
Esto se puede conseguir con una combinación de AI y nodo condicional. Deben guardar la respuesta del AI, que la pueden acceder con `{{$memory.nombreVariable}}`.
#### Prompt
```text theme={null}
Eres un router de AI que debe procesar un mensaje del cliente, para decidir a que agente especializado lo envías.
Existen 5 agentes:
1. Tarjetas de crédito (emisión, información general sobre tarjetas o American Express /AMEX)
2. Cuentas de ahorro y cuentas corrientes (aperturas e información general)
3. Documentos (certificados, estados de cuenta)
4. Consultas generales (consultas generales sobre el banco)
5. Otro
Intenta en lo posible ubicarlo en algún agente, y solo si el mensaje es muy genérico, puedes elegir el 5 (otro).
En caso de que no se entienda el mensaje, o sea muy ambigüo, vuelve a preguntarle al usuario en palabras amables.
Siempre tienes que terminar tu ejecución llamando a end_function con un esquema en formato JSON: `{flujo:[flujo]}`.
Flujo puede ser igual a "tarjetas", "cuentas", "documentos", "consultas" y "otro".
Ejemplos:
- Si usuario hace un consultas sobre tarjetas de crédito, terminas con end_function `{flujo:"tarjetas"}`
- Si usuario te pide un certificado bancario, terminas con end_function `{flujo:"documentos"}`
```
### Solicitar datos al usuario
Para procesos de registro o flujos en los que se debe pedir información al usuario.
#### Prompt
```text theme={null}
Debes solicitar al usuario su nombre y correo electrónico.
Cuando el usuario te proporcione su nombre y correo, ejecuta tu función end_function con el esquema en formato JSON:
{"nombre":[nombre],"correo":[correo]}
```
Luego, se puede acceder con `{{$memory.nombreVariable}}` para guardarla en un nodo Datum.
## Ejemplos de prompts para un AI Task
Los AI Task son peticiones a OpenAI/LLM preferido. Su prompt es más fácil de escribir y no requiere de utilizar palabras claves para terminar como `end_function`. Su objetivo siempre será cumplir lo que le dice el prompt y guardar la respuesta en formato JSON.
### Redireccionar al usuario
```text theme={null}
Debes procesar el mensaje del usuario, para decidir a que flujo se envía.
El mensaje del usuario es: {{$message.text}}
Existen 5 flujos:
1. Tarjetas de crédito (emisión, información general sobre tarjetas o American Express / AMEX)
2. Cuentas de ahorro y cuentas corrientes (aperturas e información general)
3. Documentos (certificados, estados de cuenta)
4. Consultas generales (consultas generales sobre el banco)
5. Otro
Intenta en lo posible ubicarlo en algún flujo, y solo si el mensaje es muy genérico, puedes elegir el 5 (otro).
Genera el siguiente JSON: {flujo:[flujo]}.
Donde flujo puede ser igual a "tarjetas", "cuentas", "documentos", "consultas" y "otro".
Ejemplos:
- Si usuario hace un consultas sobre tarjetas de crédito, terminas con {flujo:"tarjetas"}
- Si usuario te pide un certificado bancario, terminas con {flujo:"documentos"}
```
La diferencia es que no se hace uso de `end_function`.
## Resumen de Buenas Prácticas
| Aspecto | Recomendación |
| ----------- | ------------------------------------------------------------------------------------------------- |
| Claridad | Usa prompts directos y estructurados |
| Lenguaje | Sé consistente en los términos (ej: "herramienta") |
| Uso de JSON | Especifica con claridad el formato esperado |
| Terminación | En AI Agents, usa `end_function` correctamente. En AI Task solo asegúrate de especificar el JSON. |
| Contexto | Indica cómo evaluar entradas o referencias relevantes. |
## Tipos de contenido y formato en prompts
* Mantén un lenguaje consistente en todo el prompt.
* Especifica claramente el formato de salida (JSON recomendado).
* Si el flujo requiere terminar, define explícitamente el uso de `end_function`.ar
## Diseño Inteligente de Prompts Condicionales
Para obtener mejores resultados, es clave minimizar la cantidad de decisiones que debe tomar la IA, especialmente cuando no están directamente relacionadas con su objetivo principal. En lugar de cargar el prompt con múltiples instrucciones condicionales, es preferible preprocesar la lógica fuera del AI Agent, manteniendo el prompt limpio, enfocado y más efectivo.
### Qué evitar
Evita escribir prompts como este:
> Si tienes datos en esta variable `{{$memory.variable}}`, entonces haz tal cosa, de lo contrario, sigue estas otras instrucciones.
Este tipo de lógica agrega ambigüedad y ruido al prompt, lo cual puede dificultar la comprensión del modelo y disminuir la precisión de la respuesta.
### Alternativa recomendada
Usa lógica condicional previa en un nodo de tipo código para definir qué instrucciones debe seguir la IA antes de enviarle el prompt.
Ejemplo:
```javascript theme={null}
let promptCondicional = "Haz tal cosa primero";
if($memory.get("variable")){
promptCondicional = "Mejor haz esto";
}
$memory.set("promptCondicional", promptCondicional);
```
Luego, en tu AI Agent, simplemente haz referencia a la variable procesada:
```text theme={null}
Sigue estas instrucciones: {{$memory.promptCondicional}}
```
### Beneficios
* Reduce la carga cognitiva del modelo
* Disminuye el uso de tokens innecesarios
* Aumenta la claridad del prompt
* Mejora la consistencia de las respuestas
Este enfoque ayuda a diseñar agentes más confiables y predecibles, especialmente cuando se combinan múltiples fuentes de entrada o condiciones dinámicas.
> Para consideraciones sobre modelos (GPT-4.1, contextos largos, CoT y recordatorios de agente), consulta la `Guía general de AI Agents`.
## Herramienta de Validación de Prompts
Para facilitar la creación de prompts efectivos, puedes usar una herramienta en beta que actúa como asistente de pruebas. Si tu prompt no está funcionando como esperas o necesitas una guía rápida para comenzar, simplemente accede al siguiente enlace, escribe tu prompt u objetivo y sigue las sugerencias del asistente:
[ChatGPT - Corrector de Prompts AI Agents](https://chatgpt.com/g/g-68127b80d72481918eca6f5b97bea57e-corrector-de-prompts-ai-agents)
Esta herramienta está en fase beta, por lo que aún se encuentra en desarrollo. Sin embargo, es útil para detectar errores comunes, refinar instrucciones y validar si el prompt está bien estructurado antes de implementarlo en Brain Studio.
## Recursos adicionales
* [OpenAI Platform](https://platform.openai.com/playground) - Explora recursos de desarrollador, tutoriales, documentación de API y ejemplos dinámicos
* [AI Playground](https://ai-sdk.dev/playground) - Compara modelos de IA lado a lado: OpenAI GPT, Anthropic Claude, Google Gemini, Llama, Mistral y más
# Seguridad y Guardrails
Source: https://docs.jelou.ai/guides/agentes-ia/seguridad
Guía sobre guardrails y configuración de seguridad para AI Agents
## Guardrails en el prompt
Los guardrails mantienen al AI Agent dentro de límites seguros, predecibles y útiles. Deben definirse y utilizarse como un bloque único y coherente dentro de la caja de instrucciones, considerando los siguientes aspectos:
**Identidad y alcance:** Define expícitamente el rol y lo que soporta. El agente debe comprender con precisión el dominio para el que fue diseñado y conocer las instrucciones a seguir ante cualquier solicitud fuera de ese alcance, por ejemplo, rechazándola de forma cortés y redirigiendo la interacción hacia los temas que sí soporta.
**Anclaje al contexto:** Utilizar Knowledge como fuente principal. Asegurarse de seleccionar los documentos necesarios para el agente y proporcionar el contexto adecuado en el prompt. Indicar claramente qué documento debe usarse en cada parte, explicitar el uso exclusivo de la función de knowledge `search` y especificar que el agente debe responder únicamente con la información presente en el contexto, o devuelta de dicha función. Se debe definir el camino o la acción a seguir en caso de que la información no esté disponible.
**Protección de datos:** Indicar explícitamente qué tipos de datos sensibles no deben ser expuestos, de acuerdo con las políticas y limitaciones de cada agente. Incluir ejemplos claros como: identificadores internos, tokens, credenciales y datos personales (PII) de terceros. Asimismo, definir el comportamiento esperado del agente en caso de detectar o recibir solicitudes relacionadas con datos sensibles, estableciendo una salida segura (por ejemplo, rechazo de la solicitud, anonimización o redirección).
**Manejo de errores:** Definir explícitamente los pasos que el agente debe seguir y el tipo de respuesta que debe proporcionar cuando una tool falle, devuelva resultados incompletos o no se comporte como se esperaba. Incluir ejemplos de mensajes de error, criterios para reintentos y alternativas o acciones de contingencia cuando corresponda.
### Ejemplo
### Seguridad
Responde únicamente sobre configuración de chatbots en Jelou; si te preguntan algo fuera de ese alcance, dilo cortésmente y cambia la conversación al tema soportado.
Responde solo con base en la respuesta de tu función `search` y/o la base de conocimientos; si la respuesta no está en el contexto, di: "No tengo esa información."
No reveles IDs internos, tokens, credenciales ni PII de terceros; si te solicitan algo sensible, recházalo o redáctalo.
Si una herramienta falla o expira, discúlpate, explica brevemente lo ocurrido y ofrece escalar a un agente humano; no inventes resultados ni hagas reintentos silenciosos.
## Configuración de seguridad
Además de los guardrails que defines en el prompt, puedes activar una **capa de protección automática** directamente desde la configuración avanzada del nodo AI Agent. Este sistema de seguridad **filtra contenido, detecta amenazas y protege contra intentos de manipulación**, sin que tengas que escribir una sola línea adicional en tus instrucciones.
Para activarla, ve a la pestaña de **configuración avanzada** del nodo y habilita el interruptor de **Habilitar Seguridad**.
### Nivel de seguridad
Una vez habilitada la seguridad, selecciona el nivel que mejor se ajuste a tu caso de uso. Te recomendamos **comenzar con el nivel Bajo** e ir subiendo progresivamente a medida que identifiques las necesidades reales de tu flujo.
| Nivel | Qué incluye | Caso de uso |
| ----------- | -------------------------------------------------------------------- | ------------------------ |
| **Bajo** | Validación básica de entradas, filtrado ligero de contenido | Pruebas, flujos internos |
| **Medio** | + Detección de inyección de prompts, protección PII, auditoría | Producción general |
| **Alto** | + Sensibilidad aumentada, moderación estricta, validación avanzada | Datos sensibles |
| **Crítico** | + Bloqueo de amenazas medias, prevención de fuga de datos, sin caché | Financiero, regulado |
**Protección básica para comenzar.** Ideal para flujos internos o de prueba donde el riesgo es mínimo. Activa validación básica de entradas y filtrado ligero de contenido. Protege contra inyección de prompts y jailbreak, pero no habilita protección avanzada ni auditoría. Es un buen punto de partida para familiarizarte con la funcionalidad sin impactar el rendimiento.
**Cuándo usarlo:** flujos internos, entornos de desarrollo, pruebas iniciales o agentes con un público controlado.
**El balance ideal para producción.** Incluye todo lo del nivel Bajo y agrega detección de inyección de prompts más robusta, sanitización de respuestas, protección del prompt del sistema y registro de auditoría. También habilita la **protección avanzada de datos personales (PII)**, reemplazando automáticamente información sensible antes de enviarla al modelo.
**Cuándo usarlo:** flujos en producción con usuarios reales, agentes de atención al cliente, consultas generales.
**Protección reforzada para datos delicados.** Aumenta la sensibilidad de detección de amenazas, aplica moderación de contenido más estricta y activa validación avanzada de entradas. La detección de datos personales opera con **mayor precisión**, reduciendo la probabilidad de que información sensible pase sin ser detectada.
**Cuándo usarlo:** agentes que manejan datos personales, flujos de cobranza, consultas médicas o cualquier escenario donde la exposición de datos tenga consecuencias significativas.
**Máxima seguridad, sin excepciones.** Activa todas las funciones de protección disponibles: **prevención de fuga de datos, detección exhaustiva de contenido sensible y auditoría completa** de cada interacción. Las amenazas de gravedad alta y media se bloquean automáticamente. El caché de seguridad se desactiva para garantizar que cada mensaje se analice de forma independiente.
**Cuándo usarlo:** flujos financieros, datos médicos confidenciales, información regulada o cualquier escenario donde una fuga de datos podría tener consecuencias legales o regulatorias.
Comienza con el nivel **Bajo** y ve subiendo según lo que necesites. No es necesario saltar directamente al nivel más alto; cada nivel agrega protecciones sobre el anterior, así puedes ajustar la seguridad de forma gradual.
## Protección avanzada
Al habilitar la seguridad, se activa una capa de protección que trabaja en **dos momentos** de cada conversación: analiza los mensajes entrantes del usuario **antes** de enviarlos al modelo de IA, y revisa las respuestas del agente **antes** de entregarlas al usuario. De esta forma, se cubre tanto lo que entra como lo que sale.
### Qué detecta
* **Inyección de prompts:** intentos de manipular al agente para que ignore sus instrucciones o se comporte de forma no deseada.
* **Jailbreak:** técnicas para evadir las restricciones de seguridad del modelo.
* **Contenido dañino:** filtro de contenido responsable (violencia, discurso de odio, contenido sexual, etc.).
* **URLs maliciosas:** enlaces a sitios conocidos como peligrosos.
* **Fuga de datos sensibles (PII):** detección automática de información personal como correos electrónicos, números de teléfono, tarjetas de crédito, documentos de identidad y más.
* **Fuga del prompt del sistema:** intentos de extraer las instrucciones internas del agente.
### Protección de datos personales
Cuando se detecta información personal en un mensaje, se reemplaza automáticamente por **marcadores seguros** (por ejemplo, `[EMAIL_ADDRESS]` o `[PHONE_NUMBER]`) antes de enviarlo al modelo de IA. Esto significa que el modelo **nunca ve los datos reales** del usuario.
Si tu flujo necesita enviar esos datos reales a **herramientas externas** (como una API de consulta o un sistema de pagos), la plataforma puede restaurar los valores originales de forma segura únicamente para esas herramientas, sin exponerlos en la conversación.
### Cómo responde ante amenazas
Dependiendo del nivel de seguridad configurado y la gravedad de la amenaza detectada, el sistema puede:
* **Bloquear** la solicitud y mostrar un mensaje de error al usuario.
* **Sanitizar** el contenido eliminando las partes problemáticas y dejando pasar el resto.
* **Registrar** el evento en el log de auditoría para revisión posterior.
En el nivel **Crítico**, las amenazas de gravedad alta y media se bloquean automáticamente. En los niveles **Medio** y **Alto**, solo las amenazas de gravedad alta se bloquean; las demás se sanitizan y el flujo continúa.
## Buenas prácticas
* **Combina ambas capas:** escribe guardrails claros en el prompt **y** habilita la configuración de seguridad. Los guardrails definen el comportamiento esperado del agente; la protección automática cubre amenazas que un prompt por sí solo no puede cubrir.
* **Comienza con el nivel Bajo** y sube progresivamente. Esto te permite entender las protecciones de cada nivel sin afectar el rendimiento. Si manejas datos financieros, médicos o altamente sensibles, considera el nivel **Alto** o **Crítico**.
* **Define qué hacer cuando algo falla.** La seguridad protege contra amenazas, pero el agente necesita saber cómo responder ante errores inesperados.
* **Revisa los logs de auditoría** periódicamente para identificar patrones de amenazas y ajustar tus instrucciones o nivel de seguridad si es necesario.
# Tokens
Source: https://docs.jelou.ai/guides/agentes-ia/tokens
Entiende qué son los tokens, cómo influyen en el consumo de IA y cómo optimizar prompts y flujos en Brain Studio.
Los tokens son la **unidad de "combustible"** que usa un modelo para procesar texto. Cada mensaje que entra, cada instrucción del agente, cada variable que incluyes como contexto y cada respuesta que sale se mide (internamente) en tokens.
Piénsalo como gasolina: si tu flujo "pesa" más o viaja más lejos, **tiende** a consumir más.
> Nota: entender tokens te ayuda a diseñar agentes más rápidos, estables y baratos de operar a escala.\
> Ver: [Precios](/guides/billing/precios)
## Qué es un token
Un token es una unidad de texto que el modelo procesa. No equivale 1:1 a "una palabra": puede ser una palabra completa o un fragmento.
En una ejecución típica, consumes tokens por:
* **Entrada**: el mensaje del usuario + la **instrucción** del agente + el contexto que inyectas.
* **Salida**: la respuesta del modelo.
* **Herramientas y nodos**: por ejemplo, cuando un nodo trae datos (como un JSON) y esos datos se usan como contexto.
## La regla mental: tokens = gasolina
### 1) Más peso, más consumo
Si tu agente carga demasiadas instrucciones, ejemplos, reglas redundantes o datos innecesarios, el prompt "pesa" más.
**Causas comunes:**
* Escribes instrucciones largas o repetidas.
* Incluyes demasiado historial o haces "copypaste" de información en el contexto.
* Recibes respuestas de herramientas con payloads grandes.
Guías relacionadas:
* [Prompting](/guides/agentes-ia/prompting)
* [Guía general (Agentes IA)](/guides/agentes-ia/index)
### 2) Mientras más lejos viajas, más gastas
Las conversaciones con muchos turnos tienden a acumular contexto útil… y a veces también ruido.
Si tu flujo depende de historial largo, considera:
* Resumir o normalizar información clave.
* Guardar solo lo que necesitas para el siguiente paso (no todo el chat).
Guías relacionadas:
* [Variables - Guía rápida](/guides/variables/guia-rapida)
* [Memory](/guides/variables/memory)
* [Contexto](/guides/variables/context)
### 3) El modelo también importa
Los modelos más "grandes" o con más capacidades suelen ser más costosos de ejecutar. En general:
* Usa un modelo especializado o liviano para tareas simples (routing, validaciones, extracción).
* Reserva un modelo más capaz para razonamiento complejo o generación más rica.
## Qué suele disparar el consumo en un flujo
* **Prompts extensos** (sobre todo si incluyes texto repetido).
* **Integraciones/API** que retornan demasiado contenido (catálogos enormes, logs, JSONs sin filtrar).
* **Variables persistidas sin criterio** que vuelves a incluir en cada paso.
* **Respuestas largas** cuando el usuario solo necesita una salida corta/estructurada.
Relacionado:
* [Nodo API](/guides/nodos/api)
* [Nodo Variable](/guides/nodos/variable)
* [AI Task](/guides/nodos/ai-task)
## Señales de alerta
Si notas alguno de estos síntomas, probablemente estés consumiendo más tokens de los necesarios:
| Síntoma | Causa probable | Solución |
| ------------------------------- | ---------------------------- | ------------------------------------------ |
| Latencia alta (>5s) | Contexto muy grande | Reduce historial, filtra datos |
| Respuestas cortadas | Límite de salida alcanzado | Pide respuestas más cortas o estructuradas |
| Error "context length exceeded" | Entrada supera el límite | Reduce instrucciones o contexto |
| Respuestas inconsistentes | Demasiado ruido en el prompt | Limpia y prioriza información relevante |
| Costos inesperados | Tokens de herramientas/API | Filtra respuestas de integraciones |
## Ejemplo: antes vs después
```text theme={null}
Eres un asistente virtual de atención al cliente para la empresa
XYZ Corporation S.A. de C.V. que fue fundada en 1985 y tiene
presencia en 15 países de Latinoamérica. Tu objetivo principal
es ayudar a los clientes con sus consultas de manera amable,
profesional y eficiente. Siempre debes ser cortés y empático.
Recuerda que el cliente siempre tiene la razón y debes tratarlo
con respeto. Si no sabes algo, admítelo honestamente.
Aquí está el historial completo de la conversación:
[500 líneas de chat previo]
Aquí está el catálogo completo de productos:
[2000 productos con todos sus atributos]
Responde al cliente de forma detallada y completa.
```
**Problema**: \~15,000+ tokens solo de entrada.
```text theme={null}
Asistente de soporte de XYZ. Responde en español, máximo 2 párrafos.
Contexto del cliente:
- Nombre: {{memory.nombre}}
- Último pedido: {{memory.ultimo_pedido}}
Productos relevantes (filtrados):
{{context.productos_filtrados}}
Pregunta: {{input.message}}
```
**Resultado**: \~200-400 tokens. Mismo resultado, 50x más eficiente.
Usa el [Tokenizer de OpenAI](https://platform.openai.com/tokenizer) para analizar tus prompts y encontrar oportunidades de optimización.
## Buenas prácticas para optimizar (sin perder calidad)
### Haz prompts "delgados"
Un buen prompt suele ser:
* Breve
* Con reglas claras
* Sin ejemplos innecesarios
* Con formato de salida explícito (si aplica)
Empieza por acá:
* [Prompting](/guides/agentes-ia/prompting)
### Decide dónde vive cada dato: Context vs Memory
No todo debe persistir.
* **Context**: vive solo durante la ejecución actual. Úsalo para cálculos temporales y pasos intermedios.\
Ver: [Contexto](/guides/variables/context)
* **Memory**: persiste entre conversaciones/skills con control de tiempo de vida (TTL). Úsalo para datos que realmente reutilizas (por ejemplo, preferencias o identificadores).\
Ver: [Memory](/guides/variables/memory)
### Limita la información que traes de herramientas
Si usas API, filtra desde origen:
* Pide solo los campos necesarios.
* Pagina resultados.
* Evita traer blobs o catálogos completos "por si acaso".
Relacionado:
* [Nodo API](/guides/nodos/api)
## Tokens y facturación en Jelou
Conoce más sobre costos en:
* [Cómo funciona](/guides/billing/introduccion)
* [Precios](/guides/billing/precios)
* [Devoluciones y reintentos](/guides/billing/reembolsos)
Aunque no pagues "por token" directamente en todos los casos, optimizar tokens sigue siendo valioso porque reduces fricción operativa: latencia, ruido en contexto, respuestas inconsistentes y costo total de infraestructura cuando escalas.
## Idea guía
Cada token debería tener trabajo real que hacer.
Tokens decorativos = gasolina evaporándose.
# Tools nativas
Source: https://docs.jelou.ai/guides/agentes-ia/tools-nativas
Herramientas nativas disponibles en el nodo AI Agent para consultar productos, manipular fechas, enviar mensajes interactivos y transferir conversaciones
Las tools nativas son herramientas predefinidas e integradas en la plataforma que permiten al modelo de IA realizar acciones específicas durante una conversación. Estas herramientas extienden las capacidades del agente de IA más allá de la generación de texto, permitiéndole interactuar con el sistema, consultar información, manipular datos y ejecutar acciones concretas.
Las tools nativas se encuentran y configuran en el nodo **AI Agent**. Para acceder a ellas:
1. Selecciona el nodo AI Agent en tu flujo
2. Abre la sección **TOOLS** en la configuración del nodo
3. Haz clic en el botón **"+ Agregar tools"**
4. En el campo "Selecciona un Tool", verás la lista de todas las tools disponibles, incluyendo las nativas (marcadas con el texto "(Nativa)")
A continuación se describen todas las herramientas nativas disponibles en el nodo AI Agent:
## Búsqueda de productos
Busca productos en el catálogo disponible para recomendar al usuario. Utiliza un motor de búsqueda para encontrar coincidencias basadas en el nombre del producto o términos relacionados.
**Parámetros:**
| Nombre | Tipo | Obligatorio | Descripción |
| :-------------------- | :------: | :---------: | :--------------------------------------------------------------------------------- |
| `product` | `string` | **Sí** | Término de búsqueda o nombre del producto. |
| `top_k` | `number` | No | Cantidad máxima de resultados a retornar. |
| `score_threshold` | `number` | No | Puntuación mínima de similitud (0.0 a 1.0) para considerar un resultado relevante. |
| `client_reference_id` | `string` | No | ID de referencia del cliente para búsquedas personalizadas. |
**Ejemplo de Prompt:**
> "Si el usuario pregunta por la disponibilidad de algún artículo o modelo específico, DEBES usar la herramienta `search_products` para consultar el catálogo antes de responder."
**Ejemplo de Argumentos:**
```json theme={null}
{
"product": "iPhone 15 Pro",
"top_k": 5,
"score_threshold": 0.85
}
```
**Respuesta:**
Retorna un listado de productos encontrados o un mensaje indicando que no se encontraron resultados. En caso de error, devuelve un mensaje de sistema descriptivo.
**Notas:**
* Requiere que el canal tenga un catálogo de productos configurado y accesible.
***
## Transferir a asesor
Transfiere la conversación actual del usuario a un agente humano o a una cola de atención. Permite especificar estrategias de enrutamiento y prioridades.
**Parámetros:**
| Nombre | Tipo | Obligatorio | Descripción |
| :---------------- | :------: | :---------------------: | :---------------------------------------------------------------------------- |
| `assignment_type` | `enum` | No (Default: `direct`) | Método de asignación: `direct` (inmediato) o `queue` (cola de tickets). |
| `assignment_by` | `enum` | No (Default: `shuffle`) | Estrategia de enrutamiento: `shuffle` (auto), `team`, `operators`, `general`. |
| `teamId` | `number` | No\* | ID del equipo. \*Requerido solo si `assignment_by` es 'team'. |
| `operatorId` | `number` | No\* | ID del operador. \*Requerido solo si `assignment_by` es 'operators'. |
| `priority` | `number` | No (Default: 0) | Prioridad en la cola (0-10), solo relevante para `assignment_type='queue'`. |
**Ejemplo de Prompt:**
> "Cuando el usuario exprese frustración o solicite hablar explícitamente con una persona real, ejecuta la función `transfer_to_agent` inmediatamente."
**Ejemplo de Argumentos:**
*Caso: Transferencia directa automática (más común)*
```json theme={null}
{
"assignment_type": "direct",
"assignment_by": "shuffle"
}
```
*Caso: Transferencia directa a un equipo*
```json theme={null}
{
"assignment_type": "direct",
"assignment_by": "team",
"teamId": 4501
}
```
*Caso: Crear ticket en cola general*
```json theme={null}
{
"assignment_type": "queue",
"assignment_by": "general",
"priority": 5
}
```
**Respuesta:**
Retorna un objeto JSON con el resultado de la operación indicando si la transferencia se inició correctamente.
**Notas:**
* Puede finalizar el flujo de IA inmediatamente.
* Cuando se usa `assignment_type: "queue"` con `assignment_by: "general"`, se crea un ticket en la cola general de atención.
* **Importante:** No se deben inventar IDs de equipos u operadores; se deben usar los configurados explícitamente.
***
## Enviar mensajes interactivos
Envía un mensaje estructurado o interactivo al usuario. El formato exacto depende del canal (WhatsApp, Web, etc.), pero generalmente incluye botones o listas de opciones.
**Parámetros:**
| Nombre | Tipo | Obligatorio | Descripción |
| :-------- | :------: | :---------: | :--------------------------------------------------------------------------------------------------------------------------------------------- |
| `text` | `string` | **Sí** | Contenido principal del mensaje. Máximo 1024 caracteres. |
| `title` | `string` | No | Cabecera/título del mensaje. Máximo 60 caracteres. |
| `caption` | `string` | No | Subtítulo o descripción complementaria. Máximo 60 caracteres. En WhatsApp con 4+ opciones, se usa como texto del botón que despliega la lista. |
| `options` | `array` | **Sí** | Lista de opciones/botones. Ver estructura abajo. |
*Estructura de `options` (Array de objetos):*
* `title` (string, obligatorio): Texto de la opción/botón. Máximo 20 caracteres. No se permiten títulos duplicados.
* `description` (string, opcional): Descripción adicional de la opción. Máximo 72 caracteres.
**Ejemplo de Prompt:**
> "Si el usuario pregunta qué servicios ofrecemos DEBES usar `send_interactive_message` para mostrar un menú con las opciones disponibles."
**Ejemplo de Argumentos (Botones - 3 o menos opciones):**
```json theme={null}
{
"text": "¿Cómo deseas continuar con tu consulta?",
"title": "Opciones de Servicio",
"caption": "Selecciona una opción",
"options": [
{
"title": "Hablar con Agente",
"description": "Transferir a soporte humano"
},
{
"title": "Ver Catálogo",
"description": "Explorar productos disponibles"
}
]
}
```
**Ejemplo de Argumentos (Lista WhatsApp - 4+ opciones):**
```json theme={null}
{
"text": "Tenemos varios departamentos disponibles para atenderte.",
"title": "Departamentos",
"caption": "Ver opciones",
"options": [
{ "title": "Ventas", "description": "Consultas de productos y precios" },
{ "title": "Soporte Técnico", "description": "Ayuda con problemas técnicos" },
{ "title": "Facturación", "description": "Consultas sobre pagos" },
{ "title": "Devoluciones", "description": "Gestionar devoluciones" }
]
}
```
En WhatsApp, cuando envías 4 o más opciones, el mensaje se muestra como una lista desplegable. El valor de `caption` se usa como texto del botón que abre la lista (en el ejemplo anterior, "Ver opciones").
**Respuesta:**
Retorna `"Message sent successfully."` si el envío fue exitoso o un mensaje de error en caso contrario.
**Notas:**
* Envía un mensaje real al usuario.
* El tipo de mensaje se adapta automáticamente según el canal y la cantidad de opciones.
***
## Enviar Call to Action (CTA)
Envía un botón de llamada a la acción que abre una URL externa. Puede configurarse como WebView para formularios o pagos que requieren esperar una respuesta del usuario.
**Parámetros:**
| Nombre | Tipo | Obligatorio | Descripción |
| :------------------ | :------------------: | :---------: | :------------------------------------------------------------------------------------------------------ |
| `text` | `string` | **Sí** | Contenido principal del mensaje. |
| `url` | `string` | **Sí** | URL que se abre al hacer clic en el botón. |
| `display_text` | `string` | **Sí** | Texto del botón CTA. Máximo 20 caracteres. |
| `title` | `string` | No | Cabecera/título del mensaje. |
| `caption` | `string` | No | Subtítulo para complementar el mensaje. |
| `is_webview` | `boolean` | No | Si es `true`, envía como WebView que pausa el flujo y espera callback. Default: `false`. |
| `expiration_time` | `number` | No | Segundos para esperar el callback antes de expirar. Default: 300. Solo aplica cuando `is_webview=true`. |
| `pending_message` | `string` | No | Mensaje a mostrar si el usuario envía texto mientras se espera el callback del WebView. |
| `response_variable` | `string` | No | Nombre de la variable donde guardar la respuesta del callback. |
| `input` | `string` \| `object` | No | Datos para pasar al WebView como parámetros de URL. |
**Ejemplo de Prompt:**
> "Cuando el usuario confirme que desea realizar el pago, usa `send_call_to_action` con `is_webview=true` para enviar el enlace de pago y esperar la confirmación."
**Ejemplo de Argumentos (CTA simple):**
```json theme={null}
{
"text": "Visita nuestra tienda online para ver todos los productos disponibles.",
"url": "https://tienda.ejemplo.com/catalogo",
"display_text": "Ver Catálogo",
"title": "Tienda Online"
}
```
**Ejemplo de Argumentos (WebView con espera de respuesta):**
```json theme={null}
{
"text": "Haz clic en el botón para completar tu pago de forma segura.",
"url": "https://pagos.ejemplo.com/checkout",
"display_text": "Pagar Ahora",
"title": "Completar Pago",
"is_webview": true,
"expiration_time": 600,
"pending_message": "Por favor, completa el pago en la ventana abierta antes de continuar.",
"response_variable": "payment_result",
"input": {
"order_id": "12345",
"amount": 99.99
}
}
```
**Respuesta:**
* CTA normal: `"Call-to-action message sent successfully."`
* WebView: `"WebView message sent. Waiting for user action."`
**Notas:**
* Disponible solo para los proveedores de WhatsApp que soportan este tipo de mensajes.
* Cuando `is_webview=true`, el flujo se pausa hasta que el WebView devuelve un callback o expira el tiempo.
* El valor de `input` se convierte a parámetros de URL query string.
* La respuesta del callback se guarda en la variable especificada en `response_variable`.
***
## Fecha y hora actual
Obtiene la fecha y hora actual. Permite especificar una zona horaria para obtener la hora local correcta.
**Parámetros:**
| Nombre | Tipo | Obligatorio | Descripción |
| :--------- | :------: | :---------: | :--------------------------------------------------------------------- |
| `timezone` | `string` | No | Zona horaria deseada en formato IANA (ej: "America/Guayaquil", "UTC"). |
**Ejemplo de Prompt:**
> "Antes de procesar cualquier solicitud que dependa de la hora (como 'buenos días' o citas para 'hoy'), llama a `get_current_date_time` para saber la hora exacta."
**Ejemplo de Argumentos:**
```json theme={null}
{
"timezone": "America/Mexico_City"
}
```
**Respuesta:**
Retorna un string con la fecha y hora formateada, por ejemplo: `"Monday, December 08, 2025 10:30 AM"`.
**Notas:**
* No tiene efectos secundarios visibles para el usuario.
* Es puramente informativa para el modelo.
***
## Día de la semana
Calcula y retorna el día de la semana correspondiente a una fecha específica proporcionada.
**Parámetros:**
| Nombre | Tipo | Obligatorio | Descripción |
| :----- | :------: | :---------: | :-------------------------------------------------------------------- |
| `date` | `string` | **Sí** | Fecha a consultar (formato ISO o legible standard, ej: "2024-12-25"). |
**Ejemplo de Prompt:**
> "Si el usuario quiere agendar para una fecha específica, verifica primero qué día de la semana es usando `get_weekday_of_a_date` para asegurar que sea un día hábil."
**Ejemplo de Argumentos:**
```json theme={null}
{
"date": "2025-12-10"
}
```
**Respuesta:**
Retorna el nombre del día de la semana, por ejemplo: `"Wednesday"`.
**Notas:**
* Útil para validar citas o agendas.
***
## Gestión de memoria
Las siguientes tools se activan desde la sección **Gestión de memoria** de la pestaña Contexto del nodo AI Agent. Dan al agente memoria de trabajo, acceso al CRM de la bandeja de entrada, variables del contexto del flujo e información de plataforma. Consulta la [sección Gestión de memoria del nodo AI Agent](/guides/nodos/ai-agent#gestión-de-memoria) para ver cómo activarlas y configurar sus permisos.
## Memoria interna
Permite al agente guardar, recordar y eliminar información de la conversación para reutilizarla más adelante. Es memoria de trabajo por usuario, con vigencia limitada: recuerda datos del mismo usuario entre conversaciones, pero no es un almacén permanente.
**Operaciones:**
| Operación | Acción | Descripción |
| :----------- | :------- | :---------------------------------------- |
| **Crear** | `save` | Guarda un dato en la memoria del usuario. |
| **Buscar** | `recall` | Recupera datos previamente guardados. |
| **Eliminar** | `delete` | Borra un dato guardado. |
| **Listar** | `list` | Lista los datos guardados disponibles. |
**Notas:**
* Incluye un campo opcional **Espacio de memoria** para aislar o agrupar los guardados, evitando que distintos contextos se mezclen.
* Cada operación admite configurar sus parámetros en modo IA, variable o estático, y un comportamiento post-ejecución (Continuar, Terminar o Pausar).
***
## CRM de bandeja de entrada
Lee, define y actualiza datos del contacto del CRM (nombre, teléfono, etiquetas, notas y más) directamente desde la bandeja de entrada.
**Operaciones:**
| Operación | Acción | Descripción |
| :------------------- | :------------------- | :--------------------------------------- |
| **Leer** | `readStoredParams` | Lee los datos almacenados del contacto. |
| **Definir** | `setStoredParams` | Establece datos del contacto. |
| **Actualizar** | `updateStoredParams` | Actualiza datos existentes del contacto. |
| **Listar etiquetas** | `listTags` | Lista las etiquetas del contacto. |
| **Agregar etiqueta** | `addTag` | Agrega una etiqueta al contacto. |
| **Quitar etiqueta** | `removeTag` | Quita una etiqueta del contacto. |
**Notas:**
* Los permisos se administran por grupo (datos del contacto / etiquetas) con lectura y escritura separadas. Puedes restringir a qué etiquetas puede acceder el agente.
* Requiere al menos un canal conectado para que existan contactos sobre los cuales operar.
***
## \$context
Lee y escribe variables del contexto del flujo para compartir datos entre nodos.
**Operaciones:**
| Operación | Acción | Descripción |
| :------------- | :----- | :--------------------------------------------- |
| **Obtener** | `get` | Lee el valor de una variable del contexto. |
| **Establecer** | `set` | Escribe o actualiza una variable del contexto. |
**Notas:**
* En la lista de tools aparece como **\$context** para distinguirla de la pestaña Contexto.
* Las variables escritas quedan disponibles para nodos posteriores del flujo, por ejemplo `{{$context.mi_variable}}`.
***
## Información de plataforma
Bloques de contexto de **solo lectura** que inyectan datos de plataforma en el prompt del agente. No consumen una llamada de tool: la información se agrega automáticamente como contexto cuando la fuente está activa.
| Fuente | Campos | Descripción |
| :---------------------------- | :---------------------------------------- | :------------------------- |
| **Información del usuario** | `names`, `referenceId`, `botId`, `roomId` | Datos del usuario actual. |
| **Información del canal** | `name`, `type` | Datos del canal conectado. |
| **Información de la empresa** | `name`, `plan`, `organizationId` | Datos de la empresa. |
**Notas:**
* Las tres fuentes son de solo lectura; el agente no puede modificarlas.
* Se inyectan como bloques de contexto, por lo que no cuentan como una invocación de herramienta.
***
## Bases de datos
Conexión nativa a **Bases de datos (Datum v2)** desde la pestaña **Tools** del nodo AI Agent. Permite que el agente busque, cree, actualice y elimine registros en tus colecciones sin encadenar nodos auxiliares.
**Operaciones:** buscar, crear, actualizar y eliminar registros sobre las colecciones que habilites.
| Parámetro | Tipo | Obligatorio | Descripción |
| :------------- | :------: | :---------: | :----------------------------------------------------------------------- |
| `operation` | `enum` | **Sí** | `search`, `create`, `update` o `delete`. |
| `collectionId` | `string` | **Sí** | Colección de Bases de datos sobre la que se opera. |
| `query` | `object` | No\* | Filtros de búsqueda. \*Requerido para `search`. |
| `document` | `object` | No\* | Documento a insertar o actualizar. \*Requerido para `create` y `update`. |
| `recordId` | `string` | No\* | ID del registro. \*Requerido para `update` y `delete`. |
**Notas:**
* El agente solo puede operar sobre las colecciones de la **misma compañía** que habilites explícitamente en el popover **Configurar**.
* Las operaciones de escritura persisten de inmediato.
* El comportamiento post-ejecución (Continuar, Terminar o Pausar) se configura por operación.
***
## AI Routing
Envía la solicitud del usuario al workflow que puede atenderla, cuando pide algo que este agente no puede hacer. Usa el [AI Routing](/guides/getting-started/ai-routing) del proyecto para elegir el destino, por lo que el proyecto debe tener AI Routing habilitado.
**Parámetros:**
| Nombre | Tipo | Obligatorio | Descripción |
| :-------- | :------: | :----------------------: | :------------------------------------------------------------------------------------------------------------------------------------- |
| `mode` | `enum` | No (Default: `evaluate`) | `evaluate` (elige el destino automáticamente), `direct` (destino indicado por el usuario) o `search` (lista los destinos disponibles). |
| `message` | `string` | No\* | Mensaje del usuario tal cual. \*Requerido para `evaluate`. |
| `target` | `string` | No\* | Nombre o ID del destino. \*Requerido para `direct`. |
**Ejemplo de Prompt:**
A diferencia de otras tools, `ai_router` no necesita que le indiques cuándo usarla: al activarla se agregan automáticamente las reglas de derivación a las instrucciones del agente. Lo único que tienes que definir es **el alcance del agente**, porque todo lo que quede fuera de ahí es lo que se deriva.
> "Atiendes únicamente consultas de facturación: estados de cuenta, fechas de pago y detalle de cargos."
**Ejemplo de Argumentos:**
*Caso: el usuario pregunta algo fuera del alcance del agente (más común)*
```json theme={null}
{
"mode": "evaluate",
"message": "quiero reportar un problema con mi pedido"
}
```
*Caso: el usuario nombra explícitamente el destino*
```json theme={null}
{
"mode": "direct",
"target": "Soporte"
}
```
*Caso: consultar los destinos disponibles antes de decidir*
```json theme={null}
{
"mode": "search"
}
```
**Respuesta:**
Retorna un objeto JSON con el destino elegido, si la derivación se ejecutó y una guía para el modelo sobre qué hacer a continuación. En `search` y en `direct` sin coincidencia, retorna los destinos disponibles y **no** deriva nada.
**Notas:**
* Una derivación exitosa **termina el turno del agente**: la respuesta al usuario llega desde el workflow de destino. El agente no debe responder también.
* Si no hay coincidencia o la derivación falla, el agente sigue a cargo de la conversación y debe continuar atendiendo al usuario.
* Requiere que el proyecto tenga **AI Routing** habilitado y workflows con nombre y descripción configurados. El comportamiento posterior a la ejecución es fijo: la derivación siempre termina el turno del agente.
* El agente solo puede derivar a workflows del mismo proyecto.
Las reglas de derivación se agregan solas al activar la tool: el agente ya sabe que debe resolver por su cuenta lo que sus instrucciones cubren, que no debe derivar porque otra tool falló o porque no está seguro, y que no debe responder además de derivar. No necesitas escribir nada de esto en el prompt.
# API Keys
Source: https://docs.jelou.ai/guides/datum/api-keys
Crea y gestiona claves de API para acceder a tus datos de Databases desde aplicaciones externas
Las **API Keys** te permiten autenticarte en la API de Databases desde aplicaciones externas, scripts o integraciones. Cada clave puede tener permisos granulares y una fecha de expiración configurable.
## Crear una API Key
Ve a **Configuración > API Keys** desde la barra lateral izquierda.
El botón se encuentra en la esquina superior derecha.
Completa los siguientes campos:
* **Name** — Un nombre descriptivo para identificar la clave (ej. "CI Channel", "Backend Production")
* **Permissions** — Selecciona los permisos que necesita la clave
* **Expiration** — Tiempo de vigencia de la clave
La clave se genera y se muestra una única vez. Cópiala y guárdala en un lugar seguro.
La clave completa solo se muestra una vez al momento de crearla. Si la pierdes, deberás regenerar la clave o crear una nueva.
## Permisos disponibles
Los permisos se dividen en dos categorías:
**Records (Registros)**
| Permiso | Descripción |
| ----------------------- | ------------------------------------------------------ |
| Read records | Permite leer y buscar registros en las colecciones |
| Create & update records | Permite crear nuevos registros y actualizar existentes |
| Delete records | Permite eliminar registros |
**Files (Archivos)**
| Permiso | Descripción |
| ------------ | -------------------------------------- |
| Read files | Permite descargar archivos adjuntos |
| Upload files | Permite subir archivos a los registros |
Aplica el principio de mínimo privilegio: otorga solo los permisos que la aplicación realmente necesita. Por ejemplo, un dashboard de lectura solo requiere **Read records**.
## Opciones de expiración
| Opción | Descripción |
| ------------- | ---------------------------------------- |
| 7 days | La clave expira en 7 días |
| 30 days | La clave expira en 30 días (por defecto) |
| 60 days | La clave expira en 60 días |
| 90 days | La clave expira en 90 días |
| 180 days | La clave expira en 180 días |
| No expiration | La clave no expira |
Las claves sin expiración no se revocan automáticamente. Úsalas solo cuando sea estrictamente necesario y combínalas con permisos mínimos.
## Gestionar API Keys
La tabla de API Keys muestra todas las claves creadas con la siguiente información:
* **Name** — Nombre de la clave
* **Permissions** — Permisos asignados
* **Key** — Los últimos caracteres de la clave (enmascarada)
* **Created** — Fecha de creación
* **Expires** — Fecha de expiración
### Acciones disponibles
* **Regenerate** — Genera una nueva clave conservando el mismo nombre, los mismos permisos **y la fecha de expiración original** (no se reinicia el periodo). La clave anterior deja de funcionar inmediatamente.
* **Delete** — Elimina la clave permanentemente. Cualquier aplicación que la utilice perderá acceso inmediatamente.
## Usar la API Key
Incluye la clave en el header `X-Api-Key` de tus peticiones HTTP:
```bash cURL theme={null}
curl -s -X GET 'https://tu-instancia.jelou.cloud/api/collections/tu_coleccion/records' \
-H 'X-Api-Key: YOUR_API_KEY' \
-H 'Accept: application/json'
```
```javascript JavaScript theme={null}
const response = await fetch('https://tu-instancia.jelou.cloud/api/collections/tu_coleccion/records', {
headers: {
'X-Api-Key': 'YOUR_API_KEY',
'Accept': 'application/json'
}
});
const data = await response.json();
```
```python Python theme={null}
import requests
response = requests.get(
'https://tu-instancia.jelou.cloud/api/collections/tu_coleccion/records',
headers={
'X-Api-Key': 'YOUR_API_KEY',
'Accept': 'application/json'
}
)
data = response.json()
```
# Colecciones
Source: https://docs.jelou.ai/guides/datum/colecciones
Crea y configura colecciones para organizar tus datos con campos tipados, validaciones e índices
Una **colección** es el equivalente a una tabla en una base de datos tradicional. Cada colección tiene campos (columnas) con tipos de datos específicos y almacena registros (filas) que puedes crear, buscar, editar y eliminar.
## Crear una colección
En la barra lateral izquierda, haz clic en el botón **+ New collection** ubicado en la parte inferior del panel de colecciones.
Completa los siguientes campos:
* **Name** — Nombre de la colección (obligatorio, ej. "clientes", "productos")
* **Type** — Tipo de colección: **Base** (por defecto) o **Vista (solo lectura)**.
Toda colección nueva incluye tres campos automáticos:
* **id** — Identificador único del registro (generado automáticamente)
* **created** — Fecha de creación del registro (Autodate)
* **updated** — Fecha de última actualización (Autodate)
Haz clic en **+ New field** para agregar tus propios campos.
Haz clic en **Create** para confirmar.
## Tipos de campo
Cada campo tiene un tipo que determina qué datos puede almacenar y qué validaciones se aplican:
| Tipo | Descripción |
| ------------ | --------------------------------------------------------------------------------------- |
| **Text** | Cadenas de texto (máximo 5000 caracteres por defecto) |
| **Editor** | Texto enriquecido con formato (negritas, listas, enlaces, etc.) |
| **Number** | Valores numéricos (enteros o decimales) |
| **Boolean** | Valores verdadero/falso |
| **Email** | Direcciones de correo electrónico con validación de formato |
| **URL** | URLs con validación de formato |
| **Date** | Fecha y hora que el usuario define manualmente en cada registro |
| **Autodate** | Fecha y hora asignada automáticamente por el sistema (ej. campos `created` y `updated`) |
| **Select** | Selección de una opción de una lista predefinida |
| **Relation** | Referencia a registros de otra colección |
| **File** | Archivos adjuntos |
| **JSON** | Datos estructurados en formato JSON |
| **GeoPoint** | Coordenadas geográficas (latitud y longitud) |
| **Password** | Texto enmascarado para almacenar contraseñas o secretos |
## Configurar campos
Haz clic en el ícono de engranaje (⚙) de cualquier campo para ver sus opciones de configuración. Las opciones varían según el tipo de campo.
Para campos de tipo **Text**:
* **Min length / Max length** — Longitud mínima y máxima del texto (máximo 5000 caracteres por defecto)
* **Validation pattern** — Expresión regular para validar el formato (ej. `^[a-z0-9]+$`)
* **Nonempty** — Marca el campo como obligatorio
* **Hidden** — Oculta el campo en la interfaz de usuario.
* **Presentable** — Marca el campo como campo representativo del registro
Puedes cambiar el tipo de un campo existente desde el dropdown del tipo en la configuración de la colección. Ten en cuenta que cambiar el tipo puede afectar datos existentes.
### Opciones por tipo de campo
Cada tipo de campo ofrece sus propias opciones de configuración:
**Solo enteros** (rechaza decimales) y valores mínimo/máximo.
Lista de opciones permitidas (al menos una). Puedes habilitar **selección múltiple**, que requiere un mínimo de 2 opciones.
Tamaño máximo por archivo, archivo único o múltiple, y tipos permitidos mediante presets (Imágenes, Audio, Video, Comprimidos, Documentos).
Colección relacionada y selección única o múltiple. También puedes definir qué ocurre cuando se elimina un registro relacionado.
Configura la longitud máxima y las opciones de seguridad para proteger los valores almacenados.
Dominios permitidos o excluidos para validar el valor.
**Date**: fecha mínima y máxima. **Autodate**: define cuándo se asigna automáticamente — al **Crear**, al **Actualizar** o en ambos.
Tamaño máximo del contenido.
## Reordenar campos
Haz clic en **Reorder fields** dentro de la configuración de la colección para cambiar el orden en que se muestran los campos en la tabla y en los formularios.
## Índices y restricciones únicas
En la parte inferior de la configuración de la colección encontrarás la sección **Unique constraints and indexes**. Los índices mejoran el rendimiento de las consultas en campos que se filtran o buscan frecuentemente.
Haz clic en **Create index** para agregar un nuevo índice a tu colección.
## Configuración de la colección
Haz clic en el ícono de engranaje (⚙) junto al nombre de la colección en la parte superior para acceder a la configuración completa. Desde aquí puedes:
* Renombrar la colección
* Agregar, editar o eliminar campos
* Reordenar campos
* Crear índices
El menú **⋯** en la esquina superior derecha de la configuración ofrece acciones adicionales:
* **Duplicar** — Crea una copia de la colección.
* **Vaciar** — Elimina todos los registros y conserva la estructura de la colección. No está disponible en vistas.
* **Eliminar** — Borra la colección por completo.
**Vaciar** y **Eliminar** son irreversibles. Vaciar borra todos los registros; Eliminar borra además la colección y su estructura.
# Importaciones
Source: https://docs.jelou.ai/guides/datum/importaciones
Importa datos masivos a tus colecciones desde archivos CSV o XLSX
La función de importación te permite cargar datos masivos a tus colecciones desde archivos **CSV** o **XLSX** (Excel). Puedes importar datos a una colección existente o crear una nueva colección durante el proceso.
## Importar a una colección existente
Haz clic en el ícono de importación en la barra lateral izquierda y luego en **+ New Import** para abrir el asistente.
El asistente tiene **5 pasos**:
Asegúrate de que **Use Existing** esté seleccionado, elige la colección destino del dropdown y arrastra tu archivo CSV o XLSX a la zona de carga (o haz clic para seleccionarlo).
Tamaño máximo de archivo: **500 MB**. Formatos soportados: **CSV** y **XLSX**.
Asocia cada campo de la colección con una columna del archivo CSV. El asistente detecta coincidencias automáticamente (ej. un campo `email` se mapea solo). Para cada campo puedes seleccionar un **Subtype** (General Text, ID Number) y un **Country** cuando aplique. Las columnas del CSV que no mapees aparecerán en la sección **Unused CSV columns** y serán ignoradas.
Opcionalmente activa **Enable duplicate detection** para evitar registros duplicados. Al activarla se muestran opciones adicionales:
* **Campo único para coincidencia** — El campo que identifica un duplicado.
* **Cuando se encuentra un duplicado** — Acción a tomar: **Omitir**, **Actualizar** o **Error**.
* **Estrategia de actualización** (solo si eliges Actualizar) — **Combinar** (actualiza solo los campos mapeados y conserva el resto) o **Reemplazar** (sobrescribe todos los campos, excepto los del sistema).
Revisa una previsualización de las primeras filas. Verás contadores de filas válidas, errores y duplicados. Haz clic en cualquier fila para ver el detalle del error.
Revisa el resumen final con los detalles del archivo, la colección destino, los mapeos de columnas y los campos omitidos. Haz clic en **Start Import** para ejecutar la importación.
## Importar creando una nueva colección
Este flujo tiene **6 pasos** (uno más que el de una colección existente, porque primero defines los campos):
Haz clic en **+ New Import**, selecciona **Create New**, ingresa el nombre de la nueva colección y sube tu archivo CSV o XLSX.
Databases inferirá los tipos de campo automáticamente a partir de las columnas del archivo. Puedes ajustar los tipos manualmente si es necesario.
Revisa y ajusta la asociación entre las columnas del archivo y los campos que definiste. Para cada campo puedes elegir un **Subtype** (Texto general o Número de identificación) y un **Country** cuando aplique.
Configura la detección de duplicados con las mismas opciones descritas en el flujo anterior.
Revisa la previsualización de filas y verifica que los datos y tipos sean correctos.
Revisa el resumen final y haz clic en **Start Import**.
## Historial de importaciones
Todas las importaciones se muestran en la sección de Importaciones con su estado. Desde aquí puedes monitorear el progreso de las importaciones en curso y revisar el resultado de las anteriores. Además puedes:
* **Cancelar** una importación en curso (mientras está en proceso o pendiente).
* **Eliminar** cualquier importación del historial.
* Descargar, desde el panel de detalle de una importación, el **Reporte de rendimiento** (importaciones completadas) y el **Reporte de errores** (cuando hubo filas fallidas), ambos en formato CSV.
# Databases
Source: https://docs.jelou.ai/guides/datum/introduccion
Base de datos gestionada para crear y administrar colecciones de datos sin preocuparte por la infraestructura
## ¿Qué es Databases?
**Databases** es la solución de base de datos gestionada de Jelou que te permite crear y administrar colecciones de datos directamente desde tu navegador. No necesitas configurar servidores, gestionar réplicas ni preocuparte por el autoescalamiento — todo está gestionado automáticamente.
Con Databases puedes almacenar información estructurada (clientes, productos, pedidos, etc.) y conectarla con tus flujos de Brain Studio, APIs externas o herramientas de IA a través de MCP.
## Acceso a Databases
Puedes acceder a Databases de dos formas:
1. Desde el **dashboard principal** de Jelou, haz clic en **Databases** en la sección de acceso rápido
2. Desde la **barra lateral izquierda** de navegación
## Navegación principal
La barra lateral izquierda te da acceso a las secciones principales: **Inicio**, **Colecciones**, **Importaciones**, **Monitoreo** y **Configuración**. Los **registros** se gestionan dentro de cada colección, no desde un icono independiente de la barra lateral.
Crea y gestiona tus tablas de datos con campos tipados
Crea, edita, busca, filtra y exporta registros dentro de una colección
Importa datos masivos desde archivos CSV o XLSX
Visualiza métricas de uso y logs de actividad
API Keys, Triggers, MCP y ajustes generales
## Configuración general
Desde **Configuración > General** puedes ajustar:
* **Nombre de la base de datos** — El identificador de tu instancia de Databases
* **Operaciones por lote** — Permite la selección y eliminación masiva de registros en colecciones
* **Configuración regional** — Zona horaria utilizada para exportaciones y formato de fechas (por defecto UTC)
La sección **Zona de peligro** (*Danger Zone*) reúne las acciones sensibles sobre la base de datos:
* **Mejorar** — Sube a un plan de cómputo o almacenamiento mayor. No se permiten *downgrades*; la base debe estar activa y el volumen de datos se preserva.
* **Reprovisionar** — Reconstruye la base de datos. Tus datos se preservan, pero la base queda fuera de línea unos minutos; verás una pantalla de progreso que se actualiza automáticamente.
* **Eliminar** — Borra permanentemente la base de datos y todos sus datos.
Eliminar la base de datos es irreversible: se pierden todas las colecciones y registros.
## Estados y ciclo de vida
Tu base de datos muestra un estado en todo momento (por ejemplo, activa o en aprovisionamiento). Las bases inactivas entran en estado **Hibernating** (hibernación) para ahorrar recursos y se **reactivan automáticamente** cuando vuelves a usarlas; durante la reactivación verás una breve pantalla de espera.
# MCP
Source: https://docs.jelou.ai/guides/datum/mcp
Conecta Databases con herramientas de IA como ChatGPT, Claude, Cursor y VS Code mediante el protocolo MCP
**MCP** (Model Context Protocol) te permite conectar tu base de datos de Databases directamente con herramientas de IA. Una vez conectado, puedes consultar, crear y modificar registros usando lenguaje natural desde tu herramienta favorita.
## ¿Cómo funciona?
Databases crea una **URL MCP** para conectar tu base de datos con una herramienta de IA. A través de ella, la herramienta puede consultar y gestionar tus datos.
Para acceder a la configuración MCP, ve a **Configuración > MCP** desde la barra lateral izquierda.
## Configuración por herramienta
En ChatGPT, abre **Settings** y selecciona **Connectors** para gestionar conectores personalizados.
Habilita **Developer Mode** en la sección de Connectors → Advanced settings para aceptar URLs de conectores personalizados.
En la página de MCP de Databases, copia la URL generada.
En ChatGPT Connectors:
* Selecciona **Create connector** y agrega un nombre
* Configura la autenticación como **No Authentication**
* Pega la URL MCP y marca **I trust this application**
* Haz clic en **Create**
Abre un nuevo chat, ve a **More → Developer Mode** para activar el conector.
Haz clic en **Add sources**, activa tu base de datos de Databases e interactúa con tus datos.
En la página de MCP de Databases, copia la URL generada.
En Claude Desktop, ve a **Agregar conectores** (*Add Connectors*).
Entra en **Administrar los conectores** (*Manage connectors*).
Selecciona **Agregar un conector personalizado** (*Add custom connector*).
Asigna un nombre al conector, pega la **URL MCP** del paso 1 y haz clic en **Agregar**.
Abre **Search and Tools** y activa el conector de Databases.
**Instalación automática:**
En la página de MCP de Databases, copia la URL generada.
Haz clic en el botón **Add to Cursor** o copia el deeplink de Cursor proporcionado.
Cuando Cursor se abra:
* Revisa los detalles del conector y haz clic en **Install**
* Permite que Cursor se reinicie si es necesario
* Abre un nuevo chat y habilita las herramientas de Databases
**Instalación manual:**
Agrega este JSON en **Settings → Cursor Settings → MCP → Add new global MCP server**:
```json theme={null}
{
"datum": {
"url": "https://datum.jelou.cloud/TU_ID_UNICO/mcp"
}
}
```
En la página de MCP de Databases, copia la URL generada.
En tu workspace, crea el archivo `.vscode/mcp.json` con la configuración del servidor de Databases:
```json theme={null}
{
"servers": {
"datum": {
"type": "http",
"url": "https://datum.jelou.cloud/TU_ID_UNICO/mcp"
}
}
}
```
Usa el Command Palette para:
* Ejecutar **MCP: Add Server**, elige **HTTP** y proporciona la URL de Databases
* Selecciona **Workspace Settings** para crear `.vscode/mcp.json` si no existe
* Usar **MCP: Show Installed Servers** para confirmar que el conector está disponible
Evita incluir secretos directamente en el archivo de configuración. Usa variables de entorno o archivos de input para credenciales.
En la página de MCP de Databases, copia la URL generada.
Usa la URL generada con el método de transporte soportado por tu cliente. La mayoría de clientes soportan conexiones MCP basadas en HTTP.
## Qué puede hacer la IA conectada
Una vez conectada, la IA puede consultar, crear, actualizar y eliminar registros, además de ayudar a gestionar colecciones e importar datos.
La IA conectada puede modificar o eliminar datos. Conéctala solo con herramientas y personas de confianza.
# Monitor
Source: https://docs.jelou.ai/guides/datum/monitor
Visualiza métricas de rendimiento y logs de actividad de tu base de datos
La sección de **Monitor** te permite supervisar el estado y rendimiento de tu base de datos en tiempo real. Accede desde el ícono de Monitoreo en la barra lateral izquierda. Tiene dos pestañas, **Metrics** y **Logs**, y se abre en **Logs** por defecto.
## Métricas
La pestaña **Metrics** muestra tres indicadores principales:
| Métrica | Descripción |
| ----------------- | ---------------------------------------------------------------------- |
| **CPU Usage** | Porcentaje de uso de CPU de tu instancia (ej. 0.2% de 1 vCPU) |
| **Memory Usage** | Memoria RAM utilizada vs. disponible (ej. 75.41 MB de 207.35 MB) |
| **Storage Usage** | Espacio de almacenamiento utilizado vs. total (ej. 0.05 GB de 1.00 GB) |
Cada métrica incluye una barra visual que muestra el porcentaje de uso y el espacio libre.
### Gráficas de tendencia
Debajo de los indicadores encontrarás gráficas de tendencia para **CPU usage trend** y **Memory usage trend**. Cada gráfica incluye una línea roja punteada que indica el umbral de alerta.
### Rangos de tiempo
Puedes ajustar el período de las métricas con los botones de rango:
| Rango | Descripción |
| ------- | ------------------------------ |
| **1H** | Última hora |
| **6H** | Últimas 6 horas |
| **24H** | Últimas 24 horas (por defecto) |
| **3D** | Últimos 3 días |
| **7D** | Últimos 7 días |
Haz clic en **Refresh** para actualizar los datos en tiempo real.
## Logs
La pestaña **Logs** muestra un registro cronológico de toda la actividad de tu base de datos:
* **Peticiones a la API** (GET, POST, PUT, DELETE) con código de estado, tiempo de ejecución y dirección IP
* **Errores y advertencias** del sistema
* **Eventos de autenticación**
### Niveles de log
| Nivel | Código | Descripción |
| --------- | ------ | -------------------------------------- |
| **DEBUG** | -4 | Información detallada para diagnóstico |
| **INFO** | 0 | Operaciones normales del sistema |
| **WARN** | 4 | Situaciones que requieren atención |
| **ERROR** | 8 | Errores que requieren acción |
### Buscar y filtrar logs
Usa la barra de búsqueda en la parte superior para filtrar logs. Puedes buscar por texto libre o usar expresiones de filtro como:
```
level > 0 && data.auth = 'guest'
```
La gráfica de timeline encima de la tabla de logs muestra la distribución de eventos a lo largo del tiempo, permitiéndote identificar picos de actividad o errores.
### Detalle de un registro
Haz clic en cualquier fila de la tabla de logs para abrir el panel **Log Details**. Muestra siempre un **Resumen** (*Overview*) y, según la información del log, secciones de **Solicitud**, **Autenticación**, **Cliente**, **Información de error** y **Datos adicionales**. Incluye un botón **Download JSON** para exportar la entrada completa.
# Registros
Source: https://docs.jelou.ai/guides/datum/registros
Crea, edita, busca, filtra y exporta registros dentro de tus colecciones
Los **registros** son las filas de datos dentro de una colección. Cada registro contiene valores para los campos definidos en la colección.
## Crear un registro
Selecciona la colección donde quieres agregar un registro desde la barra lateral izquierda.
El botón se encuentra en la esquina superior derecha de la vista de colección.
El formulario muestra todos los campos de la colección con sus tipos. Los campos marcados con \* son obligatorios. El campo **id** se genera automáticamente al guardar.
El registro se crea y aparece en la tabla de la colección.
## Editar un registro
Haz clic en la flecha al final de cualquier fila para abrir el panel de edición. Modifica los valores que necesites y haz clic en **Save changes** para guardar.
Cada campo de texto muestra un contador de caracteres (ej. `5/5000 characters`) para que puedas controlar la longitud del contenido.
## Duplicar y eliminar registros
Dentro del panel de edición de un registro, haz clic en el menú **⋮** (esquina superior derecha) para acceder a:
* **Duplicate** — Crea una copia del registro con un nuevo ID
* **Delete** — Elimina el registro permanentemente
## Buscar registros
Utiliza la barra de búsqueda en la parte superior de la tabla (**Search across visible columns**) para buscar texto en todas las columnas visibles. También puedes usar los atajos **Cmd/Ctrl+K** para enfocar la búsqueda y **Cmd/Ctrl+N** para abrir el formulario de nuevo registro.
## Filtros avanzados
Haz clic en el ícono de filtro (embudo) junto a la barra de búsqueda para abrir el panel de **Advanced filters**.
Elige entre **AND** (todas las condiciones deben cumplirse) u **OR** (al menos una condición debe cumplirse).
Haz clic en **+ Add** y configura cada condición con:
* **Campo** — Selecciona el campo a filtrar
* **Operador** — Elige el operador de comparación
* **Valor** — Ingresa el valor a comparar
Haz clic en **Apply filters** para ver los resultados filtrados.
Los operadores disponibles dependen del tipo de campo:
| Tipo de campo | Operadores |
| ------------- | --------------------------------------------------------------------------- |
| **Text** | Equals, Not equal, Contains, Not contains, Is empty, Is not empty |
| **Number** | Equals, Not equal, Greater than, Greater or equal, Less than, Less or equal |
| Tipo de campo | Operadores |
| ------------------- | ----------------------------------------------------------------------------------- |
| **Boolean** | Is, Is not |
| **Date / Autodate** | Equals, Not equal, After, On or after, Before, On or before, Is empty, Is not empty |
| **Select** | Equals, Not equal, Is empty, Is not empty |
| **Multi-select** | Contains like, Not contains like, Is empty, Is not empty |
| **JSON** | Contains text, Does not contain, Is empty, Is not empty |
Los nombres de los operadores se muestran en inglés en la interfaz.
En la sección **CURRENT QUERY** puedes ver y copiar la expresión de filtro generada. Esto es útil para reutilizar el filtro en llamadas a la API.
Puedes guardar combinaciones de filtros que uses frecuentemente con el botón **Save filters** y limpiar todos los filtros activos con **Clear**.
## Ordenar registros
Haz clic en el encabezado de cualquier columna para alternar el orden de los registros:
* Primera vez: orden **ascendente** (A→Z, 0→9)
* Segunda vez: orden **descendente** (Z→A, 9→0)
* Tercera vez: sin orden específico
## Exportar registros
Haz clic en **Export** en la esquina superior derecha para abrir el menú **Descargar como** y elegir el formato: **CSV** (.csv) o **Excel** (.xlsx), cuando esté disponible. La exportación respeta los filtros activos, permitiéndote exportar solo un subconjunto de datos.
## Paginación
La tabla muestra los registros paginados. En la esquina inferior derecha puedes:
* Ver cuántos registros hay en total (ej. "1 - 10 de 10")
* Navegar entre páginas con las flechas de anterior y siguiente
* Cambiar la cantidad de filas por página con el selector de filas
## Operaciones por lote
Cuando activas **Operaciones por lote** en la configuración de la base de datos, al seleccionar registros aparece una barra de acciones en la parte inferior con:
* El número de registros seleccionados y un botón **Limpiar** (o la tecla `Esc`).
* **Exportar** — Descarga los registros seleccionados como **JSON**, **CSV** o **TSV**.
* **Eliminar** — Borra los registros seleccionados (con confirmación).
La exportación por lote incluye los registros seleccionados de la página actual. Si seleccionaste registros en otras páginas, la interfaz te pedirá cargarlos primero.
## API Preview
Haz clic en **API Preview** en la esquina superior derecha para ver la documentación interactiva de la API para la colección actual. Incluye:
* **List/Search** — Listar y buscar registros con filtros, orden y paginación
* **View** — Obtener un registro por ID
* **Create** — Crear un nuevo registro
* **Update** — Actualizar un registro existente
* **Delete** — Eliminar un registro
* **Batch** — Operaciones en lote
Cada operación incluye ejemplos de código en **cURL**, **JavaScript**, **Python**, **PHP** y **Laravel** con los endpoints y parámetros específicos de tu colección.
# Triggers
Source: https://docs.jelou.ai/guides/datum/triggers
Configura webhooks que se disparan automáticamente cuando se crean, actualizan o eliminan registros
Los **Triggers** (disparadores) te permiten ejecutar webhooks automáticamente cuando ocurren cambios en tus colecciones. Son ideales para sincronizar datos con sistemas externos, enviar notificaciones o ejecutar flujos de trabajo.
## Crear un trigger
Ve a **Configuración > Triggers** desde la barra lateral izquierda.
El botón se encuentra en la esquina superior derecha.
Completa los siguientes campos:
* **Name** — Un nombre descriptivo (ej. "Sync CRM", "Notificar nuevo pedido")
* **Collection** — La colección que quieres monitorear
* **Event** — El tipo de evento que dispara el webhook
* **Update scope** — Solo para el evento Update: dispara ante cualquier columna o solo columnas específicas
* **Webhook Request** — El método HTTP y la URL destino
* **Headers** — Headers personalizados para la petición
* **Status** — Activa o desactiva el trigger
El trigger se activa inmediatamente si el estado está en **Active**.
## Eventos disponibles
| Evento | Se dispara cuando... |
| ---------- | ---------------------------------------------------------------------- |
| **Create** | Se inserta un nuevo registro en la colección |
| **Update** | Cambia un registro existente (puedes limitarlo a columnas específicas) |
| **Delete** | Se elimina un registro |
Puedes crear múltiples triggers para la misma colección con diferentes eventos. Por ejemplo, un trigger para sincronizar datos en cada **Create** y otro para enviar una alerta en cada **Delete**.
### Alcance de actualización
Cuando el evento es **Update**, aparece un control adicional de **Update scope** (alcance de actualización) con dos opciones:
| Opción | Comportamiento |
| :----------------------- | :----------------------------------------------------------------------------------- |
| **Cualquier columna** | El webhook se dispara ante cualquier cambio en el registro. |
| **Columnas específicas** | Eliges una o más columnas; el webhook solo se dispara cuando cambia alguna de ellas. |
Al elegir **Columnas específicas** debes seleccionar al menos una columna. Los eventos **Create** y **Delete** siempre usan "cualquier columna" y no muestran este control.
## Configuración del webhook
### Método HTTP
Selecciona el método HTTP apropiado para tu endpoint:
| Método | Uso típico |
| ---------- | ------------------------------------------------- |
| **POST** | Enviar datos del registro al servidor (más común) |
| **GET** | Notificar sin enviar datos en el body |
| **PUT** | Reemplazar un recurso completo |
| **PATCH** | Actualizar parcialmente un recurso |
| **DELETE** | Solicitar eliminación de un recurso |
### URL del webhook
Ingresa la URL completa del endpoint que recibirá la notificación (ej. `https://tu-api.com/webhooks/databases`).
La URL debe ser **pública** y usar **HTTPS**.
### Headers
Agrega headers personalizados para autenticación u otros propósitos. Por defecto se incluye un header de ejemplo para **Authorization** con formato Bearer token.
Haz clic en **+ Add header** para agregar headers adicionales. Cada header tiene un campo de nombre (key) y valor (value).
## Reintentos automáticos
Si la entrega de un webhook falla, Databases la reintenta automáticamente.
## Estado del trigger
El toggle de **Status** te permite activar o desactivar un trigger sin eliminarlo. Un trigger desactivado no envía webhooks aunque ocurran eventos en la colección.
# Autenticación
Source: https://docs.jelou.ai/guides/functions/autenticacion
Cómo la plataforma protege tus funciones con API keys, el uso de runtime tokens legacy, y cómo hacer funciones públicas.
Toda función desplegada está protegida por defecto. Las peticiones sin una credencial válida reciben `401 Unauthorized`.
## Inicio rápido
```bash theme={null}
jelou functions deploy
# ✓ Deployed
# ▸ URL: https://mi-funcion.fn.jelou.ai
```
El deploy no genera ninguna credencial automáticamente. Para llamar tu función necesitas una API key de plataforma (ver el siguiente paso).
Crea una API key en [apps.jelou.ai](https://apps.jelou.ai), en la sección de configuración de la app, y envíala en el header `Authorization`:
```bash theme={null}
curl -X POST https://mi-funcion.fn.jelou.ai \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_..." \
-d '{"query": "test"}'
```
Respuesta exitosa:
```json theme={null}
{ "results": [] }
```
Sin API key o con una inválida:
```json theme={null}
{ "error": "Unauthorized", "message": "Missing or invalid auth token (use X-Jelou-Token or Authorization: Bearer)" }
```
## ¿Cómo funciona?
1. Crea una API key de plataforma en [apps.jelou.ai](https://apps.jelou.ai), en la sección de configuración de la app
2. Cada petición debe incluirla en el header `Authorization: Bearer sk_...`
3. El SDK valida la key contra el gateway de Jelou, confirma que pertenece a la misma organización dueña de la función, y cachea el resultado en memoria
4. Si la key es válida, la petición llega a tu handler. Si no, retorna `401`
## API keys (recomendado)
La creación de nuevos runtime tokens (`jelou functions tokens create`) está **deprecada** y responde `410 Gone`. Los deploys tampoco generan uno automáticamente — el mecanismo soportado para autenticar funciones, nuevas o existentes, es una API key de plataforma.
Los runtime tokens `jfn_rt_...` creados antes de esta migración se siguen validando con `X-Jelou-Token` (compatibilidad hacia atrás) y pueden listarse o revocarse con `jelou functions tokens list` / `jelou functions tokens revoke`, pero no pueden crearse nuevos ni se generan en deploys nuevos.
## Rutas sin autenticación
Las rutas `/__health` y `/openapi.json` nunca requieren credencial. Los triggers cron tampoco — la plataforma los autentica automáticamente.
## Ejemplos de uso
```bash theme={null}
curl -X POST https://mi-funcion.fn.jelou.ai \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_..." \
-d '{"telefono": "593987654321"}'
```
```javascript theme={null}
const res = await fetch("https://mi-funcion.fn.jelou.ai", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.JELOU_FUNCTION_API_KEY}`,
},
body: JSON.stringify({ telefono: "593987654321" }),
});
const data = await res.json();
```
```python theme={null}
import requests
import os
res = requests.post(
"https://mi-funcion.fn.jelou.ai",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['JELOU_FUNCTION_API_KEY']}",
},
json={"telefono": "593987654321"},
)
data = res.json()
```
Para conectar tu función como servidor MCP externo en Brain Studio:
1. URL: `https://mi-funcion.fn.jelou.ai/mcp`
2. Header: `Authorization` → `Bearer sk_...`
Consulta la [guía de Brain Studio](/guides/functions/brain) para instrucciones paso a paso.
La plataforma no valida nada — tu código es responsable.
# Brain Studio
Source: https://docs.jelou.ai/guides/functions/brain
Conecta tu Jelou Function como servidor MCP externo en Brain Studio para que tus agentes IA la invoquen como herramienta.
## Conectar tu función en Brain Studio
```bash theme={null}
jelou functions deploy
# ✓ Deployed to https://mi-funcion.fn.jelou.ai
```
Crea una API key en [apps.jelou.ai](https://apps.jelou.ai) — la necesitarás en el paso 4.
```bash theme={null}
curl https://mi-funcion.fn.jelou.ai/__health \
-H "Authorization: Bearer sk_..."
```
Respuesta esperada:
```json theme={null}
{
"status": "ok",
"mode": "define",
"tool": {
"name": "mi-funcion",
"description": "Busca información de clientes"
}
}
```
Si usas `app()`, verás múltiples tools en el array `tools`.
Si tu función tiene `config: { mcp: false }`, no se expondrá como herramienta MCP. Asegúrate de que MCP esté activo (es el default).
En el dashboard de Jelou, navega a tu agente IA:
**AI Agent → Tools → Servidores MCP externos**
Completa los campos:
| Campo | Valor |
| ------------ | ------------------------------------ |
| URL | `https://mi-funcion.fn.jelou.ai/mcp` |
| Header name | `Authorization` |
| Header value | `Bearer sk_...` |
Brain Studio descubrirá automáticamente las herramientas expuestas por tu función. Selecciona las que quieras que el agente pueda usar.
Si usas `app()` con múltiples tools, cada uno aparece como una herramienta independiente con su nombre y descripción.
Inicia una conversación con el agente y pide algo que requiera usar tu herramienta. Por ejemplo:
> "¿Cuál es el saldo del cliente 593987654321?"
El agente detectará que necesita invocar tu función, pasará los parámetros correctos, y mostrará la respuesta al usuario.
## Multi-tool en Brain Studio
Si tu función usa `app()`, Brain Studio ve cada tool individualmente:
```typescript theme={null}
export default app({
tools: {
consultarSaldo: define({
description: "Consulta el saldo de un cliente por teléfono",
input: z.object({ telefono: z.string().min(10) }),
handler: async (input) => ({ saldo: 150.00 }),
}),
crearTicket: define({
description: "Crea un ticket de soporte",
input: z.object({ asunto: z.string(), detalle: z.string() }),
handler: async (input) => ({ ticketId: "TKT-001" }),
}),
},
});
```
En Brain Studio aparecerán:
* **consultarSaldo** — "Consulta el saldo de un cliente por teléfono"
* **crearTicket** — "Crea un ticket de soporte"
Puedes habilitar o deshabilitar cada herramienta individualmente.
Las descripciones de `define()` y las anotaciones `.describe()` de Zod en los campos de input son lo que Brain Studio muestra al agente IA. Descripciones claras y específicas mejoran la precisión del agente al decidir cuándo y cómo usar tu herramienta.
## Troubleshooting
Verifica que:
1. La función está desplegada (`jelou functions deploy`)
2. MCP está activo (`config.mcp` no es `false`)
3. La URL termina en `/mcp`
4. El token es correcto
Prueba manualmente:
```bash theme={null}
curl https://mi-funcion.fn.jelou.ai/mcp \
-H "Authorization: Bearer sk_..."
```
La API key es inválida o falta en el header `Authorization`. El deploy no genera ninguna credencial automáticamente — crea una en [apps.jelou.ai](https://apps.jelou.ai) y configura el header MCP como `Authorization: Bearer `.
Si tu función es legacy y todavía usa un runtime token, puedes verificarlo con:
```bash theme={null}
jelou functions tokens list mi-funcion
```
`jelou functions tokens create` está **deprecado** (responde `410 Gone`) — ya no puedes generar un runtime token nuevo. Consulta la [guía de autenticación](/guides/functions/autenticacion#api-keys-recomendado).
Revisa la `description` de tu `define()`. Si es vaga (como "Maneja datos"), el agente no sabe cuándo usarla. Usa descripciones específicas como "Busca un cliente por número de teléfono y retorna su nombre, email y saldo".
# CLI
Source: https://docs.jelou.ai/guides/functions/cli
Referencia del subcomando jelou functions dentro del CLI unificado de Jelou: desarrollo local, despliegue, secrets, tokens, cron y logs de funciones.
El CLI de Functions forma parte del **[CLI unificado de Jelou](/guides/cli)**
(`@jelou/cli`). Los comandos de funciones viven bajo el namespace
`jelou functions …` (por ejemplo `jelou functions deploy`), mientras que la
autenticación y los perfiles se comparten con el resto de la plataforma.
¿Recién empiezas con el CLI? Revisa primero la
[introducción al CLI de Jelou](/guides/cli), la
[autenticación](/guides/cli/autenticacion) y las
[skills para editores de IA](/guides/cli/skills). Esta página documenta en
detalle el grupo `jelou functions`.
## Instalación
```bash theme={null}
npm install -g @jelou/cli
```
Verifica:
```bash theme={null}
jelou --version
```
## Autenticación
La autenticación es global del CLI (no específica de funciones). Resumen rápido;
detalles en [Autenticación y perfiles](/guides/cli/autenticacion).
```bash theme={null}
jelou login # interactivo
jelou login --token $JELOU_TOKEN # no interactivo (CI)
jelou whoami # identidad actual
jelou logout # olvida el perfil activo
```
En CI, pasa el token por variable de entorno:
```bash theme={null}
export JELOU_TOKEN=${{ secrets.JELOU_TOKEN }}
```
## Proyectos
### `jelou functions init`
Inicializa un proyecto de Jelou Functions.
```bash theme={null}
jelou functions init
# ? What is your function name? mi-funcion
# ? Add a description? Consulta de clientes
# ✓ Created mi-funcion
```
| Flag | Descripción |
| ----------------------- | --------------------------------------------------- |
| `--slug ` | Nombre de la función |
| `--description ` | Descripción |
| `--mode ` | Crear una función nueva o enlazar con una existente |
| `--force` | Sobrescribir `jelou.json` existente |
Genera: `index.ts`, `jelou.json`, `deno.json`, `.env`, `.gitignore`.
```bash theme={null}
# CI: crear una función nueva
jelou functions init --slug mi-funcion --mode create --no-input
# CI: enlazar con una función existente
jelou functions init --slug funcion-existente --mode link --no-input
```
### `jelou functions dev`
Inicia el servidor de desarrollo local con hot reload.
```bash theme={null}
jelou functions dev
# ▸ Function mi-funcion
# ▸ Port 3000
# ▸ Routes http://localhost:3000
# ▸ Health http://localhost:3000/__health
# ▸ MCP http://localhost:3000/mcp
```
| Flag | Descripción |
| -------------------- | -------------------------------- |
| `--port ` | Puerto (default: 3000) |
| `--env ` | Ruta al archivo `.env` |
| `--no-watch` | Desactivar hot reload |
| `--local-sdk ` | Ruta al directorio del SDK local |
El servidor detecta automáticamente si tu función usa `define()` o `app()` y
muestra las rutas correspondientes.
Para probar [ejecuciones diferidas](/guides/functions/diferidas) en local, arranca
con `JELOU_FN_DEFER_DEV=1`.
### `jelou functions check`
Valida el proyecto antes de desplegar. No necesita autenticación ni red.
```bash theme={null}
jelou functions check
# ✓ index.ts entrypoint válido
# ✓ deno.json JSON plano
# ⚠ package.json entrada @jelou/* de npm — usa el import map de deno.json
# ✓ Sin problemas bloqueantes
```
Detecta lo que haría fallar el arranque de tu función en producción y avisa sobre
configuraciones dudosas. Termina con error si encuentra algo bloqueante.
Ejecútalo antes de `deploy` en tus pipelines de CI: detecta los problemas sin
gastar un despliegue.
## Despliegue
### `jelou functions deploy`
Despliega a producción.
```bash theme={null}
jelou functions deploy
# ▸ Files: index.ts (1.2 KB), jelou.json (98 B), deno.json (65 B)
# ? Deploy mi-funcion? (Y/n) y
# ✓ Deployed
# ▸ URL: https://mi-funcion.fn.jelou.ai
```
| Flag | Descripción |
| ---------------- | -------------------------------------- |
| `--no-confirm` | Omitir confirmación (para CI) |
| `--follow`, `-f` | Streaming de logs después del deploy |
| `--no-wait` | No esperar a que el deploy quede listo |
| `--dry-run` | Mostrar qué se subiría sin desplegar |
El primer deploy genera automáticamente un runtime token.
### `jelou functions rollback [slug] [id]`
Revierte a un despliegue anterior.
```bash theme={null}
jelou functions rollback # interactivo
jelou functions rollback mi-funcion dep_abc12345 # directo
```
### `jelou functions deployments list [slug]`
Lista el historial de despliegues.
```bash theme={null}
jelou functions deployments list mi-funcion
# ▸ ID Status Source Files Deployed By Age
# ▸ dep_abc123.. active cli 3 alex@jelou.ai 2h ago
```
```bash theme={null}
jelou functions deployments info mi-funcion dep_abc123
jelou functions deployments download mi-funcion dep_abc123 -o artifact.tar.gz
```
### `jelou functions deployments build [slug] [id]`
Muestra el log de compilación de un despliegue — responde "¿por qué falló el
deploy que acabo de hacer?". Sin argumentos usa el despliegue más reciente del
proyecto actual.
```bash theme={null}
jelou functions deployments build
# ▸ Status failed
# ✗ index.ts:14:3 — Property 'telefono' does not exist on type ...
```
| Flag | Descripción |
| ------- | -------------------------------------------------------- |
| `--all` | Mostrar todas las líneas, no solo errores y advertencias |
Este es el log de **compilación**. Para los logs de ejecución de la función usa
`jelou functions logs`.
## Funciones
### `jelou functions list`
Lista todas las funciones.
```bash theme={null}
jelou functions list
# ▸ Slug Status URL Updated
# ▸ consultar-cliente active https://consultar-cliente.fn.jelou.ai 4/7/2026
```
### `jelou functions info `
Muestra detalles de una función.
```bash theme={null}
jelou functions info consultar-cliente
```
### `jelou functions create`
Crea una función remota (sin código local).
```bash theme={null}
jelou functions create --slug mi-api --name "Mi API"
```
### `jelou functions delete `
Elimina una función. Requiere confirmación (`-y` para omitirla en CI).
```bash theme={null}
jelou functions delete mi-funcion
# ? Delete function "mi-funcion"? This cannot be undone. (y/N)
```
## Secrets
Secrets a nivel de **función**. Para secrets de toda la organización, usa
`jelou secret` — ver [Secrets de organización](/guides/cli/secret).
### `jelou functions secrets list `
```bash theme={null}
jelou functions secrets list mi-funcion
# ▸ Key Updated
# ▸ CRM_API_KEY 2 hours ago
```
### `jelou functions secrets set `
```bash theme={null}
jelou functions secrets set mi-funcion CRM_API_KEY=sk_test_123 DB_URL=postgres://...
jelou functions secrets set mi-funcion --from-env .env.production
# Un solo secret leyendo el valor de un archivo
jelou functions secrets set mi-funcion PRIVATE_KEY --from-file key.pem
```
Los valores nuevos toman efecto en el **siguiente despliegue**. Ejecuta
`jelou functions deploy` para aplicarlos.
### `jelou functions secrets delete `
```bash theme={null}
jelou functions secrets delete mi-funcion CRM_API_KEY
```
## Tokens
Runtime tokens: los bearers (`X-Jelou-Token` / `Authorization: Bearer`) que usan
los clientes externos para invocar las rutas de una función. El deploy
auto-genera uno la primera vez.
### `jelou functions tokens list [slug]`
```bash theme={null}
jelou functions tokens list mi-funcion
# ▸ Name Prefix Last Used Created
# ▸ default jfn_rt_abc1.. 4/7/2026, 10:30 AM 4/1/2026
```
### `jelou functions tokens create [slug]`
Genera un nuevo runtime token para una función. El token completo se muestra
una sola vez en stdout — guárdalo de inmediato, porque a partir de ahí
`tokens list` solo muestra un prefijo censurado.
```bash theme={null}
jelou functions tokens create mi-funcion --name ci-deploy
# ✓ Token created
# ▸ Token jfn_rt_abc123def456... (guárdalo ahora, no vuelve a mostrarse)
```
```bash theme={null}
jelou functions tokens create # interactivo: pide slug y nombre
```
| Flag | Descripción |
| --------------- | ---------------- |
| `--name ` | Nombre del token |
Crea tokens adicionales para separar credenciales por entorno o integración
(por ejemplo uno para CI y otro para un partner externo) sin tocar el token
que generó el primer `deploy`.
### `jelou functions tokens revoke `
Revoca un runtime token existente.
```bash theme={null}
jelou functions tokens revoke mi-funcion token-uuid-123 -y
```
## Logs
### `jelou functions logs [slug]`
```bash theme={null}
jelou functions logs mi-funcion # streaming en vivo (default)
jelou functions logs mi-funcion --history # logs históricos
jelou functions logs mi-funcion -f # alias de streaming
```
| Flag | Descripción |
| ----------------------------- | -------------------------------------------------------------- |
| `--history` | Logs históricos en vez de streaming |
| `--follow`, `-f` | Streaming en vivo (default) |
| `--limit ` | Máximo de entradas a devolver |
| `--since ` / `--until ` | Rango temporal en ISO 8601 |
| `--order ` | `asc` (default, más antiguos primero) o `desc` (más recientes) |
| `--cursor ` | Cursor de paginación de una consulta anterior |
```bash theme={null}
# Últimas 100 entradas
jelou functions logs mi-funcion --history --limit 100
# Solo lo de la última hora
jelou functions logs mi-funcion --history --since 2026-08-03T14:00:00Z
```
No confundir con `jelou logs` (sin `functions`), que accede a los logs de
conversaciones de producción de un bot — ver
[Workflows y pruebas](/guides/cli/workflows).
## Cron
### `jelou functions cron list [slug]`
```bash theme={null}
jelou functions cron list mi-funcion
# ▸ Tool Expression Timezone Last Triggered
# ▸ default 0 9 * * * America/Guayaquil 2 hours ago
```
```bash theme={null}
jelou functions cron logs mi-funcion
jelou functions cron logs mi-funcion --state ERROR
```
| Flag | Descripción |
| ------------------- | ----------------------------------------------- |
| `--state ` | Filtrar por estado (`DELIVERED`, `ERROR`, etc.) |
| `--cursor ` | Cursor de paginación de una consulta anterior |
```bash theme={null}
# Página siguiente del historial de cron
jelou functions cron logs mi-funcion --cursor eyJpZCI6MTIzfQ==
```
## Diferidas
Consulta las [ejecuciones diferidas](/guides/functions/diferidas) que tu función
tiene agendadas. Las reservas se crean desde el código o por cabeceras HTTP, no
desde el CLI.
### `jelou functions defer list [slug]`
```bash theme={null}
jelou functions defer list mi-funcion
# ▸ ID Status Scheduled At Key
# ▸ dinv_01ksqbvy.. scheduled 4/8/2026, 9:00:00 AM carrito:42:c-9
# ▸ dinv_01ksqcxz.. firing 4/7/2026, 6:00:00 PM recordatorio:user-7
```
Por defecto muestra las reservas activas (`scheduled`, `firing`,
`cancel_requested`).
| Flag | Descripción |
| --------------------- | ------------------------------------------------------------------------- |
| `--status ` | `active` (default), `scheduled`, `firing`, `fired`, `cancelled`, `failed` |
| `--key ` | Filtrar por etiqueta exacta |
| `--subject ` | Filtrar por audiencia (normalmente el ID de usuario) |
| `--limit ` | Máximo de filas (1-100) |
```bash theme={null}
# Las que fallaron
jelou functions defer list mi-funcion --status failed
# Las de un usuario
jelou functions defer list mi-funcion --subject user_42
```
### `jelou functions defer get `
Muestra el detalle de una reserva, incluido el último error si falló.
```bash theme={null}
jelou functions defer get dinv_01ksqbvyqpe0prt91exc97mh4n
```
| Flag | Descripción |
| --------------- | ------------------------------------------- |
| `--slug ` | Función. Por defecto la del proyecto actual |
| `--payload` | Incluir el cuerpo del payload en la salida |
## Skills para editores de IA
El antiguo `jelou skill install` se unificó en `jelou agent install`, que
instala las skills de toda la plataforma (incluida `jelou-functions`) en tus
editores de IA.
```bash theme={null}
jelou agent install # interactivo
jelou agent install --global # global (todos los proyectos)
jelou agent install --only functions
```
Detalles en [Skills para editores de IA](/guides/cli/skills).
## Flags globales
| Flag | Descripción |
| ------------------ | ------------------------------------------------------------ |
| `--json` | Output JSON a stdout |
| `--agent` | Modo agente (implica `--json --no-input --compact NO_COLOR`) |
| `--compact` | JSON sin espacios |
| `--human` | Forzar salida legible aunque stdout esté redirigido |
| `--no-input` | Desactivar prompts interactivos |
| `--describe` | Emite el esquema del comando como JSON, sin ejecutarlo |
| `--profile ` | Usar un perfil específico |
Referencia completa de flags, modos y exit codes en
[Referencia del CLI](/guides/cli/referencia).
## Variables de entorno
| Variable | Descripción |
| ---------------- | ----------------------------------------------------------- |
| `JELOU_TOKEN` | Token de autenticación (sin necesidad de `jelou login`) |
| `JELOU_NO_INPUT` | `1` para modo no interactivo |
| `CI` | `true` para modo no interactivo (detectado automáticamente) |
| `JELOU_PROFILE` | Perfil por defecto |
En pipelines de CI, usa `JELOU_TOKEN` como variable de entorno y `--json` para
output parseable:
```bash theme={null}
export JELOU_TOKEN=${{ secrets.JELOU_TOKEN }}
jelou functions deploy --no-confirm --json | jq -r '.data.url'
```
# Context
Source: https://docs.jelou.ai/guides/functions/context
Referencia completa del objeto ctx disponible en cada handler: empresa, canal, usuario, trigger, environment, mensajería, memoria y más.
El objeto `ctx` es el segundo parámetro de todo handler. Contiene información de la empresa, canal, usuario, tipo de trigger, y da acceso a secrets, mensajería, memoria y logging.
```typescript theme={null}
handler: async (input, ctx, request) => {
ctx.log("Petición recibida", {
company: ctx.company.name,
bot: ctx.bot.channel,
user: ctx.user.id,
trigger: ctx.trigger.type,
});
}
```
## Referencia
| Propiedad | Tipo | Descripción |
| ------------------------- | ------------------------- | ------------------------------------------------- |
| `ctx.functionSlug` | `string` | Slug de la función |
| `ctx.company` | `{ id, name }` | Empresa del request |
| `ctx.bot` | `{ id, name, channel }` | Canal que originó el request |
| `ctx.user` | `{ id, names?, roomId? }` | Usuario de la conversación |
| `ctx.conversation` | `{ id?, ... }` | Datos de la conversación |
| `ctx.operator` | `{ id?, ... }` | Operador asignado |
| `ctx.trigger` | `TriggerInfo` | Tipo de trigger (http, cron, event) |
| `ctx.env` | `EnvAccessor` | Acceso a secrets |
| `ctx.params` | `Record` | Parámetros de ruta |
| `ctx.query` | `Record` | Query string params |
| `ctx.jelou` | `JelouSDK` | Mensajería WhatsApp y ejecuciones diferidas |
| `ctx.memory` | `MemorySDK` | Memoria de sesión key-value |
| `ctx.templateRegistry` | `TemplateRegistry` | Catálogo de plantillas WhatsApp aprobadas |
| `ctx.guard` | `Guard` | Cadena de verificaciones antes de actuar |
| `ctx.verifyStripe()` etc. | `(req) => Promise` | Verificación de firma de webhooks |
| `ctx.method` | `string` | Método HTTP (GET, POST, etc.) |
| `ctx.path` | `string` | Path del request |
| `ctx.requestId` | `string` | UUID único del request |
| `ctx.isCron` | `boolean` | `true` si es trigger cron |
| `ctx.isEvent` | `boolean` | `true` si es trigger event |
| `ctx.isHttp` | `boolean` | `true` si es request HTTP |
| `ctx.isScheduledFire` | `boolean` | `true` si es el disparo de una ejecución diferida |
| `ctx.skillId` | `string \| null` | ID del flujo de Brain Studio |
| `ctx.executionId` | `string \| null` | ID de ejecución de Brain Studio |
| `ctx.log()` | `(...args) => void` | Logger estructurado |
## Identidad
```typescript theme={null}
handler: async (input, ctx) => {
// Empresa
ctx.company.id; // 42
ctx.company.name; // "Tienda ABC"
// Canal
ctx.bot.id; // "bot-123"
ctx.bot.name; // "Canal de Soporte"
ctx.bot.channel; // "whatsapp"
// Usuario
ctx.user.id; // 99
ctx.user.names; // "María García" (opcional)
ctx.user.roomId; // "room-456" (opcional)
// Conversación y operador
ctx.conversation.id; // "conv-789" (opcional)
ctx.operator.id; // "op-012" (opcional)
}
```
Los datos se hidratan automáticamente desde la plataforma según los headers del request (`x-bot-id`, `x-user-id`).
## Trigger
Tres tipos de trigger determinan cómo se invocó tu función:
```typescript theme={null}
handler: async (input, ctx) => {
if (ctx.isHttp) {
// Petición HTTP normal
ctx.trigger; // { type: "http" }
}
}
```
```typescript theme={null}
handler: async (input, ctx) => {
if (ctx.isCron) {
ctx.trigger.type; // "cron"
ctx.trigger.cron; // "0 9 * * *"
ctx.trigger.cronName; // "recordatorio-mañana" (opcional)
}
}
```
```typescript theme={null}
handler: async (input, ctx) => {
if (ctx.isEvent) {
ctx.trigger.type; // "event"
ctx.trigger.event; // "pago.completado"
}
}
```
Usa los guards `ctx.isCron`, `ctx.isEvent` y `ctx.isHttp` en lugar de comparar `ctx.trigger.type` manualmente.
## Request
```typescript theme={null}
export default define({
description: "API con ruta parametrizada",
input: z.object({}),
config: { path: "/users/:id" },
handler: async (input, ctx) => {
ctx.method; // "GET"
ctx.path; // "/users/42"
ctx.params.id; // "42"
ctx.query.format // "json" (de ?format=json)
ctx.requestId; // "a1b2c3d4-..."
return { userId: ctx.params.id };
},
});
```
## Environment (secrets)
```typescript theme={null}
handler: async (input, ctx) => {
// Obtener un secret
const apiKey = ctx.env.get("CRM_API_KEY"); // string | undefined
// Verificar si existe
if (ctx.env.has("WEBHOOK_SECRET")) {
// ...
}
// Obtener todos (excepto internos __FN_*)
const all = ctx.env.toObject(); // Record
}
```
Las variables internas con prefijo `__FN_` están bloqueadas — `ctx.env.get("__FN_COMPANY_ID")` retorna `undefined`.
## Mensajería (`ctx.jelou`)
Envía mensajes de WhatsApp directamente desde tu función:
```typescript theme={null}
handler: async (input, ctx) => {
if (ctx.jelou.available) {
await ctx.jelou.send({
type: "text",
to: "+593987654321",
text: "Tu pedido está listo",
});
}
}
```
14 tipos de mensaje, templates HSM, validación de plantillas y manejo de errores.
## Ejecuciones diferidas (`ctx.jelou.schedule`)
Agenda una ejecución futura de tu propia función:
```typescript theme={null}
handler: async (input, ctx) => {
await ctx.jelou.schedule({
in: "24h",
path: "/enviar-seguimiento",
payload: { userId: input.userId, botId: ctx.bot.id },
subject: `user_${input.userId}`,
});
}
```
Agendar, consultar, cancelar y verificar antes de actuar.
## Verificaciones (`ctx.guard`)
Encadena condiciones y ejecuta la acción solo si todas se cumplen. Útil cuando la realidad pudo cambiar desde que agendaste algo:
```typescript theme={null}
handler: async (input, ctx) => {
return ctx.guard
.when("sinPagar", async () => !(await consultarPedido(input.pedidoId)).pagado)
.run(async () => {
await ctx.jelou.send({ type: "text", to: input.telefono, text: "Tu pago está pendiente" });
return { enviado: true };
});
}
```
Si alguna verificación devuelve un valor falso, la cadena se corta y devuelve `{ skipped: "" }` sin ejecutar la acción.
## Webhooks (`ctx.verify*`)
Verifica la firma de un webhook antes de procesarlo:
```typescript theme={null}
async handler(event, ctx, request) {
await ctx.verifyStripe(request);
return { received: true };
}
```
Stripe, Shopify, Meta y firmas HMAC genéricas.
## Memoria (`ctx.memory`)
Persiste datos por sesión (key-value con TTL):
```typescript theme={null}
handler: async (input, ctx) => {
if (ctx.memory.available) {
const paso = await ctx.memory.get("paso", "inicio");
await ctx.memory.set("paso", "confirmacion", 3600);
}
}
```
Primitivos, JSON, TTL, límites y patrones comunes.
## Brain Studio (`skillId`, `executionId`)
Cuando tu función es invocada desde Brain Studio como herramienta MCP:
```typescript theme={null}
handler: async (input, ctx) => {
if (ctx.skillId) {
ctx.log("Invocado desde Brain Studio", {
skillId: ctx.skillId, // "skill-abc-123"
executionId: ctx.executionId, // "exec-def-456"
});
}
}
```
Estos campos son `null` para peticiones HTTP directas y triggers cron.
## Logging
`ctx.log()` escribe JSON estructurado con metadatos automáticos:
```typescript theme={null}
handler: async (input, ctx) => {
ctx.log("Procesando pedido", { telefono: input.telefono });
// Escribe a stdout:
// {
// "requestId": "a1b2c3d4-...",
// "function": "mi-funcion",
// "company": 42,
// "timestamp": "2026-04-07T15:30:01.234Z",
// "args": ["Procesando pedido", { "telefono": "593987654321" }]
// }
}
```
Visualiza los logs con `jelou functions logs mi-funcion`.
## Ejemplo completo
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "procesar-pedido",
description: "Procesa un pedido y notifica al cliente por WhatsApp",
input: z.object({
pedidoId: z.string(),
telefono: z.string().min(10),
}),
handler: async (input, ctx) => {
ctx.log("Procesando pedido", {
pedidoId: input.pedidoId,
company: ctx.company.id,
bot: ctx.bot.name,
});
// Consultar API externa con secret
const apiKey = ctx.env.get("ORDERS_API_KEY");
const res = await fetch(`https://api.example.com/orders/${input.pedidoId}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const pedido = await res.json();
// Notificar al cliente por WhatsApp
if (ctx.jelou.available) {
await ctx.jelou.send({
type: "text",
to: input.telefono,
text: `Tu pedido ${pedido.id} está ${pedido.status}.`,
});
}
// Guardar estado en memoria
if (ctx.memory.available) {
await ctx.memory.set("ultimo_pedido", input.pedidoId, 86400);
}
return {
pedidoId: pedido.id,
status: pedido.status,
notificado: ctx.jelou.available,
};
},
});
```
Enviar WhatsApp con ctx.jelou.
Persistir datos con ctx.memory.
Variables de entorno cifradas.
Runtime tokens y funciones públicas.
# Cron
Source: https://docs.jelou.ai/guides/functions/cron
Configura tareas programadas en tus funciones: sintaxis cron, zonas horarias, guard isCron, límites y sincronización en deploy.
## Configuración
Define schedules cron directamente en el `config` de tu función. No necesitas configuración externa.
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "recordatorio-citas",
description: "Envía recordatorios de citas por WhatsApp",
input: z.object({}),
config: {
cron: [
{ expression: "0 8 * * *", timezone: "America/Guayaquil" },
{ expression: "0 8 * * *", timezone: "America/Bogota" },
],
},
handler: async (_input, ctx) => {
if (!ctx.isCron) return { skipped: true };
ctx.log("Enviando recordatorios", { cron: ctx.trigger.cron });
const apiKey = ctx.env.get("JELOU_API_KEY");
const res = await fetch("https://api.jelou.ai/v1/messages/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
botId: ctx.bot.id,
phone: "593987654321",
message: "Hola, te recordamos que tienes una cita mañana a las 10:00 AM.",
}),
});
return { enviados: 1, status: res.status };
},
});
```
## Prueba local
Inicia el servidor con `jelou functions dev`. Puedes enviar una petición HTTP a la función, pero el guard `isCron` la rechazará porque no es un trigger cron real:
```bash curl theme={null}
curl -X POST http://localhost:3000 \
-H "Content-Type: application/json" \
-d '{}'
```
```json Respuesta 200 theme={null}
{
"skipped": true,
"reason": "no es un trigger cron"
}
```
No puedes simular un trigger cron real desde curl — la plataforma inyecta `ctx.isCron` y la firma criptográfica automáticamente. Para probar la lógica del cron, usa [`createMockContext({ isCron: true })`](/guides/functions/testing) en tus tests.
## Sintaxis
Cada schedule tiene dos campos:
| Campo | Tipo | Requerido | Descripción |
| ------------ | -------- | --------- | ------------------------------------------------------- |
| `expression` | `string` | Sí | Cron estándar de 5 campos |
| `timezone` | `string` | No | Zona horaria IANA. Default: `UTC`. |
| `name` | `string` | No | Label identificativo del schedule. Max 64 caracteres. |
| `botId` | `string` | No | ID del bot para hidratar `ctx.bot`. Max 128 caracteres. |
### Formato de expresión
```
┌───────────── minuto (0-59)
│ ┌───────────── hora (0-23)
│ │ ┌───────────── día del mes (1-31)
│ │ │ ┌───────────── mes (1-12)
│ │ │ │ ┌───────────── día de la semana (0-7, 0 y 7 = domingo)
│ │ │ │ │
* * * * *
```
### Ejemplos comunes
| Expresión | Descripción |
| -------------- | ----------------------------------- |
| `0 9 * * *` | Todos los días a las 9:00 AM |
| `0 9 * * 1-5` | Lunes a viernes a las 9:00 AM |
| `*/30 * * * *` | Cada 30 minutos |
| `0 0 1 * *` | Primer día de cada mes a medianoche |
| `0 */2 * * *` | Cada 2 horas |
| `30 14 * * 3` | Miércoles a las 2:30 PM |
### Zonas horarias
Usa cualquier zona IANA válida:
```typescript theme={null}
config: {
cron: [
{ expression: "0 9 * * *", timezone: "America/Guayaquil" }, // Ecuador
{ expression: "0 9 * * *", timezone: "America/Bogota" }, // Colombia
{ expression: "0 9 * * *", timezone: "America/Mexico_City" }, // México
{ expression: "0 9 * * *", timezone: "America/Argentina/Buenos_Aires" },
{ expression: "0 3 * * *" }, // UTC por defecto
],
}
```
### Name y botId
Usa `name` para identificar cada schedule y `botId` para hidratar `ctx.bot` con un canal específico:
```typescript theme={null}
config: {
cron: [
{
expression: "0 9 * * *",
timezone: "America/Guayaquil",
name: "recordatorio-mañana",
botId: "bot-whatsapp-123",
},
],
}
```
## Guard `isCron`
Tu función puede recibir tanto peticiones HTTP como disparos cron. Usa `ctx.isCron` para distinguirlos:
```typescript theme={null}
handler: async (_input, ctx) => {
if (!ctx.isCron) {
return { skipped: true, reason: "no es un trigger cron" };
}
ctx.log("Tarea cron ejecutándose", { cron: ctx.trigger.cron });
return { ejecutado: true };
}
```
Sin el guard `isCron`, cualquier petición HTTP a tu función ejecutará la lógica del cron. Siempre incluye esta verificación.
## Cómo funciona
1. Defines los schedules en `config.cron`
2. Al ejecutar `jelou functions deploy`, la plataforma lee tu configuración y crea los schedules
3. Cuando un schedule se dispara, tu función recibe una petición con `ctx.isCron === true` y `ctx.trigger.cron` con la expresión que lo activó
4. La verificación de firma criptográfica previene invocaciones no autorizadas
## Límites
* Máximo **10** schedules cron por función
* Exceder este límite lanza un error en tiempo de definición
## Gestión
Los schedules son **declarativos** — se definen en el código y se sincronizan en cada despliegue. Para modificar un schedule, cambia `config.cron` en tu código y vuelve a desplegar.
Para ver los schedules activos:
```bash theme={null}
jelou functions cron list consultar-cliente
# ▸ Expression Timezone Enabled Last Triggered
# ▸ 0 8 * * * America/Guayaquil yes 2 hours ago
# ▸ 0 8 * * * America/Bogota yes 2 hours ago
```
## Multi-tool cron
Cuando usas `app()`, cada tool puede tener sus propios schedules cron independientes. Las peticiones cron se envían a la ruta específica de cada tool.
```typescript theme={null}
import { app, define, z } from "@jelou/functions";
export default app({
tools: {
limpiezaDiaria: define({
description: "Limpia registros obsoletos",
input: z.object({}),
config: { cron: [{ expression: "0 3 * * *", timezone: "UTC" }] },
handler: async (_input, ctx) => {
if (!ctx.isCron) return { skipped: true };
return { cleaned: true };
},
}),
sincronizacionHoraria: define({
description: "Sincroniza datos externos",
input: z.object({}),
config: { cron: [{ expression: "0 * * * *" }] },
handler: async (_input, ctx) => {
if (!ctx.isCron) return { skipped: true };
return { synced: true };
},
}),
},
});
```
El límite de **10** schedules cron es **agregado** entre todos los tools de un `app()`. Si un tool usa 6 schedules, los demás tools solo pueden usar 4 en total.
## Logs de ejecución
Consulta el historial de ejecuciones cron:
```bash theme={null}
jelou functions cron logs mi-funcion
# ▸ Time Tool State HTTP
# ▸ 4/7/2026, 9:00:00 AM default DELIVERED 200
# ▸ 4/6/2026, 9:00:00 AM default ERROR 500
```
Filtra por estado:
```bash theme={null}
jelou functions cron logs mi-funcion --state ERROR
```
Estados posibles: `DELIVERED`, `ERROR`, `RETRY`, `RETRY_SCHEDULED`, `FAILED`.
## Problemas comunes
Los schedules cron se sincronizan al hacer deploy. Si cambiaste la expresión cron, necesitas redesplegar:
```bash theme={null}
jelou functions deploy
```
Verifica que el schedule esté activo:
```bash theme={null}
jelou functions cron list mi-funcion
# ▸ Expression Timezone Enabled Last Triggered
# ▸ 0 8 * * * America/Guayaquil yes 2 hours ago
```
Si la columna `Enabled` muestra `no`, revisa que la expresión cron sea válida.
Sin el guard `isCron`, cualquier petición HTTP ejecutará la lógica del cron. Agrega la verificación al inicio del handler:
```typescript theme={null}
handler: async (_input, ctx) => {
if (!ctx.isCron) return { skipped: true };
// tu lógica cron aquí
return { ejecutado: true };
}
```
Esto retorna `{ skipped: true }` para peticiones HTTP normales y solo ejecuta la lógica cuando es un trigger cron real.
Consulta la [guía completa de multi-tool](/guides/functions/multi-tool) para más detalles sobre `app()`.
# Despliegue
Source: https://docs.jelou.ai/guides/functions/despliegue
Flujo de despliegue, límites de archivos, rollback interactivo y directo, y configuración de CI/CD con GitHub Actions.
## Flujo de despliegue
Cuando ejecutas `jelou functions deploy`, la plataforma:
1. Lee `jelou.json` para encontrar el slug y entrypoint
2. Recolecta todos los archivos desplegables del directorio
3. Muestra un resumen con nombres y tamaños
4. Sube los archivos y ejecuta el despliegue
5. Renombra tu entrypoint a `user-function.ts`
6. Genera un `main.ts` wrapper que importa tu código e inicia el servidor
7. Inyecta tus secrets como variables de entorno
```bash theme={null}
jelou functions deploy
# ▸ Files: index.ts (1.2 KB), helpers.ts (800 B), jelou.json (98 B), deno.json (65 B)
# ? Deploy consultar-cliente? (Y/n) y
# ✓ Deployed
# ▸ ID: dep_abc12345
# ▸ URL: https://consultar-cliente.fn.jelou.ai
```
El deploy no genera ningún runtime token — la función queda protegida por defecto y solo acepta llamadas autenticadas con una API key de plataforma. Créala en la sección de configuración de apps ([apps.jelou.ai](https://apps.jelou.ai)) y envíala como `Authorization: Bearer `. Consulta la [guía de autenticación](/guides/functions/autenticacion) para más detalles.
## Límites de archivos
| Límite | Valor |
| ----------------------- | ------------------------------------ |
| Archivos por despliegue | 20 |
| Tamaño por archivo | 256 KB |
| Tamaño total | 1 MB |
| Extensiones permitidas | `.ts`, `.js`, `.json`, `.md`, `.txt` |
| Entrypoint requerido | `index.ts` |
### Qué se excluye automáticamente
* `node_modules/`
* `.git/`
* `.env`
* `dist/`
* `.jelou/`
* Archivos ocultos (que empiezan con `.`)
## Omitir confirmación
Para despliegues automatizados, usa `--no-confirm`:
```bash theme={null}
jelou functions deploy --no-confirm
```
## Rollback
Si necesitas revertir a una versión anterior, usa `jelou functions rollback`.
Sin argumentos, muestra un menú con despliegues recientes:
```bash theme={null}
jelou functions rollback
# ? Select deployment to rollback to:
# ▸ dep_abc12345.. — 2 hours ago by alex@jelou.ai (current)
# dep_def67890.. — 1 day ago by ci@jelou.ai
# dep_ghi11223.. — 3 days ago by alex@jelou.ai
```
Especifica el slug y el ID del despliegue:
```bash theme={null}
jelou functions rollback consultar-cliente dep_def67890
# ✓ Rolled back to dep_def67890
```
## CI/CD con GitHub Actions
```yaml deploy.yml theme={null}
name: Deploy Function
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install CLI
run: npm install -g @jelou/cli
- name: Deploy
env:
JELOU_TOKEN: ${{ secrets.JELOU_TOKEN }}
run: jelou functions deploy --no-confirm --json | jq '.data.url'
```
```yaml deploy.yml theme={null}
name: Deploy with Secrets
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install CLI
run: npm install -g @jelou/cli
- name: Configure secrets
env:
JELOU_TOKEN: ${{ secrets.JELOU_TOKEN }}
run: |
jelou functions secrets set consultar-cliente \
CRM_API_KEY=${{ secrets.CRM_API_KEY }} \
JELOU_API_KEY=${{ secrets.JELOU_API_KEY }}
- name: Deploy
env:
JELOU_TOKEN: ${{ secrets.JELOU_TOKEN }}
run: |
DEPLOY_URL=$(jelou functions deploy --no-confirm --json | jq -r '.data.url')
echo "Deployed to $DEPLOY_URL"
```
Usa `--json` en pipelines para obtener output estructurado que puedes parsear con `jq`. El formato es siempre `{ "ok": true, "data": ... }` en stdout.
## Rutas expuestas
| Ruta | Descripción |
| ----------- | ----------------------------------------------------------- |
| `/__health` | Health check y metadata de la función |
| `/mcp` | Endpoint MCP (a menos que `config.mcp: false`) |
| Tu ruta | Ruta del handler (default: `*` coincide con cualquier path) |
| Ruta | Descripción |
| ------------- | -------------------------------------------------------------------------- |
| `/__health` | Health check con lista de todos los tools |
| `/mcp` | Servidor MCP unificado con todos los tools |
| `/` | Ruta de cada tool (kebab-case auto-generado o `config.path` personalizado) |
## Historial de despliegues
```bash theme={null}
jelou functions deployments list mi-funcion
# ▸ ID Status Source Files Deployed By Age
# ▸ dep_abc123.. active cli 3 alex@jelou.ai 2h ago
# ▸ dep_def456.. active cli 3 ci@jelou.ai 1d ago
```
Para ver detalles de un despliegue específico:
```bash theme={null}
jelou functions deployments info mi-funcion dep_abc123
```
Para descargar el artifact de un despliegue:
```bash theme={null}
jelou functions deployments download mi-funcion dep_abc123 -o artifact.tar.gz
```
# Ejecuciones diferidas
Source: https://docs.jelou.ai/guides/functions/diferidas
Programa una ejecución futura de tu función: recordatorios, seguimientos y carritos abandonados con ctx.jelou.schedule, cancelación y guards de disparo.
Una **ejecución diferida** es una invocación que agendas para que ocurra una sola vez en el futuro: un recordatorio a las 24 horas, un seguimiento de carrito abandonado, una encuesta 2 días después de la compra.
**¿Cron o diferida?**
* **[Cron](/guides/functions/cron)** — se repite en un horario fijo (todos los días a las 9:00). Se define en el código.
* **Diferida** — ocurre una vez, en un momento calculado en tiempo de ejecución (24 horas después de *este* pedido). Se agenda desde el handler o desde la petición HTTP.
## Agendar desde el handler
`ctx.jelou.schedule()` agenda una ejecución relativa a "ahora":
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "carrito-abandonado",
description: "Recibe el evento de carrito abandonado y agenda el seguimiento",
input: z.object({
userId: z.string(),
carritoId: z.string(),
}),
handler: async (input, ctx) => {
const booking = await ctx.jelou.schedule({
in: "24h",
path: "/enviar-seguimiento",
// Incluye el botId: en el disparo no hay canal resuelto
payload: { userId: input.userId, carritoId: input.carritoId, botId: ctx.bot.id },
key: `carrito:${input.userId}:${input.carritoId}`,
subject: `user_${input.userId}`,
});
ctx.log("Seguimiento agendado", { id: booking.id, cuando: booking.scheduledAt });
return { agendado: true, id: booking.id };
},
});
```
### Parámetros
| Campo | Tipo | Requerido | Descripción |
| ---------------- | ------------------ | --------- | -------------------------------------------------------- |
| `in` | `string \| number` | Sí | Duración: `"30m"`, `"24h"`, `"1h30m"`, `"3d"` o segundos |
| `path` | `string` | Sí | Ruta de tu función que se ejecutará. Empieza con `/` |
| `payload` | `object \| array` | No | Datos que recibirá el handler al dispararse |
| `key` | `string` | No | Etiqueta para buscar o cancelar después |
| `subject` | `string` | No | Audiencia, normalmente el ID del usuario |
| `idempotencyKey` | `string` | No | Evita agendar dos veces si reintentas |
| `pinDeployment` | `boolean` | No | Fija la ejecución al despliegue actual |
La duración acepta unidades compuestas de mayor a menor: `"1h30m"` es válido, `"30m1h"` no.
Para seguimientos muy cortos usa **5 segundos o más**. Con duraciones menores la latencia de red puede dejar el momento agendado en el pasado y la plataforma lo rechaza.
### Momento absoluto
`ctx.jelou.scheduleAt()` recibe una fecha en vez de una duración:
```typescript theme={null}
await ctx.jelou.scheduleAt({
at: new Date("2026-12-24T18:00:00Z"), // o el string ISO 8601
path: "/felicitacion-navidad",
payload: { userId: input.userId },
});
```
Lanza un error si la fecha ya pasó o no se puede interpretar. El resto de campos funciona igual que en `schedule()`.
### Evitar duplicados
Si el servicio que llama a tu función reintenta, `idempotencyKey` garantiza una sola reserva:
```typescript theme={null}
const booking = await ctx.jelou.schedule({
in: "24h",
path: "/enviar-seguimiento",
payload: { userId: input.userId, carritoId: input.carritoId },
idempotencyKey: `carrito-abandonado:${input.eventoId}`,
});
if (booking.idempotentReplay) {
ctx.log("Ya estaba agendado, no se duplicó", { id: booking.id });
}
```
Reusar la misma `idempotencyKey` con un `payload` distinto devuelve un error `409`.
## Agendar desde una petición HTTP
Cualquier cliente puede diferir una llamada agregando una cabecera al POST normal. Sin cabeceras de agenda, la función se ejecuta de inmediato como siempre.
```bash theme={null}
curl -X POST https://recordatorios.fn.jelou.ai/enviar-recordatorio \
-H "Authorization: Bearer " \
-H "X-Jelou-Schedule-In: 5m" \
-H "X-Jelou-Key: recordatorio:user-42" \
-H "Content-Type: application/json" \
-d '{"userId":"42","mensaje":"Tu cita es mañana"}'
```
| Cabecera | Efecto |
| ------------------------ | ------------------------------------------------ |
| `X-Jelou-Schedule-In` | Duración (`"5m"`, `"1h30m"`, `"24h"` o segundos) |
| `X-Jelou-Schedule-At` | Momento absoluto en ISO 8601 |
| `X-Jelou-Key` | Etiqueta para buscar o cancelar después |
| `X-Jelou-Subject` | Audiencia, normalmente el ID del usuario |
| `X-Jelou-Pin-Deployment` | `"true"` para fijar el despliegue actual |
| `Idempotency-Key` | Evita agendar dos veces al reintentar |
La respuesta es `202 Accepted` cuando se crea la reserva y `200 OK` cuando un reintento reutiliza una reserva existente. El cuerpo no puede superar **64 KB**.
## Recibir el disparo
Cuando llega el momento, tu función recibe el `payload` original en la ruta que indicaste. Usa `ctx.isScheduledFire` para distinguir el disparo de una petición normal:
```typescript theme={null}
handler: async (input, ctx) => {
if (ctx.isScheduledFire) {
ctx.log("Ejecución diferida disparada");
}
// ...
}
```
### Verificar antes de actuar
Entre el momento en que agendas y el momento en que se dispara, la realidad pudo cambiar: el cliente ya pagó, la plantilla se pausó, el usuario se dio de baja. `ctx.guard` encadena esas verificaciones y evita el envío si alguna falla:
```typescript theme={null}
export default define({
name: "enviar-seguimiento",
description: "Envía el seguimiento de carrito abandonado si sigue aplicando",
input: z.object({
userId: z.string(),
carritoId: z.string(),
botId: z.string(),
}),
handler: async (input, ctx) => {
// El botId viajó en el payload que agendaste antes
const registro = ctx.templateRegistry.for(input.botId);
return ctx.guard
.when("sinPagar", async () => !(await consultarCarrito(input.carritoId)).pagado)
.when("plantillaAprobada", () => registro.has("carrito_abandonado_v3"))
.run(async () => {
await ctx.jelou.sendTemplate({
template: "carrito_abandonado_v3",
to: input.userId,
params: ["María", `https://tienda.com/recuperar?c=${input.carritoId}`],
});
return { enviado: true };
});
},
});
```
Si todas las verificaciones pasan, se ejecuta `.run()` y su resultado es la respuesta. Si alguna falla, la cadena se corta y devuelve `{ skipped: "" }` sin ejecutar el envío.
La cadena termina con `.run(handler)`, no con `.then()`. `ctx.guard` es una cadena nueva en cada petición.
Si el bot no viaja en el `payload`, en un disparo diferido `ctx.bot` no está resuelto y `ctx.templateRegistry` queda sin canal asociado. Incluye el `botId` en el `payload` al agendar y enlázalo con `ctx.templateRegistry.for(botId)`. Ver [plantillas de WhatsApp](/guides/functions/mensajeria#validar-plantillas-antes-de-enviar).
## Consultar lo agendado
`ctx.jelou.findDeferred()` lista las ejecuciones pendientes de tu función:
```typescript theme={null}
const { data, total } = await ctx.jelou.findDeferred({
subject: `user_${input.userId}`,
status: "scheduled",
});
for (const fila of data) {
ctx.log("pendiente", fila.id, fila.scheduledAt, fila.key);
}
```
| Campo | Descripción |
| ------------------ | ------------------------------------------------------------------------------------- |
| `key` | Filtra por etiqueta exacta |
| `subject` | Filtra por audiencia |
| `status` | `"active"` (default), `"scheduled"`, `"firing"`, `"fired"`, `"cancelled"`, `"failed"` |
| `page` / `perPage` | Paginación. Default `20`, máximo `100` |
## Cancelar
`ctx.jelou.cancelDefer()` cancela en bloque por audiencia o etiqueta. Requiere exactamente uno de `subject`, `key` o `keyPrefix`:
```typescript theme={null}
// Baja del usuario: cancela todos sus recordatorios pendientes
const { cancelled, raced } = await ctx.jelou.cancelDefer({
subject: `user_${input.userId}`,
});
ctx.log("baja procesada", { cancelados: cancelled.length, en_curso: raced.length });
```
`raced` contiene las ejecuciones que ya se estaban disparando cuando llegó la cancelación — para esas, la verificación con `ctx.guard` en el handler es la última defensa. Escribe tus handlers para que sean idempotentes.
Antes de una cancelación amplia, previsualiza el alcance con `dryRun`:
```typescript theme={null}
const previa = await ctx.jelou.cancelDefer({
keyPrefix: "promo-verano:",
dryRun: true,
});
if (previa.wouldCancelCount < 5000) {
await ctx.jelou.cancelDefer({ keyPrefix: "promo-verano:" });
}
```
No existe "reprogramar": una reserva es inmutable. Para cambiar la hora, cancela y agenda de nuevo.
## Prueba local
`schedule`, `scheduleAt`, `findDeferred` y `cancelDefer` funcionan solo en una función **desplegada**. En local lanzan un error porque no hay credenciales de plataforma.
Para probar el flujo con `jelou functions dev`, activa la simulación:
```bash theme={null}
JELOU_FN_DEFER_DEV=1 jelou functions dev
```
En ese modo se validan los argumentos, se registra la reserva en los logs y se devuelve un resultado sintético — tu handler sigue ejecutándose, pero no se agenda nada real.
Para tests unitarios usa [`createMockContext`](/guides/functions/testing), cuyo `ctx.jelou` registra las llamadas sin red ni configuración.
## Inspeccionar desde el CLI
```bash theme={null}
# Reservas activas de la función del proyecto actual
jelou functions defer list
# Las que fallaron
jelou functions defer list mi-funcion --status failed
# Las de un usuario
jelou functions defer list mi-funcion --subject user_42
# Detalle de una reserva (incluye el último error)
jelou functions defer get dinv_01ksqbvyqpe0prt91exc97mh4n
```
Ver la [referencia del CLI](/guides/functions/cli#diferidas).
## Límites
| Límite | Valor |
| --------------------------------- | -------------------- |
| Reservas activas por empresa | 1,000 |
| Anticipación máxima | 30 días |
| Tamaño del `payload` | 64 KB |
| Cancelación en bloque por llamada | 10,000 coincidencias |
Las reservas ya disparadas, canceladas o fallidas no cuentan contra el límite de activas.
## Problemas comunes
Consulta el estado y el último error de la reserva:
```bash theme={null}
jelou functions defer get dinv_01ksqbvy... --payload
```
Si el estado es `failed`, el campo de error indica por qué falló la entrega. Si es `cancelled`, algo la canceló antes — revisa tus llamadas a `cancelDefer`.
Una cancelación que llega cuando la ejecución ya empezó no la detiene. Aparece en `raced` y tu handler se ejecuta.
Por eso la verificación va en el handler, no solo en la cancelación:
```typescript theme={null}
return ctx.guard
.when("sigueActivo", async () => await usuarioActivo(input.userId))
.run(async () => { /* envío */ });
```
```
scheduleAt unavailable: platform credentials missing
```
Estás llamando a la familia `schedule` en local. Arranca el servidor con `JELOU_FN_DEFER_DEV=1 jelou functions dev` para simular las reservas.
El servicio que llama a tu función reintentó. Agrega `idempotencyKey` (o la cabecera `Idempotency-Key`) con un valor derivado del evento:
```typescript theme={null}
idempotencyKey: `carrito-abandonado:${input.eventoId}`
```
Tareas recurrentes en horario fijo.
Enviar WhatsApp y validar plantillas.
Verificar firmas de servicios externos.
Comandos `defer list` y `defer get`.
# Jelou Functions
Source: https://docs.jelou.ai/guides/functions/index
Funciones serverless en TypeScript: endpoints HTTP, validación Zod, herramientas MCP y multi-tool con app(), todo automático.
**Vista previa** — Jelou Functions está en fase de vista previa. La API y el comportamiento pueden cambiar sin previo aviso. No lo uses en flujos de producción críticos sin contactar al equipo de soporte.
**¿Cómo obtener tu token?**
* **Clientes Enterprise:** el token debe ser solicitado al equipo de soporte técnico de Jelou.
* **Clientes Self-service:** puedes generar tu propio token desde [Autenticación](/guides/functions/autenticacion).
## ¿Qué es Jelou Functions?
Jelou Functions es una plataforma serverless de TypeScript donde con `define()` obtienes automáticamente:
* **Endpoint HTTP** listo para recibir peticiones
* **Validación** de entrada y salida con Zod
* **Herramienta MCP** para que tus agentes IA la invoquen directamente
* **Cron jobs** declarativos sin infraestructura adicional
* **Ejecuciones diferidas** para agendar seguimientos y recordatorios
* **Verificación de webhooks** de Stripe, Shopify y Meta en una línea
* **Multi-tool** con `app()` para agrupar varias herramientas en un solo despliegue
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "consultar-cliente",
description: "Busca información de un cliente por teléfono",
input: z.object({
telefono: z.string().min(10),
}),
output: z.object({
nombre: z.string(),
plan: z.string(),
}),
handler: async (input, ctx) => {
ctx.log("Buscando cliente", { telefono: input.telefono });
return { nombre: "María García", plan: "Premium" };
},
});
```
Despliega con un solo comando y obtén una URL de producción:
```bash theme={null}
jelou functions deploy
# → https://consultar-cliente.fn.jelou.ai
```
## ¿Para qué sirve?
Crea herramientas que tus agentes de WhatsApp pueden invocar: consultar datos, procesar pagos, verificar estados.
Recibe callbacks de pasarelas de pago, CRMs o cualquier servicio externo con validación automática.
Ejecuta tareas recurrentes como enviar recordatorios, sincronizar datos o limpiar sesiones inactivas.
Agenda una ejecución futura: recordatorios a 24 horas, seguimientos de carrito abandonado, encuestas post-compra.
Recibe eventos de Stripe, Shopify o Meta con verificación de firma en una línea.
Del código local a producción en segundos, con rollback incluido y soporte para CI/CD.
Agrupa múltiples herramientas en un solo despliegue con `app()`: rutas auto-generadas, MCP unificado y cron independiente.
## Prerrequisitos
Antes de empezar, asegúrate de tener:
* **Node.js 18+** instalado
* Tu cuenta de Jelou con acceso a Functions
* Tu token de acceso personal (prefijo `jfn_pat_`)
## Siguiente paso
Crea, prueba y despliega tu primera función en menos de 5 minutos.
# Límites
Source: https://docs.jelou.ai/guides/functions/limites
Límites de la plataforma Jelou Functions: archivos, tamaños, timeouts, cron, tokens, memoria y herramientas.
## Despliegue
| Límite | Valor |
| --------------------------- | ------------------------------------ |
| Archivos por despliegue | 20 |
| Tamaño por archivo | 256 KB |
| Tamaño total del despliegue | 1 MB |
| Extensiones permitidas | `.ts`, `.js`, `.json`, `.md`, `.txt` |
| Entrypoint requerido | `index.ts` |
## Ejecución
| Límite | Valor |
| ------------------- | ------------ |
| Timeout máximo | 120 segundos |
| Timeout por defecto | 30 segundos |
El timeout se configura con `config.timeout` en milisegundos:
```typescript theme={null}
export default define({
description: "Operación lenta",
input: z.object({}),
config: { timeout: 60_000 }, // 60 segundos
handler: async (_input, ctx) => {
// operación que toma hasta 60s
return { ok: true };
},
});
```
## Herramientas (tools)
| Límite | Valor |
| ------------------- | ----------------------------------------- |
| Nombre de tool | Max 64 caracteres, patrón `[a-zA-Z0-9_-]` |
| Descripción de tool | Max 1024 caracteres |
```typescript theme={null}
// ✓ Nombres válidos
define({ name: "consultar-saldo", ... })
define({ name: "search_products", ... })
define({ name: "myTool123", ... })
// ✗ Nombres inválidos
define({ name: "mi tool", ... }) // espacios no permitidos
define({ name: "tool.name", ... }) // punto no permitido
define({ name: "", ... }) // vacío
```
## Cron
| Límite | Valor |
| --------------------- | ---------------------------------------------- |
| Schedules por función | 10 (agregado entre todos los tools en `app()`) |
| Nombre de schedule | Max 64 caracteres |
| botId de schedule | Max 128 caracteres |
```typescript theme={null}
config: {
cron: [
{ expression: "0 9 * * *", timezone: "America/Guayaquil", name: "recordatorio-mañana" },
{ expression: "0 15 * * *", timezone: "America/Guayaquil", name: "recordatorio-tarde" },
],
}
```
En modo `app()`, el límite de 10 schedules es **agregado** entre todos los tools. Si un tool usa 6 schedules, los demás tools solo pueden usar 4 en total.
## Ejecuciones diferidas
| Límite | Valor |
| --------------------------------- | -------------------- |
| Reservas activas por empresa | 1,000 |
| Anticipación máxima | 30 días |
| Tamaño del `payload` | 64 KB |
| Cancelación en bloque por llamada | 10,000 coincidencias |
| Longitud de la ruta destino | Max 255 caracteres |
Las reservas ya disparadas, canceladas o fallidas no cuentan contra el límite de activas. Ver [ejecuciones diferidas](/guides/functions/diferidas).
## Tokens
| Límite | Valor |
| ---------------------- | -------------------- |
| Tamaño máximo de token | 4 KB |
| Tokens por función | Sin límite explícito |
## Memoria (`ctx.memory`)
| Límite | Valor |
| ---------------- | ------------------------------------------------------------ |
| Valor de `set()` | Max 255 caracteres (usar `setJson()` para datos más grandes) |
| TTL máximo | 86,400 segundos (24 horas) |
| Alcance | Por usuario |
```typescript theme={null}
// ✓ Correcto
await ctx.memory.set("paso", "confirmacion", 3600);
// ✗ Error: excede 255 caracteres
await ctx.memory.set("datos", jsonMuyLargo, 3600);
// ✓ Usar setJson para datos grandes
await ctx.memory.setJson("datos", { items: [...] }, 3600);
```
## Secrets
| Límite | Valor |
| ----------------- | --------------------------------------------- |
| Formato de clave | `UPPER_SNAKE_CASE` (`^[A-Z][A-Z0-9_]*$`) |
| Prefijo bloqueado | `__FN_` (variables internas de la plataforma) |
## Archivos excluidos del despliegue
Los siguientes se excluyen automáticamente:
* `node_modules/`
* `.git/`
* `.env`
* `dist/`
* `.jelou/`
* Archivos ocultos (que empiezan con `.`)
# Logs
Source: https://docs.jelou.ai/guides/functions/logs
Monitorea tus funciones con jelou functions logs, ctx.log() para logging estructurado, y jelou functions cron logs para ejecuciones cron.
## Streaming en vivo
Por defecto, `jelou functions logs` hace streaming de logs en tiempo real:
```bash theme={null}
jelou functions logs mi-funcion
# ▸ Streaming logs for mi-funcion
# ▸ Press Ctrl+C to stop
#
# 10:30:01 INFO Buscando cliente { telefono: "593987654321" }
# 10:30:02 WARN API externa lenta { latency: 2300 }
# 10:31:15 INFO Buscando cliente { telefono: "593912345678" }
```
La flag `--follow` / `-f` es equivalente (es el comportamiento por defecto):
```bash theme={null}
jelou functions logs mi-funcion --follow
```
## Logs históricos
Para obtener logs pasados en lugar de streaming:
```bash theme={null}
jelou functions logs mi-funcion --history
# ◇ Fetched 47 logs
#
# 10:25:01 INFO Deployed successfully
# 10:30:01 INFO Buscando cliente { telefono: "593987654321" }
# 10:30:02 WARN API externa lenta { latency: 2300 }
```
## Escribir logs desde tu función
Usa `ctx.log()` dentro del handler para escribir logs estructurados:
```typescript theme={null}
handler: async (input, ctx) => {
ctx.log("Procesando pedido", {
telefono: input.telefono,
company: ctx.company.id,
requestId: ctx.requestId,
});
const resultado = await procesarPedido(input);
ctx.log("Pedido completado", { resultado });
return resultado;
}
```
`ctx.log()` escribe JSON estructurado a stdout:
```json theme={null}
{
"requestId": "a1b2c3d4-...",
"function": "mi-funcion",
"company": 42,
"timestamp": "2026-04-07T15:30:01.234Z",
"args": ["Procesando pedido", { "telefono": "593987654321", "company": 42 }]
}
```
`console.log()`, `console.warn()` y `console.error()` también funcionan y son capturados por la plataforma, pero `ctx.log()` agrega metadatos útiles (requestId, function, company, timestamp) automáticamente.
## Logs de cron
Para ver el historial de ejecuciones cron:
```bash theme={null}
jelou functions cron logs mi-funcion
# ◇ Found 12 log entries
#
# ▸ Time Tool State HTTP
# ▸ 4/7/2026, 9:00:00 AM default DELIVERED 200
# ▸ 4/7/2026, 3:00:00 AM default DELIVERED 200
# ▸ 4/6/2026, 9:00:00 AM default ERROR 500
```
### Filtrar por estado
```bash theme={null}
jelou functions cron logs mi-funcion --state ERROR
```
Estados posibles: `DELIVERED`, `ERROR`, `RETRY`, `RETRY_SCHEDULED`, `ACTIVE`, `FAILED`.
### Paginación
```bash theme={null}
jelou functions cron logs mi-funcion --cursor
```
El cursor se muestra al final de la salida cuando hay más resultados disponibles.
## JSON mode
Para integrar con herramientas externas, usa `--json`:
```bash theme={null}
# Logs como JSON lines (streaming)
jelou functions logs mi-funcion --json
# Logs históricos como JSON
jelou functions logs mi-funcion --history --json
# Cron logs como JSON
jelou functions cron logs mi-funcion --json
```
En CI/CD, combina `--json` con herramientas como `jq` para filtrar logs:
```bash theme={null}
jelou functions logs mi-funcion --history --json | jq 'select(.level == "ERROR")'
```
# MCP
Source: https://docs.jelou.ai/guides/functions/mcp
Cómo Jelou Functions genera automáticamente herramientas MCP para que tus agentes IA invoquen funciones como tools.
## Auto-generación de herramientas MCP
Cuando creas una función con `define()`, se expone automáticamente como una herramienta [MCP](https://modelcontextprotocol.io/) (Model Context Protocol). Tus agentes IA pueden descubrir e invocar tu función como un tool sin configuración adicional.
## Ejemplo
Para esta función:
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "consultar-saldo",
description: "Consulta el saldo de un cliente por número de teléfono",
input: z.object({
telefono: z.string().min(10).describe("Número de teléfono con código de país"),
}),
output: z.object({
nombre: z.string(),
saldo: z.number(),
moneda: z.string(),
}),
handler: async (input, ctx) => {
ctx.log("Consultando saldo", { telefono: input.telefono });
return { nombre: "María García", saldo: 150.00, moneda: "USD" };
},
});
```
El endpoint `/mcp` expone este esquema:
```json theme={null}
{
"name": "consultar-saldo",
"description": "Consulta el saldo de un cliente por número de teléfono",
"inputSchema": {
"type": "object",
"properties": {
"telefono": {
"type": "string",
"minLength": 10,
"description": "Número de teléfono con código de país"
}
},
"required": ["telefono"]
}
}
```
## El campo `description`: lo más importante para MCP
El campo `description` de tu `define()` es lo que el agente IA lee para decidir **cuándo** invocar tu herramienta. Si la descripción es vaga, el agente no sabrá cuándo usarla — o peor, la usará en el momento incorrecto.
| ❌ Vaga | ✅ Específica | Por qué importa |
| ------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------- |
| `"Maneja usuarios"` | `"Busca un usuario por email o teléfono y retorna su perfil con saldo"` | El agente sabe exactamente qué datos puede obtener |
| `"Envía mensaje"` | `"Envía un mensaje de WhatsApp a un número con código de país (ej: 593…)"` | El agente sabe el canal y el formato esperado |
| `"Consulta API"` | `"Consulta el estado de un envío por tracking number en la API de Servientrega"` | El agente sabe para qué proveedor y qué dato necesita |
### Anotaciones `.describe()` en campos
Las anotaciones `.describe()` de Zod se convierten en descripciones de parámetros del tool MCP. Esto es lo que ven los agentes IA cuando descubren tu función:
```typescript theme={null}
input: z.object({
telefono: z.string().min(10).describe("Número con código de país, ej: 593987654321"),
tipo: z.enum(["prepago", "pospago"]).describe("Tipo de plan del cliente"),
incluirHistorial: z.boolean().default(false).describe("Si incluir las últimas 5 transacciones"),
})
```
Escribe descripciones claras y específicas en `.describe()`. Los agentes IA usan estas descripciones para decidir qué valores pasar a tu función.
## Probar el endpoint MCP
Inicia el servidor local con `jelou functions dev` y consulta el esquema MCP:
```bash curl theme={null}
curl http://localhost:3000/mcp
```
```json Respuesta theme={null}
{
"tools": [
{
"name": "consultar-saldo",
"description": "Consulta el saldo de un cliente por número de teléfono",
"inputSchema": {
"type": "object",
"properties": {
"telefono": {
"type": "string",
"minLength": 10,
"description": "Número de teléfono con código de país"
}
},
"required": ["telefono"]
}
}
]
}
```
Invoca la función directamente:
```bash curl theme={null}
curl -X POST http://localhost:3000 \
-H "Content-Type: application/json" \
-d '{"telefono": "593987654321"}'
```
```json Respuesta 200 theme={null}
{
"nombre": "María García",
"saldo": 150.00,
"moneda": "USD"
}
```
En producción:
```bash theme={null}
curl https://consultar-saldo.fn.jelou.ai/mcp \
-H "X-Jelou-Token: jfn_rt_abc123..."
```
En producción, el endpoint `/mcp` requiere el header `X-Jelou-Token`. Sin token, recibirás `401 Unauthorized`. Solo `/__health` y `/openapi.json` son públicos sin token.
## Desactivar MCP
Si tu función no debe ser descubierta como herramienta (por ejemplo, un webhook que solo recibe callbacks), desactiva MCP:
```typescript theme={null}
config: { mcp: false }
```
Cuando MCP está desactivado, el endpoint `/mcp` retorna 404.
## ¿Cómo lo usan los agentes?
Cuando configuras un agente IA en Jelou Brain Studio y le asignas funciones como tools, el agente:
1. Descubre las herramientas disponibles vía el endpoint `/mcp`
2. Lee el nombre, descripción y esquema de entrada
3. Decide cuándo invocar la herramienta basándose en la conversación del usuario
4. Envía los parámetros validados a tu función
5. Recibe la respuesta y la incorpora a la conversación
Todo esto sucede automáticamente — solo necesitas escribir la función con `define()` y asignarla al agente.
## Multi-tool MCP
Cuando usas `app()`, un solo servidor MCP en `/mcp` registra automáticamente todos los tools. Cada tool aparece como una herramienta independiente con su nombre, descripción y esquema.
Para excluir un tool específico del registro MCP, usa `mcp: false` en su config:
```typescript theme={null}
import { app, define, z } from "@jelou/functions";
export default app({
tools: {
consultarSaldo: define({
description: "Consulta el saldo de un cliente",
input: z.object({ telefono: z.string().min(10) }),
handler: async (input) => ({ saldo: 150.00 }),
}),
webhookPagos: define({
description: "Recibe callbacks de pagos",
input: z.object({ transactionId: z.string() }),
config: { mcp: false },
handler: async (input) => ({ received: true }),
}),
},
});
```
En este ejemplo, los agentes IA descubren `consultarSaldo` vía MCP pero `webhookPagos` solo es accesible por HTTP directo en `/webhook-pagos`.
Consulta la [guía completa de multi-tool](/guides/functions/multi-tool) para más detalles sobre rutas auto-generadas y combinación de config.
Guía paso a paso para configurar tu función como servidor MCP externo en Brain Studio.
# Memoria de sesión
Source: https://docs.jelou.ai/guides/functions/memoria
Guarda y lee estado por conversación con ctx.memory: flujos multi-paso, carritos, contadores y datos temporales sin base de datos externa.
## ¿Qué es `ctx.memory`?
`ctx.memory` es un cliente HTTP al **Memory API** de Jelou. Habla con el mismo almacenamiento key-value del usuario (`$memory` en el builder). Lo que escribas desde una Function es visible en placeholders `{{$memory.key}}` del workflow y viceversa — es una sola memoria por usuario, no una copia paralela.
Disponible automáticamente cuando la petición viene de una conversación activa y la empresa tiene una API key de workflow configurada.
Casos de uso típicos:
* **Flujos multi-paso** — recordar en qué paso está el usuario
* **Carritos de compras** — acumular productos durante la conversación
* **Contadores** — limitar intentos de login, tracking de reintentos
* **Preferencias del usuario** — idioma, formato, filtros que persisten entre conversaciones
## Inicio rápido
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "flujo-registro",
description: "Registro multi-paso con memoria de sesión",
input: z.object({
respuesta: z.string().optional(),
}),
handler: async (input, ctx) => {
if (!ctx.memory.available) {
return { error: "memory_unavailable" };
}
const paso = await ctx.memory.get("paso", "inicio");
if (paso === "inicio") {
await ctx.memory.set("paso", "nombre", 3600);
return { pregunta: "¿Cuál es tu nombre?" };
}
if (paso === "nombre") {
await ctx.memory.set("nombre", input.respuesta || "", 3600);
await ctx.memory.set("paso", "email", 3600);
return { pregunta: "¿Cuál es tu email?" };
}
const nombre = await ctx.memory.get("nombre", "");
await ctx.memory.delete("paso");
await ctx.memory.delete("nombre");
return { completado: true, nombre, email: input.respuesta };
},
});
```
## Verificar disponibilidad
```typescript theme={null}
if (!ctx.memory.available) {
ctx.log("Memory no disponible — fuera de una conversación o sin API key de workflow");
return { error: "memory_unavailable" };
}
```
`ctx.memory.available` es `false` cuando la petición no viene de una conversación activa o la API key del workflow no está configurada. Llamar a métodos en un cliente no disponible lanza un `Error`.
## Primitivos vs JSON
Usa `set()`/`get()` para valores simples y `setJson()`/`getJson()` para objetos:
```typescript theme={null}
await ctx.memory.set("paso", "confirmacion", 3600);
const paso = await ctx.memory.get("paso", "inicio");
await ctx.memory.setJson("carrito", { items: [], total: 0 }, 86400);
const carrito = await ctx.memory.getJson("carrito", { items: [], total: 0 });
```
El tipo de retorno de `get()` coincide con el tipo del valor por defecto:
```typescript theme={null}
const nombre = await ctx.memory.get("nombre", "anónimo"); // string
const intentos = await ctx.memory.get("intentos", 0); // number
const verificado = await ctx.memory.get("verificado", false); // boolean
```
## TTL (tiempo de vida)
Hay **dos capas de expiración** en Memory:
1. **TTL por variable** (el que pasas al `set`) — controla cuándo caduca esa variable individual.
2. **hashTTL de 30 días** (gestionado por la plataforma) — controla cuándo caduca la memoria completa del usuario. Cuando expira, se borra todo su Memory.
**Renovación del hashTTL desde `ctx.memory`**: las escrituras vía `ctx.memory` solo **inicializan** el hashTTL cuando la memoria del usuario está vacía; escrituras posteriores **no lo extienden**. En cambio, las escrituras vía `$memory` en el builder sí extienden el hashTTL en cada write. Si tu flujo persiste solo desde Functions y necesitas actividad continua, combina con al menos una escritura desde el builder.
El TTL por variable se especifica en segundos:
| Método | TTL por variable | Máximo por variable |
| ----------- | ---------------- | ------------------- |
| `set()` | Opcional | 86,400 (24h) |
| `setJson()` | **Requerido** | 86,400 (24h) |
```typescript theme={null}
await ctx.memory.set("paso", "pago"); // sin TTL explícito
await ctx.memory.set("paso", "pago", 1800); // expira en 30 min
await ctx.memory.setJson("carrito", carrito, 86400); // expira en 24h (máximo)
```
## Límites
| Restricción | Valor |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Longitud máxima de `set()` | 255 caracteres |
| TTL máximo por variable | 86,400 segundos (24h) |
| Duración total de la memoria del usuario | 30 días desde la primera escritura; solo las escrituras vía `$memory` del builder renuevan la ventana (ver Warning arriba) |
| Alcance | Por usuario — misma memoria que ve `$memory` del builder |
Valores de `set()` que excedan 255 caracteres lanzan un `Error`. Para datos más grandes, usa `setJson()`.
## Patrones comunes
```typescript theme={null}
handler: async (input, ctx) => {
const paso = await ctx.memory.get("paso", "inicio");
if (paso === "inicio") {
await ctx.memory.set("paso", "datos", 3600);
return { siguiente: "datos" };
}
if (paso === "datos") {
await ctx.memory.set("nombre", input.nombre, 3600);
await ctx.memory.set("paso", "confirmar", 3600);
return { siguiente: "confirmar", nombre: input.nombre };
}
const nombre = await ctx.memory.get("nombre", "");
await ctx.memory.delete("paso");
await ctx.memory.delete("nombre");
return { completado: true, nombre };
}
```
```typescript theme={null}
interface Carrito {
items: Array<{ id: string; nombre: string; precio: number; cantidad: number }>;
total: number;
}
handler: async (input, ctx) => {
const vacio: Carrito = { items: [], total: 0 };
const carrito = await ctx.memory.getJson("carrito", vacio);
carrito.items.push({
id: input.productoId,
nombre: input.nombre,
precio: input.precio,
cantidad: input.cantidad,
});
carrito.total = carrito.items.reduce(
(sum, i) => sum + i.precio * i.cantidad, 0
);
await ctx.memory.setJson("carrito", carrito, 86400);
return { carrito };
}
```
```typescript theme={null}
handler: async (input, ctx) => {
const intentos = await ctx.memory.get("intentos_pin", 0);
if (intentos >= 3) {
return { bloqueado: true, mensaje: "Demasiados intentos" };
}
const valido = input.pin === "1234";
if (!valido) {
await ctx.memory.set("intentos_pin", intentos + 1, 1800);
return { bloqueado: false, error: "PIN incorrecto" };
}
await ctx.memory.delete("intentos_pin");
return { bloqueado: false, verificado: true };
}
```
## Manejo de errores
```typescript theme={null}
import { MemoryApiError } from "@jelou/functions";
try {
await ctx.memory.set("paso", "pago", 3600);
} catch (err) {
if (err instanceof MemoryApiError) {
ctx.log("Memory API falló", { status: err.status, code: err.code });
if (err.isRateLimit()) {
return { error: "rate_limit", retryAfter: 2 };
}
}
throw err;
}
```
## ¿Cuándo usar `ctx.memory` vs una base de datos?
| | `ctx.memory` | Base de datos externa |
| ----------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------- |
| **Alcance** | Por usuario | Global |
| **Persistencia** | Hasta 30 días de inactividad · TTL opcional en `set()`, requerido en `setJson()` (máx 24h) | Permanente |
| **Configuración** | Cero — viene incluido | Requiere connection string en secrets |
| **Ideal para** | Estado de conversación, preferencias del usuario | Datos históricos, catálogos, config compartida |
## Acceder a memoria de otro usuario
Desde triggers **cron** o **event**, puedes acceder a la memoria de un usuario específico con `ctx.memory.for(userId)`:
```typescript theme={null}
import { define } from "@jelou/functions";
export default define({
name: "recordatorio-usuario",
handler: async (_input, ctx) => {
if (!ctx.isCron && !ctx.isEvent) return { skipped: true };
// Acceder a la memoria del usuario 12345
const userMemory = ctx.memory.for("12345");
const paso = await userMemory.get("paso", "desconocido");
if (paso === "pendiente") {
await userMemory.set("paso", "recordado", 3600);
}
return { checked: true, paso };
},
});
```
`ctx.memory.for()` solo funciona desde triggers cron o event. En requests HTTP normales, `ctx.memory` ya está vinculado a la sesión del usuario actual — llamar `.for()` lanza un error.
# Mensajería
Source: https://docs.jelou.ai/guides/functions/mensajeria
Envía mensajes de WhatsApp desde tu función con ctx.jelou: texto, imágenes, botones, listas, carruseles, templates HSM y más.
## ¿Qué es `ctx.jelou`?
`ctx.jelou` es el cliente de mensajería integrado. Permite enviar mensajes de WhatsApp directamente desde tu función sin configurar APIs externas. Está disponible automáticamente cuando la empresa tiene credenciales de la API de Jelou configuradas.
## Verificar disponibilidad
```typescript theme={null}
handler: async (input, ctx) => {
if (!ctx.jelou.available) {
return { error: "Mensajería no configurada para esta empresa" };
}
await ctx.jelou.send({ type: "text", to: input.telefono, text: "Hola" });
return { enviado: true };
}
```
`ctx.jelou.available` es `false` cuando la empresa no tiene `clientId`/`clientSecret` configurados. Llamar a `send()` o `sendTemplate()` en un cliente no disponible lanza un `Error`.
## Enviar mensajes
### `ctx.jelou.send(options)`
Envía un mensaje individual. Retorna `{ messageId: string }`.
```typescript theme={null}
await ctx.jelou.send({
type: "text",
to: "+593987654321",
text: "Tu pedido #1234 está en camino",
});
```
```typescript theme={null}
await ctx.jelou.send({
type: "image",
to: "+593987654321",
mediaUrl: "https://cdn.example.com/producto.jpg",
caption: "Producto: Laptop Dell XPS 15",
});
```
```typescript theme={null}
await ctx.jelou.send({
type: "video",
to: "+593987654321",
mediaUrl: "https://cdn.example.com/tutorial.mp4",
caption: "Tutorial de configuración",
});
```
```typescript theme={null}
await ctx.jelou.send({
type: "file",
to: "+593987654321",
mediaUrl: "https://cdn.example.com/factura.pdf",
filename: "factura-1234.pdf",
caption: "Tu factura del mes de abril",
});
```
```typescript theme={null}
await ctx.jelou.send({
type: "audio",
to: "+593987654321",
mediaUrl: "https://cdn.example.com/mensaje.ogg",
});
```
### Mensajes interactivos
```typescript theme={null}
await ctx.jelou.send({
type: "buttons",
to: "+593987654321",
text: "¿Cómo deseas recibir tu pedido?",
buttons: [
{ id: "delivery", title: "Envío a domicilio" },
{ id: "pickup", title: "Retiro en tienda" },
{ id: "express", title: "Envío express" },
],
});
```
```typescript theme={null}
await ctx.jelou.send({
type: "list",
to: "+593987654321",
text: "Selecciona una categoría de productos:",
buttonText: "Ver categorías",
sections: [
{
title: "Electrónica",
rows: [
{ id: "laptops", title: "Laptops", description: "Portátiles y notebooks" },
{ id: "phones", title: "Teléfonos", description: "Smartphones y accesorios" },
],
},
{
title: "Hogar",
rows: [
{ id: "furniture", title: "Muebles" },
{ id: "kitchen", title: "Cocina" },
],
},
],
});
```
```typescript theme={null}
await ctx.jelou.send({
type: "quick_reply",
to: "+593987654321",
text: "¿Fue útil esta información?",
replies: [
{ title: "Sí, gracias", payload: "helpful_yes" },
{ title: "No, necesito más ayuda", payload: "helpful_no" },
],
});
```
```typescript theme={null}
await ctx.jelou.send({
type: "cta_url",
to: "+593987654321",
text: "Completa tu compra en nuestra tienda:",
displayText: "Ir a la tienda",
url: "https://tienda.example.com/checkout/1234",
});
```
### Mensajes avanzados
```typescript theme={null}
await ctx.jelou.send({
type: "location",
to: "+593987654321",
lat: -2.1894,
lng: -79.8891,
name: "Tienda Guayaquil",
address: "Av. 9 de Octubre 123, Guayaquil",
});
```
```typescript theme={null}
await ctx.jelou.send({
type: "contacts",
to: "+593987654321",
contacts: [
{
name: { formatted_name: "Soporte Técnico", first_name: "Soporte" },
phones: [{ phone: "+593912345678", type: "WORK" }],
},
],
});
```
```typescript theme={null}
await ctx.jelou.send({
type: "carousel",
to: "+593987654321",
cards: [
{
mediaUrl: "https://cdn.example.com/laptop.jpg",
body: "Laptop Dell XPS 15 — $1,299",
buttons: [
{ id: "buy_laptop", title: "Comprar" },
{ id: "info_laptop", title: "Más info" },
],
},
{
mediaUrl: "https://cdn.example.com/tablet.jpg",
body: "iPad Pro 12.9\" — $999",
buttons: [
{ id: "buy_tablet", title: "Comprar" },
{ id: "info_tablet", title: "Más info" },
],
},
],
});
```
```typescript theme={null}
await ctx.jelou.send({
type: "flow",
to: "+593987654321",
text: "Completa tu registro:",
flowId: "flow_abc123",
flowCta: "Iniciar registro",
flowAction: {
screen: "REGISTER",
data: { userId: "user-42" },
},
});
```
```typescript theme={null}
await ctx.jelou.send({
type: "sticker",
to: "+593987654321",
mediaUrl: "https://cdn.example.com/sticker.webp",
});
```
## Enviar templates HSM
### `ctx.jelou.sendTemplate(options)`
Envía un template HSM aprobado por WhatsApp. Retorna `Array<{ id, destination }>`.
```typescript theme={null}
// Template básico
await ctx.jelou.sendTemplate({
template: "confirmacion_pedido",
to: "+593987654321",
language: "es",
params: ["María", "PED-1234", "$59.99"],
});
```
### Múltiples destinatarios
```typescript theme={null}
await ctx.jelou.sendTemplate({
template: "promocion_mensual",
to: ["+593987654321", "+593912345678", "+593998765432"],
language: "es",
params: ["20%", "30 de abril"],
});
```
### Template con media
```typescript theme={null}
await ctx.jelou.sendTemplate({
template: "recibo_pago",
to: "+593987654321",
language: "es",
params: ["$150.00"],
mediaUrl: "https://cdn.example.com/recibo-1234.pdf",
filename: "recibo.pdf",
});
```
## Validar plantillas antes de enviar
Un `sendTemplate` falla en silencio por tres motivos: el nombre está mal escrito, el idioma no está aprobado, o mandas más o menos parámetros de los que espera la plantilla. `ctx.templateRegistry` te da acceso al catálogo de plantillas aprobadas del canal para detectarlo antes de llamar a Meta.
```typescript theme={null}
handler: async (input, ctx) => {
if (!ctx.templateRegistry.available) {
return await ctx.jelou.send({ type: "text", to: input.telefono, text: "Tu pedido está listo" });
}
if (await ctx.templateRegistry.has("confirmacion_pedido", "es")) {
await ctx.templateRegistry.validate({
name: "confirmacion_pedido",
language: "es",
params: [input.nombre, input.pedidoId],
});
await ctx.jelou.sendTemplate({
template: "confirmacion_pedido",
to: input.telefono,
language: "es",
params: [input.nombre, input.pedidoId],
});
}
return { enviado: true };
}
```
### Métodos
| Método | Descripción |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `templateRegistry.available` | `false` si la empresa no tiene credenciales configuradas |
| `templateRegistry.has(nombre, idioma?)` | `true` si el canal actual tiene la plantilla. Nunca lanza error |
| `templateRegistry.hasAnywhere(nombre)` | `true` si cualquier canal de la empresa la tiene. Para auditorías, no para decidir un envío |
| `templateRegistry.get(nombre, idioma)` | Devuelve la plantilla o `null`. Lanza error si no hay canal asociado |
| `templateRegistry.list()` | Todas las plantillas aprobadas del canal actual |
| `templateRegistry.validate({ name, language, params })` | Lanza error si el nombre, el idioma, el estado o la cantidad de parámetros no cuadran |
| `templateRegistry.for(botId)` | Devuelve un catálogo enlazado a otro canal |
`has()` y `hasAnywhere()` son verificaciones booleanas: si no hay canal asociado o el catálogo no responde, registran una advertencia y devuelven `false` en lugar de lanzar error. Cuando necesitas una respuesta definitiva usa `get()`, `list()` o `validate()`, que lanzan `TemplateRegistryError` con un `code`: `unknown_template`, `language_not_approved`, `param_count_mismatch`, `template_paused`, `registry_unavailable` o `no_bot_context`.
`sendTemplate` **no** valida por su cuenta — el catálogo es de solo lectura. Llama a `validate()` explícitamente cuando quieras la verificación.
### En cron y ejecuciones diferidas
En un disparo de cron o de una [ejecución diferida](/guides/functions/diferidas) no hay canal resuelto, así que `has()` siempre devolvería `false`. Lleva el `botId` en tu propio payload y enlaza el catálogo explícitamente:
```typescript theme={null}
handler: async (input, ctx) => {
// El botId viajó en el payload que agendaste antes
const registro = ctx.templateRegistry.for(input.botId);
if (await registro.has("recordatorio_cita", "es")) {
await ctx.jelou.sendTemplate({
template: "recordatorio_cita",
to: input.telefono,
language: "es",
params: [input.fecha],
botId: input.botId,
});
}
}
```
`for(botId)` es seguro en cualquier ruta: en una petición normal simplemente reenlaza al canal indicado.
Los resultados se guardan en caché **90 segundos**. Una plantilla aprobada hace un momento puede no aparecer hasta que la caché expire.
## Override de canal
Todos los mensajes usan `ctx.bot.id` por defecto. Para enviar desde otro canal:
```typescript theme={null}
await ctx.jelou.send({
type: "text",
to: "+593987654321",
text: "Mensaje desde otro canal",
botId: "bot-notificaciones-456",
});
```
## Manejo de errores
```typescript theme={null}
import { JelouApiError } from "@jelou/functions";
try {
await ctx.jelou.send({ type: "text", to: "+593987654321", text: "Hola" });
} catch (err) {
if (err instanceof JelouApiError) {
if (err.isRateLimit()) {
ctx.log("Rate limit", { retryAfter: 2 });
return { error: "rate_limit" };
}
if (err.isAuth()) {
ctx.log("Credenciales inválidas", { status: err.status });
return { error: "auth_error" };
}
if (err.isValidation()) {
ctx.log("Datos inválidos", { status: err.status, code: err.code });
return { error: "validation_error" };
}
}
throw err;
}
```
## Testing
Usa `createMockJelouClient()` para testing sin enviar mensajes reales:
```typescript theme={null}
import { createMockContext, createMockJelouClient } from "@jelou/functions/testing";
const mockJelou = createMockJelouClient();
const ctx = createMockContext({ jelou: mockJelou });
await ctx.jelou.send({ type: "text", to: "+593987654321", text: "Test" });
mockJelou.calls.length; // 1
mockJelou.calls[0].method; // "send"
mockJelou.calls[0].args.text; // "Test"
mockJelou.reset(); // limpia las llamadas
```
Referencia de createMockJelouClient, createMockMemoryClient y más.
# Funciones multi-tool
Source: https://docs.jelou.ai/guides/functions/multi-tool
Agrupa múltiples herramientas en un solo despliegue con app(): rutas auto-generadas, MCP unificado, cron independiente y config compartida.
**Preview** — Jelou Functions está en fase de vista previa. La API y el comportamiento pueden cambiar sin previo aviso.
## ¿Cuándo usar `app()` vs `define()`?
| Patrón | Cuándo usarlo |
| ---------- | ------------------------------------------------------------- |
| `define()` | Una sola operación con un endpoint HTTP |
| `app()` | Varias herramientas relacionadas que quieres desplegar juntas |
Si tu proyecto tiene un solo handler, usa `define()`. Si necesitas agrupar múltiples operaciones — por ejemplo, enviar y leer emails desde el mismo servicio — usa `app()`.
## Ejemplo básico
```typescript index.ts theme={null}
import { app, define, z } from "@jelou/functions";
export default app({
config: { cors: { origin: "*" }, timeout: 30_000 },
tools: {
enviarEmail: define({
description: "Envía un correo electrónico",
input: z.object({ to: z.string(), subject: z.string(), body: z.string() }),
config: { timeout: 5_000 },
handler: async (input) => ({ sent: true }),
}),
leerBandeja: define({
description: "Lee los mensajes de la bandeja de entrada",
input: z.object({ limit: z.coerce.number().default(10) }),
handler: async (input) => ({ messages: [] }),
}),
},
});
```
## Prueba local
Inicia el servidor con `jelou functions dev` y prueba cada tool en su ruta:
```bash curl /enviar-email theme={null}
curl -X POST http://localhost:3000/enviar-email \
-H "Content-Type: application/json" \
-d '{"to": "maria@example.com", "subject": "Hola", "body": "Bienvenida"}'
```
```json Respuesta 200 theme={null}
{
"sent": true
}
```
```bash curl /leer-bandeja theme={null}
curl http://localhost:3000/leer-bandeja?limit=5
```
```json Respuesta 200 theme={null}
{
"messages": []
}
```
Si envías un campo inválido, recibes un `400` con detalles:
```bash curl con input inválido theme={null}
curl -X POST http://localhost:3000/enviar-email \
-H "Content-Type: application/json" \
-d '{"to": ""}'
```
```json Respuesta 400 theme={null}
{
"error": "Validation failed",
"details": [
{ "path": ["subject"], "message": "Required", "code": "invalid_type" },
{ "path": ["body"], "message": "Required", "code": "invalid_type" }
]
}
```
## API
```typescript theme={null}
import { app } from "@jelou/functions";
const edgeApp = app({
config: { cors: { origin: "*" }, timeout: 30_000 },
tools: {
/* ... tus define() aquí ... */
},
});
export default edgeApp;
```
Firma de tipo:
```typescript theme={null}
function app(options: {
config?: AppConfig;
tools: Record;
}): EdgeApp;
```
### `AppConfig`
```typescript theme={null}
interface AppConfig {
cors?: {
origin?: string | string[];
methods?: string[];
headers?: string[];
credentials?: boolean;
maxAge?: number;
};
timeout?: number;
methods?: string[];
mcp?: boolean;
public?: boolean;
}
```
| Campo | Tipo | Predeterminado | Descripción |
| --------- | ---------- | --------------------------------------- | ----------------------------------------------------------- |
| `cors` | `object` | `{ origin: "*" }` | Configuración CORS global |
| `timeout` | `number` | `30000` | Timeout en ms para todos los tools |
| `methods` | `string[]` | `["GET","POST","PUT","PATCH","DELETE"]` | Métodos HTTP permitidos |
| `mcp` | `boolean` | `true` | Registrar tools en el servidor MCP |
| `public` | `boolean` | `false` | Desactivar autenticación de plataforma para todos los tools |
## Rutas auto-generadas
Tus keys del objeto `tools` se convierten automáticamente a kebab-case para generar las rutas HTTP:
| Key | Ruta |
| ------------- | --------------- |
| `enviarEmail` | `/enviar-email` |
| `leerBandeja` | `/leer-bandeja` |
| `MiTool` | `/mi-tool` |
Para personalizar una ruta, usa `config.path` en el `define()` individual:
```typescript theme={null}
tools: {
enviarEmail: define({
config: { path: "/emails/enviar" },
handler: async (input) => ({ sent: true }),
}),
}
```
## Combinación de config
La configuración global de `app()` se aplica a todos los tools. Cada `define()` puede sobreescribir valores específicos:
| Campo | Comportamiento |
| --------- | ------------------------------ |
| `timeout` | El tool sobreescribe el global |
| `methods` | El tool sobreescribe el global |
| `cors` | El tool sobreescribe el global |
| `mcp` | El tool sobreescribe el global |
| `public` | El tool sobreescribe el global |
| `path` | Siempre por tool |
| `cron` | Siempre por tool |
```typescript theme={null}
export default app({
config: { timeout: 30_000, cors: { origin: "*" } },
tools: {
rapido: define({
config: { timeout: 5_000 },
handler: async () => ({ ok: true }),
}),
lento: define({
handler: async () => ({ ok: true }),
}),
},
});
```
En este ejemplo, `rapido` tiene un timeout de 5 segundos y `lento` hereda los 30 segundos globales. Ambos comparten la configuración CORS.
## Endpoint de salud
En modo app, `/__health` retorna información de todos los tools:
```json theme={null}
{
"mode": "app",
"tools": [
{
"key": "enviarEmail",
"name": "Enviar Email",
"description": "Envía un correo electrónico",
"path": "/enviar-email",
"cron": []
},
{
"key": "leerBandeja",
"name": "Leer Bandeja",
"description": "Lee los mensajes de la bandeja de entrada",
"path": "/leer-bandeja",
"cron": []
}
]
}
```
## Guardas de tipo
Usa `isEdgeApp()` e `isEdgeFunction()` para identificar el tipo de export en runtime:
```typescript theme={null}
import { isEdgeApp, isEdgeFunction } from "@jelou/functions";
isEdgeApp(module); // true si fue creado con app()
isEdgeFunction(module); // true si fue creado con define()
```
## Multi-tool cron
Cada tool dentro de `app()` puede tener sus propios schedules cron independientes. Las peticiones cron se envían a la ruta específica de cada tool.
```typescript theme={null}
export default app({
tools: {
limpiezaDiaria: define({
description: "Limpia registros obsoletos",
input: z.object({}),
config: { cron: [{ expression: "0 3 * * *", timezone: "UTC" }] },
handler: async (_input, ctx) => {
if (!ctx.isCron) return { skipped: true };
return { cleaned: true };
},
}),
sincronizacionHoraria: define({
description: "Sincroniza datos externos",
input: z.object({}),
config: { cron: [{ expression: "0 * * * *" }] },
handler: async (_input, ctx) => {
if (!ctx.isCron) return { skipped: true };
return { synced: true };
},
}),
},
});
```
El límite de **10** schedules cron es **agregado** entre todos los tools de un `app()`. Si `limpiezaDiaria` tiene 3 y `sincronizacionHoraria` tiene 4, quedan 3 disponibles.
## Multi-tool MCP
Un solo servidor MCP en `/mcp` registra automáticamente todos los tools del `app()`. Para excluir un tool del registro MCP, usa `mcp: false` en su config:
```typescript theme={null}
export default app({
tools: {
toolPublico: define({
description: "Visible para MCP",
input: z.object({}),
handler: async () => ({}),
}),
toolInterno: define({
description: "Solo HTTP",
input: z.object({}),
config: { mcp: false },
handler: async () => ({}),
}),
},
});
```
En este ejemplo, los agentes IA solo descubren `toolPublico` vía MCP. `toolInterno` solo es accesible por HTTP directo en su ruta `/tool-interno`.
## Rutas expuestas (modo app)
| Ruta | Descripción |
| ------------- | --------------------------------------------------------------- |
| `/__health` | Health check con lista de tools |
| `/mcp` | Servidor MCP unificado (a menos que `config.mcp: false` global) |
| `/` | Ruta de cada tool (kebab-case o `config.path` personalizado) |
## Problemas comunes
Las keys de `tools` se convierten a **kebab-case**. Si tu key es `enviarEmail`, la ruta es `/enviar-email`, no `/enviarEmail`.
Verifica las rutas reales con el health check:
```bash curl theme={null}
curl http://localhost:3000/__health
```
```json Respuesta theme={null}
{
"mode": "app",
"tools": [
{ "key": "enviarEmail", "path": "/enviar-email" },
{ "key": "leerBandeja", "path": "/leer-bandeja" }
]
}
```
La validación del schema se ejecuta antes que el handler. Si recibes un `400`, revisa el array `details`:
```bash curl theme={null}
curl -X POST http://localhost:3000/enviar-email \
-H "Content-Type: application/json" \
-d '{"to": 12345}'
```
```json Respuesta 400 theme={null}
{
"error": "Validation failed",
"details": [
{ "path": ["to"], "message": "Expected string, received number", "code": "invalid_type" },
{ "path": ["subject"], "message": "Required", "code": "invalid_type" },
{ "path": ["body"], "message": "Required", "code": "invalid_type" }
]
}
```
Cada entrada en `details` indica el campo (`path`), qué esperaba (`message`) y el código Zod (`code`).
Si un tool tiene `config: { mcp: false }`, no se registra en el servidor MCP. Verifica qué tools están expuestos:
```bash curl theme={null}
curl http://localhost:3000/mcp
```
```json Respuesta theme={null}
{
"tools": [
{ "name": "enviar-email", "description": "Envía un correo electrónico", "inputSchema": { "..." } }
]
}
```
Si falta un tool, revisa que no tenga `mcp: false` en su `config`.
## Public per-tool
Puedes mezclar tools públicos y protegidos en el mismo `app()`:
```typescript theme={null}
export default app({
tools: {
webhookPagos: define({
description: "Recibe callbacks de Stripe",
input: z.object({ event: z.string() }),
config: { public: true, mcp: false },
handler: async (input, ctx) => {
// Cualquier cliente puede llamar — valida la firma
return { acknowledged: true };
},
}),
consultarSaldo: define({
description: "Consulta saldo de un cliente",
input: z.object({ telefono: z.string() }),
handler: async (input) => {
// Requiere X-Jelou-Token
return { saldo: 150.00 };
},
}),
},
});
```
En `/__health`, cada tool reporta su estado `public`:
```json theme={null}
{
"tools": [
{ "key": "webhookPagos", "path": "/webhook-pagos", "public": true },
{ "key": "consultarSaldo", "path": "/consultar-saldo", "public": false }
]
}
```
Consulta la [guía de funciones públicas](/guides/functions/public) para más detalles sobre `public` y cómo validar webhooks.
## Siguientes pasos
Aprende a probar funciones multi-tool con `createMockApp()`.
Configura el servidor MCP unificado para tus agentes IA.
Schedules independientes por tool dentro de `app()`.
# OpenAPI
Source: https://docs.jelou.ai/guides/functions/openapi
Cada función genera automáticamente una especificación OpenAPI 3.1 accesible en /openapi.json.
## Endpoint automático
Toda función desplegada expone su especificación OpenAPI en:
```
https://.fn.jelou.ai/openapi.json
```
Este endpoint es **siempre público** — no requiere `X-Jelou-Token`, sin importar la configuración de autenticación.
## Ejemplo
Para una función con esta definición:
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "consultar-saldo",
description: "Consulta el saldo de un cliente por teléfono",
input: z.object({
telefono: z.string().min(10).describe("Número con código de país"),
}),
output: z.object({
nombre: z.string(),
saldo: z.number(),
}),
handler: async (input) => ({
nombre: "María García",
saldo: 150.00,
}),
});
```
La spec generada en `/openapi.json`:
```json theme={null}
{
"openapi": "3.1.0",
"info": {
"title": "consultar-saldo",
"version": "1.0.0"
},
"paths": {
"/": {
"post": {
"operationId": "consultar-saldo",
"summary": "Consulta el saldo de un cliente por teléfono",
"security": [{ "jelouToken": [] }],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"telefono": {
"type": "string",
"minLength": 10,
"description": "Número con código de país"
}
},
"required": ["telefono"]
}
}
}
},
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"nombre": { "type": "string" },
"saldo": { "type": "number" }
}
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"jelouToken": {
"type": "apiKey",
"in": "header",
"name": "X-Jelou-Token"
}
}
}
}
```
## Funciones públicas vs protegidas
| Config | `security` en la spec |
| -------------------------- | ------------------------ |
| Sin `public` (default) | `[{ "jelouToken": [] }]` |
| `config: { public: true }` | `[]` (sin seguridad) |
En modo `app()`, cada tool puede tener su propia configuración de `public`, y la spec refleja esto por ruta.
## Multi-tool
En modo `app()`, la spec incluye una ruta por cada tool:
```json theme={null}
{
"paths": {
"/crear-contacto": { "post": { "operationId": "crearContacto", ... } },
"/buscar-contactos": { "get": { "operationId": "buscarContactos", ... } },
"/eliminar-contacto": { "post": { "operationId": "eliminarContacto", ... } }
}
}
```
## Probar en local
```bash theme={null}
jelou functions dev
curl http://localhost:3000/openapi.json | jq .
```
## Uso con Swagger UI
Puedes visualizar la spec con cualquier herramienta compatible con OpenAPI:
```bash theme={null}
# Descargar la spec
curl https://mi-funcion.fn.jelou.ai/openapi.json -o openapi.json
# Abrirla en Swagger Editor online
# Pega el contenido en https://editor.swagger.io
```
Las anotaciones `.describe()` de Zod se convierten en el campo `description` de cada propiedad en la spec. Esto mejora la documentación automática de tu API.
# Funciones públicas
Source: https://docs.jelou.ai/guides/functions/public
Expón funciones sin autenticación con config: { public: true } para webhooks, callbacks y APIs públicas.
## ¿Cuándo usar funciones públicas?
Usa `config: { public: true }` cuando el llamador no puede enviar un `X-Jelou-Token`:
* **Webhooks** de servicios externos (Stripe, GitHub, Twilio)
* **Callbacks** de pasarelas de pago
* **APIs públicas** accesibles desde el navegador
## En modo `define()`
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
description: "Webhook de Stripe — recibe eventos de pago",
input: z.object({
type: z.string(),
data: z.object({ id: z.string(), amount: z.number() }),
}),
config: {
public: true,
methods: ["POST"],
mcp: false,
},
handler: async (input, ctx) => {
ctx.log("Stripe webhook", { type: input.type, id: input.data.id });
return { received: true };
},
});
```
Con `public: true` en `define()`, la autenticación de plataforma se omite **completamente** — ninguna ruta requiere token.
## En modo `app()` — global
```typescript theme={null}
export default app({
config: { public: true },
tools: {
webhookPagos: define({
description: "Recibe pagos",
input: z.object({ event: z.string() }),
config: { mcp: false },
handler: async (input) => ({ ok: true }),
}),
webhookEnvios: define({
description: "Recibe actualizaciones de envío",
input: z.object({ trackingId: z.string() }),
config: { mcp: false },
handler: async (input) => ({ ok: true }),
}),
},
});
```
Todos los tools son públicos.
## En modo `app()` — per-tool
```typescript theme={null}
export default app({
tools: {
webhookPagos: define({
description: "Webhook público de pagos",
input: z.object({ event: z.string() }),
config: { public: true, mcp: false },
handler: async (input) => ({ acknowledged: true }),
}),
consultarSaldo: define({
description: "Consulta saldo (requiere token)",
input: z.object({ telefono: z.string() }),
handler: async (input) => ({ saldo: 150.00 }),
}),
},
});
```
Solo `/webhook-pagos` es público. `/consultar-saldo` requiere `X-Jelou-Token`.
## Override: app público, tool protegido
```typescript theme={null}
export default app({
config: { public: true },
tools: {
apiPublica: define({
description: "Endpoint público",
input: z.object({}),
handler: async () => ({ status: "ok" }),
}),
adminProtegido: define({
description: "Panel de admin (requiere token)",
input: z.object({ action: z.string() }),
config: { public: false },
handler: async (input) => ({ done: true }),
}),
},
});
```
`public: false` en el tool **sobrescribe** el `public: true` global.
## Health check
`/__health` reporta el estado `public` de cada tool:
```json theme={null}
{
"mode": "app",
"tools": [
{ "key": "webhookPagos", "path": "/webhook-pagos", "public": true },
{ "key": "consultarSaldo", "path": "/consultar-saldo", "public": false }
]
}
```
## OpenAPI
La spec generada en `/openapi.json` refleja la configuración de seguridad:
* Tools protegidos: `"security": [{ "jelouToken": [] }]`
* Tools públicos: `"security": []`
## Seguridad: validar webhooks
Con funciones públicas, la plataforma no valida nada. **Tu código es responsable** de verificar la autenticidad:
```typescript theme={null}
handler: async (input, ctx, request) => {
const signature = request.headers.get("x-webhook-signature");
const secret = ctx.env.get("WEBHOOK_SECRET");
if (!signature || !secret) {
return { error: "missing_signature" };
}
// Verificar HMAC-SHA256
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"],
);
const body = await request.clone().text();
const sigBytes = Uint8Array.from(atob(signature), (c) => c.charCodeAt(0));
const valid = await crypto.subtle.verify("HMAC", key, sigBytes, encoder.encode(body));
if (!valid) {
ctx.log("Firma inválida", { signature });
return { error: "invalid_signature" };
}
// Firma válida — procesar el evento
ctx.log("Webhook verificado", { event: input.type });
return { acknowledged: true };
}
```
```bash theme={null}
jelou functions secrets set mi-webhook WEBHOOK_SECRET=whsec_...
```
Nunca confíes en una función pública sin validar la firma. Cualquier persona puede enviar peticiones a la URL.
## Rutas siempre públicas
Estas rutas nunca requieren token, independientemente de la configuración `public`:
| Ruta | Descripción |
| --------------- | ----------------------- |
| `/__health` | Health check y metadata |
| `/openapi.json` | Especificación OpenAPI |
# Inicio rápido
Source: https://docs.jelou.ai/guides/functions/quickstart
Crea, prueba y despliega tu primera Jelou Function en menos de 5 minutos.
```bash theme={null}
npm install -g @jelou/cli
```
Verifica la instalación:
```bash theme={null}
jelou --version
```
Necesitas un token de acceso personal (créalo en el dashboard de Jelou).
```bash theme={null}
jelou login
# Paste your access token: ****
# ✓ Logged in
```
En CI puedes pasar el token directamente: `jelou login --token jfn_pat_tu_token`
Crea un directorio y ejecuta `jelou functions init`:
```bash theme={null}
mkdir consultar-cliente && cd consultar-cliente
jelou functions init
# ? Function slug: consultar-cliente
# ? Description: Busca información de clientes para el canal de WhatsApp
# ? Create new or link existing? Create new
# ✓ Created consultar-cliente
```
Esto te genera los archivos base:
| Archivo | Propósito |
| ------------ | ---------------------------- |
| `index.ts` | Tu función principal |
| `jelou.json` | Configuración del proyecto |
| `deno.json` | Import map del SDK |
| `.env` | Variables de entorno locales |
Reemplaza el contenido de `index.ts` con una función que busca clientes por teléfono:
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "consultar-cliente",
description: "Busca información de un cliente por número de teléfono para el canal de soporte",
input: z.object({
telefono: z.string().min(10).describe("Número de teléfono del cliente"),
}),
output: z.object({
nombre: z.string(),
email: z.string(),
plan: z.string(),
saldo: z.number(),
}),
handler: async (input, ctx) => {
ctx.log("Buscando cliente", {
telefono: input.telefono,
company: ctx.company.id,
});
const apiKey = ctx.env.get("CRM_API_KEY");
const res = await fetch(
`https://crm.example.com/api/clientes?tel=${input.telefono}`,
{ headers: { Authorization: `Bearer ${apiKey}` } },
);
const cliente = await res.json();
return {
nombre: cliente.nombre,
email: cliente.email,
plan: cliente.plan,
saldo: cliente.saldo,
};
},
});
```
Inicia el servidor de desarrollo:
```bash theme={null}
jelou functions dev
# ▸ Listening on http://localhost:3000
# ▸ Watching for changes...
```
Tu servidor se recarga automáticamente cuando editas archivos.
En otra terminal, envía una petición:
```bash theme={null}
curl -X POST http://localhost:3000 \
-H "Content-Type: application/json" \
-d '{"telefono": "593987654321"}'
```
Respuesta:
```json theme={null}
{
"nombre": "María García",
"email": "maria@example.com",
"plan": "Premium",
"saldo": 150.00
}
```
También puedes probar el endpoint MCP en `http://localhost:3000/mcp` y el health check en `http://localhost:3000/__health`.
Antes de desplegar, agrega las variables de entorno que necesita tu función:
```bash theme={null}
jelou functions secrets set consultar-cliente CRM_API_KEY=YOUR_CRM_API_KEY
# ✓ Set 1 secret
```
```bash theme={null}
jelou functions deploy
# ▸ Files: index.ts (1.2 KB), jelou.json (98 B), deno.json (65 B)
# ? Deploy consultar-cliente? (Y/n) y
# ✓ Deployed to https://consultar-cliente.fn.jelou.ai
```
Tu función queda protegida por defecto. Para llamarla necesitas una API key de plataforma — créala en [apps.jelou.ai](https://apps.jelou.ai), en la sección de configuración de la app, y envíala como `Authorization: Bearer sk_...`. Consulta la [guía de autenticación](/guides/functions/autenticacion) para más detalles.
Tu función ya está disponible en producción. Los agentes IA de Jelou pueden invocarla como herramienta MCP automáticamente.
```bash theme={null}
curl -X POST https://consultar-cliente.fn.jelou.ai \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_..." \
-d '{"telefono": "593987654321"}'
```
Monitorea los logs en tiempo real:
```bash theme={null}
jelou functions logs consultar-cliente
```
## Siguientes pasos
* [Validación de entrada/salida](/guides/functions/validacion) — esquemas Zod, coerción de tipos y formato de errores
* [Funciones multi-tool](/guides/functions/multi-tool) — agrupa varias herramientas en un solo despliegue con `app()`
# Respuestas HTTP
Source: https://docs.jelou.ai/guides/functions/respuestas
Controla el status code y headers de tus respuestas con el response builder: status personalizados, headers custom y comportamiento en MCP.
## Comportamiento por defecto
Cuando tu handler retorna un objeto plano, la plataforma responde con `200 OK` y `Content-Type: application/json`:
```typescript theme={null}
handler: async (input) => {
return { nombre: "María", saldo: 150.00 };
}
// → 200 OK
// → { "nombre": "María", "saldo": 150.00 }
```
Esto es suficiente para la mayoría de los casos. Pero cuando necesitas un status code diferente o headers personalizados, usa el **response builder**.
## Response builder
Importa `response` desde `@jelou/functions`:
```typescript theme={null}
import { define, response, z } from "@jelou/functions";
```
### Status code personalizado
```typescript theme={null}
export default define({
description: "Crea un usuario",
input: z.object({ nombre: z.string(), email: z.string().email() }),
output: z.object({ id: z.string(), nombre: z.string() }),
handler: async (input) => {
const usuario = await crearUsuario(input);
return response
.status(201)
.json({
id: usuario.id,
nombre: usuario.nombre,
});
},
});
```
### Headers personalizados
```typescript theme={null}
handler: async (input) => {
return response
.header("X-Request-Id", crypto.randomUUID())
.header("Cache-Control", "max-age=300")
.json({ datos: [] });
}
```
### Múltiples headers de una vez
```typescript theme={null}
handler: async (input) => {
return response
.headers({
"X-Request-Id": crypto.randomUUID(),
"X-Powered-By": "Jelou Functions",
"Cache-Control": "no-store",
})
.json({ ok: true });
}
```
### Encadenamiento completo
El builder es **inmutable** — cada método retorna una nueva instancia sin modificar la anterior:
```typescript theme={null}
handler: async (input) => {
return response
.status(201)
.header("Location", `/usuarios/${input.id}`)
.header("X-Created-By", "api")
.json({
id: input.id,
nombre: input.nombre,
});
}
// → 201 Created
// → Location: /usuarios/abc123
// → X-Created-By: api
// → { "id": "abc123", "nombre": "María" }
```
## API
```typescript theme={null}
interface JsonResponseBuilder {
status(code: number): JsonResponseBuilder;
header(name: string, value: string): JsonResponseBuilder;
headers(init: HeadersInit | Record): JsonResponseBuilder;
json>(body: TBody): JsonResponse;
}
```
| Método | Descripción |
| ------------------------------ | --------------------------------------- |
| `response.status(code)` | Establece el status HTTP (default: 200) |
| `response.header(name, value)` | Agrega un header |
| `response.headers(init)` | Agrega múltiples headers |
| `response.json(body)` | Finaliza con un body JSON |
| `response.noContent()` | Finaliza con `204 No Content` sin body |
`.json()` y `.noContent()` son los métodos terminales — después de llamarlos obtienes un response final, no un builder. Los métodos `.status()`, `.header()` y `.headers()` retornan un nuevo builder encadenable.
## Ejemplos prácticos
```typescript theme={null}
handler: async (input) => {
const ticket = await crearTicket(input);
return response
.status(201)
.header("Location", `/tickets/${ticket.id}`)
.json({ ticketId: ticket.id, estado: "abierto" });
}
```
```typescript theme={null}
handler: async (input) => {
const cliente = await buscarCliente(input.telefono);
if (!cliente) {
return response
.status(404)
.json({ error: "not_found", message: "Cliente no encontrado" });
}
return { nombre: cliente.nombre, saldo: cliente.saldo };
}
```
```typescript theme={null}
handler: async (input) => {
const productos = await listarProductos(input.categoria);
return response
.header("Cache-Control", "public, max-age=300")
.header("ETag", `"${hashProductos(productos)}"`)
.json({ productos, total: productos.length });
}
```
```typescript theme={null}
handler: async (input, ctx) => {
ctx.log("Webhook recibido", { event: input.event });
procesarEvento(input).catch((err) =>
ctx.log("Error procesando evento", { error: err.message })
);
return response
.status(202)
.json({ acknowledged: true });
}
```
```typescript theme={null}
handler: async (input, ctx) => {
await eliminarRecurso(input.id);
return response.noContent();
}
// → 204 No Content (sin body)
```
## Validación de output
Cuando usas `response.json(body)` con un schema `output` definido, la validación se aplica al **body** del response — exactamente igual que con objetos planos:
```typescript theme={null}
export default define({
description: "Crear usuario",
input: z.object({ nombre: z.string() }),
output: z.object({ id: z.string(), nombre: z.string() }),
handler: async (input) => {
return response
.status(201)
.json({
id: "usr_123",
nombre: input.nombre,
// extra: "este campo no está en output pero no causa error"
});
},
});
```
La validación de output nunca bloquea la respuesta. Si el body no coincide con el schema, se registra un warning en los logs pero el cliente recibe la respuesta con el status que configuraste.
## Funciona en `app()` también
```typescript theme={null}
import { app, define, response, z } from "@jelou/functions";
export default app({
tools: {
crearContacto: define({
description: "Crea un contacto en el CRM",
input: z.object({ nombre: z.string(), email: z.string().email() }),
handler: async (input) => {
return response
.status(201)
.header("X-Contact-Created", "true")
.json({ id: "ct_abc", nombre: input.nombre });
},
}),
buscarContacto: define({
description: "Busca un contacto por email",
input: z.object({ email: z.string() }),
handler: async (input) => {
const contacto = await buscar(input.email);
if (!contacto) {
return response.status(404).json({ error: "not_found" });
}
return contacto; // 200 por defecto
},
}),
},
});
```
## Comportamiento en MCP
Cuando tu función es invocada vía MCP (por un agente IA en Brain Studio), el response builder funciona diferente:
* El **body** se emite como `structuredContent` del tool result
* El **status code** y los **headers** se **ignoran** — MCP no tiene concepto de HTTP status
* También se envía el body como texto JSON para compatibilidad con clientes MCP que esperan `content[].text`
```typescript theme={null}
// HTTP: → 201 Created + X-User-Created: true + { id: "usr_123", nombre: "María" }
// MCP: → structuredContent: { id: "usr_123", nombre: "María" } (status y headers ignorados)
handler: async (input) => {
return response
.status(201)
.header("X-User-Created", "true")
.json({ id: "usr_123", nombre: input.nombre });
}
```
No necesitas condicionar tu código para HTTP vs MCP — usa el response builder normalmente. La plataforma extrae el body y descarta los metadatos HTTP cuando la invocación es vía MCP.
## Content-Type
El response builder **siempre** retorna `application/json`. Si intentas sobreescribir `Content-Type`, la plataforma lo fuerza de vuelta a `application/json`:
```typescript theme={null}
// El Content-Type siempre será application/json
response.header("Content-Type", "text/plain").json({ ok: true });
// → Content-Type: application/json
```
Para respuestas no-JSON (binarios, HTML, etc.), usa **raw mode** en lugar del response builder.
## Limitaciones
| Limitación | Detalle |
| ----------------------- | ---------------------------------------------------------------------------------- |
| JSON o vacío | Solo `.json()` y `.noContent()` — no hay `.text()`, `.html()`, `.blob()` |
| Sin Web Response nativo | Retornar un `new Response()` desde `define()` / `app()` no funciona (produce `{}`) |
| Content-Type fijo | Siempre `application/json` en `.json()`, no se puede sobreescribir |
| OpenAPI | La spec en `/openapi.json` solo documenta respuesta `200` por ahora |
Para necesidades más avanzadas (streaming, binarios, status dinámico basado en content-type), usa raw mode.
## Mezclar con objetos planos
Puedes retornar objetos planos o `response.json()` desde el mismo handler — la plataforma detecta automáticamente cuál usas:
```typescript theme={null}
handler: async (input) => {
if (input.action === "crear") {
return response.status(201).json({ id: "new-123", creado: true });
}
// Objeto plano → 200 por defecto
return { id: "existing-456", creado: false };
}
```
# Secretos
Source: https://docs.jelou.ai/guides/functions/secrets
Gestiona variables de entorno cifradas: tres modos para configurarlas, acceso con ctx.env, restricciones de prefijo y uso en CI.
## ¿Qué son los secrets?
Los secrets son variables de entorno cifradas que accedes en runtime a través de `ctx.env`. Úsalos para API keys, URLs de base de datos, tokens de servicios externos y cualquier valor sensible.
## Configurar secrets
Tienes tres formas de configurar secrets con el CLI:
Pasa pares `KEY=VALUE` directamente:
```bash theme={null}
jelou functions secrets set consultar-cliente CRM_API_KEY=sk_test_EXAMPLE JELOU_API_KEY=jfn_pat_EXAMPLE
# ✓ Set 2 secrets
```
Importa desde un archivo `.env`:
```bash theme={null}
jelou functions secrets set consultar-cliente --from-env .env.production
```
El archivo sigue el formato estándar:
```bash .env.production theme={null}
CRM_API_KEY=sk_test_EXAMPLE
JELOU_API_KEY=jfn_pat_EXAMPLE
DATABASE_URL=postgres://user:pass@host:5432/db
```
Se ignoran líneas vacías y comentarios (`#`).
Sin argumentos, el CLI te guía paso a paso:
```bash theme={null}
jelou functions secrets set consultar-cliente
# ? Secret key: CRM_API_KEY
# ? Secret value: ****
# ? Add another secret? (y/N) y
# ? Secret key: JELOU_API_KEY
# ? Secret value: ****
# ? Add another secret? (y/N) n
# ✓ Set 2 secrets
```
Los valores se enmascaran durante la entrada.
Las claves deben ser `UPPER_SNAKE_CASE` (patrón: `^[A-Z][A-Z0-9_]*$`).
## Acceder a secrets en tu función
Usa `ctx.env` dentro del handler:
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "enviar-whatsapp",
description: "Envía un mensaje de WhatsApp usando la API de Jelou",
input: z.object({
telefono: z.string().min(10),
mensaje: z.string().min(1),
}),
handler: async (input, ctx) => {
const apiKey = ctx.env.get("JELOU_API_KEY");
const botId = ctx.env.get("BOT_ID");
if (!apiKey) {
ctx.log("Error: JELOU_API_KEY no configurada");
return { enviado: false, error: "API key faltante" };
}
const res = await fetch("https://api.jelou.ai/v1/messages/send", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
botId,
phone: input.telefono,
message: input.mensaje,
}),
});
return { enviado: res.ok, status: res.status };
},
});
```
### Métodos de `ctx.env`
| Método | Retorna | Descripción |
| -------------------- | ------------------------ | ------------------------------------- |
| `ctx.env.get("KEY")` | `string \| undefined` | Obtiene el valor de un secret |
| `ctx.env.has("KEY")` | `boolean` | Verifica si un secret existe |
| `ctx.env.toObject()` | `Record` | Obtiene todos los secrets como objeto |
## Variables bloqueadas
Las variables internas de la plataforma con prefijo `__FN_` están bloqueadas. Llamar a `ctx.env.get("__FN_COMPANY_ID")` retorna `undefined`.
## Desarrollo local
En desarrollo local, el servidor de `jelou functions dev` carga secrets desde tu archivo `.env`:
```bash .env theme={null}
CRM_API_KEY=sk-test-local123
JELOU_API_KEY=jfn_pat_test_local
DATABASE_URL=postgres://localhost:5432/mydb
```
Puedes especificar un archivo diferente:
```bash theme={null}
jelou functions dev --env .env.local
```
Nunca subas tu archivo `.env` al repositorio. `jelou functions init` lo agrega automáticamente al `.gitignore`.
## Listar y eliminar secrets
```bash theme={null}
jelou functions secrets list consultar-cliente
# ▸ Key Updated
# ▸ CRM_API_KEY 2 hours ago
# ▸ JELOU_API_KEY 3 days ago
jelou functions secrets delete consultar-cliente CRM_API_KEY
# ✓ Deleted CRM_API_KEY
```
## Secrets en CI/CD
En pipelines de CI, usa variables de entorno del sistema para inyectar secrets:
```yaml deploy.yml theme={null}
- name: Configure secrets and deploy
env:
JELOU_TOKEN: ${{ secrets.JELOU_TOKEN }}
run: |
jelou functions secrets set consultar-cliente \
CRM_API_KEY=${{ secrets.CRM_API_KEY }} \
JELOU_API_KEY=${{ secrets.JELOU_API_KEY }}
jelou functions deploy --no-confirm
```
# Codear con IA
Source: https://docs.jelou.ai/guides/functions/skill-ia
Instala el flujo de Jelou Functions en tu herramienta de IA para que entienda el API define(), los comandos del CLI y las convenciones de la plataforma.
Con `jelou agent install` instalas un archivo `SKILL.md` en tu herramienta de codificación con IA. Este archivo le enseña todo sobre Jelou Functions: el API `define()`, el objeto `ctx`, validación con Zod, cron, testing y los comandos del CLI. Con esto, tu herramienta puede escribir código correcto para la plataforma desde el primer prompt.
## Instalación
Desde la raíz de tu proyecto de Jelou Functions:
```bash theme={null}
jelou agent install
```
El CLI detecta automáticamente qué herramientas tienes instaladas:
```bash theme={null}
✓ Installed jelou-functions skill for 4 agents
▸ Claude Code
▸ Cursor
▸ Windsurf
▸ Cline
Installed locally — commit .agents/ to share with your team.
```
El flujo se activa automáticamente cuando la herramienta detecta código relacionado con Jelou Functions (`define()`, `@jelou/functions`, `ctx.env`, etc.).
## Herramientas soportadas
El instalador detecta 12 herramientas de codificación con IA. Cada una usa su propio directorio de workflows, pero todas reciben el mismo contenido.
**Directorio:** `.claude/skills/jelou-functions/SKILL.md`
Se detecta automáticamente. El flujo aparece como contexto disponible en tus conversaciones.
**Directorio:** `.cursor/skills/jelou-functions/SKILL.md`
Se detecta automáticamente. Cursor carga el flujo cuando trabajas en archivos de Jelou Functions.
**Directorio:** `.windsurf/skills/jelou-functions/SKILL.md`
Se detecta automáticamente al abrir el proyecto.
**Directorio compartido:** `.agents/skills/jelou-functions/SKILL.md`
Las siguientes herramientas leen del directorio universal `.agents/skills/`:
| Herramienta | Detección |
| -------------- | ---------------------------------------- |
| Amp | `~/.config/amp` |
| Codex | `~/.codex` |
| Gemini CLI | `~/.gemini` |
| GitHub Copilot | `.github/` en el proyecto o `~/.copilot` |
| OpenCode | `~/.config/opencode` |
| Kimi Code CLI | `~/.kimi` |
Las siguientes herramientas tienen su propio directorio de workflows:
| Herramienta | Directorio |
| ----------- | ------------------- |
| Cline | `.cline/skills/` |
| Continue | `.continue/skills/` |
| Roo Code | `.roo/skills/` |
El instalador crea un symlink desde cada directorio hacia la copia canónica en `.agents/skills/`.
## Cómo usarlo
Una vez instalado, tu herramienta de IA entiende la plataforma completa. Prueba con prompts como:
```
Crea una función que consulte el saldo de un cliente por teléfono
```
```
Agrega validación Zod al input con .describe() para que el MCP tenga buenas descripciones
```
```
Configura un cron que se ejecute cada hora para limpiar registros viejos
```
```
Escribe tests con createMockContext para la función de consulta de clientes
```
El flujo se activa automáticamente cuando la herramienta detecta patrones como `define()`, `@jelou/functions`, `ctx.env`, `ctx.isCron`, `createMockContext`, o comandos `jelou`.
## Local vs global
| Modo | Comando | Ubicación | Uso |
| ------ | ------------------------------ | ------------------------------ | -------------------------------------------- |
| Local | `jelou agent install` | `.agents/skills/` del proyecto | Por proyecto, comparte con el equipo via git |
| Global | `jelou agent install --global` | Directorio home (`~`) | Activo en todos los proyectos |
**Local** es ideal para equipos: al hacer commit de `.agents/skills/`, cualquier miembro que clone el repo tendrá el flujo disponible sin ejecutar nada.
**Global** es útil si trabajas en múltiples proyectos de Jelou Functions y quieres el flujo siempre activo.
Haz commit de `.agents/skills/` en tu repositorio para que todo tu equipo tenga el flujo automáticamente al clonar el proyecto.
# Pruebas
Source: https://docs.jelou.ai/guides/functions/testing
Utilidades de pruebas para Jelou Functions: mock de context, cron, eventos, requests y clientes de mensajería/memoria.
## Configuración
Importa las utilidades de testing desde `@jelou/functions/testing`:
```typescript theme={null}
import {
createMockContext,
createMockCronContext,
createMockEventContext,
createMockRequest,
createMockPlatformRequest,
createMockJelouClient,
createMockMemoryClient,
} from "@jelou/functions/testing";
```
## `createMockContext(overrides?)`
Crea un context con valores por defecto sensatos para testing.
```typescript theme={null}
const ctx = createMockContext();
const ctx2 = createMockContext({
company: { id: 42, name: "Tienda ABC" },
user: { id: 99, names: "María García" },
bot: { id: "bot-123", name: "Canal de Soporte", channel: "whatsapp" },
env: {
get: (key) => key === "CRM_API_KEY" ? "sk-test-123" : undefined,
has: (key) => key === "CRM_API_KEY",
toObject: () => ({ CRM_API_KEY: "sk-test-123" }),
},
});
```
## `createMockCronContext(expression, overrides?, cronName?)`
Crea un context con `isCron: true` y `trigger.type: "cron"`.
```typescript theme={null}
const ctx = createMockCronContext("0 9 * * *");
// ctx.isCron === true
// ctx.trigger === { type: "cron", cron: "0 9 * * *" }
// ctx.isHttp === false
const ctx2 = createMockCronContext("0 9 * * *", {}, "recordatorio-diario");
// ctx2.trigger === { type: "cron", cron: "0 9 * * *", cronName: "recordatorio-diario" }
```
## `createMockEventContext(eventName, overrides?)`
Crea un context con `isEvent: true` y `trigger.type: "event"`.
```typescript theme={null}
const ctx = createMockEventContext("pago.completado");
// ctx.isEvent === true
// ctx.trigger === { type: "event", event: "pago.completado" }
```
## `createMockRequest(body?, options?)`
Crea un objeto Web `Request` estándar.
```typescript theme={null}
const req = createMockRequest();
// GET http://localhost:8000/
const req2 = createMockRequest({ telefono: "593987654321" });
// POST con body JSON, Content-Type: application/json
const req3 = createMockRequest(null, {
method: "PUT",
url: "https://consultar-cliente.fn.jelou.ai/clientes/42",
headers: { "x-api-key": "sk-test-123" },
});
```
## `createMockPlatformRequest(token, body?, options?)`
Crea un `Request` con header `X-Jelou-Token`. Útil para testing de funciones que reciben peticiones autenticadas con runtime tokens.
```typescript theme={null}
const req = createMockPlatformRequest("jfn_rt_test123");
// GET con X-Jelou-Token: jfn_rt_test123
const req2 = createMockPlatformRequest("jfn_rt_test123", { query: "test" });
// POST con body JSON + X-Jelou-Token: jfn_rt_test123
const req3 = createMockPlatformRequest("jfn_rt_test123", null, {
method: "PUT",
url: "https://mi-funcion.fn.jelou.ai/admin",
headers: { "x-custom": "value" },
});
// PUT con X-Jelou-Token + headers adicionales
```
`createMockPlatformRequest` es un wrapper sobre `createMockRequest` que agrega el header `x-jelou-token` automáticamente. Es equivalente a:
```typescript theme={null}
createMockRequest(body, { ...options, headers: { ...options?.headers, "x-jelou-token": token } })
```
## `createMockJelouClient(options?)`
Crea un mock del cliente de mensajería con grabación de llamadas.
```typescript theme={null}
const mockJelou = createMockJelouClient();
const ctx = createMockContext({ jelou: mockJelou });
await ctx.jelou.send({ type: "text", to: "+593987654321", text: "Hola" });
// Inspeccionar llamadas
mockJelou.calls.length; // 1
mockJelou.calls[0].method; // "send"
mockJelou.calls[0].args.type; // "text"
mockJelou.calls[0].timestamp; // Date.now()
```
### Resultados personalizados
```typescript theme={null}
const mockJelou = createMockJelouClient({
sendResult: { messageId: "msg-custom-123" },
templateResult: [{ id: "tmpl-1", destination: "+593987654321" }],
});
```
### Reset entre tests
```typescript theme={null}
mockJelou.reset(); // limpia las llamadas grabadas
```
## `createMockMemoryClient(options?)`
Crea un mock del cliente de memoria con un store in-memory.
```typescript theme={null}
const mockMemory = createMockMemoryClient({
store: { paso: "inicio", intentos: 0 },
});
const ctx = createMockContext({ memory: mockMemory });
const paso = await ctx.memory.get("paso", "desconocido"); // "inicio"
await ctx.memory.set("paso", "confirmacion", 3600);
```
### Inspeccionar llamadas
```typescript theme={null}
mockMemory.calls.length; // 2 (get + set)
mockMemory.calls[1].method; // "set"
mockMemory.calls[1].args; // { key: "paso", value: "confirmacion", ttl: 3600 }
```
### Reset entre tests
```typescript theme={null}
mockMemory.reset(); // limpia las llamadas y restaura el store inicial
```
## Ejemplo completo
```typescript index.test.ts theme={null}
import { assertEquals } from "jsr:@std/assert";
import { define, z } from "@jelou/functions";
import {
createMockContext,
createMockCronContext,
createMockRequest,
createMockPlatformRequest,
createMockJelouClient,
createMockMemoryClient,
} from "@jelou/functions/testing";
const fn = define({
name: "consultar-cliente",
description: "Busca información de un cliente por teléfono",
input: z.object({
telefono: z.string().min(10),
}),
output: z.object({
nombre: z.string(),
plan: z.string(),
companyId: z.number(),
}),
handler: async (input, ctx) => {
return {
nombre: "María García",
plan: "Premium",
companyId: ctx.company.id,
};
},
});
Deno.test("retorna info del cliente con el company ID correcto", async () => {
const ctx = createMockContext({
company: { id: 42, name: "Tienda ABC" },
bot: { id: "bot-123", name: "Canal Soporte", channel: "whatsapp" },
});
const req = createMockRequest({ telefono: "593987654321" });
const result = await fn.handler(
{ telefono: "593987654321" },
ctx,
req,
);
assertEquals(result.nombre, "María García");
assertEquals(result.plan, "Premium");
assertEquals(result.companyId, 42);
});
Deno.test("cron skip cuando no es trigger cron", async () => {
const cronFn = define({
name: "limpieza-diaria",
input: z.object({}),
config: {
cron: [{ expression: "0 3 * * *" }],
},
handler: async (_input, ctx) => {
if (!ctx.isCron) return { skipped: true };
return { cleaned: true };
},
});
const httpCtx = createMockContext();
const req = createMockRequest();
const result = await cronFn.handler({}, httpCtx, req);
assertEquals(result, { skipped: true });
const cronCtx = createMockCronContext("0 3 * * *");
const result2 = await cronFn.handler({}, cronCtx, req);
assertEquals(result2, { cleaned: true });
});
Deno.test("mock de mensajería graba llamadas", async () => {
const mockJelou = createMockJelouClient();
const ctx = createMockContext({ jelou: mockJelou });
await ctx.jelou.send({
type: "text",
to: "+593987654321",
text: "Tu pedido está listo",
});
assertEquals(mockJelou.calls.length, 1);
assertEquals(mockJelou.calls[0].method, "send");
assertEquals(mockJelou.calls[0].args.type, "text");
});
Deno.test("mock de memoria persiste valores", async () => {
const mockMemory = createMockMemoryClient({
store: { paso: "inicio" },
});
const ctx = createMockContext({ memory: mockMemory });
const paso = await ctx.memory.get("paso", "desconocido");
assertEquals(paso, "inicio");
await ctx.memory.set("paso", "confirmacion", 3600);
const paso2 = await ctx.memory.get("paso", "desconocido");
assertEquals(paso2, "confirmacion");
});
Deno.test("platform request incluye header de token", () => {
const req = createMockPlatformRequest("jfn_rt_test123", { action: "test" });
assertEquals(req.headers.get("x-jelou-token"), "jfn_rt_test123");
assertEquals(req.method, "POST");
});
```
## `createMockApp(tools, config?)`
Crea un `EdgeApp` mock para testing de funciones multi-tool.
```typescript theme={null}
import { assertEquals } from "jsr:@std/assert";
import { createMockApp, createMockContext, createMockRequest } from "@jelou/functions/testing";
import { define, z } from "@jelou/functions";
const myApp = createMockApp({
enviarEmail: define({
description: "Envía un correo electrónico",
input: z.object({ to: z.string() }),
handler: async (input) => ({ sent: true }),
}),
leerBandeja: define({
description: "Lee los mensajes de la bandeja de entrada",
input: z.object({}),
handler: async () => ({ messages: [] }),
}),
});
Deno.test("app tiene los tools correctos", () => {
assertEquals(Object.keys(myApp.tools), ["enviarEmail", "leerBandeja"]);
assertEquals(myApp.__jelou_edge_app, true);
});
Deno.test("cada tool funciona independientemente", async () => {
const ctx = createMockContext();
const req = createMockRequest({ to: "user@example.com" });
const result = await myApp.tools.enviarEmail.handler(
{ to: "user@example.com" },
ctx,
req,
);
assertEquals(result, { sent: true });
});
```
Ejecuta los tests con Deno:
```bash theme={null}
deno test
```
# Tokens de Autenticación
Source: https://docs.jelou.ai/guides/functions/tokens
Runtime tokens legacy: uso con X-Jelou-Token, gestión con CLI, y migración a API keys de plataforma.
## ¿Qué son los runtime tokens?
Los runtime tokens (prefijo `jfn_rt_`) son credenciales **legacy** que autentican peticiones a funciones desplegadas antes de la migración a API keys de plataforma. Las funciones desplegadas hoy no reciben ningún runtime token — usan una [API key de plataforma](#alternativa-recomendada-api-keys) desde el primer deploy.
La creación de nuevos runtime tokens (`jelou functions tokens create`) está **deprecada** y responde `410 Gone`. Los deploys tampoco generan uno automáticamente. Si tu función es nueva o perdiste tu runtime token, usa una [API key de plataforma](#alternativa-recomendada-api-keys).
## Cómo usarlos
Si tu función ya tenía un runtime token activo antes de la migración, sigue funcionando: envíalo en el header `X-Jelou-Token`:
```bash theme={null}
curl -X POST https://mi-funcion.fn.jelou.ai \
-H "Content-Type: application/json" \
-H "X-Jelou-Token: jfn_rt_abc123..." \
-d '{"telefono": "593987654321"}'
```
```javascript theme={null}
const res = await fetch("https://mi-funcion.fn.jelou.ai", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Jelou-Token": process.env.JELOU_FUNCTION_TOKEN,
},
body: JSON.stringify({ telefono: "593987654321" }),
});
```
```python theme={null}
import requests, os
res = requests.post(
"https://mi-funcion.fn.jelou.ai",
headers={
"Content-Type": "application/json",
"X-Jelou-Token": os.environ["JELOU_FUNCTION_TOKEN"],
},
json={"telefono": "593987654321"},
)
```
| Regla | Detalle |
| ------------- | ------------------------------------------------------------------------------------- |
| Header | `X-Jelou-Token` (los runtime tokens legacy no se validan vía `Authorization: Bearer`) |
| Formato | No query param, no body — solo header |
| Tamaño máximo | 4 KB |
| Sin token | `401 { "error": "Unauthorized" }` |
## Gestión con CLI
### Listar tokens
```bash theme={null}
jelou functions tokens list mi-funcion
# ▸ Name Prefix Last Used Created
# ▸ default jfn_rt_abc1.. 4/7/2026, 10:30:00 AM 4/1/2026, 2:00:00 PM
# ▸ ci-deploy jfn_rt_def4.. never 4/5/2026, 9:00:00 AM
```
### Crear token adicional (deprecado)
`jelou functions tokens create` responde `410 Gone`: *"Runtime tokens are deprecated. Create an API key in the apps configuration section ([https://apps.jelou.ai](https://apps.jelou.ai)) and send it as `Authorization: Bearer `..."*. Ya no es posible crear runtime tokens nuevos, ni siquiera adicionales para un token existente.
```bash theme={null}
jelou functions tokens create mi-funcion --name ci-deploy
# ✗ 410 Runtime tokens are deprecated. Create an API key in the apps
# configuration section (https://apps.jelou.ai) and send it as
# `Authorization: Bearer ` when invoking the function.
```
Para credenciales nuevas, usa una API key de plataforma — ver [alternativa recomendada](#alternativa-recomendada-api-keys) más abajo.
### Revocar token
Revocar tokens existentes (creados antes de la deprecación) sigue funcionando:
```bash theme={null}
jelou functions tokens revoke mi-funcion
# ? Revoke token jfn_rt_def4... for mi-funcion? (y/N) y
# ✓ Token revoked
```
Revocar un token también genera un **redeploy automático**. Los clientes que usen ese token empezarán a recibir `401` una vez que el redeploy termine.
## Múltiples tokens
Si tu función ya tiene varios tokens activos de antes de la deprecación (uno por entorno, servicio o equipo), puedes seguir listándolos y revocándolos individualmente. Revocar uno no afecta a los demás.
## ¿Perdiste el token?
Ya no puedes generar un reemplazo con el CLI. En su lugar:
1. Crea una API key en [apps.jelou.ai](https://apps.jelou.ai)
2. Envíala como `Authorization: Bearer ` — ver [alternativa recomendada](#alternativa-recomendada-api-keys)
3. Si el runtime token anterior fue comprometido, revócalo: `jelou functions tokens revoke mi-funcion `
## Alternativa recomendada: API keys
El mecanismo soportado para autenticar funciones nuevas (todo deploy nuevo usa esto desde el inicio) o para reemplazar un runtime token perdido es una API key de plataforma, no un runtime token:
```bash theme={null}
curl -X POST https://mi-funcion.fn.jelou.ai \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_..." \
-d '{"telefono": "593987654321"}'
```
Consulta la [guía de autenticación](/guides/functions/autenticacion#api-keys-recomendado) para el flujo completo.
## CI/CD
En pipelines, usa una API key de plataforma en vez de crear runtime tokens:
```bash theme={null}
# La API key se crea una sola vez en apps.jelou.ai y se guarda como secret de CI
curl -X POST https://mi-funcion.fn.jelou.ai \
-H "Authorization: Bearer ${{ secrets.FUNCTION_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"telefono": "593987654321"}'
```
## Brain Studio
Configura tu función como servidor MCP externo usando tu API key:
| Campo | Valor |
| ------------ | ------------------------------------ |
| URL | `https://mi-funcion.fn.jelou.ai/mcp` |
| Header name | `Authorization` |
| Header value | `Bearer sk_...` |
Si tu función es legacy y todavía usa un runtime token, usa `X-Jelou-Token` / `jfn_rt_abc123...` en su lugar. Consulta la [guía de Brain Studio](/guides/functions/brain) para el flujo completo.
# Validación
Source: https://docs.jelou.ai/guides/functions/validacion
Esquemas Zod para validar entradas y salidas de tus funciones: tipos, coerción en GET, formato de errores y comportamiento de output.
## Validación de entrada
Cuando defines un esquema `input`, cada petición se valida antes de ejecutar el handler. Si la validación falla, el handler **no se ejecuta** y se retorna un `400`:
```json theme={null}
{
"error": "Validation failed",
"details": [
{
"path": ["email"],
"message": "Invalid email",
"code": "invalid_string"
}
]
}
```
### Ejemplo con esquema completo
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "crear-ticket",
description: "Crea un ticket de soporte desde WhatsApp",
input: z.object({
nombre: z.string().min(1).describe("Nombre del cliente"),
email: z.string().email().describe("Email de contacto"),
asunto: z.string().max(200).describe("Asunto del ticket"),
prioridad: z.enum(["baja", "media", "alta"]).default("media"),
}),
output: z.object({
ticketId: z.string(),
estado: z.string(),
}),
handler: async (input, ctx) => {
ctx.log("Creando ticket", { cliente: input.nombre, prioridad: input.prioridad });
return { ticketId: "TKT-2024-0042", estado: "abierto" };
},
});
```
### Tipos soportados
Puedes usar cualquier tipo de Zod dentro de `z.object()`:
```typescript theme={null}
input: z.object({
nombre: z.string().min(1),
edad: z.number().int().positive(),
email: z.string().email(),
activo: z.boolean().default(true),
rol: z.enum(["admin", "usuario", "invitado"]),
tags: z.array(z.string()).optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
})
```
### Coerción en peticiones GET
Para peticiones GET, los query parameters son strings. Usa `z.coerce` para convertir tipos automáticamente:
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "buscar-pedidos",
description: "Busca pedidos por estado",
input: z.object({
q: z.string(),
limit: z.coerce.number().default(10),
pagina: z.coerce.number().default(1),
activo: z.coerce.boolean().default(true),
}),
handler: async (input, ctx) => {
ctx.log("Buscando", { q: input.q, limit: input.limit });
return { resultados: [], total: 0 };
},
});
```
```bash theme={null}
curl "https://buscar-pedidos.fn.jelou.ai/?q=pendiente&limit=5&pagina=2"
```
### Anotaciones `.describe()`
Usa `.describe()` en cada campo para documentar los parámetros. Estas descripciones aparecen automáticamente en el esquema MCP, lo que ayuda a los agentes IA a entender cómo usar tu función:
```typescript theme={null}
input: z.object({
telefono: z.string().min(10).describe("Número de teléfono con código de país, ej: 593987654321"),
incluirHistorial: z.boolean().default(false).describe("Si incluir el historial de conversaciones"),
})
```
## Validación de salida
Cuando defines un esquema `output`, el valor retornado por el handler se valida **después** de la ejecución. Si no coincide:
* Se registra una advertencia en los logs
* La respuesta se envía normalmente con status `200`
La validación de output nunca bloquea la respuesta. Es una herramienta de desarrollo para detectar inconsistencias.
```typescript theme={null}
output: z.object({
nombre: z.string(),
saldo: z.number(),
})
```
Si el handler retorna `{ nombre: "María", saldo: "150" }` (saldo como string), verás una advertencia en los logs pero el cliente recibe la respuesta sin cambios.
## Formato de errores de validación
Cada error en el array `details` contiene:
| Campo | Tipo | Descripción |
| --------- | ---------- | -------------------------------------------------------------------------------- |
| `path` | `string[]` | Ruta al campo con error, ej: `["email"]` o `["direccion", "ciudad"]` |
| `message` | `string` | Mensaje legible del error |
| `code` | `string` | Código de error de Zod (ej: `invalid_string`, `too_small`, `invalid_enum_value`) |
```json theme={null}
{
"error": "Validation failed",
"details": [
{
"path": ["nombre"],
"message": "String must contain at least 1 character(s)",
"code": "too_small"
},
{
"path": ["prioridad"],
"message": "Invalid enum value. Expected 'baja' | 'media' | 'alta', received 'urgente'",
"code": "invalid_enum_value"
}
]
}
```
# Webhooks
Source: https://docs.jelou.ai/guides/functions/webhooks
Verifica la firma de webhooks de Stripe, Shopify y Meta con una línea: ctx.verifyStripe, verifyShopify, verifyMeta y verifyHmac.
Cuando tu función recibe webhooks de un servicio externo, cualquiera que conozca la URL puede enviarle datos falsos. La verificación de firma confirma que el evento viene realmente del servicio.
`ctx.verify*` hace esa verificación en una línea: lee la cabecera de firma, resuelve el secret desde tus [secrets](/guides/functions/secrets) y lanza un error si no coincide.
## Stripe
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "stripe-webhook",
description: "Recibe eventos de Stripe",
input: z.object({}).passthrough(),
config: {
public: true,
path: "/webhooks/stripe",
methods: ["POST"],
mcp: false,
},
async handler(event, ctx, request) {
await ctx.verifyStripe(request);
ctx.log("Evento verificado", { tipo: event.type });
if (event.type === "payment_intent.succeeded") {
await ctx.jelou.send({
type: "text",
to: event.data.object.metadata.telefono,
text: "¡Recibimos tu pago!",
});
}
return { received: true };
},
});
```
Configura el secret una sola vez:
```bash theme={null}
jelou functions secrets set stripe-webhook STRIPE_WEBHOOK_SECRET=whsec_...
```
El `event` que recibe tu handler ya es el cuerpo validado con Zod. La plataforma consumió el cuerpo del request para validarlo, así que llamar a `request.json()` dentro del handler lanza un error — usa el primer parámetro.
## Proveedores soportados
| Verificador | Cabecera | Secret |
| ------------------------------- | ----------------------- | ----------------------------------------- |
| `ctx.verifyStripe(request)` | `stripe-signature` | `STRIPE_WEBHOOK_SECRET` |
| `ctx.verifyShopify(request)` | `x-shopify-hmac-sha256` | `SHOPIFY_WEBHOOK_SECRET` |
| `ctx.verifyMeta(request)` | `x-hub-signature-256` | `META_WEBHOOK_SECRET` o `META_APP_SECRET` |
| `ctx.verifyHmac(request, opts)` | La que indiques | `opts.secretEnv` o `opts.secret` |
Stripe además rechaza eventos con más de 5 minutos de antigüedad, lo que impide que alguien reenvíe un evento antiguo capturado.
## Otros servicios
Para Twilio, GitHub, Slack o cualquier servicio con firma HMAC-SHA256, usa `ctx.verifyHmac`. La cabecera es obligatoria porque no hay un valor por defecto:
```typescript theme={null}
await ctx.verifyHmac(request, {
secretEnv: "WEBHOOK_SECRET",
header: "x-signature",
});
```
## Rotar secrets
Durante una rotación, acepta el secret viejo y el nuevo a la vez separándolos por coma:
```bash theme={null}
jelou functions secrets set mi-webhook STRIPE_WEBHOOK_SECRET=whsec_nuevo,whsec_viejo
```
Cuando confirmes que el proveedor ya usa el nuevo, vuelve a dejar uno solo.
## Manejar el fallo
Los verificadores lanzan `WebhookVerificationError` con un código que indica qué pasó:
```typescript theme={null}
import { define, WebhookVerificationError, z } from "@jelou/functions";
async handler(event, ctx, request) {
try {
await ctx.verifyStripe(request);
} catch (err) {
if (err instanceof WebhookVerificationError) {
ctx.log("Firma rechazada", { code: err.code, provider: err.provider });
return new Response(null, { status: 401 });
}
throw err;
}
return { received: true };
}
```
| Código | Significado |
| ------------------- | ------------------------------------------ |
| `missing_signature` | El request llegó sin cabecera de firma |
| `invalid_signature` | La firma no coincide con el cuerpo |
| `expired_timestamp` | El evento es demasiado antiguo (Stripe) |
| `missing_secret` | No configuraste el secret de ese proveedor |
Si no capturas el error, la función responde con error y el proveedor reintentará el webhook. Para Stripe y Shopify eso suele ser lo correcto solo cuando el fallo es temporal; ante una firma inválida es mejor responder `401` y no reintentar.
## Funciones públicas
Los webhooks necesitan `config.public: true` para que el servicio externo pueda llamarlos sin credenciales de Jelou. Esa es justamente la razón por la que la verificación de firma es obligatoria: es el único control de acceso que queda.
```typescript theme={null}
config: {
public: true, // el proveedor no tiene API key de Jelou
methods: ["POST"], // los webhooks siempre son POST
mcp: false, // no tiene sentido como herramienta de IA
}
```
Ver [funciones públicas](/guides/functions/public).
## Testing
En los tests, `createMockContext()` deja cada verificador lanzando `missing_secret`. Para probar el resto del handler, desactiva la verificación:
```typescript theme={null}
import { createMockContext, createMockWebhookVerifiers } from "@jelou/functions/testing";
const verifiers = createMockWebhookVerifiers({ stripe: true });
const ctx = createMockContext({
verifyStripe: verifiers.stripe,
});
// Después de ejecutar el handler puedes revisar las llamadas registradas
verifiers.calls; // [{ method: "stripe", ... }]
```
Ver la [guía de testing](/guides/functions/testing).
Recibir peticiones sin credenciales de Jelou.
Guardar los secrets de cada proveedor.
Agendar un seguimiento al recibir el webhook.
Ejemplo completo listo para copiar.
# AI Routing
Source: https://docs.jelou.ai/guides/getting-started/ai-routing
Configura el enrutamiento inteligente de conversaciones para que cada mensaje llegue al workflow correcto automáticamente.
Cuando un proyecto tiene varios workflows, el **AI Routing** decide automáticamente cuál ejecutar para cada mensaje entrante. En lugar de enrutar por palabras clave exactas, el sistema compara semánticamente el mensaje con el nombre y descripción de cada workflow y elige la mejor coincidencia — similar a un coordinador que lee el contexto completo antes de asignar una tarea.
## Cómo funciona
Los mensajes iniciales se comparan automáticamente con el nombre y descripción de todos los workflows del proyecto. El sistema elige el workflow cuyo propósito más se acerca a lo que el usuario está preguntando y lo ejecuta. Si ningún workflow encaja bien, se ejecuta el **workflow predeterminado**.
El AI Routing se configura en dos niveles: a nivel de proyecto (estrategia general y habilitación) y a nivel de workflow (nombre, descripción y reglas de ejecución individuales).
**Validación por canal.** El AI Router elige entre todos los workflows del proyecto y, una vez seleccionado uno, verifica que ese workflow esté disponible en el canal por el que llegó el mensaje (WhatsApp, Facebook, etc.). Si el workflow no está disponible en ese canal, se ejecuta el **workflow predeterminado**.
## Configuración por proyecto
La página de **AI Routing** en Ajustes centraliza la configuración de enrutamiento para cada proyecto. Selecciona el proyecto en el selector del lado izquierdo para ver todas las tarjetas de configuración.
### Estrategia de despliegue
Esta sección solo aparece si el proyecto todavía tiene intenciones legacy configuradas.
Define cómo se distribuye el tráfico hacia el AI Routing. Elige entre dos modos:
**Porcentaje activo** — usa el slider o el campo numérico para definir qué porcentaje de usuarios usará AI Routing. El resto continúa con las intenciones legacy.
* `0 %` → todo el tráfico usa intenciones legacy
* `100 %` → todo el tráfico usa AI Routing
* Útil para hacer una migración progresiva sin cortar el sistema anterior de golpe
**Número de teléfono** — ingresa una lista de números específicos que podrán probar el AI Routing. Los demás usuarios continúan con el sistema legacy. Ideal para validar el enrutamiento con un grupo controlado antes de abrirlo al 100 %.
### Toma de control
Define cuándo el AI Router toma el control de la conversación. Es útil para manejar usuarios que inician la conversación con un saludo antes de exponer su consulta real.
Elige entre dos modos:
**Ruteo inmediato** — el AI Router intenta derivar al usuario desde su primer mensaje. Ideal cuando tus usuarios suelen escribir directamente su consulta sin preámbulos.
**Filtrar saludos** — el sistema ignora mensajes cortos de cortesía como "Hola" o "Buenos días" y espera a detectar una intención real antes de enrutar al usuario. Con este modo activo, puedes configurar dos valores adicionales:
| Campo | Descripción |
| ----------------------- | ------------------------------------------------------------------------------------------------------- |
| **Tiempo de espera** | Segundos que el sistema aguarda sin detectar intención antes de enviar un mensaje al usuario. |
| **Mensaje por defecto** | Texto que se envía al usuario cuando el tiempo de espera vence sin que se haya detectado una intención. |
### Workflow predeterminado
Selecciona en el desplegable el workflow que se ejecutará cuando el AI Routing no pueda determinar una intención clara, o cuando el AI Routing esté deshabilitado. Haz clic en **Guardar** para aplicar el cambio.
### Exportar descripciones
Descarga un archivo con el listado de todos los workflows del proyecto y sus descripciones actuales. Elige entre formato **Markdown** o **Excel** según tu flujo de trabajo. Útil para revisar o auditar la calidad de las descripciones que el AI Routing usa para enrutar.
### Workflows con AI Router
La tarjeta inferior muestra todos los workflows del proyecto separados en dos grupos — los que ya tienen AI Routing configurado y los que aún no — con un indicador de estado:
* **Punto verde** — el workflow tiene descripción y el AI Routing puede usarlo para enrutar.
* **Punto naranja** — el workflow no tiene descripción; el AI Routing no podrá seleccionarlo correctamente.
Haz clic en cualquier workflow para abrirlo directamente en el canvas y configurarlo.
Los workflows sin descripción no se enrutan correctamente. Si ves puntos naranjas, agrega una descripción clara a esos workflows desde el nodo Start.
## Configuración por workflow
Accede al panel de configuración desde el nodo **Start** de cualquier workflow. El panel tiene tres pestañas.
### Pestaña AI Router
Configura el nombre y la descripción que el sistema usa para enrutar mensajes hacia este workflow.
| Campo | Descripción | Límite |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
| **Nombre** | Identificador corto del workflow. Debe describir de forma concisa qué hace. | 50 caracteres |
| **Descripción** | Explica en detalle cuándo debe ejecutarse este workflow y qué tipo de mensajes atiende. Cuanto más específica, más preciso el enrutamiento. | 5 000 caracteres |
| **Workflow predeterminado** | Marca este workflow como el destino cuando ningún otro coincide con el mensaje. Solo puede haber un workflow predeterminado por proyecto. | — |
Escribe descripciones claras y específicas: qué tipo de solicitudes atiende el workflow, qué problemas resuelve y en qué contextos debe ejecutarse. Cuanto más detallada sea la descripción, más preciso será el enrutamiento.
#### Probar enrutamiento
Antes de publicar, verifica que los mensajes se enruten al workflow correcto desde esta misma pestaña.
Haz clic en **Probar enrutamiento**. Se abre el modal de evaluación.
Escribe una o varias frases en el campo de texto, o descarga la plantilla, complétala con los mensajes que quieres evaluar y súbela para probar en volumen.
Para cada mensaje evaluado verás:
* **Workflow seleccionado**: el workflow al que se enrutaría el mensaje.
* **Razonamiento**: explicación breve de por qué se eligió ese workflow.
* **Indicador de coincidencia**: verde si el mensaje coincide con el workflow que se está probando, gris si coincide con otro workflow del proyecto, ámbar si no hubo ninguna coincidencia y se ejecutará el workflow predeterminado.
### Pestaña Avanzado
Configura restricciones de ejecución que limitan cuándo y para quién se activa el workflow.
**Ocultar workflow** — excluye este workflow del AI Routing. Cuando está oculto, el sistema nunca lo seleccionará automáticamente; solo puede ejecutarse mediante un redireccionamiento forzado desde otro workflow.
No puedes ocultar el workflow predeterminado. Desactívalo primero antes de ocultarlo.
Las siguientes reglas restringen la ejecución del workflow a usuarios o contextos específicos:
Restringe el workflow a un grupo específico de números de teléfono. Solo los usuarios de la lista podrán activarlo, independientemente del contenido del mensaje.
* Formato requerido: E.164 (ej. `+593999123456`, `+5215512345678`)
* Máximo **20 entradas** por workflow
* Cada entrada incluye número de teléfono y nombre opcional para identificación
Si activas esta regla y un usuario fuera de la lista envía un mensaje, el workflow no se ejecutará aunque el contenido coincida semánticamente. El mensaje se enruta al workflow predeterminado.
Limita el workflow a usuarios que se comuniquen desde ciertos países, usando el prefijo telefónico internacional.
* Ejemplos: `+593` (Ecuador), `+57` (Colombia), `+52` (México), `+55` (Brasil)
* Puedes seleccionar múltiples prefijos simultáneamente
* Si el número del usuario no coincide con ningún prefijo, se enruta al workflow predeterminado
### Pestaña Intenciones Legacy
Esta pestaña solo aparece si la cuenta todavía tiene intenciones configuradas.
Si el 100 % del tráfico ya usa AI Routing, las intenciones legacy no tienen efecto. Puedes mantenerlas como respaldo durante la transición.
Gestiona las frases clave del sistema de enrutamiento anterior. Estas intenciones activaban el workflow antes de que el AI Routing estuviera disponible y se mantienen para proyectos que migran gradualmente usando la estrategia de despliegue por porcentaje.
## Migrar desde intenciones legacy
Los proyectos que todavía usan el sistema de intenciones legacy verán el botón **Migrar proyecto** en la página de **AI Routing** en Ajustes. Al abrirse la ventana de migración, el sistema usa las intenciones configuradas en cada workflow para inferir automáticamente una descripción inicial — la misma que el AI Routing empleará para enrutar mensajes.
En la página de **AI Routing**, haz clic en el botón **Migrar proyecto**. La ventana muestra todos los workflows del proyecto junto con la cantidad de intenciones legacy asociadas a cada uno.
El listado muestra cada workflow con su cantidad de intenciones y la descripción inferida a partir de ellas. Revisa cada descripción y ajústala si no refleja con precisión el propósito del workflow — cuanto más clara sea, más preciso será el enrutamiento.
Ajusta el slider para determinar qué porcentaje del tráfico comenzará a usar AI Routing. El tráfico restante seguirá usando las intenciones legacy. Empieza con un valor bajo —entre 10 % y 20 %— para validar el enrutamiento antes de escalar.
Haz clic en **Migrar**. El porcentaje configurado entra en efecto de inmediato. Puedes ajustarlo en cualquier momento desde la sección **Estrategia de despliegue**.
Antes de aumentar el porcentaje, usa **Probar enrutamiento** desde el nodo Start de cada workflow para verificar que los mensajes se enrutan correctamente con AI Routing.
Las intenciones legacy no se eliminan durante la migración. Permanecen activas para el porcentaje del tráfico que todavía no usa AI Routing.
## Casos de uso
Un proyecto con workflows de soporte (devoluciones, reclamos), ventas (catálogo, precios) y pagos (facturación, métodos de pago): el AI Routing analiza el mensaje del usuario y lo dirige al workflow correcto sin menús ni botones de selección. El workflow predeterminado maneja consultas ambiguas con una respuesta genérica.
Una empresa con tres líneas de producto configura un workflow por línea, cada uno con una descripción detallada de los problemas que atiende. Los mensajes que mencionan síntomas específicos llegan directamente al workflow especializado sin que el usuario deba indicar el producto.
Un equipo de atención al cliente activa la lista de usuarios permitidos en su workflow de soporte premium. Solo los números registrados como VIP acceden a ese workflow; el resto de los usuarios es atendido por el workflow estándar.
Un equipo que ya tiene intenciones configuradas usa la estrategia de despliegue por porcentaje para migrar gradualmente. Comienza con un 20 % del tráfico en AI Routing, valida los resultados con el panel de pruebas y sube el porcentaje semana a semana hasta llegar al 100 %.
Visualiza el AI Router y sus workflows en un mapa dentro de Configuración del proyecto → Workflows.
Despliega tu proyecto con AI Routing configurado a producción.
Revisa y restaura versiones anteriores de tu configuración.
Monitorea cómo se ejecutan los workflows enrutados en producción.
Configura el AI Agent dentro de cada workflow para procesar los mensajes enrutados.
# Clonar workflows existentes
Source: https://docs.jelou.ai/guides/getting-started/clonar-workflows
Copia uno o varios workflows a otro proyecto de tu compañía sin volver a construirlos.
Cuando ya tienes un workflow que funciona, no hace falta rehacerlo en otro proyecto. Puedes seleccionar los workflows que quieras y copiarlos a otro proyecto de tu compañía: Jelou lleva el canvas completo, los inputs, las preguntas de entrenamiento y la ruta de AI Routing.
Es la forma habitual de pasar un flujo de un ambiente de pruebas a producción, de replicar una implementación para otra marca o de reutilizar un flujo que ya resolviste.
Aquí copias **workflows sueltos** entre proyectos que ya existen. Si lo que quieres es crear un proyecto nuevo a partir de otro, con toda su configuración, usa **Clonar existente** al [crear un proyecto](/guides/getting-started/crear-proyecto).
## Antes de empezar
* Necesitas permiso para **crear workflows** en el proyecto. Si no lo tienes, el ícono no aparece.
* La opción no está disponible en modo de solo lectura ni mientras revisas una versión publicada.
* El proyecto de destino debe ser **otro** proyecto de tu compañía. El proyecto que tienes abierto no aparece en la lista.
## Clona los workflows
En la barra lateral, en la sección **Workflows**, haz clic en el ícono de clonar workflows que está junto al **+**.
La lista entra en modo de selección: cada workflow muestra una casilla y se ocultan los menús de acciones de cada fila.
Marca la casilla de cada workflow que quieras copiar. Puedes elegir uno o varios.
El botón del pie de la barra lateral cambia a **Clonar workflow** o **Clonar Workflows** según cuántos hayas marcado. Permanece deshabilitado hasta que selecciones al menos uno.
En **Clonar en** selecciona el proyecto al que quieres copiar los workflows. El campo tiene buscador, útil si tu compañía tiene muchos proyectos.
Al elegir el destino, Jelou revisa la selección y te dice qué va a pasar antes de tocar nada:
* Si hay algo que impide la copia, lo muestra en rojo y el botón de confirmar queda deshabilitado.
* Si solo hay advertencias, las muestra en ámbar y puedes continuar.
* Si no hay nada que reportar, no aparece ningún mensaje.
Cuando nada bloquea la copia aparece la casilla **Ir al proyecto de destino cuando la clonación termine**, marcada por defecto. Desmárcala si prefieres quedarte donde estás.
Haz clic en **Clonar** con el número de workflows seleccionados. Mientras se ejecuta, el botón muestra **Clonando...** y el proyecto de destino y la casilla quedan fijos para que la operación no cambie a mitad de camino.
Al terminar verás una notificación de confirmación. Si dejaste la casilla marcada, Jelou te lleva a la lista de workflows del proyecto de destino.
## Cómo se decide el destino de cada workflow
Jelou empareja cada workflow por su **nombre**, tal como se llama hoy en cada proyecto. De ahí sale lo que va a hacer con él:
| Situación en el proyecto de destino | Qué hace Jelou |
| ------------------------------------- | --------------------------------------------------------------------------- |
| No hay ningún workflow con ese nombre | Lo **crea**, conservando el nombre del original |
| Hay exactamente uno con ese nombre | Lo **reemplaza**: el contenido del destino se sobrescribe con el del origen |
| Hay más de uno con ese nombre | **Bloquea la copia**, porque no puede elegir a cuál de los dos apuntar |
Ningún workflow se renombra en el proceso. Cuando hay reemplazo, el workflow del destino conserva su propio nombre — que es justamente el que lo emparejó.
Reemplazar sobrescribe el contenido del workflow que ya estaba en el proyecto de destino. Antes de confirmar, revisa la advertencia del modal: te dice exactamente qué workflow se va a sobrescribir.
## Qué se copia
| Elemento | Cómo llega al proyecto de destino |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Canvas de cada canal | Se copian los nodos, sus conexiones y su configuración |
| Inputs | Se copian tal cual |
| Preguntas de entrenamiento | Se copian tal cual |
| Ruta de AI Routing | Se crea o se actualiza apuntando al workflow copiado |
| Canales | Se emparejan por tipo. Si el workflow de origen tiene un canal de un tipo que no existe en el destino, Jelou lo crea |
| Derivaciones a agente humano | Conservan su configuración de asignación |
| Nodos de base de datos | Siguen apuntando a las mismas bases de datos de tu compañía |
El proyecto de origen no se modifica en ningún momento: la clonación solo lo lee.
## Qué debes volver a configurar
Algunos nodos viajan con su estructura y sus conexiones intactas, pero pierden la referencia que estaba atada al proyecto de origen. El nodo sigue en el canvas, en su sitio y conectado, pero tienes que volver a elegir el recurso:
| Nodo | Qué se conserva | Qué tienes que hacer |
| ------------------------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| Plantilla HSM | Sus salidas y sus conexiones | Volver a elegir la plantilla |
| Mensaje con WhatsApp Flow | El nodo y sus conexiones | Volver a elegir el flow, y la pantalla y los datos si los usabas |
| AI Agent | Su prompt, su modelo y sus tools | Volver a cargar el conocimiento: las fuentes llegan vacías y la lectura de archivos queda desactivada |
| Mensajes que redirigen a otro workflow al expirar | El mensaje y sus botones | Si el workflow al que redirigía no viajó en la selección ni existe en el destino, la redirección al expirar queda desactivada. Vuelve a configurarla |
Revisa estos nodos **antes de publicar** en el proyecto de destino. Un nodo de plantilla HSM o de WhatsApp Flow sin recurso seleccionado falla en ejecución, y un AI Agent sin su conocimiento responderá sin la información que esperabas.
## Variables y secretos
Las variables y los secretos **no se copian**. Los nodos que los usan siguen buscándolos por nombre en el proyecto de destino, así que tienen que existir allí antes de clonar.
Si un workflow usa una variable o un secreto que no existe en el proyecto de destino, la copia se bloquea y el modal te dice qué nombre falta. Créalo primero en el proyecto de destino y vuelve a intentarlo.
## Qué puede impedir la clonación
Estos son los casos que bloquean la copia. En todos, el mensaje del modal nombra el workflow o el recurso involucrado para que sepas qué corregir:
| Motivo | Qué significa |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Variable o secreto inexistente | El workflow usa una variable o un secreto que no está en el proyecto de destino |
| Nombre ambiguo en el destino | El proyecto de destino tiene varios workflows con el mismo nombre y no se puede elegir a cuál apuntar |
| Nombres repetidos en tu selección | Seleccionaste varios workflows que se llaman igual, así que no pueden emparejarse todos |
| Referencia a un workflow que no viaja | Un nodo llama a otro workflow que no está en tu selección ni existe en el proyecto de destino |
| Referencia ambigua | Un nodo llama a otro workflow por nombre y el destino tiene varios con ese nombre |
| Referencia a un workflow eliminado | Un nodo llama a un workflow que ya no existe |
| Canales duplicados del mismo tipo | El workflow de origen o el de destino tiene más de un canal del mismo tipo, así que el emparejamiento sería ambiguo |
| Workflow ajeno al proyecto de origen | El workflow seleccionado no pertenece al proyecto desde el que estás clonando |
Si un workflow llama a otro, inclúyelos en la misma selección. Así la referencia se resuelve entre los workflows que viajan juntos y no depende de lo que ya exista en el destino.
## Después de clonar
Abre los nodos de plantilla HSM y de WhatsApp Flow y elige el recurso que corresponda en el proyecto de destino. Si el workflow tiene un AI Agent, vuelve a cargar su conocimiento.
Si Jelou creó un canal que no existía en el destino, revisa su configuración antes de salir a producción.
Usa el [tester](/guides/getting-started/tester) en el proyecto de destino para verificar que el workflow responde como esperas.
La copia llega al canvas del proyecto de destino, pero no se publica sola. Publica una versión allí para que empiece a atender. Consulta [publicar versiones](/guides/getting-started/publicar-versiones).
## Preguntas frecuentes
No. La clonación solo lee el proyecto de origen. Sus workflows, sus canales y su configuración quedan intactos.
No. La lista de **Clonar en** solo muestra proyectos de tu propia compañía.
No. El proyecto que tienes abierto no aparece en la lista de destinos.
Jelou revierte todo lo que había escrito hasta ese momento, incluidos los workflows y canales que hubiera creado. El proyecto de destino queda como estaba y puedes volver a intentarlo.
Reemplazar sobrescribe el workflow en el canvas del proyecto de destino, así que su contenido anterior del canvas no se puede recuperar desde allí. Lo que no cambia por sí solo es producción: el reemplazo no publica nada, así que la versión ya publicada en el destino sigue atendiendo hasta que publiques el reemplazo. Eso te da margen para revisar el resultado antes — y si tienes dudas, cambia el nombre del workflow de origen para que se cree uno nuevo en lugar de sobrescribir.
Porque la plantilla y el flow están atados al proyecto de origen, así que la referencia no se traslada. El nodo conserva su sitio y sus conexiones para que solo tengas que volver a elegir el recurso en el proyecto de destino.
No. El nodo llega con su prompt, su modelo y sus tools, pero las fuentes de conocimiento llegan vacías y la lectura de archivos queda desactivada. Vuelve a cargarlas en el proyecto de destino.
No. El modo de clonación solo aparece en la sección **Workflows** de la barra lateral.
Porque no has elegido un proyecto de destino o porque la validación encontró algo que impide la copia. Revisa los mensajes en rojo del modal.
## Siguientes pasos
Crea un proyecto desde cero o clona uno completo con toda su configuración.
Publica el workflow copiado en el proyecto de destino.
Entiende cómo se decide qué workflow atiende cada mensaje.
Verifica el flujo copiado antes de publicarlo.
# Crea un proyecto
Source: https://docs.jelou.ai/guides/getting-started/crear-proyecto
Crea un proyecto desde cero o clona uno existente para reutilizar toda su configuración.
Un proyecto es el espacio donde viven tus workflows, tus canales, tus variables y tu configuración. Al crear uno nuevo puedes empezar desde cero o partir de una copia de un proyecto que ya tienes.
## Crea el proyecto
Entra a **Proyectos** y haz clic en **Crear proyecto**. También puedes crearlo desde el selector de proyectos en la parte superior.
El modal te ofrece dos opciones:
* **Desde cero** — un proyecto nuevo, sin configuración heredada.
* **Clonar existente** — copia la configuración de un proyecto que ya tienes.
El **nombre** admite entre 3 y 50 caracteres y la **descripción** hasta 120. Ambos campos muestran un contador para que sepas cuánto te queda.
Si eliges **Clonar existente**, selecciona el proyecto de origen en **Proyecto a clonar**. Si eliges **Desde cero**, puedes abrir **Configuración avanzada** para escribir un **Error predeterminado**.
Haz clic en **Crear proyecto** o en **Clonar proyecto**. Al terminar, Jelou te lleva directamente al proyecto nuevo.
## Desde cero
Es la opción para arrancar de cero. Jelou crea el proyecto y lo deja listo para trabajar:
* Un primer workflow con un único nodo **Start**, el punto de entrada desde el que empiezas a construir.
* Un canal de WhatsApp de pruebas que se llama igual que tu proyecto — si el proyecto es `Mi proyecto`, el canal también — para que puedas probar tus flujos sin conectar tu propio número.
En **Configuración avanzada** puedes definir el **Error predeterminado**: el mensaje que recibe la persona si algún nodo del flujo falla y no hay ningún otro error configurado. Es opcional y puedes cambiarlo después desde la configuración del proyecto.
Si aún no tienes claro qué construir, empieza desde cero y sigue la guía de [tu primer workflow](/guides/getting-started/tu-primer-workflow).
## Clonar existente
Clonar copia la **estructura** de un proyecto, no sus datos. Es la opción ideal para crear un ambiente de pruebas, replicar una implementación para otra marca o partir de un flujo que ya funciona sin tocar el original.
En el campo **Proyecto a clonar** solo aparecen los proyectos de tu propia compañía. El proyecto de origen no se modifica en ningún momento.
### Qué se copia
| Elemento | Cómo llega al proyecto nuevo |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Workflows y nodos | Se copian todos los canvas de cada workflow, con sus nodos, conexiones y posiciones |
| Canales | Los del proyecto original no se replican. El proyecto nuevo recibe un canal de WhatsApp de pruebas con el nombre del proyecto, igual que al empezar desde cero |
| Rutas de AI Routing | Se recrean apuntando a los workflows copiados, con el mismo nombre y estado |
| Preguntas de entrenamiento | Se copian tal cual en cada workflow |
| Configuración del proyecto | Ajustes generales y de e-commerce se heredan del original |
| Integraciones instaladas | Se mantienen disponibles en el proyecto nuevo |
| Nodos de apps del marketplace y de bases de datos | Se conservan y siguen usando los mismos recursos de tu compañía |
| Derivaciones a agente humano | Conservan su configuración de asignación |
| Variables | Se copian con su definición |
| Secretos | Se copian solo por nombre, sin su valor |
### Qué debes revisar antes de publicar
Algunas cosas quedan intencionalmente en blanco porque están atadas al proyecto original o son información sensible:
| Elemento | Qué tienes que hacer |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Valores de los secretos | Los secretos se copian solo por nombre, sin su valor. Vuelve a escribirlos. |
| Conocimiento del AI Agent | Las fuentes y archivos de conocimiento no se copian. Vuelve a cargarlos. |
| Nodos que envían un WhatsApp Flow | El nodo y sus conexiones se conservan, pero el flow queda sin seleccionar. |
| Nodos de plantilla HSM | Conservan sus salidas y conexiones, pero debes volver a elegir la plantilla. |
| Canales | Ningún canal del proyecto original se traslada, ni siquiera el número de WhatsApp. Conecta los tuyos o prueba con el canal de WhatsApp de pruebas. |
| Conversaciones e historial | Cada proyecto tiene su propio historial. El clon empieza vacío. |
Los secretos se copian solo por nombre, sin su valor. Si publicas el clon sin volver a escribirlos, los nodos que dependan de ellos fallarán en ejecución.
Al terminar la clonación, Jelou publica automáticamente una primera versión del proyecto nuevo para que puedas probarlo de inmediato. Consulta [publicar versiones](/guides/getting-started/publicar-versiones) para entender cómo funciona el historial.
## Identifica los proyectos clonados
Todo proyecto creado por clonación queda marcado con la etiqueta **Clonado**. Así distingues una copia de un proyecto creado desde cero sin abrir su configuración. Pasa el cursor sobre la etiqueta para ver el detalle: *Este proyecto se creó como una copia de otro proyecto*.
| Dónde la ves | Cómo aparece |
| -------------------------- | --------------------------------------------------------------------------- |
| Tabla de **Proyectos** | Etiqueta **Clonado** junto al nombre del proyecto |
| Selector de proyectos | Ícono junto a cada proyecto de la lista |
| Barra superior de Studio | Ícono junto al proyecto que tienes abierto y en cada opción del desplegable |
| Configuración del proyecto | Etiqueta **Clonado** en la tarjeta de información |
### Origen del clon
En la configuración del proyecto, la tarjeta de información suma dos filas cuando el proyecto es un clon:
| Fila | Qué muestra |
| -------------- | -------------------------------------------------------------------- |
| **Origen** | La etiqueta **Clonado** |
| **Clonado de** | El nombre del proyecto de origen y su ID, con un botón para copiarlo |
Si el proyecto de origen se eliminó, **Clonado de** muestra solo el ID. La trazabilidad se mantiene aunque el original ya no exista.
## Después de crear el proyecto
Ve a la configuración del proyecto y escribe el valor de cada secreto que se copió por nombre.
Abre los nodos marcados como pendientes y elige el WhatsApp Flow o la plantilla HSM que corresponda.
Usa el canal de WhatsApp de pruebas o el [tester](/guides/getting-started/tester) para verificar que todo responde como esperas.
Cuando el proyecto esté listo, [conecta tu número de WhatsApp](/guides/channels/whatsapp) para salir a producción.
## Preguntas frecuentes
No. La lista de **Proyecto a clonar** solo muestra proyectos de tu propia compañía, y la clonación se rechaza si el proyecto no te pertenece.
No. La clonación solo lee el proyecto de origen. Su nombre, sus workflows y sus canales quedan intactos.
Jelou revierte todo lo que había creado hasta ese momento. No te queda un proyecto a medias en la lista: puedes volver a intentarlo desde cero.
Sí. Desde **Proyectos**, abre el menú del proyecto y elige **Editar** para cambiar el nombre, la descripción y el error predeterminado.
No. La etiqueta refleja cómo se creó el proyecto, así que se mantiene aunque cambies su nombre o su configuración.
No. Los nodos de base de datos del clon siguen apuntando a las mismas bases de datos de tu compañía. Si quieres datos separados, crea una base nueva y cámbiala en el nodo.
## Siguientes pasos
Construye un flujo simple de pregunta y respuesta en tu proyecto nuevo.
Entiende cómo se decide qué workflow atiende cada mensaje.
Aprende a publicar cambios y a volver a una versión anterior.
Lleva tu proyecto a producción con tu propio número.
# Ejecuciones de Workflow
Source: https://docs.jelou.ai/guides/getting-started/ejecuciones-workflow
Entiende cómo funcionan las ejecuciones en Brain Studio y su impacto en la facturación
Una **ejecución de workflow** es la unidad métrica principal de Brain Studio. Representa el ciclo completo desde que se activa un workflow hasta que termina su trabajo, sin importar cuántos mensajes intercambies o cuántos pasos internos ejecute.
## Conceptos clave
### Workflows vs Tools
Diseñado para conversar con usuarios. Contiene nodos de mensajes (texto, imágenes, botones) y nodos lógicos (código, API, condicionales).
Diseñado para ejecutar operaciones técnicas. Solo contiene nodos lógicos. No conversa, solo procesa.
## ¿Qué cuenta como una ejecución?
La regla es simple: **un inicio de workflow = una ejecución**, sin importar cuántos procesos internos se ejecuten.
Inicias un Workflow llamado "Atención al Cliente". Durante la conversación:
* El Workflow intercambia 20 mensajes con el usuario
* Llama a una Tool "Consultar Saldo"
* Deriva a otro Workflow "Encuesta de Satisfacción"
**Resultado**: una ejecución
Ejecutas directamente una Tool vía API (sin pasar por un Workflow):
* La Tool hace su cálculo y termina
**Resultado**: una ejecución
Las Tools no pueden llamar a otros Workflows ni Tools. Son unidades atómicas.
## ¿Qué incluye el costo?
### ✅ Incluido en una ejecución
* El inicio del workflow (Workflow o Tool)
* Todas las derivaciones internas (Workflow → Workflow)
* El uso de herramientas internas (Workflow → Tool)
* **Mensajes ilimitados** dentro de la misma sesión
### 💰 Se cobra por separado
* **[Tokens de modelos IA](/guides/agentes-ia/tokens)**: El procesamiento de lenguaje natural se factura según el uso del modelo (GPT-4, Claude, etc.)
* **[KYC](/guides/integraciones/identidad/index-kyc)**: Las verificaciones de identidad tienen un costo independiente por cada validación realizada.
* **[Firma electrónica](/guides/integraciones/firma)**: Cada proceso de firma se factura de forma individual.
* **[Tools transaccionales](/guides/integraciones/pagos)**: Las Tools que realizan operaciones transaccionales (pagos, cobros, etc.) tienen tarifa propia por cada transacción ejecutada.
## Ejemplos prácticos
Un usuario inicia conversación → tu Workflow de "Soporte" lo atiende → llama a 3 Tools diferentes (validar usuario, consultar historial, crear ticket) → deriva a Workflow de "Escalamiento" → cierra la conversación.
**Total**: 1 ejecución
Un usuario te escribe 3 veces en el mismo día:
* 9:00 AM - Pregunta por su saldo
* 2:00 PM - Solicita un reporte
* 6:00 PM - Pide actualizar datos
**Total**: 3 ejecuciones (cada conversación inicia un nuevo workflow)
Tu sistema externo llama a la Tool "Calcular Descuento" 50 veces durante el día.
**Total**: 50 ejecuciones
## Obtener el ID de una ejecución
Cuando inicias una ejecución, recibes un identificador único (`executionId`) que Brain Studio genera automáticamente. Este ID permanece constante durante toda la ejecución, incluso si derivas el workflow entre Workflows o llamas a Tools.
Puedes acceder a él desde cualquier nodo utilizando:
```
{{$context.executionId}}
```
## Resumen
En Brain Studio, pagas por resolver problemas completos, no por cada paso del proceso. Esto te permite crear workflows complejos y conversaciones largas sin preocuparte por incrementar costos.
La arquitectura de ejecuciones fomenta la modularidad: puedes orquestar workflows complejos (Workflow → Tool → Workflow) sin inflar costos, permitiéndote enfocarte en crear la mejor experiencia posible.
## Artículos relacionados
Conceptos clave sobre la facturación por organización en Jelou.
Detalles de precios para conversaciones, campañas y asientos de Connect.
Crea tu primer Tool desde cero y publícala para reutilizarla en tus workflows.
Configura un AI Agent que recolecta datos de tus usuarios.
Accede al executionId y otras variables disponibles durante la ejecución.
# Jelou Agent 2
Source: https://docs.jelou.ai/guides/getting-started/jelou-agent
El agente unificado de Brain Studio: un solo composer, trabajo en toda tu compañía, memorias, artefactos en vivo y aprobaciones.
Jelou Agent 2 reemplaza los dos modos anteriores (Construir e Insights) por un **agente unificado** en la vista de inicio. Trabaja en toda tu compañía sin obligarte a elegir un proyecto o un workflow antes de escribir — y cuando tu petición sí necesita uno, te lo pregunta durante la conversación. Al enviar un prompt, la vista se convierte en un chat completo con el agente que puedes colapsar para ver el resultado en el canvas de Brain Studio.
## Qué puede hacer el agente
El agente cubre las principales áreas de Jelou. Puedes pedirle ayuda con:
* **Functions**: listar, ver, desplegar (con rollback), revisar logs y eliminar Jelou Functions.
* **Bases de Datos**: crear bases, gestionar colecciones y registros, exportar a CSV y configurar webhooks/triggers.
* **Canales y plantillas**: conectar canales (WhatsApp, Web, Facebook, Instagram, etc.) y listar, crear, editar o enviar plantillas de WhatsApp — individuales, por lote o campañas masivas.
* **Voz**: consultar llamadas, agentes, números, campañas y facturación de voz.
* **Proyectos y workflows**: crear y editar proyectos, construir, editar y publicar workflows, instalar plantillas de Brain y probarlos.
* **Shop**: productos, categorías, sucursales, cupones, órdenes e importaciones masivas.
* **Métricas e insights**: catálogo de métricas, dashboards, reportes e insights en HTML y gráficos.
* **Otros**: gestión de Secrets, operadores de Connect, integraciones del marketplace, logs de conversaciones y tareas en segundo plano.
## Casos de uso
Estos son algunos de los ejemplos de lo que puedes pedirle al agente:
El agente planifica cómo dividir un proyecto en workflows con responsabilidades claras, los crea y los conecta con AI Routing para que cada uno se active según la intención del usuario.
```txt wrap theme={null}
Crea un proyecto para reservar mesas en mi restaurante: uno para tomar reservas (fecha, hora, número de personas y nombre del cliente) y otro para marcar mesas como disponibles u ocupadas cuando el equipo lo indique.
```
Adjunta un CSV o Excel con los destinatarios y pídele una campaña. El agente lee el archivo, valida la plantilla contra la aprobada por Meta, arma la campaña y te muestra un resumen antes de enviarla.
```txt wrap theme={null}
Envía una campaña de WhatsApp usando mi plantilla Recordatorio de pagos a los números adjuntos en el archivo. Usa el nombre del contacto como variable de saludo.
```
Describe los campos que necesitas guardar; el agente crea la base, define la colección y te confirma la creación con un enlace para abrir la base.
```txt wrap theme={null}
Crea una base de datos para registrar mis reservas con los campos: fecha, hora, número de personas, nombre del cliente, teléfono y estado (confirmada, cancelada, completada).
```
Sube un archivo con tu catálogo — puedes partir del archivo de ejemplo disponible en Jelou Shop para la importación de productos — y pídele al agente que lo importe. Reconoce columnas comunes (nombre, precio, SKU, stock, categoría, imagen) y las mapea a tu tienda; te avisa cuáles filas necesitan atención antes de publicarlas.
```txt wrap theme={null}
Agrega los productos adjuntos en este archivo a mi tienda de Jelou Shop. Créalos como borrador y agrúpalos por la columna de categoría.
```
Pídele un reporte con el rango que necesites; el agente consulta los logs, agrega las métricas y te devuelve un HTML interactivo con tablas y gráficos que puedes descargar o compartir.
```txt wrap theme={null}
Genera un reporte HTML de todas mis conversaciones durante el último trimestre. Incluye volumen por canal, motivos más frecuentes y tasa de escalamiento a humano.
```
## Componentes del composer
Escribe lo que necesitas construir o consultar. Sé específico con el objetivo, los datos y la lógica de negocio. No hace falta seleccionar proyecto ni workflow antes de enviar: si tu petición requiere trabajar sobre un workflow, el propio agente te preguntará en cuál — o en qué proyecto — quieres hacerlo.
```txt wrap theme={null}
Crea un flujo de atención al cliente que salude al usuario, le pregunte su número de orden y consulte el estado del pedido vía API en https://tu-url.com con este header: x-api-key: abc-123.
```
Usa el ícono de clip para adjuntar archivos al mensaje. Los adjuntos quedan en cola sobre el composer y se transfieren a la conversación cuando envías el prompt. Útil para pasarle al agente PDFs, capturas, CSVs o cualquier referencia.
Abre el listado de tus chats anteriores desde el ícono de reloj. Cada conversación conserva su contexto y el trabajo del panel lateral. También tienes acceso a la vista completa del historial desde la barra de navegación.
El ícono de ajustes abre el diálogo de **Memorias**. Ahí ves y editas las cosas que el agente recuerda de tu compañía entre conversaciones.
Puedes agregar una memoria sin abrir el diálogo: escríbela como una nota corta (por ejemplo `Recuerda que nuestro horario de atención es 9–18 hrs`) y el agente la guardará automáticamente en lugar de enviarla como un mensaje.
El botón de envío se convierte en **Detener** mientras el agente está respondiendo, para que puedas cancelar una ejecución en curso.
## La conversación con el agente
Cuando envías un prompt, la vista se convierte en un **chat completo** con el agente. Ahí responde, te hace las preguntas que necesita para confirmar detalles y ejecuta lo que le pediste.
Cuando la ocasión lo amerita — por ejemplo, al **crear o editar un workflow** — puedes colapsar el chat para ver el workflow directamente en el canvas de Brain Studio. Desde ahí revisas el resultado, ajustas nodos y sigues iterando; el chat queda a un lado, listo para retomar la conversación cuando lo vuelvas a abrir.
## Aprobaciones
Antes de ejecutar una operación destructiva o que cambia estado (borrar recursos, sobrescribir configuraciones, aplicar cambios en producción), el agente te pide una **aprobación**. Puedes:
* Aprobar solo para esta conversación.
* Aprobar para todo el proyecto.
* Aprobar para toda la compañía.
* Marcar **Permitir siempre** para no ver la pregunta de nuevo en ese alcance.
Así mantienes el control sobre lo que el agente ejecuta sin bloquear su trabajo cotidiano.
Si Jelou tuvo que cambiar temporalmente al modelo secundario (por saturación del principal), verás un banner de **modelo alternativo** al inicio de la respuesta. Es informativo — no requiere acción de tu parte.
## Plantillas
En la pestaña **Templates** del inicio de Jelou encuentras las plantillas curadas de Brain Studio. Al abrir una, el agente arranca una conversación para instalarla en tu cuenta y te guía por los recursos que necesita conectar (Secrets, integraciones, bases de datos, herramientas, conocimiento).
### Crear una plantilla desde tu workflow
Con el workflow abierto, escribe en el chat:
```txt wrap theme={null}
Crea una plantilla de este workflow
```
El agente analiza el flujo, detecta los recursos que requiere y prepara el paquete.
Se abre un modal para ajustar el **nombre**, la **descripción** y una **imagen de portada** opcional (JPEG, PNG, WebP o GIF, hasta 5 MB). Esto es lo que verá quien reciba tu enlace.
Al guardar, el agente devuelve una tarjeta con la plantilla y un botón para **copiar el enlace**. El enlace tiene la forma `https://apps.jelou.ai/studio/template/tpl_xxxxx` y funciona para cualquier persona con acceso.
Solo el creador de una plantilla puede editar su nombre, descripción o imagen de portada. Para hacerlo, pídele al agente ver tus plantillas de workflows y usa el botón **Editar** en la que quieras actualizar.
Antes de compartir la plantilla, prueba el workflow con el [link de pruebas](/guides/getting-started/compartir-link-pruebas) para confirmar que se comporta como esperas.
## Siguientes pasos
Construye un flujo básico de pregunta y respuesta paso a paso.
Usa un nodo AI Agent para recolectar datos estructurados del usuario.
Lleva tus cambios a producción y consulta el historial de versiones.
Conecta tu workflow a WhatsApp, Facebook, Instagram o Web Widget.
# Usa Jelou Apps
Source: https://docs.jelou.ai/guides/getting-started/jelou-apps
Comparte tus workflows conversacionales con cualquier persona usando el número de WhatsApp proporcionado por Jelou.
**Jelou Apps** es un número de WhatsApp proporcionado por Jelou que te permite compartir y probar tus workflows conversacionales y agentes sin necesidad de conectar tu propio número de WhatsApp.
En la esquina superior derecha del canvas, haz clic en **Compartir** para abrir el panel y compartir la última versión. Lo que verás depende de si tu proyecto tiene la [auto-publicación](/guides/getting-started/publicar-versiones#auto-publicación) activada.
## Con auto-publicación activada
Cuando la auto-publicación está activada, el panel muestra directamente el enlace y el código QR de tu proyecto, listos para compartir.
* **Enlace** (`jlu.ai`): haz clic sobre la URL para copiarla al portapapeles. También puedes presionar `Shift` o `Ctrl` al hacer clic para abrirla directamente en otra pestaña.
* **Código de proyecto** (`XXX-XXX`): identificador único por proyecto que el usuario puede escribir al número de **Jelou Apps** para iniciar un flujo.
* **Código QR**: al escanearlo, abre WhatsApp con el código del proyecto ya preescrito, listo para enviar.
## Con auto-publicación desactivada
Cuando la auto-publicación está desactivada, el panel muestra dos pestañas: **Público** y **Privado**.
### Enlace público
La pestaña **Público** genera un enlace y un código de proyecto que cualquier persona puede usar para probar tu workflow directamente en **Jelou Apps**.
Si es la primera vez que compartes, haz clic en **Publicar y compartir**. Si ya tienes versiones publicadas, el botón dirá **Publicar enlace**.
Una vez publicado verás:
* **Enlace** (`jlu.ai`): haz clic sobre la URL para copiarla al portapapeles. También puedes presionar `Shift` o `Ctrl` al hacer clic para abrirla directamente en otra pestaña.
* **Código de proyecto** (`XXX-XXX`): identificador único por proyecto que el usuario puede escribir al número de **Jelou Apps** para iniciar un flujo.
* **Código QR**: al escanearlo, abre WhatsApp con el código del proyecto ya preescrito, listo para enviar.
Envía el enlace o el QR a stakeholders, QA o cualquier persona que necesite probar el flujo. No requieren cuenta en Jelou.
```txt theme={null}
https://jlu.ai/?text=123-456
```
| Parámetro | Descripción |
| ---------------- | ------------------------------------------------------------------------------------ |
| `` | Número de WhatsApp de **Jelou Apps**. |
| `123-456` | Código de 6 dígitos (tres y tres separados por guion) que identifica tu publicación. |
#### Revocar el enlace público
Haz clic en **Despublicar** dentro de la pestaña Público. El enlace dejará de funcionar inmediatamente.
Revocar el enlace es irreversible. Si necesitas compartir nuevamente, deberás generar uno nuevo.
### Modo privado
La pestaña **Privado** te permite agregar números de WhatsApp específicos para pruebas controladas directamente en el canal real.
Escribe el número en formato internacional (ej. `+593991234567`) y haz clic en **Agregar**. El sistema validará que sea un número válido.
El sistema publicará tu proyecto si hay cambios pendientes e iniciará una sesión en WhatsApp. El usuario recibirá un mensaje para comenzar la interacción.
Las pruebas privadas requieren que tu proyecto tenga un canal de WhatsApp configurado. Si no tienes uno, consulta la guía de [activación de WhatsApp](/guides/channels/whatsapp).
## Conecta tu propio WhatsApp
**Jelou Apps** es ideal para probar y compartir tus proyectos de forma rápida. Cuando estés listo para operar en producción, puedes [conectar tu propio número de WhatsApp](/guides/channels/whatsapp).
Al hacerlo, cualquier persona que escriba a tu número interactuará directamente con tu proyecto — ya no necesitas **Jelou Apps** ni el código de acceso.
# Mapa del proyecto
Source: https://docs.jelou.ai/guides/getting-started/mapa-del-proyecto
Visualiza en un solo canvas el AI Router y los workflows del proyecto desde Configuración del proyecto → Workflows.
El **Mapa del proyecto** es la vista visual dentro de **Configuración del proyecto → Workflows** que muestra el **AI Router** conectado a todos los workflows del proyecto. El nodo del AI Router se ubica a la izquierda, cada nodo de workflow representa un workflow del proyecto, y las líneas representan las derivaciones que el router puede tomar. Desde ahí ves de un vistazo cuántos workflows tienes, cuáles están activos, cuáles están ocultos del router y cómo se distribuyen por canal, sin salir de la sección.
El mapa está disponible únicamente en proyectos con **AI Routing** activo. Si tu proyecto no tiene AI Routing activo, la sección **Workflows** te lleva a la lista tabular clásica.
## Cómo acceder
Entra al proyecto donde quieres ver el mapa.
Desde la barra lateral abre **Configuración del proyecto** (`/brain/:projectId/settings`) y elige la sección **Workflows** en el menú lateral (junto a **General** y **Meta Business Agent**). También puedes ir directo con `/brain/:projectId/settings?section=workflows`.
Si el proyecto tiene AI Routing activo, en pantallas grandes la vista se abre con el mapa a la izquierda y la lista de workflows a la derecha (en pantallas pequeñas se colapsan en un tab switcher — ver [Anatomía del mapa](#anatomía-del-mapa)).
No necesitas configurar nada extra: el mapa se activa automáticamente cuando el AI Router está habilitado.
## Anatomía del mapa
La pantalla se divide en dos paneles redimensionables, con controles adicionales sobre cada uno:
**Canvas izquierdo — grafo del router.** El **AI Router** aparece a la izquierda; los workflows aparecen a la derecha, uno por nodo, conectados al router por una arista. Cada nodo muestra el nombre del workflow y los íconos de todos los canales para los que tiene un canvas asociado. Los workflows ocultos aparecen atenuados con borde punteado; el workflow predeterminado lleva el badge **Predeterminado** y un borde punteado destacado.
En la esquina superior derecha del canvas viven los **chips de canal** (uno por cada canal presente en el proyecto o en los workflows) y el botón **Auto-organizar**; abajo a la derecha aparece un minimapa.
**Lista derecha — accordion de workflows.** Los mismos workflows en formato de lista, con la misma UX que la tarjeta de **Ajustes → AI Routing**. En la parte superior hay un filtro con tres opciones: **Todos**, **AI Router** (solo los que participan del router) y **Ocultos**, con el conteo de cada grupo. Cada fila muestra:
* Un **punto de estado** — verde si el workflow tiene descripción, ámbar si no la tiene.
* El **nombre** del workflow y una descripción resumida (si es larga, un chevron a la derecha la expande dentro de la fila).
* Badge **Predeterminado** para el workflow predeterminado.
* Un **switch Ocultar/Mostrar en AI Routing**. El workflow predeterminado viene siempre activo y no se puede ocultar.
* Dos íconos que aparecen al pasar el cursor: **Configuración** abre el panel lateral con la configuración del workflow (mismo panel que se abre al hacer clic en su nodo del canvas); **Ver workflow** abre el workflow en Studio, en la misma pestaña.
Hacer clic sobre la fila en sí no abre nada — solo los íconos de hover y el chevron reaccionan.
En pantallas pequeñas los dos paneles se colapsan en un tab switcher entre **Mapa** y **Workflows**.
## Interacciones principales
Haz clic en el nodo del mapa o en el ícono **Configuración** (hover) de la fila para abrir el panel lateral con la configuración inline del workflow (mismo panel del nodo Start): nombre, descripción, reglas de enrutamiento y opciones avanzadas como allowlist de usuarios, prefijo de país y horario. Para abrir el canvas completo del workflow, usa el ícono **Ver workflow** — en el encabezado del panel o en la fila.
Haz clic en el nodo del AI Router para abrir el panel de configuración del router. Ahí puedes ajustar la configuración a nivel de proyecto y probar el enrutamiento con mensajes reales usando el botón **Probar enrutamiento** — el mapa resalta el workflow al que el router derivaría cada mensaje. El ícono de enlace externo del encabezado te lleva a la pantalla completa de **AI Routing** en Ajustes.
Tienes dos formas: usar el **switch** de la fila del workflow, o arrastrar el nodo del mapa hacia el panel **Ocultos** (aparece a la derecha del canvas en cuanto hay al menos un workflow oculto). Ambas hacen lo mismo — el workflow queda excluido del AI Router pero permanece accesible desde la vista. Para volver a exponerlo, activa el switch de nuevo o arrastra la tarjeta desde el panel de ocultos hacia el mapa. Cada vez que cambias la visibilidad, el mapa reorganiza los nodos automáticamente para que nunca queden apretados.
Selecciona un canal en los chips de la esquina superior derecha del canvas. Solo se mostrarán los workflows que tengan canvas para ese canal. El mapa hace *fitView* automático al cambiar de canal para maximizar la visibilidad. Un segundo clic sobre el canal activo lo desactiva.
Usa el filtro de la barra superior de la lista para alternar entre **Todos**, **AI Router** u **Ocultos**. El filtro afecta sólo la lista; el mapa mantiene su vista.
Puedes arrastrar cada nodo a mano y su posición se mantiene aunque selecciones otro nodo o el router. Si prefieres volver al layout automático, usa el botón **Auto-organizar** de la esquina superior derecha del canvas: reaplica el algoritmo Dagre LR y hace fitView.
Antes de publicar cambios de visibilidad, usa **Probar enrutamiento** desde el panel del AI Router con mensajes reales de tu operación. El resaltado sobre el mapa te dice si un mensaje que antes iba a un workflow ahora cae en el predeterminado por haberlo ocultado.
## Casos de uso
Un equipo que va a lanzar el proyecto en WhatsApp e Instagram: abre el mapa, filtra por cada canal con los chips y confirma que los workflows críticos aparecen en ambos. Los workflows sin canvas para un canal desaparecen del canvas, así que se detectan de un vistazo los huecos de cobertura.
Un equipo que recibió un ticket de un usuario cuyo mensaje terminó en el workflow equivocado: abre el mapa, hace clic en el AI Router, pega el mensaje real en **Probar enrutamiento** y ve resaltado el workflow al que el router lo derivaría hoy. Desde ahí ajusta la descripción o las reglas del workflow correcto sin salir de la vista.
Un equipo que está construyendo dos workflows nuevos que aún no deben recibir tráfico: los oculta con el switch o arrastrándolos al panel **Ocultos**, mantiene el resto de la lista intacta y publica el proyecto. Los workflows ocultos siguen editables desde la lista y el mapa, pero el router no los considera hasta que se vuelven a exponer.
## Estados del mapa
**Proyecto vacío.** Si el proyecto no tiene workflows, verás un estado vacío que te invita a crear el primero.
**Un solo workflow.** Si el proyecto tiene exactamente un workflow, el mapa no se dibuja: verás un estado vacío con el CTA **Abrir workflow** para ir directo al canvas.
**Todos los workflows ocultos.** Si todos los workflows están marcados como ocultos, verás un estado vacío indicando que ningún workflow está expuesto al router; expón al menos uno para recuperar el mapa.
## Preguntas frecuentes
No. El aterrizaje del proyecto sigue igual. El mapa vive dentro de **Configuración del proyecto → Workflows**, así que sólo lo ves cuando entras a esa sección.
Ocultar un workflow desde el mapa **sí** cambia el enrutamiento en producción — mismo efecto que ocultarlo desde Ajustes → AI Routing — y aplica cuando publicas el proyecto. El resto de interacciones con el mapa (mover nodos, filtrar por canal, filtrar la lista) son puramente visuales y no afectan cómo el router decide.
Haz clic en el nodo del AI Router para abrir su panel lateral y usa el botón **Probar enrutamiento**. Ingresa mensajes reales; el mapa resalta el workflow al que el router derivaría cada mensaje.
Es intencional: el clic sobre la fila no dispara ninguna acción para que puedas seleccionar texto o desplazarte sin abrir el panel por accidente. Usa el ícono **Configuración** para abrir la configuración inline, o el ícono **Ver workflow** para saltar al canvas del workflow en Studio.
## Contenido relacionado
Aprende cómo se configura el AI Routing a nivel de proyecto y de workflow — la funcionalidad que habilita el mapa.
# Publicar y versiones
Source: https://docs.jelou.ai/guides/getting-started/publicar-versiones
Gestiona cómo tus cambios llegan a producción: con auto-publicación cada guardado se despliega de inmediato, o publica manualmente para controlar qué versión está activa y mantener el historial de versiones.
Brain Studio tiene dos modos de publicación. En cuentas nuevas self-service, la **auto-publicación** está activa: lo que guardas en el canvas llega directamente a producción sin pasos adicionales. En cuentas existentes, los cambios permanecen como borrador hasta que haces clic en **Publicar** — y cada publicación genera una nueva versión en el historial de versiones.
## Auto-publicación
Con la auto-publicación activa, lo que ves en el editor es lo que corre en producción. El botón **Publicar** desaparece de la barra del editor y el botón de compartir muestra siempre la URL pública del workflow.
| Tipo de cuenta | Estado por defecto | ¿Quién puede cambiar la configuración? |
| ---------------------------------------------- | ------------------------- | -------------------------------------- |
| Nuevas cuentas self-service | Auto-publicación activa | Super admin |
| Cuentas existentes (enterprise o self-service) | Publicación manual activa | Super admin |
La configuración aplica a **nivel de cuenta**: cuando se activa, afecta a todos los workflows regulares de la compañía al mismo tiempo.
La auto-publicación solo aplica a workflows regulares. Las Tools y Workflows del Marketplace siempre requieren publicación manual.
Con auto-publicación activa, los cambios se despliegan en producción de inmediato y no se genera historial de versiones. No es posible hacer rollback a un estado anterior.
### Activar o desactivar la auto-publicación
En la barra superior de Brain, haz clic en el ícono de opciones en la esquina superior derecha.
Busca la opción **Auto-publicación** y activa o desactiva el toggle según necesites.
## Publicar manualmente
Cuando la auto-publicación está desactivada, los cambios permanecen como borrador hasta que los publicas. Cada publicación crea una **versión inmutable** que se registra en el historial de versiones.
Cuando existan cambios sin publicar, el botón **Publicar** en la barra superior mostrará un indicador naranja. Esto significa que tu borrador difiere de la última versión publicada.
Haz clic en el botón **Publicar** de la barra superior. Se abre un popover de confirmación indicando que los cambios se despliegan a producción.
Al publicar, todos los cambios de los workflows de tu proyecto actual pasarán a producción. Puedes forzar esta nueva versión para que los usuarios que ya están en medio del flujo adopten la nueva versión, o añadir un nombre a esta versión para identificarla en el historial de versiones.
Haz clic en **Publicar cambios**. El sistema crea un nuevo commit con la fecha, hora y tu usuario como autor.
Una vez publicado, los cambios se aplican inmediatamente a todos los canales conectados (WhatsApp, Web, etc.). Asegúrate de probar tu flujo antes de publicar.
## Publicar al Marketplace
Puedes publicar **Workflows** y **Tools** individuales al Marketplace para que otros equipos los reutilicen. Esta publicación es siempre manual, independientemente del modo de publicación de tu cuenta.
Desde el canvas de tu workflow o Tool, haz clic en el botón **Publicar** del toolbar.
Rellena los campos requeridos:
* **Versión**: nombre de la versión (ej. `v1.0.0`, `v2.1.0`).
* **Descripción**: resumen breve de los cambios o funcionalidad.
* **Privacidad**: elige entre **Pública** (visible para todos los usuarios) o **Privada** (acceso solo bajo autorización).
Haz clic en **Publicar**. La versión quedará disponible en el Marketplace y podrá ser seleccionada desde los nodos AI Agent u otros workflows.
### Seleccionar versiones en nodos
Cuando agregas un workflow o Tool publicada a un nodo AI Agent, puedes elegir qué versión utilizar desde el selector de versiones en el panel de configuración. Cada versión muestra si es pública o privada.
Si publicas una nueva versión con cambios en los inputs u outputs, los workflows que usen versiones anteriores no se verán afectados hasta que actualices manualmente la versión seleccionada.
## Historial de versiones
El historial de versiones registra todas las publicaciones manuales de tu proyecto. Desde aquí puedes previsualizar cualquier versión, comparar cambios y restaurar una versión anterior.
El historial de versiones solo está disponible cuando la auto-publicación está desactivada. Con auto-publicación activa, los cambios no generan entradas en el historial de versiones.
En la barra superior de Brain, abre el menú de opciones en la esquina superior derecha y selecciona **Historial de versiones**. Se desplegará un panel lateral con la lista de versiones ordenadas por fecha.
Cada entrada muestra:
* **Fecha y hora** de publicación
* **Usuario** que realizó la publicación
* Badge **Actual** en la versión activa en producción
Selecciona una versión distinta para ver en el canvas cómo estaba construido tu workflow en ese momento. Úsalo para comparar antes de decidir si restaurar.
Si necesitas volver a una versión anterior, haz clic en **Restaurar esta versión** en la esquina superior derecha — el botón solo aparece en modo previsualización. Ten en cuenta que restaurar reemplaza la versión activa en producción de forma inmediata.
Prueba tu workflow antes de publicar
Comparte y prueba en producción
# Prueba tus workflows
Source: https://docs.jelou.ai/guides/getting-started/tester
Prueba tus workflows directamente desde el canvas de Brain Studio con un panel que muestra la conversación, los nodos ejecutados y el detalle de cada paso.
El Tester es el panel que se abre desde el canvas para probar un workflow antes de publicarlo. Te permite enviar mensajes como si fueras un usuario final, ver cada nodo que se ejecuta, e inspeccionar input, output y estado de cualquier paso del flujo. Comparte la misma interfaz visual que el [Debugger de producción](/guides/getting-started/ejecuciones-workflow), así que aprender uno es aprender el otro.
## Abrir el Tester
En la barra superior del canvas, haz clic en **Probar**. Se abrirá un panel lateral con el header del workflow, el área de conversación y un campo para escribir mensajes.
Si el workflow nunca se ejecutó, el primer mensaje que envíes inicia una sesión nueva. Cada sesión consume una ejecución (ver [Ejecuciones de Workflow](/guides/getting-started/ejecuciones-workflow)).
## Vista Mensajes y Vista Ejecución
El panel tiene un toggle al pie con dos modos:
| Modo | Qué muestra |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| **Mensajes** | Solo la conversación entre el canal y el usuario simulado. Útil para validar el flujo conversacional sin distracciones. |
| **Ejecución** | Conversación + indicadores de cada nodo del workflow que se ejecutó (ícono, nombre y estado). Útil para depurar lógica interna. |
## Indicadores de nodo
En la vista Ejecución, cada nodo del workflow que corre durante la prueba aparece como una pequeña tarjeta intercalada entre los mensajes. La tarjeta muestra:
* **Ícono del canvas** del nodo (Start, HTTP, Código, AI Agent, Condicional, Tool, etc.)
* **Nombre del nodo** tal como está configurado en el canvas
* **Estado** (verde para éxito, rojo para error)
Cuando varios nodos se ejecutan en secuencia dentro de la misma ejecución, se agrupan en una sola tarjeta con un footer (`N nodos · Xms total`). Si el workflow llama a un sub-workflow, los nodos internos del sub-workflow aparecen como un grupo aparte para marcar la frontera de ejecución.
## Panel de detalle de nodo
Al hacer clic sobre cualquier indicador, se abre un panel lateral redimensionable con toda la información de la ejecución del nodo:
El payload exacto que entró al nodo.
Lo que el nodo retornó (response, mensaje generado, resultado del condicional, etc.).
Snapshot de `$memory` y `$context` antes y después de la ejecución del nodo.
Si el nodo falló, mensaje y stack trace.
### Copiar el path de un valor
Haz clic sobre cualquier key del JSON en `Estado inicial` o `Estado final` para copiar el path completo en notación bracket al portapapeles. Por ejemplo, click sobre la key `url` dentro de `finalState.tool` copia `finalState.tool.url`. Aparece un tooltip "Copiado ✓" como confirmación.
El path copiado se pega directo en cualquier campo de variable expression (`{{finalState.tool.url}}`) o en un bug report sin tener que reconstruirlo a mano.
## Acciones del panel de detalle
Encima del input/output, el panel tiene botones contextuales:
Centra el canvas detrás del Tester en el nodo correspondiente. Solo aparece para nodos del workflow activo. Para nodos internos de un sub-workflow, el botón se oculta porque el nodo no vive en este canvas.
Solo aparece para nodos de tipo Tool. Abre la ejecución interna del Tool con el listado completo de los nodos que corrieron dentro, incluyendo sus propios input/output/errores.
## Burbujas interactivas
Las burbujas que en producción son interactivas (botones, listas, CTAs) ahora son clickeables dentro del Tester. No tienes que escribir manualmente la respuesta para avanzar el flujo: haz clic en el botón como lo haría el usuario final.
| Tipo de burbuja | Cómo se simula |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Respuestas rápidas** | Botones tipo pill bajo el mensaje. Click envía el `payload.targetId` y avanza al nodo destino. |
| **Lista de opciones** | Cards con título y descripción opcional. Click resuelve la opción contra `node.configuration.messages[0].options` y avanza por el branch correcto. |
| **CTA con URL** | Botón con ícono de enlace externo. Click abre la URL en una pestaña nueva del navegador. No avanza el flujo (igual que en WhatsApp). |
| **Carrusel** | Tarjetas horizontales con imagen, título y descripción. El botón con URL abre el link en pestaña nueva. |
## Limitaciones
* **Compatibilidad parcial con algunos nodos**: algunos nodos no son totalmente compatibles con el Tester y pueden comportarse diferente a como lo harían en producción. Cuando el Tester detecta uno de estos nodos en tu flujo, muestra un aviso con la opción **Probar en WhatsApp** para una prueba completa.
* **Nodos internos de sub-workflows**: el botón **Ir al nodo** no aplica a nodos que viven en un canvas distinto al activo. Para inspeccionarlos, abre el sub-workflow por separado o usa **Debuggear Tool** si el contenedor es un Tool.
* **Pruebas no afectan producción**: la sesión del Tester es independiente del tráfico real. Las conversaciones, variables y memoria que generas aquí no quedan registradas en el [Debugger de producción](/guides/getting-started/ejecuciones-workflow) ni se contabilizan como conversaciones de usuarios reales (sí cuentan como ejecuciones para facturación).
* **Una sesión a la vez**: si cierras el Tester sin terminar una conversación, la sesión se descarta. La próxima vez que hagas clic en **Probar** se inicia una nueva.
## Casos de uso
Un equipo de telco arma un flujo que valida facturas vía API. La API responde inconsistente y el flujo cae en producción. Ejecuta la prueba en el Tester, hace clic en el nodo HTTP que falla, ve el request y la response completos, y copia el path del campo problemático para reportarlo al equipo de backend, todo sin salir del builder.
Un equipo de QA en retail valida flujos de cobro de suscripciones antes de cada release. Una rama del Condicional siempre da problemas porque la lógica de matching depende de strings con tildes. Ejecutan la prueba, ven exactamente qué término hizo match en el panel del nodo Condicional, y corrigen sin escalar al equipo técnico.
Un product manager configura un AI Agent que pide nombre y correo y los guarda en `$memory.datosUsuarios`. Desde el Tester confirma que el Estado final contiene la estructura correcta (`{ nombre, correo }`), copia el path `finalState.datosUsuarios.correo` y lo usa en el siguiente nodo de texto sin reconstruirlo a mano.
Un workflow padre llama a un sub-workflow de validación de identidad. En el Tester ven que el sub-workflow aparece como un grupo aparte de indicadores. Hacen clic en el nodo Tool dentro del sub-workflow y usan **Debuggear Tool** para inspeccionar la ejecución interna del Tool sin abrir el sub-workflow por separado.
Un builder construye un flujo con un nodo Botones que ofrece tres opciones. En vez de escribir manualmente "1", "2" o "3" como hacía antes, hace clic directo en el botón dentro del Tester y el flujo avanza al nodo destino correcto, igual que como lo hará el usuario final en WhatsApp.
## Artículos relacionados
Construye tu primer workflow paso a paso y pruébalo desde el Tester.
Cómo se cuentan las ejecuciones y su impacto en facturación.
Genera un enlace público o sesión privada por WhatsApp para que tu equipo pruebe el workflow.
Cuando el flujo está listo en el Tester, publica una versión para producción.
# Tu primer workflow
Source: https://docs.jelou.ai/guides/getting-started/tu-primer-workflow
Crea tu primer workflow con un flujo simple de pregunta y respuesta
En esta guía vas a construir tu primer workflow: un workflow sencillo que pregunta el nombre, lo guarda en memoria y lo usa para saludar al usuario.
## Empieza con una plantilla
Si prefieres arrancar desde un caso listo — e-commerce, agendamiento, captación de leads o biometría/KYC — puedes instalar una plantilla en tu cuenta en lugar de construir el workflow desde cero. Cada plantilla se instala a través del [Jelou Agent](/guides/getting-started/jelou-agent#convierte-tu-workflow-en-una-plantilla), que te guía por las configuraciones específicas de tu cuenta.
Abre la pestaña **Plantillas** en la vista de inicio para ver el catálogo completo y elegir la que se ajuste a tu caso.
Si prefieres aprender construyendo, sigue los pasos de abajo.
## Construye desde cero
En tu canvas, desde el punto a la derecha del nodo `start`, arrastra tu cursor y selecciona el nodo `pregunta`. En el campo de texto pega:
```txt pregunta.txt theme={null}
Soy tu primer workflow. ¿Cuál es tu nombre?
```
En el campo “Guardar respuesta como”, escribe: `nombre`.
Partiendo del nodo pregunta que acabas de colocar, conecta un nodo texto con el siguiente contenido:
```txt texto.txt theme={null}
Hola, {{$memory.nombre}}
```
No olvides guardar la respuesta del nodo pregunta en el paso 1.
En la esquina superior derecha, haz clic en `Probar`. Puedes hacerlo desde nuestro tester o agregar tu número para probar directamente desde WhatsApp.
¡Y listo! Así de fácil puedes construir un workflow.
# Workflows con AI
Source: https://docs.jelou.ai/guides/getting-started/workflows-con-ai
Configura un AI Agent que recolecta datos de tus usuarios
En esta guía replicarás el flujo de `Tu primer workflow`, pero usando un nodo AI Agent para solicitar y almacenar datos estructurados del usuario.
Desde el nodo `start`, arrastra una conexión y elige el nodo `AI Agent`. En el campo de instrucción pega:
```txt title="ai-agent.txt" wrap theme={null}
Debes solicitar al usuario su nombre y correo electrónico.
Cuando el usuario te proporcione su nombre y correo, finaliza la interacción con el esquema en formato JSON:
{"nombre": [nombre], "correo": [correo]}
```
Todos nuestros AI Agents pueden terminar la interacción con un formato estructurado que guarda la información en memoria. Más detalles en la guía de [variables](/guides/variables/guia-rapida).
En la sección inferior de la configuración del nodo, escribe `datosUsuarios` en el campo **Guardar respuesta como**. Esto almacenará el JSON de salida en la memoria bajo esa clave.
Desde la salida del nodo AI, conecta un nodo `texto` con el siguiente contenido:
```txt title="texto.txt" wrap theme={null}
Bienvenido, {{$memory.datosUsuarios.nombre}}. Su correo es {{$memory.datosUsuarios.correo}}
```
Este texto usa la variable en memoria `datosUsuarios` para personalizar la respuesta con los datos entregados por el agente.
En la esquina superior derecha, haz clic en **Probar**. Interactúa con el flujo desde el tester o mediante WhatsApp para verificar que el AI Agent pida los datos y que el nodo texto muestre la información almacenada.
Asegúrate de que el nodo AI Agent guarde la respuesta como `datosUsuarios` para que el nodo texto pueda acceder a los datos.
# Comenzar
Source: https://docs.jelou.ai/guides/integraciones/identidad/index-kyc
Guía rápida para elegir canal e implementar KYC en Jelou.
Con Jelou puedes validar identidad en tres canales: **Conversacional (video selfie)**, **WebView (foto selfie)** y **WhatsApp Flows**.
El flujo completo combina **prueba de vida**, **validación de documento** y **comparación facial**, con evidencias auditables.
## Empieza aquí
Elige la ruta que necesitas:
Compara Conversacional, WebView y Flows según tu caso de uso.
Requisitos mínimos para evitar bloqueos en implementación y pruebas.
Configura un flujo end-to-end en un solo agente.
Implementa prueba de vida, document check o facematch por separado.
Agrega consultas externas como Interpol o Policía Nacional.
Revisa evidencias, campos del reporte y buenas prácticas de datos.
Si ya definiste el canal, ve directo a la guía de implementación (Conversacional, WebView o Flows) en la barra lateral para reducir tiempos de salida a producción.
La opción **Agentes por etapa** está disponible solo para usuarios con rol **Developer**.
# AI Agent
Source: https://docs.jelou.ai/guides/nodos/ai-agent
Nodo de agente inteligente para conversaciones autónomas impulsadas por IA
El nodo **AI Agent** es el componente central para crear conversaciones autónomas impulsadas por inteligencia artificial en Brain Studio. Permite configurar un agente que procesa mensajes del usuario, consulta bases de conocimiento, ejecuta herramientas y genera respuestas contextuales de forma autónoma.
El panel de configuración se organiza en **cinco pestañas**:
1. **General** — Modelo e instrucciones
2. **Tools** — Herramientas nativas, personalizadas y MCP
3. **Contexto** — Mensaje inicial, historial y conocimiento externo
4. **Avanzado** — Guardar respuesta, modelo de respaldo, seguridad, expiración, DLP y más
5. **Eventos** — Eventos de seguimiento y condiciones de evaluación
***
## General
La pestaña General contiene la configuración esencial del agente.
### Modelo
Selecciona el modelo de lenguaje (LLM) que utilizará el agente para generar respuestas. Considera latencia, costo y complejidad de las tareas antes de seleccionarlo.
Los modelos disponibles incluyen:
**OpenAI**
* **GPT 4.1 Mini**: variante simplificada de GPT-4.1 optimizada para respuestas rápidas con menor demanda de recursos.
* **GPT 4.1**: evolución refinada de GPT-4, con mejor comprensión, razonamiento y precisión.
* **GPT 4-o (Azure)**: versión de GPT 4-o alojada en Azure, enfocada en estabilidad y rendimiento en entornos empresariales. Soporta visión.
* **GPT 4-o Mini**: versión más rápida y ligera de GPT 4-o, orientada a casos donde prima la velocidad. Soporta visión.
* **GPT 5.2**: modelo de última generación de OpenAI con razonamiento avanzado, mayor contexto y alta precisión en tareas complejas.
**Anthropic (Claude)**
* **Claude 3.5 Sonnet**: excelente para tareas complejas que requieren textos más elaborados y contextos extensos.
* **Claude 4 Sonnet**: alta capacidad de razonamiento y análisis.
* **Claude 4.6 Sonnet**: última generación de Claude, con razonamiento mejorado y mayor precisión.
**Google (Gemini)**
* **Gemini 2.5 Flash**: procesamiento rápido con capacidades multimodales. Soporta visión.
* **Gemini 2.5 Pro**: razonamiento avanzado con capacidades multimodales. Soporta visión.
* **Gemini 3 Flash**: última generación de Gemini con procesamiento multimodal optimizado. Soporta visión.
**Meta (Llama)**
* **Llama 4 Scout**: modelo ágil y de baja latencia, ideal para ideas rápidas e interacciones ligeras.
* **Llama 4 Maverick**: modelo de alto rendimiento diseñado para razonamientos exigentes y resolución multistep.
Los modelos marcados con **"Soporta visión"** pueden procesar imágenes subidas en la pestaña Contexto. Si necesitas que el agente interprete imágenes como parte de su contexto, selecciona uno de estos modelos.
También puedes agregar **modelos personalizados** usando el botón "Agregar modelo". Esto permite conectar modelos propios o de terceros que no estén en la lista predefinida.
### Instrucciones
Define el comportamiento base del agente mediante un prompt de sistema. Las instrucciones determinan qué rol adopta, qué tono utiliza y qué pasos sigue antes de responder. Asegúrate de que sean breves, concretas y libres de ambigüedades.
Las instrucciones soportan **interpolación de variables** usando la sintaxis `{{$variable}}`, lo que permite personalizar el comportamiento dinámicamente según el contexto de la conversación.
El campo incluye un contador de caracteres que se ajusta según el modelo seleccionado, ya que cada modelo tiene un límite máximo diferente.
[Ver recomendaciones y ejemplos de prompting](/guides/agentes-ia/prompting)
***
## Tools
La pestaña Tools permite agregar herramientas que amplían las capacidades del agente más allá de la generación de texto. El agente decide autónomamente cuándo invocar cada herramienta según el contexto de la conversación.
Para agregar herramientas, haz clic en el botón **"+ Agregar tools"** y selecciona las herramientas que necesites.
### Tools nativas
Son herramientas predefinidas e integradas en la plataforma. Están marcadas con el texto "(Nativa)" en el selector:
| Herramienta | Descripción |
| :----------------------------- | :----------------------------------------------------------------- |
| **Búsqueda de productos** | Consulta el catálogo de productos configurado en el canal |
| **Enviar mensaje interactivo** | Renderiza botones, listas y quick replies al usuario |
| **Enviar Call to Action** | Envía botones CTA con URL, con soporte de WebView |
| **Fecha y hora actual** | Obtiene la fecha y hora del momento en una zona horaria específica |
| **Día de la semana** | Calcula el día correspondiente a una fecha |
| **Transferir a asesor** | Transfiere la conversación a un agente humano o cola de atención |
[Ver documentación detallada de tools nativas](/guides/agentes-ia/tools-nativas)
### Tools personalizadas
Herramientas creadas por el usuario desde Brain Studio. Al agregar una tool personalizada puedes configurar:
* **Nombre** y **descripción** que el modelo utiliza para decidir cuándo invocarla.
* **Parámetros de entrada** personalizados.
* **Acción al ejecutar**: comportamiento posterior a la ejecución de la herramienta.
[Ver cómo crear tu primer tool](/guides/tools/tu-primer-tool)
### Tools MCP (Model Context Protocol)
Integra servicios externos mediante el estándar MCP. Existen dos tipos de integración:
#### Apps MCP nativas
Aplicaciones del marketplace de Jelou que se instalan directamente en la plataforma. Cada app MCP puede configurarse en dos modos:
* **Modo integración**: habilita todas las herramientas de la app de una vez.
* **Modo granular**: permite seleccionar herramientas individuales de la app y configurar cada una por separado.
#### Parámetros por defecto
En modo granular, cada herramienta individual permite configurar **parámetros por defecto**. Haz clic en una herramienta para abrir su vista de detalle, donde puedes:
* Ver todos los parámetros de la herramienta con su tipo, descripción y si son requeridos.
* **Fijar valores por defecto** que el agente usará en cada invocación, en lugar de dejar que la IA los decida.
* Usar el **selector de variables** para inyectar valores dinámicos del flujo (ej: `{{$context.email}}`).
* Alternar entre vista de **formulario** y **editor JSON** para editar todos los parámetros de una vez.
* Buscar parámetros por nombre cuando la herramienta tiene muchos.
Cada parámetro muestra un indicador:
| Indicador | Significado |
| :--------- | :--------------------------------------------------------------- |
| **Manual** | Tiene un valor fijado por ti — el agente usará ese valor siempre |
| **Auto** | Sin valor fijado — el agente decide el valor en cada invocación |
Si dejas todos los parámetros en "Auto", el comportamiento es idéntico al anterior: el agente decide todos los valores según el contexto de la conversación.
#### Servidores MCP externos
Conexiones a servidores MCP propios mediante URL personalizada.
Ingresa la URL del endpoint de tu servidor MCP.
Configura headers personalizados (nombre y valor) para autenticación u otros metadatos.
Una vez conectado, selecciona las herramientas disponibles que deseas habilitar.
### Acciones de herramientas
Cada herramienta (nativa, personalizada o MCP) puede configurar una acción que se ejecuta tras su invocación:
| Acción | Comportamiento |
| :--------------------- | :--------------------------------------------------------- |
| **Ninguna** | El agente continúa la conversación normalmente |
| **Finalizar función** | La ejecución del flujo termina tras usar la herramienta |
| **Pausar interacción** | La conversación se pausa hasta que se reanude externamente |
### Optimización de tokens
En la parte superior de la pestaña Tools encontrarás el interruptor **Optimización de tokens**. Al activarlo, los resultados que las herramientas devuelven al agente se serializan en formato **TOON** (Token-Oriented Object Notation) en lugar de JSON antes de enviarse al modelo.
TOON es un formato compacto de datos estructurados pensado para modelos de lenguaje: declara los campos una sola vez y elimina la puntuación repetitiva del JSON (llaves, comillas y claves duplicadas en cada elemento). La información es la misma; solo cambia cómo se representa, por lo que el agente la interpreta igual usando menos tokens.
El mismo resultado de una herramienta en ambos formatos:
```json JSON theme={null}
{
"productos": [
{ "id": 1, "nombre": "Camiseta", "precio": 19.9 },
{ "id": 2, "nombre": "Gorra", "precio": 9.9 }
]
}
```
```text TOON theme={null}
productos[2]{id,nombre,precio}:
1,Camiseta,19.9
2,Gorra,9.9
```
El ahorro es mayor cuanto más grandes y uniformes son los resultados: en listas largas de objetos con los mismos campos (catálogos de productos, resultados de búsqueda, registros de una base de datos) la reducción en el consumo de tokens es significativa.
Activa esta opción cuando tus herramientas devuelvan listas grandes de datos estructurados. Para respuestas pequeñas o de estructura variable el ahorro es menor, aunque activarla no cambia el comportamiento del agente.
***
## Contexto
La pestaña Contexto centraliza la configuración del contexto de entrada del agente: cómo inicia cada conversación, cuántos mensajes anteriores recuerda y qué fuentes de conocimiento consulta para generar sus respuestas.
### Mensaje inicial
Define el texto que el agente recibe como primer turno de usuario cuando el nodo comienza a ejecutarse. Esta configuración determina con qué información arranca el agente para generar su primera respuesta.
| Opción | Comportamiento | Cuándo usarla |
| :-------------------------- | :------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------- |
| **Pasar el último mensaje** | El agente recibe el último mensaje de la conversación como entrada inicial | La mayoría de los casos: atención al cliente, soporte técnico, consultas generales |
| **Sin mensaje de usuario** | No se envía ningún turno de usuario al modelo; el agente responde solo con sus instrucciones de sistema | Cuando no hay un mensaje de usuario relevante que pasar al agente (campañas HSM, workflows automatizados, post-recolección de datos) |
| **Personalizado** | Muestra un campo de texto con selector de variables para definir un valor personalizado | Cuando necesitas combinar variables o enviar un contexto específico al agente |
**Pasar el último mensaje** es la opción predeterminada y la recomendada para la mayoría de los casos. El agente recibe automáticamente lo que el usuario escribió (equivale internamente a `{{$message.text}}`), lo que permite una experiencia conversacional natural.
#### Ejemplos por caso de uso
**`Pasar el último mensaje`** — Atención al cliente
Un usuario escribe "¿Cuál es el horario de atención?" y el agente lo recibe directamente como entrada, generando una respuesta basada en su base de conocimiento.
**`Sin mensaje de usuario`** — Cuando no hay una consulta real del usuario
Usa esta opción cuando el último mensaje disponible no representa una consulta real del usuario. Escenarios comunes:
* **Post-campaña HSM**: el usuario respondió a una plantilla de WhatsApp tocando un botón como "Sí, me interesa". Ese payload no es una consulta — el agente debe iniciar desde sus instrucciones y el contexto del flujo.
* **Workflows automatizados**: el flujo fue disparado por un webhook, un scheduler o una API externa. No existe un mensaje de usuario porque ningún usuario escribió.
* **Post-recolección de datos**: nodos Input anteriores ya recopilaron nombre, número de orden, etc. El último mensaje es un dato (ej: `"ORD-12345"`), no una pregunta. El agente debe responder con base en la información ya almacenada en memoria.
En este modo, el agente genera su primera respuesta basándose únicamente en sus instrucciones de sistema. Si necesitas que el agente salude al usuario, configúralo en las instrucciones del agente.
**`Personalizado`** — Contexto a medida con variables
Permite construir un mensaje de entrada combinando variables del flujo. Por ejemplo, para pasar información de contexto al agente:
```
Producto: {{$context.product_name}}. Consulta: {{$message.text}}
```
Esto es útil cuando el agente necesita contexto adicional más allá del mensaje del usuario, como datos recopilados en nodos anteriores del flujo.
Al seleccionar **Personalizado**, el campo soporta interpolación de variables con la sintaxis `{{$variable}}` y tiene un límite de 100 caracteres. Usa el selector de variables para explorar las variables disponibles en tu flujo.
### Recordar mensajes previos
Cuando está habilitado, el agente incluye como contexto los últimos N mensajes de la conversación cada vez que inicia. Esto permite mantener coherencia conversacional cuando el usuario retoma una sesión después de un intervalo.
Activa el toggle de **Recordar mensajes previos**.
Ingresa cuántos mensajes anteriores debe recordar el agente. El rango válido es de **1 a 50 mensajes**.
Usa un valor bajo (5–10 mensajes) para conversaciones transaccionales y un valor más alto para soporte técnico donde el historial completo puede ser relevante.
### Bloque de carga
El bloque de carga unificado acepta documentos e imágenes en la misma interacción. Puedes agregar archivos de tres formas:
* **Arrastra y suelta** archivos directamente sobre el bloque
* **Haz clic** en el bloque para abrir el selector de archivos
* **Pega una URL** en el campo de enlace ubicado en la parte inferior del bloque (soporta los mismos formatos de documentos e imágenes listados abajo)
| Tipo | Formatos soportados | Tamaño máximo |
| :------------- | :---------------------------------------------- | :---------------- |
| **Documentos** | `.pdf`, `.xlsx`, `.md`, `.txt`, `.json`, `.csv` | 2 MB por archivo |
| **Imágenes** | `.jpg`, `.jpeg`, `.png` | 10 MB por archivo |
Además de `.pdf` y `.csv`, puedes cargar archivos `.xlsx`, `.md`, `.txt` y `.json` como fuentes de conocimiento.
Los documentos se procesan uno por uno. Si seleccionas o arrastras varios documentos simultáneamente, solo el primero se procesará y recibirás un aviso para cargar los restantes individualmente. Las imágenes, en cambio, se procesan en lote.
### Documentos
Al cargar un documento se abre un panel donde defines su **nombre** (máximo 30 caracteres) y una **descripción** opcional que ayuda al agente a entender el contenido del archivo. Una vez cargado, puedes editar sus metadatos o eliminarlo desde la lista.
### Imágenes
Las imágenes se suben directamente sin pasos adicionales y aparecen como miniaturas debajo del bloque de carga. Puedes subir hasta **3 imágenes** por agente.
* Haz clic en una miniatura para verla en pantalla completa
* Pasa el cursor sobre una miniatura para ver el botón de eliminar
Las imágenes requieren un modelo con **capacidades multimodales** (marcados con "Soporta visión" en la [lista de modelos](#modelo)). Si el modelo seleccionado no soporta visión, verás un banner de advertencia y el agente no procesará las imágenes durante la conversación.
### Datastores
Conecta bases de datos de Datum para que el agente consulte información estructurada en tiempo real:
* Selecciona el datastore a conectar
* Configura las operaciones permitidas sobre la base de datos
Se recomienda cargar siempre documentos en la pestaña Contexto para anclar las respuestas del agente a la documentación oficial de tu negocio. Asigna descripciones claras a cada archivo cargado para maximizar la efectividad. El modelo priorizará la información de tus archivos y datastores sobre su conocimiento general, reduciendo drásticamente el riesgo de alucinaciones.
### Contexto externo
Permite reanudar este nodo desde un sistema externo mediante la API de reanudación. Útil para flujos que requieren procesamiento asíncrono externo, como validaciones, pagos o aprobaciones.
Activa el toggle **Agregar contexto externo** para desplegar la configuración.
Copia la URL del endpoint que tu sistema externo deberá llamar:
```
POST https://gateway.jelou.ai/workflows/v1/skills/resume
```
Tu sistema externo debe enviar la siguiente petición:
**Headers**
```json theme={null}
{
"x-api-key": "Tu API key",
"Content-Type": "application/json"
}
```
**Payload**
```json theme={null}
{
"executionId": "{{$context.executionId}}",
"message": "string (opcional)",
"pauseInteraction": false
}
```
El `executionId` está disponible como `{{$context.executionId}}` dentro del flujo y tiene una validez de 24 horas.
### Gestión de memoria
La sección **Gestión de memoria** agrupa un conjunto de fuentes que, al activarlas, conectan tools nativas para que el agente pueda **guardar y recordar información**, **leer y actualizar datos del contacto en el CRM**, **compartir variables entre nodos** y **acceder a datos de plataforma** (usuario, canal y empresa) como contexto.
Cada fuente es un toggle independiente: enciendes solo las capacidades que el agente necesita. Las fuentes de memoria y CRM exponen operaciones que el agente invoca de forma autónoma, mientras que los bloques de contexto de plataforma se inyectan automáticamente en el prompt.
| Fuente | Tool | Tipo | Qué hace |
| :---------------------------- | :---------------- | :-------------------------------- | :----------------------------------------------------------------------------------------------------------------------- |
| **Memoria interna** | `memory_manager` | Operaciones | Permite al agente guardar, recordar y eliminar información de la conversación para reutilizarla más adelante. |
| **CRM de bandeja de entrada** | `crm_connect` | Operaciones | Lee, define y actualiza datos del contacto del CRM (nombre, teléfono, etiquetas, notas y más). |
| **\$context** | `flow_context` | Operaciones | Lee y escribe variables del contexto para compartir datos entre nodos. |
| **Información del usuario** | `platform_reader` | Bloque de contexto (solo lectura) | Acceso de solo lectura a los datos del usuario actual: nombre, teléfono, atributos y metadatos. |
| **Información del canal** | `platform_reader` | Bloque de contexto (solo lectura) | Acceso de solo lectura a los datos del canal: configuración, identificadores y atributos públicos del canal conectado. |
| **Información de la empresa** | `platform_reader` | Bloque de contexto (solo lectura) | Acceso de solo lectura a los datos de la empresa: nombre, plan y atributos públicos inyectados como contexto del prompt. |
**Operaciones configurables** — Cada operación de las fuentes de tipo operaciones (Memoria interna, CRM de bandeja de entrada, \$context) tiene un popover donde defines cómo se resuelven sus parámetros en modo **IA**, **variable** o **estático**, y un **comportamiento post-ejecución** (Continuar, Terminar o Pausar).
**Permisos del CRM** — La fuente **CRM de bandeja de entrada** administra permisos por grupo (datos del contacto / etiquetas) con lectura y escritura separadas. Puedes restringir a qué etiquetas puede acceder el agente.
**Espacio de memoria** — La fuente **Memoria interna** incluye un campo opcional **Espacio de memoria** para aislar o agrupar los guardados, de modo que distintos contextos no se mezclen.
**Bloques de contexto de solo lectura** — Las fuentes **Información del usuario**, **Información del canal** e **Información de la empresa** son de solo lectura. Se inyectan como bloques de contexto en el prompt y no consumen una llamada de tool. Las descripciones anteriores son ilustrativas; consulta la [referencia de tools nativas](/guides/agentes-ia/tools-nativas#información-de-plataforma) para ver los campos exactos que expone cada bloque.
La **Memoria interna** es memoria de trabajo por usuario: recuerda datos del mismo usuario entre conversaciones (memoria de trabajo, con vigencia limitada). No es un almacén permanente.
**CRM de bandeja de entrada** requiere al menos un canal conectado para que existan contactos sobre los cuales leer o escribir.
[Ver documentación detallada de estas tools](/guides/agentes-ia/tools-nativas#gestión-de-memoria)
***
## Avanzado
La pestaña Avanzado contiene configuraciones adicionales para un control más fino del comportamiento del agente.
### Guardar respuesta
Permite almacenar la última respuesta del agente en una variable para usarla en nodos posteriores del flujo.
Activa el toggle correspondiente.
Escribe el nombre de la variable donde se almacenará la respuesta (por ejemplo: `ai_agent_response`).
La variable estará disponible como `{{$context.ai_agent_response}}` en los nodos siguientes del flujo.
### Modelo de respaldo (Fallback)
Selecciona un modelo alternativo que se utilizará automáticamente si el modelo principal falla o no está disponible.
El modelo de respaldo no puede ser el mismo que el modelo principal.
### Opciones de procesamiento
Opciones independientes para habilitar capacidades adicionales sobre los mensajes que el usuario envía durante la conversación:
| Opción | Descripción |
| :------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------- |
| **Soporte PDF** | Permite al agente leer y procesar documentos PDF enviados por el usuario durante la conversación (solo texto, no imágenes dentro del PDF) |
| **Leer URLs de imagen** | Habilita al agente para procesar imágenes que el usuario envía como URL con texto descriptivo durante la conversación |
| **Soporte Quick Reply Payload** | Permite al agente interpretar el payload de respuestas rápidas de mensajes interactivos |
Estas opciones aplican a los archivos que el **usuario final** envía durante la conversación. Las imágenes y documentos que tú configuras como contexto del agente se gestionan desde la pestaña **Contexto**.
### Respuesta en varios mensajes
Divide la respuesta del agente en burbujas separadas cuando contiene saltos de párrafo (`\n\n`), URLs de medios o listas. Las URLs de imagen, video, audio o documento se envían como burbujas de medios independientes según el canal, mientras que las listas numeradas se mantienen juntas.
Activado por defecto en los agentes creados a partir del 24 de abril de 2026. Los agentes anteriores mantienen el comportamiento previo (una sola burbuja) hasta que actives el toggle manualmente.
### Seguridad (Guardrails)
Configura el nivel de protección del agente contra usos indebidos, inyecciones de prompt y solicitudes fuera de alcance.
Activa el toggle de seguridad para acceder a las opciones de configuración.
Elige el preset que mejor se ajuste a tu caso de uso:
| Nivel | Descripción |
| :---------- | :---------------------------------------------------------- |
| **Bajo** | Protección mínima, mayor flexibilidad en respuestas |
| **Medio** | Balance entre seguridad y flexibilidad (recomendado) |
| **Alto** | Protección estricta, restringe respuestas fuera del alcance |
| **Crítico** | Máxima protección, ideal para contextos regulados |
Habilita una capa adicional de protección que filtra entradas y salidas del modelo para detectar contenido malicioso.
[Ver guía completa de seguridad y guardrails](/guides/agentes-ia/seguridad)
### Expiración
Configura un tiempo límite para la sesión del agente. Si el usuario no responde dentro del tiempo configurado, la sesión expira automáticamente.
Activa el toggle de expiración.
Define el tiempo y selecciona la unidad:
* **Minutos**: rango de 1 a 1200
* **Horas**: rango de 1 a 20
* **Valor por defecto**: 8 horas (28,800 segundos)
### DLP (Prevención de pérdida de datos)
Habilita la detección y enmascaramiento automático de información sensible en las conversaciones del agente.
Activa el toggle de DLP para acceder a la configuración.
Elige qué tipos de información deben ser detectados y enmascarados automáticamente:
| Tipo de dato | Valor de reemplazo por defecto |
| :--------------------------- | :----------------------------- |
| Número de tarjeta de crédito | `[CreditCardNumber]` |
| Track de tarjeta de crédito | `[CreditCardTrackNumber]` |
| Correo electrónico | `[EmailAddress]` |
| Número de cuenta financiera | `[FinancialAccountNumber]` |
| Dirección IP | `[IpAddress]` |
| Ubicación | `[Location]` |
| Coordenadas geográficas | `[LocationCoordinates]` |
| Número de teléfono | `[PhoneNumber]` |
| Fecha | `[Date]` |
| Nombre de persona | `[PersonName]` |
Modifica el valor de reemplazo para cada tipo de dato según tus necesidades.
El tipo **Número de tarjeta de crédito** siempre está seleccionado por defecto y no puede deshabilitarse.
### Mensaje de seguimiento
Permite que el agente retome de forma proactiva una conversación inactiva, enviando un recordatorio al usuario que dejó de responder. Al activar el toggle **Habilitar mensaje de seguimiento** aparece un botón de configuración que abre un modal donde defines cómo se comporta el recordatorio.
Activa el toggle para mostrar el botón de configuración. Haz clic en el ícono de engranaje para abrir el modal **Mensaje de seguimiento**.
Selecciona cómo se genera el recordatorio:
* **Automático**: el agente redacta el recordatorio por sí mismo a partir de la conversación.
* **Manual**: escribes una instrucción que define cómo debe comportarse el agente al retomar. El campo admite hasta **900** caracteres.
Ajusta los dos controles que evitan que el agente insista de más:
| Campo | Descripción | Valores |
| :---------------------------------------- | :------------------------------------------------------------------------------ | :---------------------------------------------------------- |
| **Espera entre wakeups** | Tiempo de inactividad antes de enviar cada recordatorio. | 5, 10, 15, 20, 25, 30, 45, 60 o 120 minutos (por defecto 5) |
| **Detener tras # intentos sin respuesta** | Cantidad máxima de recordatorios consecutivos sin respuesta antes de detenerse. | 1 a 5 (por defecto 2) |
Las opciones disponibles se limitan al **tiempo de expiración** del agente: solo puedes elegir valores menores a ese tiempo. Una vez que la sesión expira, el agente deja de enviar recordatorios.
***
## Eventos
La pestaña Eventos agrupa la instrumentación del nodo en dos secciones: **Configuración de eventos** y **Condiciones de evaluación**. Ambas alimentan tus métricas y paneles de rendimiento.
### Configuración de eventos
Registra eventos de seguimiento que se disparan cada vez que el nodo se ejecuta, con nombre y hasta 5 propiedades personalizadas por evento. Esta sección funciona igual que en el resto de nodos instrumentables.
[Ver guía completa de eventos de nodo](/guides/observabilidad/eventos)
### Condiciones de evaluación
Las condiciones de evaluación permiten medir la calidad y los resultados de tu agente. Defines mediante un prompt qué debe cumplirse en una conversación; cuando el agente finaliza la conversación, analiza el historial completo contra cada condición activa. Por cada condición que se cumple, se dispara un evento con el nombre de la condición, listo para analizarse en **Métricas**.
Haz clic en **"Agregar condición"** y completa los dos campos:
| Campo | Descripción | Límite |
| :----------------------- | :--------------------------------------------------------------------------- | :------------- |
| **Nombre** | Identifica la condición y define el nombre del evento que se dispara | 60 caracteres |
| **Instrucción (prompt)** | Describe qué debe suceder en la conversación para que la condición se cumpla | 900 caracteres |
Cada condición tiene un toggle independiente. Las condiciones desactivadas no se evalúan ni disparan eventos, sin necesidad de eliminarlas.
Puedes crear hasta **10 condiciones** por agente, buscarlas por nombre y editarlas o eliminarlas desde el menú de cada ítem.
Escribe cada condición para un escenario concreto y verificable, por ejemplo: "el usuario confirmó una cita con fecha y hora". Para distinguir varios escenarios, crea condiciones separadas — cada una dispara su propio evento.
La evaluación se ejecuta únicamente cuando el agente **finaliza la conversación** (por ejemplo, mediante una tool con acción **Finalizar función**). Si la sesión expira por inactividad, la evaluación no se ejecuta. Las conversaciones de prueba del tester tampoco generan eventos de evaluación.
#### Casos de uso
Un workflow con un agente que agenda citas médicas: se configura la condición `cita_agendada` con la instrucción "Se cumple si el usuario confirmó una cita con fecha y hora específicas". Cada conversación que termina con una cita confirmada dispara el evento `cita_agendada`, y las métricas muestran cuántas citas genera el agente por día.
Un workflow de atención al cliente: se configura la condición `cliente_insatisfecho` con la instrucción "Se cumple si el usuario expresó molestia o frustración, o pidió hablar con un humano sin ser atendido". El evento permite cuantificar conversaciones problemáticas y priorizar mejoras en las instrucciones del agente.
***
## Ejemplo de configuración
Un flujo típico de configuración para un agente de atención al cliente:
Selecciona **GPT-4.1** como modelo y escribe instrucciones claras definiendo el rol, tono y alcance del agente.
Habilita **Transferir a asesor** para escalar conversaciones complejas, **Búsqueda de productos** si el agente gestiona consultas de catálogo, y **Enviar mensaje interactivo** para mostrar opciones al usuario.
Selecciona **Pasar el último mensaje** como mensaje inicial, sube el documento de preguntas frecuentes en formato PDF, agrega imágenes de referencia si el modelo soporta visión, y conecta el datastore de productos si aplica.
Configura un **modelo de respaldo**, establece la **expiración** en 30 minutos, habilita **seguridad** en nivel medio y activa **DLP** si se manejan datos financieros o personales.
# AI Task
Source: https://docs.jelou.ai/guides/nodos/ai-task
Ejecuta una tarea de IA con respuesta estructurada para procesar datos o tomar decisiones
El nodo **AI Task** hace un llamado a un modelo de lenguaje (LLM) para procesar información y devolver un resultado estructurado. A diferencia del nodo AI Agent, que mantiene una conversación continua con el usuario, el AI Task ejecuta una tarea puntual y devuelve su resultado sin interactuar directamente con el usuario.
## Cuándo usar AI Task vs AI Agent
| Escenario | Nodo recomendado |
| :----------------------------------------------- | :--------------------------------- |
| Conversar con el usuario de forma autónoma | [AI Agent](/guides/nodos/ai-agent) |
| Clasificar un mensaje en categorías | **AI Task** |
| Extraer datos de un texto (nombre, fecha, monto) | **AI Task** |
| Resumir información para tomar una decisión | **AI Task** |
| Generar una respuesta basada en contexto extenso | [AI Agent](/guides/nodos/ai-agent) |
## Configuración
El AI Task utiliza los mismos campos de configuración que el AI Agent:
* **Modelo**: LLM a utilizar
* **Instrucciones**: Prompt que define qué tarea ejecutar
* **Temperatura**: Control de creatividad (0 = preciso, 1 = creativo)
* **Bases de conocimiento**: Documentos y datastores para contexto
* **MCPs y Tools**: Herramientas externas disponibles
[Ver detalles de configuración de modelos e instrucciones](/guides/agentes-ia)
## Diferencia clave: respuesta estructurada
El AI Task no necesita la función `end_function` como el AI Agent. En su lugar, retorna directamente un resultado en formato JSON que puedes guardar en memoria para usarlo en nodos posteriores (por ejemplo, en un nodo Condicional).
**Instrucciones:**
El resultado se guarda en memoria y puedes usar un nodo Condicional para dirigir el flujo según la categoría detectada.
# Aleatorio
Source: https://docs.jelou.ai/guides/nodos/aleatorio
Distribuye la conversación entre múltiples rutas según probabilidades
El nodo **Aleatorio** funciona como un distribuidor de tráfico: envía cada conversación por una ruta diferente según los porcentajes que configures. Es ideal para pruebas A/B, balanceo de carga o variedad en respuestas.
## Configuración
Haz clic en **"Nueva ruta"** para agregar caminos. Cada ruta necesita:
* **Nombre**: Un identificador descriptivo (por ejemplo, "Versión A", "Respuesta formal")
* **Porcentaje**: La probabilidad de que esta ruta sea seleccionada (0-100%)
Define los porcentajes para cada ruta. El total debe sumar exactamente **100%**.
Cada ruta tiene un punto de conexión a la derecha. Arrastra una línea desde cada punto hacia el nodo que debe ejecutarse en esa ruta.
**Distribución inválida**: si el total de porcentajes no suma 100%, el nodo mostrará una alerta indicando el porcentaje actual.
## Validación
El nodo muestra el estado de la distribución en tiempo real:
* **Distribución válida** (verde): los porcentajes suman exactamente 100%
* **Distribución inválida** (amarillo): los porcentajes no suman 100%, mostrando el total actual
## Reordenar rutas
Puedes reordenar las rutas arrastrándolas con el ícono de agarre (⠿) que aparece a la izquierda de cada ruta.
## Ejemplo de uso
### Pruebas A/B
Distribuir usuarios entre dos versiones de un mensaje para evaluar cuál funciona mejor:
| Ruta | Porcentaje |
| :---------------- | :--------- |
| Mensaje versión A | 50% |
| Mensaje versión B | 50% |
### Variedad en respuestas
Hacer la conversación más natural alternando entre diferentes estilos:
| Ruta | Porcentaje |
| :------------------ | :--------- |
| Respuesta formal | 40% |
| Respuesta casual | 40% |
| Respuesta con emoji | 20% |
# API
Source: https://docs.jelou.ai/guides/nodos/api
Conecta tu flujo con servicios externos mediante llamadas HTTP
El nodo **API** te permite comunicarte con servicios externos desde tu flujo de conversación. Puedes consultar datos, enviar información, autenticarte contra APIs y procesar las respuestas — todo sin escribir código.
Piensa en este nodo como un mensajero: le dices a dónde ir (URL), qué llevar (body) y qué traer de vuelta (respuesta).
***
## URL y método HTTP
En la parte superior del panel configuras los dos campos esenciales:
**Método HTTP** — define qué tipo de operación realizarás:
| Método | Para qué sirve | Ejemplo |
| :--------- | :----------------------------- | :-------------------------------------- |
| **GET** | Obtener datos | Consultar el saldo de un cliente |
| **POST** | Crear un recurso | Registrar un nuevo pedido |
| **PUT** | Reemplazar un recurso completo | Actualizar todos los datos de un perfil |
| **PATCH** | Actualizar parcialmente | Cambiar solo el correo de un usuario |
| **DELETE** | Eliminar un recurso | Cancelar una suscripción |
**URL** — la dirección del servicio. Puedes inyectar variables directamente:
```
https://api.ejemplo.com/usuarios/{{$user.id}}/pedidos/{{$memory.pedidoId}}
```
Si pegas una URL que ya contiene parámetros (como `?clave=valor`), el nodo los extrae automáticamente y los mueve a la pestaña de Parámetros.
### Importar desde cURL
Si tienes un comando cURL listo (por ejemplo, de la documentación de un API), puedes pegarlo directamente en el campo de URL. El nodo interpreta automáticamente el método, headers, body y autenticación del comando.
***
## Pestañas de configuración
El panel tiene **5 pestañas** para configurar todos los aspectos de la petición:
### Parámetros
Agrega parámetros de consulta (query string) como pares clave-valor. Cada parámetro tiene un checkbox para habilitarlo o deshabilitarlo sin eliminarlo.
```
usuario: {{$user.id}}
fecha: {{$context.fecha}}
```
Los parámetros se agregan automáticamente a la URL en formato `?param1=valor1¶m2=valor2`.
### Autenticación
Dos métodos de autenticación disponibles:
| Método | Configuración |
| :--------------- | :------------------------------------------------------------------- |
| **Basic Auth** | Usuario y contraseña. Se codifican automáticamente en base64 |
| **Bearer Token** | Token de acceso enviado en el header `Authorization: Bearer ` |
Ambos métodos soportan variables en sus campos, lo que permite usar credenciales almacenadas dinámicamente.
Las credenciales se mantienen en contexto durante la ejecución del workflow. Puedes editarlas desde el modal de credenciales si ya están almacenadas.
### Headers
Agrega headers HTTP personalizados como pares clave-valor. Cada header tiene un checkbox para activarlo o desactivarlo.
```
Content-Type: application/json
X-API-Key: {{$memory.apiKey}}
```
El header `Content-Type` se actualiza automáticamente cuando cambias el tipo de body.
### Body
Para métodos que envían datos (POST, PUT, PATCH), configura el cuerpo de la petición en distintos formatos:
| Formato | Uso típico |
| :------------------- | :---------------------------------------------------------------- |
| **JSON** | El más común para APIs REST. Editor con validación en tiempo real |
| **XML** | APIs SOAP o servicios legacy |
| **Texto plano** | Datos sin estructura específica |
| **Multipart Form** | Envío de archivos junto con datos de texto |
| **Form URL Encoded** | Formularios tradicionales web |
El editor JSON valida la estructura en tiempo real y soporta variables dentro del contenido:
```json theme={null}
{
"usuario_id": "{{$user.id}}",
"correo": "{{$memory.correo}}",
"nombre": "{{$user.names}}"
}
```
### Settings
Configuraciones avanzadas para controlar el comportamiento de la petición:
#### Certificado SSL
Habilita la verificación SSL/TLS para conexiones seguras. Incluye un campo de **timeout** en milisegundos (por defecto 3000ms).
#### Certificados mTLS
Autenticación mutua TLS con el servidor. Esta opción solo aparece si tu compañía tiene al menos un certificado configurado.
* Al activar mTLS, el certificado SSL se desactiva automáticamente (son mutuamente excluyentes)
* Si existe un certificado primario, se selecciona automáticamente
* Puedes elegir cualquier certificado de la lista disponible
* Incluye un campo de **timeout** configurable entre **1000ms (1s)** y **120000ms (2min)** (por defecto 30000ms)
[Ver gestión de certificados mTLS](/guides/configuracion/compania/certificados-mtls)
#### Reintentos
Configura reintentos automáticos cuando una petición falla:
| Opción | Descripción |
| :------------------------- | :-------------------------------------------------------- |
| **Condición de reintento** | Solo errores de red (por defecto) o reintentar siempre |
| **Cantidad de reintentos** | Número de intentos adicionales |
| **Tipo de espera** | Sin espera, exponencial o personalizada (en milisegundos) |
| **Resetear timeout** | Reinicia el tiempo de espera en cada reintento |
***
## Guardar la respuesta
Activa el toggle **"Guardar respuesta"** en la parte superior y define un nombre de variable. La respuesta completa del API se almacenará en esa variable.
### Acceder a la respuesta sin manipulación
Usa la variable directamente en otros nodos:
```
{{$context.apiResponse}}
```
### Manipular la respuesta en un nodo Código
Si necesitas extraer datos específicos del JSON:
```javascript theme={null}
let apiResponse = $context.getHttpResponse('apiResponse')
apiResponse = apiResponse.json()
let nombre = apiResponse.data.usuario.nombre
$memory.set('nombre', nombre)
```
La llave que uses en `$context.getHttpResponse('apiResponse')` debe coincidir exactamente con la que especificaste en el campo "Guardar respuesta".
***
## Respuesta de prueba
Después de ejecutar una prueba desde el panel, la respuesta se muestra en una sección colapsable con:
* **Código de estado** (por ejemplo, `200 OK`)
* **Pestaña Body** — Respuesta formateada como JSON con botón de copiar
* **Pestaña Headers** — Headers de respuesta del servidor
# Audio
Source: https://docs.jelou.ai/guides/nodos/audio
Envía archivos de audio al usuario dentro de la conversación
Con el nodo **Audio** envías un archivo de audio al usuario como parte de la conversación.
## Configuración
* **Archivo o URL**: Sube un archivo de audio o pega una URL pública
## Límites
| Formatos soportados | Tamaño máximo |
| :--------------------------------------------------------------------------------- | :------------ |
| `audio/aac`, `audio/mp4`, `audio/mpeg`, `audio/amr`, `audio/ogg` (solo codec opus) | 16 MB |
Si usas una URL, debe ser públicamente accesible vía HTTPS, no debe requerir autenticación y debe apuntar directamente al archivo de audio (no a una página HTML).
# Bases de datos
Source: https://docs.jelou.ai/guides/nodos/bases-de-datos
Lee, crea, actualiza y elimina registros de tus bases de Datum desde el canvas
El nodo **Bases de datos** conecta tu workflow con tus bases de datos y ejecuta operaciones sobre los registros sin escribir código. Eliges la base, la colección y la operación, y el panel arma la petición por ti.
Piensa en este nodo como un intermediario entre tu workflow y la base de datos: tú le indicas qué quieres hacer con los registros y él se encarga de hablar con el servicio.
Para administrar tus bases, colecciones, campos y registros en Datum, consulta la [documentación completa de Datum](/guides/datum/introduccion). Este nodo asume que la base ya existe.
***
## Concepto clave
Toda operación del nodo se resuelve contra **una base + una colección + una operación**. Los tres son obligatorios para que el nodo pueda construir la petición correcta.
| Concepto | Qué significa |
| :---------------- | :------------------------------------------------------------------------------------------ |
| **Base de datos** | El contenedor que tienes provisionado en Datum (equivale a "la base"). |
| **Colección** | La tabla dentro de la base: usuarios, pedidos, productos, etc. |
| **Operación** | La acción que realizarás sobre la colección: listar, obtener, crear, actualizar o eliminar. |
Cambiar cualquiera de los tres limpia los campos dependientes de abajo. Si ya habías ingresado datos que se perderían, el panel te avisa con un modal antes de aplicar el cambio.
***
## Configuración
El panel se organiza de arriba hacia abajo, en el mismo orden en que debes configurarlo.
### Base de datos
Selector con las bases que tu cuenta tiene disponibles en Datum. Junto al label aparece un botón **Abrir** que abre la base en una pestaña nueva del app de Datum, útil para revisar colecciones y registros mientras configuras el nodo.
Si la lista aparece vacía o ves un error 401/403, tu usuario no tiene permiso sobre esa base. Contacta al administrador de Datum en tu cuenta.
### Colección
Solo se habilita cuando hay una base seleccionada. Muestra las colecciones que creaste en la base (las colecciones internas de Datum no aparecen). Mientras cargan, el selector queda deshabilitado; si la carga falla, se mantiene deshabilitado hasta que la base cambie o se corrijan los permisos.
### Operación
Define qué hará el nodo sobre la colección:
| Operación | Para qué sirve |
| :---------------------- | :------------------------------------------------------ |
| **Listar registros** | Traer varios registros con filtros, orden y paginación. |
| **Obtener registro** | Traer un único registro por su `id`. |
| **Crear registro** | Insertar un nuevo registro. |
| **Actualizar registro** | Modificar un registro existente por `id`. |
| **Eliminar registro** | Borrar un registro por `id`. |
### Campos según operación
El resto del panel cambia en función de la operación elegida.
#### ID del registro
Requerido por **Obtener**, **Actualizar** y **Eliminar**. Acepta un valor fijo o una variable:
```text theme={null}
{{$context.userRecordId}}
```
Si eliges **Obtener**, **Actualizar** o **Eliminar** y dejas el ID vacío, el servicio responde con `404 Record not found`. Esto es intencional: protege de llamadas accidentales contra toda la colección cuando lo que pedías era un único registro.
#### Valores de los campos
Para **Crear** y **Actualizar**, el panel abre un editor con un control específico por tipo de campo declarado en la colección:
| Tipo del campo | Control |
| :------------------------------- | :-------------------------------------------------- |
| `text`, `email`, `url`, `number` | Input con soporte de variables |
| `bool` | Interruptor `true`/`false` o entrada de variable |
| `date` | Calendario + selector de hora o entrada de variable |
| `select` (simple o múltiple) | Selector desplegable o entrada de variable |
| `editor` | Editor de texto enriquecido |
| `json` | Área de texto con sintaxis JSON |
En los tipos que lo soportan, un selector junto al campo alterna entre **Valor fijo** (el control propio del tipo) y **Variable** (entrada libre para inyectar un `{{$memory.x}}` o `{{$context.y}}`).
#### Filtros (solo Listar)
Constructor visual con uno o varios términos unidos por AND. Cada fila se compone de campo, operador y valor.
| Operador | Significado |
| :------------------- | :------------------------------ |
| `=`, `!=` | Igual / distinto |
| `>`, `>=`, `<`, `<=` | Comparación numérica o de fecha |
| `~` | Contiene el texto |
| `!~` | No contiene el texto |
Los términos a los que les falta el campo o el valor se ignoran al ejecutar — no bloquean la petición ni generan error; simplemente no forman parte del filtro final.
#### Ordenamiento (solo Listar)
Constructor con una fila por criterio. En cada fila eliges el campo y la dirección (ascendente o descendente). Los criterios se aplican en el orden en que están visibles.
#### Paginación (solo Listar)
Dos campos numéricos: **página** (por defecto `1`) y **por página** (por defecto `50`). Si dejas uno vacío o con un número inválido, el nodo usa el valor por defecto al ejecutar.
***
## Guardar la respuesta
Activa el toggle **Guardar respuesta** en el encabezado del panel y define un nombre de variable. La respuesta completa del servicio se almacena en esa variable y queda disponible en los nodos siguientes.
### Acceder a la respuesta sin manipulación
Usa la variable directamente en otros nodos:
```text theme={null}
{{$context.basesRespuesta}}
```
### Manipular la respuesta en un nodo Código
```javascript theme={null}
let respuesta = $context.getHttpResponse('basesRespuesta')
respuesta = respuesta.json()
const primerRegistro = respuesta.items?.[0]
$memory.set('primerRegistro', primerRegistro)
```
La llave que uses en `$context.getHttpResponse('basesRespuesta')` debe coincidir exactamente con la que configuraste en "Guardar respuesta".
***
## Respuesta de prueba
El botón **Probar** del encabezado ejecuta la petición contra Datum y abre un popover con el resultado. El popover muestra:
* **Código de estado** (por ejemplo, `200` o `404`)
* **Pestaña Cuerpo** — respuesta formateada como JSON con botón de copiar
* **Pestaña Encabezados** — encabezados de respuesta del servicio
Si cierras el popover y lo vuelves a abrir sin tocar la configuración, el nodo muestra la última respuesta en lugar de hacer una nueva llamada al servicio. En cuanto modificas cualquier campo, esa respuesta se marca como desactualizada y la próxima apertura ejecuta de nuevo.
***
## Casos de uso
Un workflow de WhatsApp que captura leads necesita registrar el teléfono y el nombre del usuario sin pasar por un endpoint externo. Mapea `{{$user.phone}}` y `{{$memory.nombre}}` a los campos de la colección `leads`, elige la operación **Crear** y el registro queda guardado sin escribir una sola línea de código.
Un workflow de atención al cliente que personaliza respuestas consulta primero la colección `clientes` filtrando por el email del usuario. El resultado se guarda en una variable y los nodos siguientes lo usan para adaptar el mensaje. Antes esto requería un nodo API con URL construida a mano; ahora son tres dropdowns y un filtro visual.
Un workflow de seguimiento de pedidos recibe un código, busca el registro con la operación **Obtener** y actualiza el campo `status` a `entregado` con la fecha tomada de `{{$context.now}}`. El mismo proceso que antes requería dos nodos API ahora se resuelve en uno solo.
# Botones
Source: https://docs.jelou.ai/guides/nodos/botones
Muestra botones interactivos para que el usuario seleccione una opción
El nodo **Botones** envía un mensaje con botones que el usuario puede tocar para elegir una opción. Es ideal para guiar la conversación por caminos específicos sin que el usuario tenga que escribir.
## Configuración general
* **Encabezado**: Título del mensaje (máximo 60 caracteres)
* **Contenido**: Mensaje principal que acompaña los botones (máximo 1,024 caracteres en WhatsApp, 640 en Facebook/Instagram)
* **Pie de página**: Texto adicional debajo del contenido (opcional)
### Opciones
Cada botón tiene:
* **Nombre de la opción**: Texto visible en el botón (máximo 20 caracteres)
* **Descripción**: Texto adicional de contexto (opcional, máximo 72 caracteres)
Puedes agregar hasta **3 botones**. Para mostrar más opciones, usa el nodo [Lista](/guides/nodos/lista).
### Tipos de botón
| Tipo | Comportamiento |
| :----------- | :--------------------------------------------------------- |
| **Postback** | Envía un payload al flujo y continúa por la ruta conectada |
| **URL** | Abre una página web en el navegador |
| **Teléfono** | Inicia una llamada telefónica |
### Opciones dinámicas
Si las opciones vienen de un dato variable (por ejemplo, una lista de productos de tu API), puedes activar el modo **dinámico** en lugar de definirlas manualmente.
Configura:
* **Variable fuente**: La variable que contiene la lista (por ejemplo, `{{$memory.productos}}`)
* **Plantilla de etiqueta**: Cómo se muestra cada opción (por ejemplo, `{{$item.nombre}} - ${{$item.precio}}`)
* **Plantilla de descripción**: Texto adicional por opción (por ejemplo, `{{$item.descripcion}}`)
Plantillas predefinidas disponibles: Lista simple, Productos, Horarios disponibles, Sucursales.
## Variables en mensajes
Puedes usar variables en el encabezado y contenido:
```
Encabezado: Hola {{$user.names}}
Contenido: Elige una opción para {{$memory.categoria}}
```
## Configuración avanzada
### Selección obligatoria
Cuando está activada, el usuario **debe** tocar un botón para continuar. Si escribe texto libre, verá un mensaje de error personalizable (máximo 250 caracteres).
### Variable de respuesta
Guarda la opción que el usuario seleccionó en una variable de memoria para usarla más adelante en el flujo.
**Cómo configurarlo:**
1. Activa el interruptor **Guardar respuesta**.
2. Escribe el nombre de la variable (por ejemplo, `departamento`).
#### Valor guardado con opciones estáticas
Cuando los botones están definidos manualmente, se guarda el **payload** del botón elegido como texto plano.
Ejemplo con estos botones:
| Botón | Payload |
| :-------------- | :------------ |
| Ventas | `ventas` |
| Soporte Técnico | `soporte` |
| Facturación | `facturacion` |
Si el usuario toca **Soporte Técnico**:
```javascript theme={null}
// {{$memory.departamento}} contiene:
const departamento = "soporte";
```
#### Valor guardado con opciones dinámicas
Cuando los botones se generan desde una variable fuente, se guarda el **objeto completo** del array al que pertenece la opción seleccionada.
Supón que `{{$memory.departamentos}}` contiene:
```json theme={null}
[
{ "id": "dep1", "nombre": "Ventas", "correo": "ventas@empresa.com" },
{ "id": "dep2", "nombre": "Soporte", "correo": "soporte@empresa.com" },
{ "id": "dep3", "nombre": "Facturación", "correo": "facturacion@empresa.com" }
]
```
Si el usuario toca **Soporte**, la variable queda con el objeto completo:
```javascript theme={null}
// {{$memory.departamento}} contiene el objeto completo:
const departamento = {
id: "dep2",
nombre: "Soporte",
correo: "soporte@empresa.com"
};
```
Puedes acceder a cada propiedad del objeto en nodos posteriores:
```javascript theme={null}
// Acceso a propiedades de {{$memory.departamento}}:
departamento.nombre; // "Soporte"
departamento.correo; // "soporte@empresa.com"
departamento.id; // "dep2"
```
#### Casos de uso
Conecta un nodo [Condicional](/guides/nodos/condicional) y crea una rama por cada payload:
```
Si {{$memory.departamento}} = "ventas" → rama Ventas
Si {{$memory.departamento}} = "soporte" → rama Soporte
Si {{$memory.departamento}} = "facturacion" → rama Facturación
```
Con el objeto completo guardado, puedes usarlo directamente en mensajes o nodos posteriores sin nuevas consultas:
```
Texto: "Te conectaré con el equipo de {{$memory.departamento.nombre}}.
Escríbeles a {{$memory.departamento.correo}}"
```
Pasa la selección como contexto al nodo [AI Agent](/guides/nodos/ai-agent):
```
El usuario seleccionó el departamento: {{$memory.departamento.nombre}}.
Correo de contacto: {{$memory.departamento.correo}}.
Responde con información específica para ese departamento.
```
Usa un nodo [API](/guides/nodos/api) o [Datum](/guides/nodos/datum) para guardar la selección:
```json theme={null}
{
"userId": "{{$user.id}}",
"departamentoId": "{{$memory.departamento.id}}",
"departamentoNombre": "{{$memory.departamento.nombre}}",
"timestamp": "{{$context.timestamp}}"
}
```
### Botón de un solo uso
Tras la primera selección, los botones se desactivan. Puedes configurar qué sucede después:
* **Enviar texto**: Muestra un mensaje informativo
* **Redirigir a flujo**: Lleva al usuario a otro flujo
### Botón expira
Si el usuario no selecciona ningún botón dentro del tiempo configurado en tu organización:
* **Enviar texto**: Muestra un mensaje de expiración
* **Redirigir a flujo**: Lleva al usuario a otro flujo
# Call to Action
Source: https://docs.jelou.ai/guides/nodos/call-to-action
Envía un mensaje con un botón que abre una URL externa
El nodo **Call to Action** envía un mensaje con un botón que, al tocarlo, abre una página web. Es ideal para dirigir al usuario a un sitio externo: una tienda, un formulario, una página de pago o cualquier URL.
## Configuración
| Campo | Descripción | Límite |
| :------------------- | :----------------------------------- | :--------------- |
| **Encabezado** | Título del mensaje | 60 caracteres |
| **Contenido** | Mensaje principal | 1,024 caracteres |
| **Pie de página** | Texto adicional debajo del contenido | 60 caracteres |
| **Nombre del botón** | Texto del botón CTA | 18 caracteres |
| **URL** | Dirección web que se abrirá | 4,096 caracteres |
Todos los campos son requeridos. Los campos de texto soportan variables.
## Webviews en WhatsApp
El comportamiento de apertura de la URL depende del estado de verificación de tu cuenta de Meta:
* **Cuenta verificada**: La URL se abre dentro de un webview integrado en WhatsApp, manteniendo al usuario dentro de la app.
* **Cuenta no verificada**: La URL se abre en el navegador del dispositivo.
# Carrusel
Source: https://docs.jelou.ai/guides/nodos/carrusel
Envía un conjunto de tarjetas deslizables con imagen o video, texto y botones en WhatsApp
El nodo **Carrusel** envía un conjunto de tarjetas que el usuario puede deslizar horizontalmente. Cada tarjeta combina una imagen o video, texto y hasta dos botones. Es ideal para mostrar catálogos de productos, opciones de servicio o cualquier lista visual donde el orden importa.
El Carrusel está disponible únicamente en canales de **WhatsApp Cloud**. Otros proveedores de WhatsApp y otros canales no lo soportan.
## Configuración general
* **Título del carrusel**: Texto principal que acompaña al carrusel (requerido, máximo 1,024 caracteres). Puedes escribir texto libre, agregar variables como `{{$user.names}}` o `{{$memory.categoria}}`, o combinar ambos.
* **Tipo de encabezado para tarjetas**: **Imagen** o **Video**. Aplica a todas las tarjetas del carrusel; no puede mezclarse.
Cambiar el **Tipo de encabezado para tarjetas** (Imagen ↔ Video) borra los archivos multimedia ya cargados en todas las tarjetas. Confirma el tipo antes de subir el contenido.
## Tarjetas
Cada carrusel admite entre **2 y 10 tarjetas**.
Cada tarjeta tiene:
* **Media**: Imagen o video que se muestra en el encabezado. Puedes subir el archivo o pegar una URL. El tipo debe coincidir con el **Tipo de encabezado para tarjetas** elegido.
* **Texto de la tarjeta**: Descripción corta que aparece debajo del media.
* **Botones**: Acciones que el usuario puede tocar.
### Vistas del panel
Puedes alternar entre dos vistas con el ícono a la derecha del encabezado **Tarjetas**:
| Vista | Cuándo usarla |
| :------------ | :------------------------------------------------------------ |
| **Expandida** | Editar el contenido de una o varias tarjetas al mismo tiempo. |
| **Compacta** | Reordenar tarjetas o eliminarlas rápidamente. |
Arrastra el ícono de la izquierda de cada tarjeta para cambiar el orden. La cuenta actual se muestra abajo a la derecha (por ejemplo, `4/10`).
## Botones
Cada tarjeta admite hasta **2 botones** de estos tipos:
| Tipo | Comportamiento |
| :----------------------- | :------------------------------------------------------------------- |
| **Respuesta** (postback) | Envía un payload al flujo y continúa por la ruta conectada al botón. |
| **Web URL** | Abre una URL externa en el navegador. |
Puedes reordenar los botones dentro de una tarjeta arrastrándolos.
Todas las tarjetas del carrusel deben tener **el mismo número de botones** y **el mismo tipo de botón**. La primera tarjeta define la estructura y el resto la hereda.
Además, los botones de tipo **Web URL** admiten **1 por tarjeta** como máximo. Los botones de tipo **Respuesta** admiten hasta dos.
## Casos de uso
Un workflow que consulta el inventario de una tienda y muestra los productos como tarjetas con foto, nombre, precio y un botón **Comprar** que abre la URL del producto. Sirve para respuestas de un AI Agent o para campañas con contenido dinámico.
Un workflow de atención donde el carrusel muestra cada servicio (Cita médica, Facturación, Reclamos) con una imagen ilustrativa y un botón **Elegir** de tipo Respuesta. El payload seleccionado se guarda en memoria y un nodo Condicional enruta la conversación al equipo correcto.
Un workflow de post-venta que muestra las sucursales cercanas con foto de la fachada, dirección y un botón **Cómo llegar** que abre Google Maps. Las tarjetas se generan a partir de una consulta a una base de datos o a una API.
Un carrusel con una tarjeta por plan de suscripción, video demostrativo, resumen del plan y un botón **Contratar** que dispara la siguiente parte del flujo. El plan elegido se guarda en memoria para personalizar el cobro y la confirmación.
Un workflow de un banco que muestra su portafolio de tarjetas de crédito: cada tarjeta del carrusel usa la imagen del plástico, un resumen de beneficios y dos botones de tipo Respuesta — **Ver beneficios**, que despliega el detalle dentro del chat, y **Solicitar**, que continúa el flujo de solicitud. La tarjeta elegida se guarda en memoria para prellenar los datos del cliente.
## Nodos relacionados
Hasta 3 botones interactivos sin tarjetas.
Menú desplegable con hasta 10 opciones.
Botón único que abre una URL externa.
Plantillas pre-aprobadas de WhatsApp con soporte de carrusel.
# Código
Source: https://docs.jelou.ai/guides/nodos/codigo
Ejecuta código JavaScript personalizado para transformar datos y aplicar lógica compleja
El nodo **Código** te permite ejecutar JavaScript personalizado dentro de tu flujo. Es útil para operaciones que no se pueden lograr con los nodos estándar: transformar datos, hacer cálculos, manipular textos, firmar peticiones o integrar lógica de negocio personalizada.
El panel del nodo se organiza en tres pestañas:
* **Configuración** — el editor de JavaScript, formateo, cheat sheet, autocompletado y la sección DLP.
* **Avanzado** — el manejo del error por defecto para decidir a dónde va el flujo si la ejecución del script falla.
* **Eventos** — registra eventos de seguimiento que se disparan cuando el nodo se ejecuta, para alimentar tus métricas.
## Configuración del editor
El editor de código incluye herramientas para agilizar la escritura y depuración:
* **Formatear**: aplica Prettier al script y agrega automáticamente `await` en los métodos asincrónicos que lo requieran.
* **Cheat sheet**: abre una referencia rápida con los objetos globales disponibles (`$context`, `$memory`, `$user`, `$message`, `$input`, `$output`, `$utils`) y sus métodos.
* **Autocompletado**: al escribir `$`, el editor sugiere las funciones y namespaces disponibles con su descripción.
* **Modo claro / oscuro**: cambia el tema del editor desde el selector en la esquina superior derecha.
* **Validación en vivo**: el editor marca errores comunes de lint y advierte cuando falta `await` en un método asincrónico.
## Acceso a variables
Dentro del nodo Código puedes leer y escribir variables del flujo usando objetos globales predefinidos.
### Contexto (`$context`)
Variables que persisten solo durante la ejecución actual del flujo:
```javascript theme={null}
// Leer
const nombre = $context.get('nombre')
const nombre = $context.get('nombre', 'Juan') // con valor por defecto
// Escribir
$context.set('ultimoPedido.estado', 'entregado')
$context.set('usuario', { nombre: 'Juan', plan: 'gold' })
```
### Memoria (`$memory`)
Variables que persisten entre workflows, tools y nodos. Soportan tiempo de vida (TTL):
```javascript theme={null}
// Primitivos (string, number, boolean) - TTL opcional
$memory.set('nombre', 'Juan')
$memory.set('intentos', 3, 3600) // expira en 1 hora
// JSON - TTL requerido (en segundos, máximo 86,400 = 1 día)
$memory.setJson('usuario', { nombre: 'Juan', plan: 'gold' }, 86400)
// Archivos - async, TTL requerido (máximo 604,800 = 1 semana)
await $memory.setFile('comprobante', base64String, 604800, 'application/pdf')
```
#### Lectura
```javascript theme={null}
const nombre = $memory.get('nombre')
const usuario = $memory.getJson('usuario')
const url = $memory.getFile('comprobante').toUrl() // URL temporal
const base64 = await $memory.getFile('comprobante').toBase64()
```
#### Eliminación
```javascript theme={null}
$memory.delete('temporal')
$memory.delete(['tmp1', 'tmp2', 'cache']) // múltiples claves
```
#### Límites por tipo de dato
| Tipo | Tamaño máximo | TTL | TTL máximo |
| :---------- | :------------- | :-------- | :------------------ |
| **String** | 255 caracteres | Opcional | — |
| **Number** | 15 dígitos | Opcional | — |
| **Boolean** | — | Opcional | — |
| **JSON** | 15 KB | Requerido | 86,400s (1 día) |
| **File** | 10 MB | Requerido | 604,800s (1 semana) |
Los métodos `$memory.setFile()`, `$memory.getFile().toBase64()` y `$memory.getFile().toRaw()` son **asincrónicos**. Debes usar `await` al llamarlos.
### Migración desde nodos legacy
1. Crea un nuevo nodo **Código (Memory V2)**.
2. Copia y pega el script del nodo legacy.
3. Usa los nuevos métodos disponibles (`$memory.setJson`, `$memory.setFile`, etc.) si lo necesitas.
### Usuario (`$user`)
Información del contacto que interactúa con tu flujo:
```javascript theme={null}
const userId = $user.get('id')
const userName = $user.get('names')
```
### Mensaje (`$message`)
El último mensaje que envió el usuario:
```javascript theme={null}
const texto = $message.get('text')
const tipo = $message.get('type') // TEXT, IMAGE, AUDIO, VIDEO, LOCATION, FILE
const urlAdjunto = $message.get('mediaUrl')
const lat = $message.get('lat')
const lng = $message.get('lng')
```
### Input/Output (`$input`, `$output`)
Para comunicación entre tools y workflows:
```javascript theme={null}
const ciudad = $input.get('ciudad', 'Quito') // con valor por defecto
$output.set('data', resultado)
```
### Respuestas HTTP
Si guardaste la respuesta de un nodo API:
```javascript theme={null}
let apiResponse = $context.getHttpResponse('apiResponse')
apiResponse = apiResponse.json()
$memory.set('nombre', apiResponse.data.usuario.nombre)
```
La llave en `$context.getHttpResponse()` debe coincidir exactamente con la que configuraste en el campo "Guardar respuesta" del nodo API.
## Utilidades disponibles (`$utils`)
### Logger
Registra valores para depuración en formato clave-valor. Los logs aparecen en el historial de ejecuciones:
```javascript theme={null}
// Primitivos se almacenan tal cual
$utils.logger.log('userId', 1245)
$utils.logger.log('estado', 'activo')
// Objetos y arrays se convierten a string (truncado a 2,000 caracteres)
$utils.logger.log('orderData', { id: 1, nombre: 'cristian' })
```
Cada nodo permite un máximo de **5 registros**. El valor de cada registro tiene un límite de **2,000 caracteres**.
### Crypto
Funciones criptográficas estándar:
```javascript theme={null}
$utils.crypto.createHash('sha256')
$utils.crypto.createHmac('sha256', key)
$utils.crypto.randomBytes(32)
```
### Lodash
Utilidades para manipular arrays, objetos y strings:
```javascript theme={null}
$utils._.get(objeto, 'ruta.anidada', valorPorDefecto)
$utils._.uniq(array)
$utils._.chunk(array, tamano)
$utils._.merge(objeto1, objeto2)
```
## DLP (Prevención de pérdida de datos)
Al final de la pestaña **Configuración** encuentras la sección **DLP**, que enmascara automáticamente los datos sensibles que el nodo escribe en los logs. Activa el toggle **"Habilitar DLP"** para elegir qué tipos de información se enmascaran (número de tarjeta, correo, teléfono, etc.). Es la protección recomendada cuando tu script trabaja con datos personales, financieros o de identidad.
## Pestaña Avanzado
Configura el **camino de error por defecto**. Si la ejecución del script lanza una excepción no capturada (por ejemplo, un JSON inválido o un `await` faltante), el flujo continúa por el camino configurado en lugar de detenerse. Úsalo para mostrar un mensaje amigable al usuario o para escalar a un humano cuando el script falla.
## Pestaña Eventos
Registra eventos de seguimiento que se disparan cuando el nodo se ejecuta. Sirven para alimentar tus métricas y correlacionar el paso por este nodo con conversiones o abandonos.
Consulta la guía de [Eventos](/guides/observabilidad/eventos) para conocer cómo configurarlos y consumirlos.
## Casos de uso
Un workflow donde un nodo **AI Agent** genera una respuesta estructurada (por ejemplo, un JSON con `intencion`, `producto` y `sentimiento`) y la guarda en contexto con la opción **Guardar respuesta** activada. El nodo Código posterior lee ese objeto, valida sus campos y los deja en variables independientes para que los nodos siguientes puedan reaccionar sin volver a llamar al modelo.
```javascript theme={null}
// El AI Agent guardó su respuesta como $context.ai_agent_response
const respuesta = $context.get('ai_agent_response')
// Los campos vienen en el objeto structured output del agente
const intencion = $utils._.get(respuesta, 'intencion', 'desconocida')
const producto = $utils._.get(respuesta, 'producto.nombre')
const sentimiento = $utils._.get(respuesta, 'sentimiento', 'neutral')
// Deja cada campo listo para los nodos siguientes
$context.set('intencion', intencion)
$context.set('producto', producto)
$context.set('sentimiento', sentimiento)
// Persiste en memoria lo que quieras reutilizar en próximas conversaciones
$memory.setJson('ultimaInteraccion', { intencion, producto, sentimiento }, 86400)
$utils.logger.log('intencion', intencion)
$utils.logger.log('producto', producto)
```
Usa `$utils._.get(obj, 'ruta.anidada', valorPorDefecto)` para leer campos anidados sin romper si el modelo omite alguno.
Un workflow donde un nodo API devuelve una lista de pedidos con nombres de campo en inglés y los nodos siguientes esperan español. El nodo Código transforma la respuesta antes de continuar.
```javascript theme={null}
const response = $context.getHttpResponse('pedidosApi').json()
const pedidos = response.data.map((p) => ({
numero: p.order_number,
cliente: p.customer_name,
total: p.total_amount,
}))
$memory.setJson('pedidos', pedidos, 3600)
$context.set('cantidadPedidos', pedidos.length)
```
Un workflow donde un nodo **Pregunta** anterior recolectó el correo del usuario y lo guardó en contexto. El nodo Código lo valida y lo enmascara antes de guardarlo o mostrarlo, y devuelve solo lo estrictamente necesario.
```javascript theme={null}
const email = $context.get('email')
const emailValido = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
if (!emailValido) {
$context.set('emailValido', false)
return
}
const [usuario, dominio] = email.split('@')
const enmascarado = `${usuario.slice(0, 2)}***@${dominio}`
$context.set('emailValido', true)
$context.set('emailEnmascarado', enmascarado)
$memory.set('email', email, 604800) // 1 semana
```
Un workflow que necesita procesar una lista grande (por ejemplo, 300 contactos) llamando a una API que solo acepta 50 por request. El nodo Código divide la lista en lotes y los deja en memoria para que un nodo posterior itere sobre ellos.
```javascript theme={null}
const contactos = $memory.getJson('contactos')
const lotes = $utils._.chunk(contactos, 50)
$memory.setJson('lotesContactos', lotes, 3600)
$context.set('totalLotes', lotes.length)
$context.set('loteActual', 0)
```
**Tipos MIME permitidos para `$memory.setFile()`:**
| Categoría | Tipos |
| :--------- | :---------------------------------------------------------------------- |
| Texto | `text/plain` |
| JSON/XML | `application/json`, `application/xml`, `text/xml` |
| Imágenes | `image/jpeg`, `image/png`, `image/gif` |
| Videos | `video/mp4`, `video/ogg`, `video/webm`, `video/x-msvideo`, `video/mpeg` |
| Audios | `audio/mpeg`, `audio/wav`, `audio/ogg`, `audio/aac`, `audio/flac` |
| Documentos | `application/pdf` |
## Siguientes pasos
Configura un agente y guarda su respuesta en contexto para procesarla con Código.
Llama a servicios externos y lee la respuesta con `$context.getHttpResponse()`.
Cómo funcionan las variables temporales del flujo que lees con `$context`.
Persiste datos entre workflows, tools y conversaciones.
# Condicional
Source: https://docs.jelou.ai/guides/nodos/condicional
Dirige la conversación por diferentes caminos según las respuestas o datos del usuario
El nodo **Condicional** funciona como un punto de decisión dentro de tu flujo: evalúa información que ya tienes del usuario y decide qué camino tomar. Piensa en él como una bifurcación en el camino — dependiendo de lo que responda o de los datos que tenga el usuario, la conversación seguirá una ruta u otra.
El panel del nodo se organiza en dos pestañas:
* **General** — defines los caminos, sus reglas y la protección de datos sensibles (DLP).
* **Eventos** — registras eventos de seguimiento que se disparan cuando el nodo se ejecuta, para alimentar tus métricas.
## Concepto clave: caminos y reglas
Antes de configurar, es importante entender dos conceptos:
* **Camino**: es una ruta que la conversación puede seguir. Cada camino tiene un nombre descriptivo y una o más reglas que deben cumplirse para que se active.
* **Regla**: es una condición individual que compara una variable con un valor. Por ejemplo: *"la edad del usuario es mayor o igual a 18"*.
Si un camino tiene **varias reglas**, **todas** deben cumplirse para que ese camino se active (lógica AND). Si necesitas que se active cuando **cualquiera** de las condiciones se cumpla, crea **caminos separados** para cada una.
### Camino por defecto: "Si no"
Todo nodo Condicional incluye automáticamente un camino llamado **"Si no"** (fallback). Este camino se activa cuando **ninguno** de los otros caminos se cumple. Es tu red de seguridad para garantizar que la conversación siempre tenga un camino a seguir.
***
## Configuración paso a paso
Haz clic en **"Nuevo camino"** para agregar tu primera condición. Asígnale un nombre descriptivo que te ayude a identificar rápidamente qué evalúa (por ejemplo: "Mayor de edad", "Cliente premium", "Horario laboral").
Cada regla tiene tres partes:
1. **Variable** — el dato que quieres evaluar (por ejemplo, `{{$memory.edad}}`)
2. **Operador** — cómo quieres comparar ese dato (por ejemplo, "Mayor o igual")
3. **Valor** — contra qué lo comparas (por ejemplo, `18`)
Si necesitas que varias condiciones se cumplan al mismo tiempo, haz clic en **"+"** dentro del mismo camino para agregar reglas adicionales. Todas las reglas dentro de un camino se evalúan con lógica **AND** (todas deben ser verdaderas).
Repite el proceso para cada ruta alternativa que necesites. Cada camino se evalúa en orden: el primero que se cumpla será el que se ejecute.
Cada camino (incluyendo "Si no") tiene un punto de conexión a la derecha. Arrastra una línea desde cada punto hacia el nodo que debe ejecutarse en esa ruta.
***
## Operadores disponibles
Los operadores definen **cómo** se compara la variable con el valor. Se organizan en cuatro categorías:
### Comparación de igualdad
| Operador | Descripción | Ejemplo |
| :----------- | :---------------------------- | :------------------------------------------ |
| **Igual** | El valor es exactamente igual | `{{$memory.status}}` igual a `activo` |
| **No igual** | El valor es diferente | `{{$memory.status}}` no igual a `cancelado` |
### Comparación numérica
| Operador | Descripción | Ejemplo |
| :---------------- | :------------------------------ | :----------------------------------------- |
| **Mayor** | El valor es estrictamente mayor | `{{$memory.edad}}` mayor que `18` |
| **Mayor o igual** | El valor es mayor o igual | `{{$memory.puntaje}}` mayor o igual a `70` |
| **Menor** | El valor es estrictamente menor | `{{$memory.intentos}}` menor que `3` |
| **Menor o igual** | El valor es menor o igual | `{{$memory.deuda}}` menor o igual a `0` |
### Búsqueda en texto
| Operador | Descripción | Ejemplo |
| :----------------- | :------------------------------------- | :------------------------------------------- |
| **Contiene** | El texto incluye la palabra o frase | `{{$message.text}}` contiene `ayuda` |
| **No contiene** | El texto no incluye la palabra o frase | `{{$message.text}}` no contiene `cancelar` |
| **Empieza con** | El texto comienza con el valor | `{{$user.email}}` empieza con `admin` |
| **No empieza con** | El texto no comienza con el valor | `{{$user.phone}}` no empieza con `+593` |
| **Termina con** | El texto termina con el valor | `{{$user.email}}` termina con `@empresa.com` |
### Validación de tipo (Es del tipo / No es del tipo)
Los operadores **Es del tipo** y **No es del tipo** permiten verificar si una variable corresponde a un tipo de dato específico. En lugar de escribir un valor, seleccionas el tipo desde una lista:
| Tipo | Qué valida |
| :--------------- | :------------------------------- |
| **Alfanumérico** | Letras y números combinados |
| **Alfabético** | Solo letras |
| **Nombre(s)** | Formato de nombre de persona |
| **Número** | Solo valores numéricos |
| **Email** | Formato de correo electrónico |
| **Cédula** | Número de documento de identidad |
| **URL** | Formato de enlace web |
| **Fecha** | Formato de fecha |
| **Imagen** | Archivo de imagen |
| **RUC** | Registro Único de Contribuyente |
| **Ubicación** | Coordenadas o dirección |
**Ejemplo**: `{{$memory.correo}}` **Es del tipo** → **Email** verifica que lo que escribió el usuario tenga formato de correo electrónico.
### Verificación de vacío
| Operador | Descripción | Ejemplo |
| :----------- | :---------------------------------- | :--------------------------------- |
| **Vacío** | La variable no tiene valor asignado | `{{$memory.nombre}}` está vacío |
| **No vacío** | La variable tiene algún valor | `{{$memory.nombre}}` no está vacío |
Los operadores **Vacío** y **No vacío** no requieren un valor de comparación — solo evalúan si la variable tiene o no contenido.
### Expresiones regulares (Regex)
| Operador | Descripción | Ejemplo |
| :------------------ | :------------------------------ | :----------------------------------------- |
| **Regex match** | Coincide con el patrón regex | `{{$message.text}}` regex match `^\d{10}$` |
| **Regex not match** | No coincide con el patrón regex | `{{$message.text}}` regex not match `[<>]` |
Las expresiones regulares son una herramienta avanzada. Si no estás familiarizado con regex, los demás operadores cubren la mayoría de los escenarios comunes.
***
## Lógica de evaluación
El nodo Condicional evalúa los caminos **de arriba hacia abajo**, en el orden en que aparecen en el panel. El primer camino cuyas reglas se cumplan será el que se ejecute.
```
¿Se cumple Camino 1?
└─ Sí → Ejecuta la ruta del Camino 1
└─ No → ¿Se cumple Camino 2?
└─ Sí → Ejecuta la ruta del Camino 2
└─ No → ¿Se cumple Camino 3?
└─ Sí → Ejecuta la ruta del Camino 3
└─ No → Ejecuta la ruta "Si no" (fallback)
```
Puedes **reordenar los caminos** arrastrándolos con el ícono de agarre (⠿) que aparece a la izquierda de cada camino. Esto es importante porque el orden afecta cuál se evalúa primero.
### Lógica AND vs OR
| Lo que necesitas | Cómo configurarlo |
| :------------------------------------------------ | :------------------------------------------------- |
| Que se cumplan **todas** las condiciones a la vez | Agrega varias reglas **dentro del mismo camino** |
| Que se cumpla **cualquiera** de las condiciones | Crea **caminos separados**, uno por cada condición |
***
## Modos de vista
El panel de configuración ofrece dos modos de visualización que puedes alternar con el botón de vista en la esquina superior:
* **Vista expandida**: muestra cada regla como una tarjeta con etiquetas descriptivas (Variable, Operador, Valor). Ideal cuando estás configurando condiciones por primera vez.
* **Vista compacta**: muestra cada regla en una sola línea horizontal. Ideal cuando ya conoces las condiciones y quieres una vista más rápida del conjunto.
Además de reordenar caminos, puedes eliminar cualquiera con el ícono de papelera del encabezado de cada tarjeta y agregar reglas al camino con el botón **+**. Al eliminar un camino se desconecta automáticamente el nodo enlazado a esa salida.
***
## DLP (Prevención de pérdida de datos)
Al final del panel General encuentras la sección **DLP**, que enmascara automáticamente los datos sensibles que atraviesan el nodo antes de que se guarden en logs o se envíen a integraciones. Activa el toggle **"Habilitar DLP"** para acceder al selector de tipos de información.
Activa el toggle para mostrar el selector de tipos.
Elige qué tipos deben detectarse y enmascararse:
| Tipo de dato |
| :--------------------------- |
| Número de tarjeta de crédito |
| Track de tarjeta de crédito |
| Correo electrónico |
| Número de cuenta financiera |
| Dirección IP |
| Ubicación |
| Coordenadas geográficas |
| Número de teléfono |
| Fecha |
| Nombre de persona |
| DNI de Perú |
| Contraseña |
| Hash de contraseña débil |
| Token de autenticación |
| Cookie HTTP |
| PIN de acceso |
**Número de tarjeta de crédito** siempre está seleccionado y no puede deshabilitarse: es el tipo mínimo protegido cuando DLP está activo.
DLP se aplica sobre los valores que evalúa el nodo. No modifica el mensaje que ve el usuario ni el contenido de tu memoria — solo enmascara la información sensible en los registros que quedan de la ejecución del nodo.
***
## Eventos
La pestaña **Eventos** te permite registrar hasta **10 eventos de seguimiento** por nodo, cada uno con un nombre y hasta **5 propiedades personalizadas** en el payload. Cada vez que el Condicional se ejecuta, los eventos activos disparan y alimentan tus paneles de **Métricas**.
Haz clic en **"Agregar evento"** y escribe un nombre en **snake\_case** (por ejemplo, `ruta_premium_activada`). Si el nombre no cumple el formato, verás el error **"Debe ser snake\_case"**.
En **"Propiedades del evento"** agrega hasta 5 pares de clave/valor. El valor puede ser texto explícito o una variable evaluable como `{{$context.user_id}}` o `{{$memory.tipo_cliente}}`.
Cada evento tiene un toggle independiente. Los eventos desactivados no disparan pero se conservan para reactivarlos después. Desde el menú de cada ítem puedes editarlos o eliminarlos.
Consulta la [guía completa de eventos de nodo](/guides/observabilidad/eventos) para conocer límites, buenas prácticas y ejemplos de análisis.
***
## Ejemplos prácticos
Un flujo que necesita verificar si el usuario es mayor de edad:
| Camino | Regla | Variable | Operador | Valor |
| :------------ | :---- | :----------------- | :------------ | :---- |
| Mayor de edad | 1 | `{{$memory.edad}}` | Mayor o igual | `18` |
| **Si no** | — | — | — | — |
El camino "Mayor de edad" se activa si la edad es 18 o más. Cualquier otro caso sigue por "Si no".
Un flujo que dirige a clientes premium de Colombia a un flujo especial:
| Camino | Regla | Variable | Operador | Valor |
| :--------------- | :---- | :------------------------- | :------- | :--------- |
| Premium Colombia | 1 | `{{$user.country}}` | Igual | `Colombia` |
| | 2 | `{{$memory.tipo_cliente}}` | Igual | `premium` |
| Colombia regular | 1 | `{{$user.country}}` | Igual | `Colombia` |
| **Si no** | — | — | — | — |
En "Premium Colombia", **ambas** reglas deben cumplirse (AND): el país debe ser Colombia **y** el tipo de cliente debe ser premium. Si solo se cumple la primera, el flujo pasa a evaluar "Colombia regular".
Un flujo que valida si el usuario ingresó un correo electrónico:
| Camino | Regla | Variable | Operador | Tipo |
| :----------- | :---- | :------------------- | :---------- | :---- |
| Email válido | 1 | `{{$memory.correo}}` | Es del tipo | Email |
| **Si no** | — | — | — | — |
Si el dato tiene formato de correo, sigue por "Email válido". Si no, puedes usar el camino "Si no" para pedir al usuario que lo ingrese nuevamente.
Un flujo que detecta palabras clave en el mensaje del usuario:
| Camino | Regla | Variable | Operador | Valor |
| :-------------- | :---- | :------------------ | :------- | :--------- |
| Quiere cancelar | 1 | `{{$message.text}}` | Contiene | `cancelar` |
| Quiere ayuda | 1 | `{{$message.text}}` | Contiene | `ayuda` |
| Saludo | 1 | `{{$message.text}}` | Contiene | `hola` |
| **Si no** | — | — | — | — |
Cada camino es independiente (lógica OR): si el mensaje contiene "cancelar", sigue la primera ruta; si contiene "ayuda", la segunda, y así sucesivamente.
# Contacto
Source: https://docs.jelou.ai/guides/nodos/contacto
Comparte información de contacto estructurada con el usuario
El nodo **Contacto** envía una tarjeta de contacto al usuario con información estructurada como nombre, teléfono, correo y dirección. El usuario puede guardar el contacto directamente en su dispositivo.
## Configuración
### Datos básicos
| Campo | Descripción | Límite |
| :------------ | :---------------------------- | :------------- |
| **Nombres** | Nombres del contacto | 127 caracteres |
| **Apellidos** | Apellidos del contacto | 127 caracteres |
| **Cargo** | Posición o título profesional | 256 caracteres |
| **Compañía** | Nombre de la empresa | 256 caracteres |
### Secciones expandibles
Cada sección permite agregar múltiples entradas clasificadas como **Personal** o **Trabajo**:
* **Teléfonos**: Uno o más números de teléfono
* **Correos electrónicos**: Una o más direcciones de email
* **Direcciones**: Una o más direcciones físicas
* **Sitios web**: Una o más URLs
Puedes agregar máximo una entrada de cada tipo (Personal/Trabajo) por sección.
# Nodo Datum
Source: https://docs.jelou.ai/guides/nodos/datum
Conecta tus flujos de Brain Studio con tus bases de datos de Datum para leer y escribir registros
El **nodo Datum** te permite interactuar con tus colecciones de Datum directamente desde los flujos de Brain Studio. Puedes leer, crear, actualizar y eliminar registros como parte de tus automatizaciones.
Para conocer todas las funcionalidades de Datum (colecciones, importaciones, API Keys, triggers y MCP), consulta la [documentación completa de Datum](/guides/datum/introduccion).
## ¿Qué es Datum?
**Datum** es la solución de base de datos gestionada de Jelou. No necesitas configurar servidores, gestionar réplicas ni preocuparte por el autoescalamiento — todo está gestionado automáticamente.
Crea y gestiona tus tablas de datos
CRUD, filtros, búsqueda y exportación
Importa datos desde CSV o XLSX
Acceso programático a tus datos
Webhooks automáticos por eventos
Conecta con ChatGPT, Claude, Cursor y VS Code
Métricas de rendimiento y logs de actividad
## Tipos de datos soportados
| Tipo | Descripción |
| ------------ | ----------------------------------------- |
| **Text** | Cadenas de texto (máximo 5000 caracteres) |
| **Number** | Valores numéricos (enteros o decimales) |
| **Boolean** | Valores verdadero/falso |
| **Email** | Direcciones de correo electrónico |
| **URL** | URLs con validación de formato |
| **Date** | Fechas y timestamps |
| **Select** | Selección de una lista predefinida |
| **Relation** | Referencia a registros de otra colección |
| **File** | Archivos adjuntos |
# Documento
Source: https://docs.jelou.ai/guides/nodos/documento
Envía documentos y archivos al usuario dentro de la conversación
El nodo **Documento** envía un archivo descargable al usuario como parte de la conversación.
## Configuración
* **Archivo o URL**: Sube un archivo o pega una URL pública
* **Nombre del archivo**: El nombre que verá el usuario al recibir el documento
| Canal | Nombre máximo |
| :------------ | :------------- |
| WhatsApp | 47 caracteres |
| Otros canales | 255 caracteres |
## Límites
| Formatos soportados | Tamaño máximo |
| :--------------------------------------------------------------------------------- | :------------ |
| `text/plain`, `application/pdf`, `.doc`, `.docx`, `.ppt`, `.pptx`, `.xls`, `.xlsx` | 100 MB |
Si usas una URL, debe ser públicamente accesible vía HTTPS, no debe requerir autenticación y debe apuntar directamente al archivo (no a una página HTML).
# Plantilla de WhatsApp
Source: https://docs.jelou.ai/guides/nodos/hsm
Envía plantillas de WhatsApp preaprobadas con parámetros dinámicos para personalizar tus mensajes.
El nodo de **Plantilla de WhatsApp** permite enviar mensajes previamente aprobados por Meta, los cuales cuentan con una estructura definida y campos dinámicos.
Aprende a crear y configurar tu primera plantilla de WhatsApp.
***
### ¿Cuándo usarlo?
Las plantillas son obligatorias para iniciar conversaciones fuera de la ventana de 24 horas en WhatsApp.
Casos de uso más comunes:
* Notificaciones y alertas
* Confirmaciones de pedido o cita
* Recordatorios de pago
* Campañas de marketing
***
### Configuración
#### General
Elige una plantilla desde tu portafolio de WhatsApp Business. Puedes actualizar la lista con el botón de refrescar.
También puedes crear una nueva plantilla desde el módulo de campañas. Se mostrará una previsualización de la plantilla seleccionada.
Agrega uno o más destinatarios: números de teléfono o BSUID.
Puedes usar variables dinámicas como `{{$user.phone}}`; si esa variable contiene el BSUID de un usuario (por ejemplo, recibido en un webhook entrante), el mensaje se enviará usando ese BSUID.
Si la plantilla incluye contenido multimedia, sube el archivo correspondiente:
* Imagen
* Video
* Documento
Los campos dinámicos aparecerán automáticamente al seleccionar la plantilla.
Completa cada uno con:
* Texto fijo
* Variables dinámicas
Si la plantilla incluye botones, puedes configurar su comportamiento:
* **Vincular a flujo**: redirige al usuario a otro flujo.
* **Enviar texto**: responde automáticamente con un mensaje.
***
### Opciones adicionales
* **Botón de un solo uso**: desactiva los botones después de la primera interacción del usuario.
* **Parámetros en memoria**: permite guardar información cuando el usuario interactúa con un botón. Máximo permitido: 15 parámetros.
# Imagen
Source: https://docs.jelou.ai/guides/nodos/imagen
Envía imágenes al usuario dentro de la conversación
El nodo **Imagen** envía una imagen al usuario como parte de la conversación.
## Configuración
* **Archivo o URL**: Sube un archivo de imagen o pega una URL pública
* **Caption** (opcional): Texto que aparece junto a la imagen
El caption soporta variables: `Producto: {{$memory.nombreProducto}}`
## Límites
| Formatos soportados | Tamaño máximo |
| :------------------------ | :------------ |
| `image/jpeg`, `image/png` | 5 MB |
| Canal | Caption máximo |
| :------------------- | :--------------- |
| WhatsApp | 1,024 caracteres |
| Facebook / Instagram | No soportado |
| Web | 1,024 caracteres |
Si usas una URL, debe ser públicamente accesible vía HTTPS, no debe requerir autenticación y debe apuntar directamente al archivo de imagen (no a una página HTML).
# Nodos
Source: https://docs.jelou.ai/guides/nodos/introduction
Bloques de construcción para crear flujos conversacionales en Brain Studio
Los nodos son los bloques de construcción de tus flujos en Brain Studio. Cada nodo realiza una acción específica — enviar un mensaje, hacer una pregunta, consultar un API, tomar una decisión — y se conectan entre sí para crear conversaciones completas.
## Tipos de nodos
### Inteligencia Artificial
Conversaciones autónomas impulsadas por IA
Tareas puntuales de IA con respuesta estructurada
### Mensajes
Mensajes de texto simples
Preguntas que esperan respuesta del usuario
Envío de imágenes
Envío de videos
Envío de archivos de audio
Envío de documentos y archivos
Envío de stickers
Tarjetas de contacto
Envío de ubicaciones en el mapa
Solicitar la ubicación del usuario
### Interactivos
Botones interactivos de selección
Hasta 3 botones de enlace (Facebook e Instagram)
Listas desplegables de opciones
Botones que abren URLs externas
Formularios nativos de WhatsApp
Plantillas pre-aprobadas de WhatsApp
### Lógica
Bifurcaciones basadas en condiciones
Guardar valores en memoria
Distribución aleatoria por porcentajes
Detener el flujo temporalmente
### Integraciones
Recibe solicitudes HTTP externas para iniciar o reanudar ejecuciones
Llamadas HTTP a servicios externos
Ejecución de JavaScript personalizado
Consulta y gestión de bases de datos
Transferencia a operadores humanos
# Atención humana con Jelou
Source: https://docs.jelou.ai/guides/nodos/jelou
Transfiere la conversación desde el workflow a la Bandeja de entrada de Jelou, con asignación directa o por cola.
El nodo **Jelou** es la opción nativa de atención humana: entrega la conversación a un operador dentro de la **Bandeja de entrada** de Jelou. Lo usas cuando tu equipo atiende directamente desde Jelou y quieres que el workflow decida cuándo escalar y a quién.
Al ejecutarse, el nodo mueve la sesión del usuario a la Bandeja de entrada y termina la ejecución del workflow. Desde allí, el operador atiende directamente. Si el usuario debe volver a un flujo automatizado, se hace derivando la conversación a otro workflow desde la Bandeja de entrada — no ocurre automáticamente al cerrar el caso.
Este nodo aparece en la barra inferior bajo **Atención humana > Jelou**. Es la misma capacidad histórica del nodo **Transferir a asesor**, empaquetada dentro del menú de handoff junto con HubSpot y Genesys.
***
## Requisitos previos
* Al menos un **operador activo** en la Bandeja de entrada.
* Si vas a asignar por equipo, tener el equipo creado en **Configuración > Equipos**.
* El canal del workflow debe estar conectado a un canal soportado por el panel (por ejemplo, WhatsApp o Web Widget). Los canales **Slack**, **Teams** y **Custom** no admiten este nodo.
***
## Configuración
El panel del nodo se organiza en tres pestañas:
* **Configuración** — define cómo se asigna la conversación al operador.
* **Avanzado** — controla el comportamiento cuando la asignación falla y el manejo de errores por defecto.
* **Eventos** — registra eventos de seguimiento para tus métricas.
### Tipo de asignación
Define cómo llega la conversación al operador:
| Tipo | Comportamiento |
| :---------- | :---------------------------------------------------------------------------------------------------------------- |
| **Directa** | La conversación entra directo a la bandeja del operador seleccionado. |
| **Cola** | La conversación entra a una cola de espera; el operador la toma manualmente desde la bandeja general o de equipo. |
### Asignar por
Determina el destinatario según el tipo elegido.
**Cuando el tipo es Directa:**
| Opción | Descripción |
| :------------ | :--------------------------------------------------------------------------------------------------- |
| **Equipo** | Se asigna a un miembro del equipo elegido. Tienen prioridad los operadores con menos chats abiertos. |
| **Operador** | Se asigna a un operador específico. |
| **Aleatorio** | Se elige al azar entre operadores activos, priorizando a los que tienen menos carga. |
**Cuando el tipo es Cola:**
| Opción | Descripción |
| :---------- | :------------------------------------------------------------------------------ |
| **General** | Cae en la cola general; cualquier operador puede tomarla. |
| **Equipo** | Cae en la cola de un equipo específico; solo los miembros de ese equipo la ven. |
### Prioridad (solo en Cola)
Un slider de **0 a 10** que define la urgencia dentro de la cola:
* **0** — Urgente. Se atiende primero.
* **10** — Prioridad más baja.
Úsalo para que casos sensibles (pagos, reclamos, VIP) se muestren arriba en la bandeja del equipo.
***
## Salidas del nodo
El nodo Jelou expone una salida de éxito y varias salidas de error, cada una conectable a un flujo distinto de recuperación:
| Salida | Cuándo se activa |
| :------------------------- | :---------------------------------------------------------------------------- |
| **Asignación exitosa** | La conversación entró correctamente al panel y quedó asignada o en cola. |
| **Operador no encontrado** | El operador o equipo configurado no existe o no está activo. |
| **Fuera de horario** | No hay operadores dentro del horario definido para el equipo. |
| **Error general** | Cualquier otro fallo de la asignación no cubierto por las salidas anteriores. |
Conecta cada salida de error a un mensaje distinto: por ejemplo, "estamos fuera de horario, escríbenos entre 9:00 y 18:00" para **Fuera de horario**, y un mensaje genérico de reintento para **Error general**.
***
## Pestaña Avanzado
### Crear conversación cuando no sea posible asignar
Toggle que controla qué pasa cuando la asignación falla:
* **Activado (por defecto)** — Se crea un registro en la bandeja **"Por recuperar"** de Monitoreo. Un supervisor puede retomar el caso manualmente.
* **Desactivado** — La conversación no queda registrada en Monitoreo si no logra asignarse.
Desactivar este toggle hace que las conversaciones no asignadas se pierdan del panel de Monitoreo. Úsalo solo si tienes un mecanismo alterno de seguimiento (por ejemplo, un webhook o una base de datos).
### Manejo de errores por defecto
Define el nodo al que debe saltar el workflow cuando ocurre un error que no tenga una salida específica conectada. Sirve como red de seguridad para no dejar la conversación sin respuesta.
***
## Casos de uso
Un workflow que atiende ventas, soporte y reclamos con **un único nodo Jelou** en lugar de tres workflows separados.
Antes del handoff, un nodo **Condicional** evalúa una variable como `{{$memory.motivo_contacto}}` y guarda el equipo destino en `{{$memory.equipo_destino}}`. Luego, en el nodo Jelou:
* **Tipo de asignación**: Directa
* **Asignar por**: Equipo
* **Equipo**: `{{$memory.equipo_destino}}`
| Camino del Condicional | Regla | Valor guardado en `{{$memory.equipo_destino}}` |
| :--------------------- | :---------------------------------------------- | :--------------------------------------------- |
| Ventas | `{{$memory.motivo_contacto}}` igual a `venta` | `Ventas` |
| Soporte | `{{$memory.motivo_contacto}}` igual a `soporte` | `Soporte Técnico` |
| Reclamos | `{{$memory.motivo_contacto}}` igual a `reclamo` | `Postventa` |
| **Si no** | — | `Atención General` |
El workflow enruta a los tres equipos con un solo nodo de handoff. Mantener la lógica en un Condicional evita duplicar el flujo por cada equipo y facilita agregar un cuarto camino después.
Un workflow que reserva un asesor específico para clientes premium y usa la cola general para el resto.
El Condicional evalúa `{{$user.plan}}`:
| Camino | Regla | Rama del handoff |
| :-------- | :--------------------------------- | :------------------------------------------------------------ |
| Premium | `{{$user.plan}}` igual a `premium` | Nodo Jelou en modo **Directa > Operador > Ana (asesora VIP)** |
| **Si no** | — | Nodo Jelou en modo **Cola > General**, prioridad 5 |
Dos nodos Jelou distintos, cada uno con su configuración, alimentados desde un único Condicional.
Un workflow que intenta primero asignar al equipo de soporte y, si falla, escala automáticamente a un supervisor.
* Primer nodo Jelou: **Directa > Equipo > Soporte Técnico**.
* La salida **Fuera de horario** se conecta a un mensaje que informa el horario y termina.
* La salida **Operador no encontrado** y **Error general** se conectan a un segundo nodo Jelou: **Directa > Operador > Supervisor de guardia**.
Con esta cadena, ningún caso queda sin atender aunque el equipo principal no esté disponible.
Un workflow de reclamos que ajusta la prioridad en la cola según lo urgente que suene el mensaje.
Un nodo **AI Agent** analiza el mensaje del usuario y guarda un score en `{{$memory.urgencia}}` (`alta`, `media`, `baja`). Luego un Condicional dirige a tres nodos Jelou distintos, todos en modo **Cola > Equipo > Reclamos**, cambiando la prioridad:
| Camino | `{{$memory.urgencia}}` | Prioridad |
| :------ | :--------------------- | :-------- |
| Urgente | `alta` | 0 |
| Normal | `media` | 5 |
| Baja | `baja` | 9 |
Los operadores ven los casos urgentes arriba de la bandeja del equipo sin necesidad de intervenir manualmente.
***
## Buenas prácticas
Cuando debas enrutar a varios operadores o equipos, prefiere **un solo nodo Jelou** alimentado por un Condicional antes que múltiples workflows paralelos. El workflow queda más simple de mantener y los cambios en la lógica de enrutamiento se hacen en un solo lugar.
Conecta siempre la salida **Fuera de horario** a un mensaje explícito con el horario de atención. Es el error más frecuente en handoffs y el que más frustra al usuario cuando queda en silencio.
Usa variables de `{{$memory}}` o `{{$user}}` para poblar el campo **Equipo** o **Operador** en lugar de valores estáticos. Así puedes decidir el destinatario dinámicamente sin tocar la configuración del nodo.
***
## Relacionados
Cómo definir caminos y reglas para enrutar antes del handoff.
Handoff hacia el Inbox de HubSpot en lugar del panel de Jelou.
Handoff hacia agentes de Genesys Cloud.
Cómo trabajan los operadores dentro del panel una vez recibida la conversación.
# Lista
Source: https://docs.jelou.ai/guides/nodos/lista
Muestra una lista desplegable de opciones para que el usuario seleccione
El nodo **Lista** envía un mensaje con un botón que, al tocarlo, despliega una lista de opciones. Permite mostrar hasta **10 opciones**, cada una con título y descripción.
En WhatsApp, las listas se muestran como un menú desplegable nativo. Es la mejor opción cuando tienes entre 4 y 10 opciones.
## Configuración general
* **Encabezado**: Título del mensaje (máximo 60 caracteres)
* **Contenido**: Mensaje principal (máximo 1,024 caracteres, requerido)
* **Nombre del botón**: Texto del botón que despliega la lista (máximo 20 caracteres)
### Opciones
Cada opción tiene:
* **Nombre de la opción**: Texto visible en la lista (máximo 24 caracteres)
* **Descripción**: Texto adicional debajo del nombre (opcional, máximo 72 caracteres)
Puedes agregar hasta **10 opciones**. Las opciones se pueden reordenar arrastrándolas y duplicar para crear variantes rápidas.
### Opciones dinámicas
Si las opciones provienen de datos variables, activa el modo **dinámico**:
* **Variable fuente**: `{{$memory.opciones}}`
* **Plantilla de etiqueta**: `{{$item.nombre}}`
* **Plantilla de descripción**: `{{$item.detalle}}`
Plantillas predefinidas: Lista simple, Productos, Horarios disponibles, Sucursales.
## Variables en mensajes
```
Encabezado: Hola {{$user.names}}
Contenido: Selecciona una de las siguientes opciones
```
## Configuración avanzada
### Selección obligatoria
Cuando está activada, el usuario **debe** elegir una opción de la lista para continuar. Si escribe texto libre, verá un mensaje de error personalizable (máximo 250 caracteres).
### Variable de respuesta
Guarda la opción que el usuario seleccionó en una variable de memoria para usarla más adelante en el flujo.
**Cómo configurarlo:**
1. Activa el interruptor **Guardar respuesta**.
2. Escribe el nombre de la variable (por ejemplo, `motivo_consulta`).
#### Valor guardado con opciones estáticas
Cuando las opciones están definidas manualmente, se guarda el **nombre** de la opción seleccionada como texto plano.
Ejemplo con estas opciones:
| Opción |
| :-------------- |
| Ventas |
| Soporte Técnico |
| Facturación |
| Devoluciones |
Si el usuario elige **Soporte Técnico**:
```javascript theme={null}
// {{$memory.motivo_consulta}} contiene:
const motivo_consulta = "Soporte Técnico";
```
#### Valor guardado con opciones dinámicas
Cuando las opciones se generan desde una variable fuente, se guarda el **objeto completo** del array al que pertenece la opción seleccionada.
Supón que `{{$memory.servicios}}` contiene:
```json theme={null}
[
{ "id": "s1", "nombre": "Ventas", "agentes": 5, "horario": "L-V 8:00-18:00" },
{ "id": "s2", "nombre": "Soporte Técnico", "agentes": 3, "horario": "L-V 9:00-17:00" },
{ "id": "s3", "nombre": "Facturación", "agentes": 2, "horario": "L-V 8:00-16:00" }
]
```
Si el usuario elige **Soporte Técnico**, la variable queda con el objeto completo:
```javascript theme={null}
// {{$memory.motivo_consulta}} contiene el objeto completo:
const motivo_consulta = {
id: "s2",
nombre: "Soporte Técnico",
agentes: 3,
horario: "L-V 9:00-17:00"
};
```
Puedes acceder a cada propiedad del objeto en nodos posteriores:
```javascript theme={null}
// Acceso a propiedades de {{$memory.motivo_consulta}}:
motivo_consulta.nombre; // "Soporte Técnico"
motivo_consulta.horario; // "L-V 9:00-17:00"
motivo_consulta.agentes; // 3
```
#### Casos de uso
Conecta un nodo [Condicional](/guides/nodos/condicional) y crea una rama por cada opción:
```
Si {{$memory.motivo_consulta}} = "Ventas" → rama Ventas
Si {{$memory.motivo_consulta}} = "Soporte Técnico" → rama Soporte
Si {{$memory.motivo_consulta}} = "Facturación" → rama Facturación
```
Con el objeto completo guardado, puedes informar al usuario con los datos exactos del servicio elegido:
```
Texto: "El área de {{$memory.motivo_consulta.nombre}} atiende
{{$memory.motivo_consulta.horario}} y cuenta con
{{$memory.motivo_consulta.agentes}} agentes disponibles."
```
Pasa el objeto al nodo [AI Agent](/guides/nodos/ai-agent) para que adapte su respuesta:
```
El usuario necesita ayuda con: {{$memory.motivo_consulta.nombre}}.
Horario de atención: {{$memory.motivo_consulta.horario}}.
Responde con información específica para ese servicio.
```
Usa un nodo [API](/guides/nodos/api) o [Datum](/guides/nodos/datum) para registrar la selección con su ID de servicio:
```json theme={null}
{
"userId": "{{$user.id}}",
"servicioId": "{{$memory.motivo_consulta.id}}",
"servicioNombre": "{{$memory.motivo_consulta.nombre}}",
"timestamp": "{{$context.timestamp}}"
}
```
### Lista expira
Si el usuario no selecciona ninguna opción dentro del tiempo configurado:
* **Enviar texto**: Muestra un mensaje de expiración
* **Redirigir a flujo**: Lleva al usuario a otro flujo
## Ejemplo
**Configuración:**
* Encabezado: `¿En qué puedo ayudarte?`
* Contenido: `Selecciona una opción de la lista`
* Nombre del botón: `Ver opciones`
* Opciones: Ventas, Soporte Técnico, Facturación, Devoluciones
# Lista numerada
Source: https://docs.jelou.ai/guides/nodos/lista-numerada
Muestra una lista numerada de opciones para que el usuario responda con el número de su elección
El nodo **Lista numerada** envía un mensaje con opciones presentadas como una lista de ítems numerados. El usuario responde escribiendo el número correspondiente a su elección.
A diferencia del nodo [Lista](/guides/nodos/lista), que usa el menú desplegable nativo de WhatsApp, la lista numerada funciona en **todos los canales** ya que envía las opciones como texto plano.
## Configuración general
* **Contenido**: Mensaje introductorio que acompaña la lista (máximo 1,024 caracteres)
### Opciones
Cada opción tiene:
* **Nombre de la opción**: Texto visible para el ítem (máximo 24 caracteres)
* **Descripción**: Texto adicional debajo del nombre (opcional, máximo 72 caracteres)
Puedes agregar múltiples opciones. Las opciones se pueden reordenar arrastrándolas y duplicar para crear variantes rápidas.
### Opciones dinámicas
Si las opciones provienen de datos variables, activa el modo **dinámico**:
* **Variable fuente**: `{{$memory.opciones}}`
* **Plantilla de etiqueta**: `{{$item.nombre}}`
* **Plantilla de descripción**: `{{$item.detalle}}`
Plantillas predefinidas: Lista simple, Productos, Horarios disponibles, Sucursales.
## Variables en mensajes
```
Contenido: Hola {{$user.names}}, ¿en qué te puedo ayudar hoy?
```
## Configuración avanzada
### Selección obligatoria
Cuando está activada, el usuario **debe** responder con un número válido de la lista para continuar. Si escribe texto libre o un número fuera de rango, verá un mensaje de error personalizable (máximo 250 caracteres).
La selección obligatoria está disponible únicamente en el canal **WhatsApp**.
### Variable de respuesta
Guarda la opción que el usuario seleccionó en una variable de memoria para usarla más adelante en el flujo.
**Cómo configurarlo:**
1. Activa el interruptor **Guardar respuesta**.
2. Escribe el nombre de la variable (por ejemplo, `opcion_elegida`).
#### Valor guardado con opciones estáticas
Cuando las opciones están definidas manualmente, se guarda el **nombre** de la opción seleccionada como texto plano. El número que el usuario escribe solo indica la posición — no se guarda el número.
Ejemplo con estas opciones:
| # | Opción |
| :- | :-------------- |
| 1 | Ventas |
| 2 | Soporte Técnico |
| 3 | Facturación |
| 4 | Devoluciones |
Si el usuario responde `2`:
```javascript theme={null}
// {{$memory.opcion_elegida}} contiene:
const opcion_elegida = "Soporte Técnico";
```
#### Valor guardado con opciones dinámicas
Cuando las opciones se generan desde una variable fuente, se guarda el **objeto completo** del array al que pertenece la opción seleccionada.
Supón que `{{$memory.sucursales}}` contiene:
```json theme={null}
[
{ "id": "suc1", "nombre": "Centro", "direccion": "Av. Principal 100", "telefono": "555-0101" },
{ "id": "suc2", "nombre": "Norte", "direccion": "Calle Norte 200", "telefono": "555-0202" },
{ "id": "suc3", "nombre": "Sur", "direccion": "Av. Sur 300", "telefono": "555-0303" }
]
```
Con la lista numerada configurada con variable fuente `{{$memory.sucursales}}`, el usuario recibe:
```
¿En qué sucursal te encuentras?
1. Centro
2. Norte
3. Sur
```
Si el usuario responde `1`, la variable queda con el objeto completo:
```javascript theme={null}
// {{$memory.opcion_elegida}} contiene el objeto completo:
const opcion_elegida = {
id: "suc1",
nombre: "Centro",
direccion: "Av. Principal 100",
telefono: "555-0101"
};
```
Puedes acceder a cada propiedad del objeto en nodos posteriores:
```javascript theme={null}
// Acceso a propiedades de {{$memory.opcion_elegida}}:
opcion_elegida.nombre; // "Centro"
opcion_elegida.direccion; // "Av. Principal 100"
opcion_elegida.telefono; // "555-0101"
```
#### Casos de uso
Conecta un nodo [Condicional](/guides/nodos/condicional) y crea una rama por cada opción:
```
Si {{$memory.opcion_elegida}} = "Ventas" → rama Ventas
Si {{$memory.opcion_elegida}} = "Soporte Técnico" → rama Soporte
Si {{$memory.opcion_elegida}} = "Facturación" → rama Facturación
```
Con el objeto completo guardado, puedes responder al usuario con información precisa de su selección sin nuevas consultas:
```
Texto: "Tu pedido se entregará en la sucursal {{$memory.opcion_elegida.nombre}},
ubicada en {{$memory.opcion_elegida.direccion}}.
Puedes llamar al {{$memory.opcion_elegida.telefono}} si tienes dudas."
```
Pasa el objeto al nodo [AI Agent](/guides/nodos/ai-agent) para personalizar la respuesta:
```
El usuario seleccionó la sucursal: {{$memory.opcion_elegida.nombre}}.
Dirección: {{$memory.opcion_elegida.direccion}}.
Teléfono: {{$memory.opcion_elegida.telefono}}.
Confirma los datos y ofrece asistencia adicional.
```
Usa un nodo [API](/guides/nodos/api) o [Datum](/guides/nodos/datum) para registrar la selección con el ID del objeto:
```json theme={null}
{
"userId": "{{$user.id}}",
"sucursalId": "{{$memory.opcion_elegida.id}}",
"sucursalNombre": "{{$memory.opcion_elegida.nombre}}",
"canal": "{{$context.channel}}",
"timestamp": "{{$context.timestamp}}"
}
```
### Lista expira
Si el usuario no responde dentro del tiempo configurado:
* **Enviar texto**: Muestra un mensaje de expiración
* **Redirigir a flujo**: Lleva al usuario a otro flujo
La expiración de lista está disponible únicamente en el canal **WhatsApp**.
## Ejemplo
**Configuración:**
* Contenido: `¿Cuál es el motivo de tu consulta?`
* Opciones: `1. Ventas`, `2. Soporte Técnico`, `3. Facturación`, `4. Devoluciones`
**El usuario recibe:**
```
¿Cuál es el motivo de tu consulta?
1. Ventas
2. Soporte Técnico
3. Facturación
4. Devoluciones
```
El usuario responde escribiendo `2` y el flujo continúa por la ruta correspondiente.
## ¿Cuándo usar Lista numerada vs Lista?
| | Lista numerada | Lista |
| :--------------------- | :----------------------------------------- | :------------------------------------------ |
| **Canales** | Todos (WhatsApp, Web, Facebook, Instagram) | WhatsApp |
| **Interfaz** | Texto plano con números | Menú desplegable nativo |
| **Máximo de opciones** | Sin límite fijo | 10 opciones |
| **Recomendado cuando** | Necesitas compatibilidad multicanal | Tienes entre 4-10 opciones solo en WhatsApp |
# Lógica
Source: https://docs.jelou.ai/guides/nodos/logica
Nodos de lógica para controlar el flujo de tus conversaciones
Los nodos de lógica te permiten controlar el flujo y la estructura de tus conversaciones mediante condiciones, variables y otras operaciones lógicas.
## Contenido
Bifurcaciones basadas en condiciones
Guardar valores en memoria
Distribución aleatoria por porcentajes
Detener el flujo temporalmente
Tarea puntual de IA con respuesta estructurada
# Mensaje con URL
Source: https://docs.jelou.ai/guides/nodos/mensaje-con-url
Mensaje con botones de enlace en canales de Facebook e Instagram
El usuario final ve un texto y, debajo, **hasta tres botones que abren un enlace**: al tocar uno se abre la página web correspondiente. Sirve para llevar a la persona fuera de la conversación —un catálogo, una ficha de producto, un formulario, una página de pago o cualquier destino web— y solo aparece en canales de **Facebook** e **Instagram**.
**Los botones no ramifican el flujo.** El nodo tiene una sola salida: el workflow continúa por ella sin importar qué botón toque el usuario, o si no toca ninguno. Para ramificar según lo que elija, usa [Botones](/guides/nodos/botones), [Lista](/guides/nodos/lista) o [WebView](/guides/nodos/webview).
## Configuración
### Mensaje
| Campo | Obligatorio | Límite | Descripción |
| :---------- | :---------- | :------------- | :-------------------------------------------------------- |
| **Mensaje** | Sí | 640 caracteres | Texto que se muestra sobre los botones. Admite variables. |
### Botones
Se configura de uno a tres botones. Cada botón tiene dos campos:
| Campo | Obligatorio | Límite | Descripción |
| :------------------ | :---------- | :--------------- | :-------------------------------------------------------------- |
| **Texto del botón** | Sí | 20 caracteres | Lo que lee el usuario en el botón. Por ejemplo: «Ver catálogo». |
| **URL** | Sí | 2,048 caracteres | Dirección que se abre al tocarlo. |
**Sobre la URL.** Debe empezar por `http://` o `https://`. También se acepta una variable —por ejemplo `{{$enlace_pedido}}`— para armar el enlace en tiempo de ejecución. Cualquier otro valor se marca como URL inválida y no deja guardar.
### Agregar y quitar botones
* **Agregar botón** aparece mientras haya menos de tres.
* **Eliminar botón** aparece solo si hay más de uno: siempre debe quedar al menos un botón configurado.
## Notas
* Los botones de enlace no devuelven respuesta a la conversación. Si necesitas saber si la persona entró al enlace, hay que resolverlo del lado de la página de destino.
* El orden de los botones en el panel es el orden en que se envían.
* Los límites de caracteres son de Meta, no de Jelou: no se pueden ampliar desde la plataforma.
# Mensajes
Source: https://docs.jelou.ai/guides/nodos/mensajes
Nodos para enviar diferentes tipos de contenido al usuario
Los nodos de mensajes te permiten enviar contenido al usuario durante la conversación. Cada tipo de mensaje está optimizado para un formato específico.
## Texto
Mensajes de texto simples
Preguntas que esperan respuesta del usuario
## Multimedia
Envío de imágenes con caption opcional
Envío de videos con caption opcional
Envío de archivos de audio
Envío de stickers animados o estáticos
Envío de documentos y archivos descargables
## Interactivos
Hasta 3 botones interactivos
Hasta 3 botones de enlace (Facebook e Instagram)
Lista desplegable con hasta 10 opciones
Lista numerada de opciones, compatible con todos los canales
Tarjetas deslizables con imagen o video, texto y botones
Botón que abre una URL externa
Botón que abre una vista web embebida en el chat
Formularios nativos de WhatsApp
Plantillas pre-aprobadas de WhatsApp
## Otros
Tarjetas de contacto con datos estructurados
Envío de puntos en el mapa
Solicitar ubicación al usuario
# Notas
Source: https://docs.jelou.ai/guides/nodos/notas
Agrega anotaciones visuales en el canvas para documentar procesos, sin afectar la ejecución del flujo
El nodo **Notas** te permite agregar anotaciones directamente sobre el canvas para documentar procesos, dejar recordatorios o brindar contexto a otros usuarios que trabajen en el flujo.
Las notas son únicamente informativas y **no participan en la ejecución del bot o workflow** — puedes usarlas para organizar mejor el diseño de tus automatizaciones sin afectar su funcionamiento.
## ¿Cuándo utilizar una nota?
Las notas son especialmente útiles cuando un flujo crece y participan varias personas en su construcción. Algunos casos de uso comunes:
* Explicar la lógica de una sección del flujo.
* Documentar decisiones tomadas durante el desarrollo.
* Dejar tareas pendientes para otro miembro del equipo.
* Identificar integraciones o dependencias externas.
* Separar visualmente diferentes procesos dentro del mismo workflow.
Por ejemplo, agrega una nota indicando que un bloque de validación depende de un servicio externo, o recordando que una API debe actualizarse antes de pasar el flujo a producción.
## Crear y configurar una nota
Desde el menú lateral del Builder selecciona **Notas** y arrástralo hacia el canvas, o haz clic sobre él para agregarlo automáticamente.
Colócala en el lugar donde deseas agregar la información. Puedes moverla libremente en cualquier momento para reorganizar el contenido.
Selecciona la nota para abrir su panel de configuración y escribe el texto. Los cambios se guardan automáticamente mientras editas.
## Cambiar el color
Cada nota puede personalizarse con diferentes colores para facilitar la organización visual del workflow. Una práctica recomendada es usar un color distinto según el propósito de la nota:
| Color | Uso sugerido |
| :---------- | :----------------------------------------------- |
| Amarillo | Información general o recordatorios |
| Verde | Procesos finalizados o validados |
| Azul | Información técnica o integraciones |
| Rojo | Pendientes, riesgos o tareas críticas |
| Rosa o lila | Comentarios de negocio o documentación funcional |
Los colores son únicamente organizativos y no modifican el comportamiento del flujo.
## Buenas prácticas
* Escribe notas cortas y fáciles de entender.
* Usa títulos o frases claras.
* Coloca la nota cerca del bloque al que hace referencia.
* Usa colores de forma consistente en todo el flujo.
* Elimina notas que ya no sean necesarias para evitar información desactualizada.
# Catálogo de proveedores
Source: https://docs.jelou.ai/guides/nodos/pagos
Selecciona un proveedor para ver cómo funciona, obtener credenciales y configurarlo en Brain Studio.
> Nota: La disponibilidad por país aquí se refiere a **disponibilidad de la integración en Jelou**, no necesariamente a la cobertura global del proveedor.
## Multi-país (LATAM)
}
/>
}
/>
## Global
}
/>
}
/>
## Ecuador
}
/>
}
/>
}
/>
}
/>
}
/>
## Colombia
}
/>
## México
}
/>
## Perú
}
/>
}
/>
## ¿Tu proveedor no está en este catálogo?
Describe declarativamente cómo llamar a la API de tu PSP y conéctalo sin esperar una integración dedicada.
# Paso
Source: https://docs.jelou.ai/guides/nodos/pasos
Un nodo placeholder que se transforma automáticamente en el nodo que sueltes encima
El nodo **Paso** funciona como un marcador de posición en el canvas: una casilla vacía que puedes ubicar mientras planificas tu flujo, y que se reemplaza automáticamente por el nodo real cuando sueltas otro nodo encima.
El Paso no tiene salidas de éxito/error como los nodos de integración — usa una única conexión de salida, igual que la mayoría de los nodos del flujo.
## ¿Cuándo utilizar un Paso?
Es útil cuando quieres bosquejar la estructura de un flujo antes de decidir qué nodo va en cada punto, o cuando trabajas en equipo y quieres dejar reservado un lugar con una nota de lo que falta por definir.
## Crear y configurar un Paso
Desde el menú lateral del Builder selecciona **Paso** y arrástralo hacia el canvas.
Haz clic sobre el Paso para abrir su panel de configuración y define un **título** y un **comentario** que describan qué debería ir ahí. El comentario admite hasta **100 caracteres**.
Mientras el Paso no tiene título ni comentario, se muestra vacío con el texto "Nuevo paso". Una vez que agregas contenido, se muestra con un borde punteado tipo "zona de destino" (dropzone).
## Reemplazo automático
Cuando sueltas otro nodo encima de un Paso, este se transforma automáticamente en ese nodo — hereda su tipo y configuración, y se elimina cualquier conexión de salida que el Paso tuviera previamente.
Esto te permite dejar "huecos" en el flujo mientras lo diseñas, y llenarlos después sin tener que reconectar manualmente los nodos vecinos.
## Consideraciones importantes
* El título del nodo no es personalizable — siempre se identifica como "Paso".
* No puede colapsarse como otros nodos del canvas.
* No cuenta como contenido real del flujo para funciones como el asistente de IA, que lo ignora al evaluar si un workflow está vacío.
# Pausa
Source: https://docs.jelou.ai/guides/nodos/pausa
Detiene temporalmente el flujo antes de continuar con el siguiente nodo
Con el nodo **Pausa** detienes la ejecución del flujo durante un tiempo determinado. Lo usas para simular tiempos de espera naturales, dar tiempo al usuario para leer un mensaje largo o esperar un proceso externo.
## Configuración
Ingresa la duración de la pausa como valor numérico (mínimo 1). Este valor se combina con la unidad que elijas en el siguiente paso.
Elige la unidad de tiempo que se aplicará al valor configurado:
| Unidad | Uso típico |
| :----------- | :---------------------------------------- |
| **Segundos** | Pausas breves entre mensajes consecutivos |
| **Minutos** | Esperas cortas para procesos externos |
| **Horas** | Recordatorios o seguimientos programados |
| **Días** | Flujos de seguimiento a largo plazo |
Usa pausas de **1–3 segundos** entre mensajes de texto consecutivos para simular una conversación natural y evitar que lleguen todos de golpe.
Las pausas en **horas** o **días** mantienen la ejecución del flujo activa. Asegúrate de que tu configuración de expiración de sesión sea compatible con el tiempo de pausa configurado.
## Ejemplo
Si configuras **Tiempo** = `3` y **Unidad** = `Segundos`, el flujo espera 3 segundos antes de continuar al siguiente nodo. Esto te permite dar un breve respiro entre dos mensajes de texto consecutivos.
## Reanudar una ejecución que no continuó tras la pausa
Si una ejecución **no continúa** tras un nodo de Pausa —la pausa no avanza por sí sola y la ejecución permanece en estado **Procesando**— puedes reanudarla manualmente desde los **Logs** del Brain.
En la sección de **Logs**, abre la ejecución en estado **Procesando** que no continuó tras la Pausa.
En la tarjeta del nodo **Pausa**, junto al botón de depuración, presiona **Reanudar** y confirma.
El flujo continúa desde el nodo siguiente a la pausa, conservando el contexto acumulado de la ejecución.
El botón **Reanudar** solo se habilita cuando el **último nodo del log** es un nodo de **Pausa** y la ejecución está en estado **Procesando**.
# Pregunta
Source: https://docs.jelou.ai/guides/nodos/pregunta
Envía una pregunta al usuario, espera su respuesta y la guarda en una variable
Con el nodo **Pregunta** envías un mensaje al usuario y **esperas su respuesta** antes de continuar con el flujo. La respuesta se guarda automáticamente en una variable de memoria que defines tú.
A diferencia del nodo Texto, el nodo Pregunta **pausa el flujo** hasta que el usuario responde.
## Configuración
### Pestaña General
* **Texto**: Escribe el mensaje o pregunta que deseas enviar al usuario. Puedes incluir variables y emojis.
```
Hola {{$user.names}}, ¿cuál es tu correo electrónico?
```
### Pestaña Avanzado
* **Guardar respuesta como**: Define el nombre de la variable donde guardas la respuesta del usuario (por ejemplo: `correoUsuario`).
* **Usar memoria**: Si activas esta opción, el nodo verifica si la variable ya tiene un valor guardado. Si lo tiene, no vuelve a preguntar y continúa automáticamente.
La respuesta queda disponible en `{{$memory.correoUsuario}}` y puedes usarla en cualquier nodo posterior del flujo.
## Tipos de entrada
El nodo Pregunta soporta distintos tipos de captura según lo que necesites del usuario:
| Tipo | Qué captura |
| :------------------------- | :------------------------------------------------------------------------- |
| **Texto** | Cualquier mensaje de texto del usuario |
| **Ubicación** | Coordenadas GPS (latitud y longitud) mediante botón de compartir ubicación |
| **Documento de identidad** | Imagen de documento de identificación |
| **Video selfie** | Video grabado con la cámara del dispositivo |
### Formato de ubicación
Cuando capturas ubicación, la respuesta se guarda como un objeto JSON:
```json theme={null}
{
"latitude": -0.1806532,
"longitude": -78.4678382
}
```
## Límites por canal
| Canal | Caracteres máximos |
| :------------ | :----------------- |
| WhatsApp | 4,096 |
| Facebook | 2,000 |
| Instagram | 1,000 |
| Web / Twitter | 4,096 |
## Ejemplo
Si configuras:
* **Texto**: `¿Cuál es tu nombre?`
* **Guardar respuesta como**: `nombreUsuario`
La respuesta del usuario se guardará en `{{$memory.nombreUsuario}}` y podrás usarla en cualquier nodo posterior del flujo.
# SOAP
Source: https://docs.jelou.ai/guides/nodos/soap
Realiza peticiones SOAP a servicios externos y captura la respuesta en tu flujo
El nodo **SOAP** te permite conectar tu flujo con servicios web SOAP, un protocolo común en sistemas legacy y empresariales. Es un nodo de ejecución real: hace la petición y expone salidas de **éxito** y **error** para continuar el flujo, igual que el nodo API.
El nodo SOAP comparte la infraestructura de credenciales y ejecución con el nodo **[API](/guides/nodos/api)** — si ya conoces ese nodo, la configuración te resultará familiar.
## Configuración básica
* **URL** — la dirección del servicio SOAP.
* **Operation Name** — el nombre de la operación SOAP que quieres invocar (texto libre; no se valida contra un WSDL).
Tanto la URL como el Operation Name son obligatorios — el nodo se marca como inválido si alguno queda vacío.
El método HTTP es siempre **POST**.
## Namespaces
Define los namespaces del envelope SOAP en una tabla reordenable (arrastra para cambiar el orden):
| Campo | Descripción |
| :----- | :--------------------------------------------- |
| Scope | `envelope` o `functionName` |
| Prefix | Prefijo del namespace (por ejemplo, `soapenv`) |
| URI | La URI del namespace |
Puedes usar variables del workflow al definir el URI.
## Headers
Agrega headers personalizados como pares clave-valor.
El header `Content-Type` se gestiona automáticamente según la versión de SOAP que elijas en Settings, y no se puede editar manualmente.
## Body
El cuerpo de la petición se edita en un editor de código libre (JSON o XML). El nodo no construye visualmente el envelope SOAP — el armado final del envelope se resuelve en el motor de ejecución.
## Autenticación
Tres opciones disponibles:
| Método | Configuración |
| :-------------------- | :------------------- |
| **Sin autenticación** | Opción por defecto |
| **Basic Auth** | Usuario y contraseña |
| **Bearer Token** | Token de acceso |
## Settings
* **Versión de SOAP** — `1.1` o `1.2`. Cambiarla reescribe automáticamente el header `Content-Type`.
* **Timeout** — en milisegundos, por defecto `30000` (30s).
* **Transformación de respuesta** — extrae solo la parte de la respuesta que necesitas definiendo un `extractPath` (por ejemplo, `GetUserResponse.Data`).
## Guardar la respuesta
Activa **"Guardar respuesta"** y define un nombre de variable para almacenar la respuesta del servicio SOAP.
### Acceder a la respuesta desde un nodo Código
```javascript theme={null}
let soapResponse = $context.getSoapResponse('miVariable')
soapResponse = soapResponse.json()
```
También puedes leer el request original con `$context.getSoapRequest('miVariable')`. Ambos accesores soportan `.headers()` y `.json()`; `getSoapResponse` además incluye `.status()`.
## Probar la petición
El botón **"Send"** ejecuta la petición y muestra el resultado: código de estado, y pestañas de Body y Headers de la respuesta, con opción de copiar al portapapeles.
## Manejo de errores
El nodo expone dos salidas: **éxito** y **error**. Conecta la salida de error al siguiente paso que deba manejar la falla, sin interrumpir el resto del flujo.
# Solicitud de Ubicación
Source: https://docs.jelou.ai/guides/nodos/solicitud-ubicacion
Solicita la ubicación del usuario mediante un botón interactivo
El nodo **Solicitud de Ubicación** envía un mensaje con un botón que le permite al usuario compartir su ubicación actual directamente desde su dispositivo.
## Configuración
* **Mensaje**: Texto que explica al usuario por qué necesitas su ubicación
* **Guardar respuesta como**: Nombre de la variable donde se almacenarán las coordenadas
## Formato de la respuesta
La ubicación se guarda como un objeto JSON:
```json theme={null}
{
"latitude": -0.1806532,
"longitude": -78.4678382
}
```
Puedes acceder a las coordenadas individuales en nodos posteriores:
* Latitud: `{{$memory.ubicacion.latitude}}`
* Longitud: `{{$memory.ubicacion.longitude}}`
Este nodo **solicita** la ubicación del usuario (espera respuesta). Si necesitas **enviar** una ubicación al usuario, usa el nodo [Ubicación](/guides/nodos/ubicacion).
# Sticker
Source: https://docs.jelou.ai/guides/nodos/sticker
Envía stickers al usuario dentro de la conversación
Con el nodo **Sticker** envías un sticker al usuario como parte de la conversación.
## Configuración
* **Archivo o URL**: Sube un archivo de sticker o pega una URL pública
## Límites
| Formato soportado | Tamaño máximo |
| :---------------- | :------------ |
| `image/webp` | 100 KB |
Si usas una URL, debe ser públicamente accesible vía HTTPS, no debe requerir autenticación y debe apuntar directamente al archivo (no a una página HTML).
# Texto
Source: https://docs.jelou.ai/guides/nodos/texto
Envía mensajes de texto simples al usuario
Con el nodo **Texto** envías un mensaje de texto al usuario y el flujo continúa inmediatamente. Es el nodo más básico para comunicarte con el usuario.
## Configuración
* **Contenido**: Escribe el texto del mensaje que deseas enviar al usuario.
## Variables en mensajes
Puedes insertar variables para personalizar el mensaje dinámicamente:
```
Hola {{$user.names}}, el estado de tu pedido es: {{$memory.pedido.status}}
```
## Límites por canal
| Canal | Caracteres máximos |
| :------------ | :----------------- |
| WhatsApp | 4,096 |
| Facebook | 2,000 |
| Instagram | 1,000 |
| Web / Twitter | 4,096 |
| Omnichannel | 2,000 |
El nodo Texto no espera la respuesta del usuario. Si necesitas hacer una pregunta y capturar la respuesta, usa el nodo [Pregunta](/guides/nodos/pregunta).
# Ubicación
Source: https://docs.jelou.ai/guides/nodos/ubicacion
Envía una ubicación en el mapa al usuario
El nodo **Ubicación** envía un punto en el mapa al usuario. El usuario podrá ver la ubicación y abrirla en su aplicación de mapas.
## Configuración
Puedes seleccionar la ubicación de dos formas:
1. **Buscar dirección**: Escribe una dirección en el campo de búsqueda y selecciona una de las sugerencias
2. **Seleccionar en el mapa**: Haz clic directamente en el mapa interactivo para elegir el punto
Una vez seleccionada, se muestran las coordenadas (latitud y longitud) y puedes agregar opcionalmente:
* **Nombre de la ubicación**: Un nombre descriptivo para el punto
* **Dirección**: La dirección en texto
Este nodo **envía** una ubicación al usuario. Si necesitas **solicitar** la ubicación del usuario, usa el nodo [Solicitud de Ubicación](/guides/nodos/solicitud-ubicacion).
# Variable
Source: https://docs.jelou.ai/guides/nodos/variable
Guarda valores en memoria para usarlos en otros nodos del flujo
El nodo **Variable** te permite guardar uno o más valores en memoria (`$memory`) sin enviar ningún mensaje al usuario. Es útil para preparar datos antes de usarlos en nodos posteriores.
## Configuración
Cada variable se define con dos campos:
* **Variable**: El nombre con el que se guardará en memoria
* **Valor**: El dato a almacenar (puede ser texto fijo, un número o una referencia a otra variable)
Puedes agregar múltiples variables en un solo nodo usando el botón **"Agregar nueva variable"**.
Los nombres de variable deben ser únicos dentro del mismo nodo. Si usas un nombre repetido, verás un error de validación.
Cada nodo Variable permite un máximo de **20 variables**. Si necesitas guardar más valores, distribúyelos en varios nodos Variable.
## Ejemplo
Si configuras:
| Variable | Valor |
| :--------------- | :--------------------------------- |
| `nombreCompleto` | `{{$user.names}}` |
| `canal` | `whatsapp` |
| `saludo` | `Hola {{$user.names}}, bienvenido` |
Las variables estarán disponibles como:
* `{{$memory.nombreCompleto}}`
* `{{$memory.canal}}`
* `{{$memory.saludo}}`
# Video
Source: https://docs.jelou.ai/guides/nodos/video
Envía videos al usuario dentro de la conversación
Con el nodo **Video** envías un archivo de video al usuario como parte de la conversación.
## Configuración
* **Archivo o URL**: Sube un archivo de video o pega una URL pública
* **Caption** (opcional): Texto que aparece junto al video
El caption soporta variables: `Tutorial: {{$memory.temaActual}}`
## Límites
| Formatos soportados | Tamaño máximo |
| :----------------------- | :------------ |
| `video/mp4`, `video/3gp` | 16 MB |
| Canal | Caption máximo |
| :------------------- | :--------------- |
| WhatsApp | 1,024 caracteres |
| Facebook / Instagram | No soportado |
| Web | 1,024 caracteres |
Si usas una URL, debe ser públicamente accesible vía HTTPS, no debe requerir autenticación y debe apuntar directamente al archivo de video (no a una página HTML).
# Webhook
Source: https://docs.jelou.ai/guides/nodos/webhook
Recibe solicitudes HTTP externas para iniciar o reanudar ejecuciones de tus workflows
El nodo **Webhook** convierte tu workflow en un endpoint HTTP accesible desde cualquier sistema externo. Permite dos flujos principales: **iniciar nuevas ejecuciones** y **reanudar ejecuciones existentes** — todo mediante una simple llamada HTTP.
Piensa en este nodo como la puerta de entrada de tu workflow: sistemas externos tocan la puerta (envían un request HTTP), y tu flujo decide qué hacer con lo que traen.
Solo puede existir **un nodo Webhook por canvas**.
***
## URL del webhook
La URL la genera el propio nodo. Ábrelo en Studio y usa **Copiar URL**: el identificador final es el **ID del nodo Webhook**, no un identificador aparte.
Las llamadas entran por el gateway de Jelou. El nodo genera dos URLs, según qué versión del workflow quieras ejecutar.
**Producción** — ejecuta la última versión **publicada** del workflow:
```bash theme={null}
curl -X POST "https://gateway.jelou.ai/workflows/v2/webhooks/skill/{workflowId}/{canal}/{nodeId}" \
-H "Content-Type: application/json" \
-H "x-api-key: $JELOU_API_KEY"
```
**Pruebas (draft)** — ejecuta el workflow **tal como está en el canvas ahora**, con los cambios que aún no publicaste:
```bash theme={null}
curl -X POST "https://gateway.jelou.ai/workflows/v2/webhooks/skill/{workflowId}/{canal}/test/{nodeId}" \
-H "Content-Type: application/json" \
-H "x-api-key: $JELOU_API_KEY"
```
`{canal}` es el canal del workflow en minúsculas: `whatsapp`, `web`, `instagram`, `facebook`, `slack`, `teams`, `twitter` o `custom`.
El gateway requiere autenticación. Envía tu API key en el header `x-api-key` en cada llamada al webhook; sin ese header la respuesta es `401 Unauthorized`. Créala y adminístrala en [Claves API](/guides/configuracion/claves-api).
¿Ya tienes una integración apuntando al host anterior, `workflows.jelou.ai`? **Sigue funcionando** y no necesitas migrarla con urgencia. La URL del gateway aplica a las integraciones nuevas.
Copia siempre la URL desde el nodo en lugar de construirla a mano.
Los ejemplos de esta página usan la URL de producción. Para probar contra el draft, cambia `{nodeId}` por `test/{nodeId}`.
***
## Modos de operación
El nodo Webhook opera en dos modos, determinados automáticamente por la presencia del `executionId`:
### Iniciar nueva ejecución
Cuando el request **no incluye** un `executionId`, el webhook crea una nueva ejecución del workflow desde cero.
**Caso de uso:** Un sistema externo (CRM, ERP, pasarela de pagos) necesita disparar un proceso automatizado en Jelou.
Para iniciar una ejecución nueva hace falta el identificador del usuario. En el ejemplo va como `?userId=`; la otra forma se explica en [Webhook en Workflows](#webhook-en-workflows).
```bash theme={null}
curl -X POST "https://gateway.jelou.ai/workflows/v2/webhooks/skill/{workflowId}/{canal}/{nodeId}?userId=%2B593999999999" \
-H "Content-Type: application/json" \
-H "x-api-key: $JELOU_API_KEY" \
-d '{"evento": "nuevo_pedido", "cliente": "12345"}'
```
### Reanudar ejecución existente
Cuando el request **incluye** un `executionId`, el webhook reanuda una ejecución que estaba pausada esperando una respuesta externa.
**Caso de uso:** Una pasarela de pagos notifica que el pago fue procesado, y el flujo debe continuar desde donde se detuvo.
```bash theme={null}
curl -X POST https://gateway.jelou.ai/workflows/v2/webhooks/skill/{workflowId}/{canal}/{nodeId} \
-H "Content-Type: application/json" \
-H "x-api-key: $JELOU_API_KEY" \
-d '{"executionId": "abc-123", "status": "paid"}'
```
El `executionId` puede enviarse en el **body** (usando el path configurado en dot notation) o como **query param** `executionId`. Esto permite usar métodos HTTP como GET que no tienen body.
***
## Configuración
### Nombre de la variable
Define el nombre bajo el cual se almacenarán los datos del webhook dentro de la variable `$webhook`. Los datos recibidos por el request quedarán disponibles en `$webhook.`. Por ejemplo, si defines `miWebhook`, podrás acceder al body via `{{$webhook.miWebhook.body}}`, a los headers via `{{$webhook.miWebhook.headers}}`, etc.
### Método HTTP
Selecciona los métodos HTTP que acepta el webhook. Soporta GET, POST, PUT, PATCH y DELETE.
El método HTTP es **obligatorio**. Si publicas el nodo sin haber seleccionado uno, toda petición a la URL será rechazada. Selecciónalo antes de publicar.
Para métodos sin body (como GET), el `executionId` para reanudar debe enviarse como query param: `?executionId=abc-123`.
### Reanudar múltiples veces (oneTimeOnly)
Por defecto, una ejecución puede ser reanudada **más de una vez** por el webhook. Si necesitas restringir esto — por ejemplo, en integraciones de pagos donde una confirmación solo debe procesarse una vez — puedes activar la opción **"Solo reanudar una vez"** en la configuración avanzada.
| Configuración | Comportamiento |
| :---------------------------------------- | :------------------------------------------------------------------------------- |
| **oneTimeOnly desactivado** (por defecto) | El webhook puede reanudar la misma ejecución múltiples veces |
| **oneTimeOnly activado** | El webhook solo reanuda la ejecución una vez; intentos posteriores son ignorados |
***
## Variable `$webhook`
Cuando un request llega al nodo Webhook, toda la información del request queda disponible a través de la variable `$webhook`. Funciona de manera similar a `$memory` y `$context`.
| Propiedad | Descripción | Ejemplo |
| :----------------------------------------- | :------------------------------- | :---------------------------------------- |
| `$webhook..body` | Cuerpo del request HTTP | `{"evento": "pago_exitoso", "monto": 50}` |
| `$webhook..headers` | Headers enviados en el request | `{"content-type": "application/json"}` |
| `$webhook..query` | Query params de la URL | `{"ref": "campaign-123"}` |
| `$webhook..method` | Método HTTP utilizado | `POST` |
| `$webhook..mode` | Modo de la ejecución | `new` o `resume` |
| `$webhook..executionId` | ID de la ejecución actual | `exec-abc-123` |
| `$webhook..isNewExecution` | Indica si es una ejecución nueva | `true` o `false` |
### Ejemplo de uso en el flujo
Suponiendo que definiste `miWebhook` como nombre de la variable, puedes usar las propiedades en cualquier nodo posterior:
```
# En un nodo Condicional
{{$webhook.miWebhook.body.evento}} == "pago_exitoso"
# En un nodo Texto
El pago de {{$webhook.miWebhook.body.monto}} fue procesado correctamente.
# En un nodo API
Authorization: {{$webhook.miWebhook.headers.authorization}}
```
***
## Webhook en Workflows
El nodo Webhook está disponible en Workflows, lo que permite que flujos conversacionales sean disparados o reanudados por eventos externos.
### Consideraciones para Workflows
Para iniciar una nueva ejecución de un Workflow vía webhook, el request **debe incluir el identificador del usuario** (por ejemplo, el número de teléfono en WhatsApp). Esto es necesario para que el Workflow pueda enviar mensajes al usuario a través del canal correspondiente.
Tienes dos formas de enviarlo:
* **En el body:** configura el campo **ID de usuario** en el panel del nodo con la ruta (dot notation) donde viene el identificador.
* **Como query param:** agrega `?userId=` a la URL, sin configurar nada en el panel.
```bash theme={null}
# ID en el body, con el campo "ID de usuario" configurado como `phone`
curl -X POST https://gateway.jelou.ai/workflows/v2/webhooks/skill/{workflowId}/whatsapp/{nodeId} \
-H "Content-Type: application/json" \
-H "x-api-key: $JELOU_API_KEY" \
-d '{"phone": "+593999999999", "evento": "recordatorio"}'
# ID como query param
curl -X POST "https://gateway.jelou.ai/workflows/v2/webhooks/skill/{workflowId}/whatsapp/{nodeId}?userId=%2B593999999999" \
-H "Content-Type: application/json" \
-H "x-api-key: $JELOU_API_KEY" \
-d '{"evento": "recordatorio"}'
```
Si no llega el identificador por ninguna de las dos vías, el webhook responde con un error indicando que falta el `userId`.
Para reanudar una ejecución existente, solo se necesita el `executionId`. El sistema ya tiene el contexto del usuario desde la ejecución original.
En plataformas como WhatsApp, si **no hay una sesión activa** (ventana de 24 horas) entre el usuario y el canal, el webhook no podrá enviar mensajes directamente. En estos casos, puedes usar un **nodo HSM** dentro del flujo para enviar una plantilla aprobada que reabre la ventana conversacional.
El webhook siempre ejecuta la **última versión publicada** del Workflow. Si hay más de un canal del mismo tipo conectado al proyecto, se utiliza el más reciente.
Cuando un Workflow hijo hereda webhooks de su Workflow padre, si el Workflow hijo recibe un request, este **sobrescribe** los datos del webhook del flujo padre. La data de `$webhook` se propaga a ejecuciones hijas (nodos Workflow internos).
***
## Testing (Draft)
Para probar el webhook durante el desarrollo (antes de publicar), el nodo genera una URL de pruebas que incluye el segmento `/test` antes del ID del nodo:
```bash theme={null}
curl -X POST "https://gateway.jelou.ai/workflows/v2/webhooks/skill/{workflowId}/{canal}/test/{nodeId}" \
-H "Content-Type: application/json" \
-H "x-api-key: $JELOU_API_KEY"
```
La URL `/test` ejecuta el draft actual del workflow, así puedes iterar sin afectar la versión publicada. Además, no exige el identificador del usuario: esos parámetros salen del Workflow Tester.
En Workflows, el testing del draft se realiza a través del **Workflow Tester**. Los parámetros del usuario configurados en el Workflow Tester también aplican cuando se invoca el webhook en modo test.
***
## Retrocompatibilidad
Los webhooks creados antes de esta actualización **siguen funcionando sin cambios**. Las mejoras aplican solo hacia adelante:
* **Webhooks existentes** conservan su configuración legacy, incluyendo el campo "variable" que guardaba la data en `$context`.
* **Webhooks nuevos** siempre guardan la información en la nueva entidad `$webhook`, lo que ofrece una estructura más rica y consistente.
* **Las URLs anteriores siguen atendiendo**, como se indica en [URL del webhook](#url-del-webhook).
Para webhooks legacy que tenían configurada una variable de contexto, el componente de configuración de variable se seguirá mostrando por retrocompatibilidad. En webhooks nuevos, este campo no aparece ya que la data siempre se almacena en `$webhook`.
***
## Casos de uso comunes
Recibe confirmaciones de pasarelas como Stripe o MercadoPago y reanuda el flujo de compra.
Dispara flujos automatizados cuando se crea o actualiza un registro en tu CRM.
Procesa eventos como abandono de carrito, envío de pedido o devolución.
Recibe datos de sensores o dispositivos para disparar flujos de alerta o procesamiento.
# WebView
Source: https://docs.jelou.ai/guides/nodos/webview
Abre una interfaz web en el chat, pausa el flujo y continúa según el callback o el tiempo de expiración
El nodo **WebView** abre una interfaz web para que el usuario complete una acción (por ejemplo, un pago o un formulario). Si activas la opción de bloquear el flujo, este permanece pausado hasta que recibe una respuesta del callback o se cumple el tiempo de expiración.
## Cómo funciona
En el nodo, ingresa la URL de tu interfaz web en el campo correspondiente. El flujo envía ese enlace al usuario como botón o enlace en el chat.
**Siempre** se añade el `executionId` como parámetro en la URL cuando se abre el WebView. Tu página debe leerlo de la URL y enviarlo en el callback.
La URL que recibe el usuario tiene este formato (el `executionId` se añade automáticamente):
```
https://tu-dominio.com?executionId=
```
Si tu URL ya lleva otros query params, se añade con `&`: `https://tu-dominio.com?foo=1&executionId=`.
El usuario hace clic en el botón y completa la acción en la interfaz web (pago, formulario, etc.).
Cuando el usuario termina, tu página web debe llamar al endpoint de callback para desbloquear el flujo.
**Endpoint (método POST):**
```
https://workflows.jelou.ai/v1/webview/callback
```
**Cuerpo del request (JSON):**
```json theme={null}
{
"executionId": "exec_abc123xyz",
"success": true,
"data": {
"paymentId": "pay_789",
"status": "completed",
"amount": 99.99
}
}
```
| Campo | Tipo | Obligatorio | Descripción |
| :------------ | :------ | :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `executionId` | string | Sí | Valor que viene en la URL del WebView cuando se abre (query param). Identifica el flujo pausado. |
| `success` | boolean | Sí | `true` = ruta de éxito del WebView (SuccessCallback), `false` = ruta de error del WebView (ErrorCallback). |
| `data` | object | No | Objeto con la información recogida en el WebView para continuar el flujo (ej. opciones seleccionadas). Se guarda en la variable de respuesta configurada en el nodo. |
## Mientras espera
Cuando la opción de bloquear el flujo está activada, si el usuario escribe algo en el chat mientras el WebView está abierto:
* Recibe el mensaje configurado en **Mensaje de espera**.
* El flujo no avanza hasta que el WebView responda o se cumpla el tiempo de expiración.
El flujo solo se bloquea si activas la opción de "Bloquear flujo hasta respuesta", ubicado en el tab avanzado del nodo.
## Tres salidas posibles
Cuando el bloqueo del flujo está activado, cada resultado del callback se conecta a una rama distinta a través de su propia arista (edge):
| Resultado | Condición | Rama |
| :----------- | :------------------------------------------ | :----------------------------------------- |
| **Éxito** | `success: true` en el callback | Flujo continúa por la salida de éxito |
| **Error** | `success: false` en el callback | Flujo continúa por la salida de error |
| **Expirado** | No hubo respuesta antes de `expirationTime` | Flujo continúa por la salida de expiración |
Conecta las tres aristas del nodo WebView a los nodos que correspondan: uno para éxito, uno para error y uno para cuando expire el tiempo.
## Configuración
| Campo | Descripción |
| :------------------------ | :---------------------------------------------------------------------------------------------------- |
| **URL** | Dirección web que se abre al hacer clic. La configuras tú en el nodo. |
| **Variable de entrada** | Datos que se pasan a la URL como query params. |
| **Tiempo de expiración** | Segundos máximos de espera antes de tomar la salida Expired. Solo aplica si el bloqueo está activado. |
| **Mensaje pendiente** | Mensaje que recibe el usuario si escribe en el chat mientras el flujo espera. |
| **Variable de respuesta** | Variable donde se guardan los datos enviados en `data` del callback. |
## Cerrar el WebView
Si quieres cerrar el WebView luego de una acción, un truco: redirige a un enlace de WhatsApp para regresar al chat:
```javascript theme={null}
window.location.href = "https://wa.me/13239183195";
```
# WhatsApp Flows
Source: https://docs.jelou.ai/guides/nodos/whatsapp-flows
Inserta formularios interactivos directamente dentro de la conversación de WhatsApp
El nodo **WhatsApp Flows** te permite insertar formularios interactivos y visuales directamente dentro de una conversación de WhatsApp. A diferencia de los nodos individuales de pregunta, los Flows de WhatsApp son pantallas completas con campos de texto, menús desplegables, botones y otros elementos visuales que el usuario completa en una sola vista.
## Cuándo usarlo
Usa este nodo cuando necesites recopilar varios datos del usuario de forma ordenada en una sola pantalla, en lugar de hacer múltiples preguntas individuales. Es especialmente útil para:
* Formularios de registro
* Encuestas de satisfacción
* Recopilación de datos de envío
* Cualquier escenario donde necesites múltiples campos a la vez
## Requisitos
Este nodo solo aparece si tu cuenta de Meta Business está verificada. Los Flows deben ser creados previamente desde el Flow Builder en WhatsApp Manager.
* Cuenta de Meta Business aprobada y verificada
* Flows creados en WhatsApp Manager
* No todos los dispositivos o versiones de WhatsApp soportan esta función — prueba antes de lanzar en producción
## Configuración
* **Selección de Flow**: Elige un Flow publicado o en borrador de tu cuenta de WhatsApp Business
* **Texto del botón**: El texto del botón que el usuario toca para abrir el formulario
* **Acción del flujo**: Define cómo se comporta el Flow al abrirse:
* **Navegar (Navigate)**: el Flow abre en una pantalla estática predefinida (comportamiento por defecto).
* **Intercambio de datos (Data exchange)**: al abrirse, WhatsApp consulta el endpoint del negocio, que resuelve dinámicamente la pantalla inicial y sus datos.
* **Datos dinámicos** (opcional): Pasa datos del flujo al formulario usando variables (por ejemplo, `{{$memory.userId}}`)
Para usar **Intercambio de datos (Data exchange)** primero debes:
* Haber ingresado la **public key** y la **private key** del cifrado del Flow.
* Tener el Flow publicado en Meta con su endpoint configurado (`data_api_version`).
Si seleccionas **Intercambio de datos (Data exchange)** sobre un Flow que no cumple estos requisitos, Meta rechazará el envío.
Desde el panel de configuración puedes acceder directamente a tu administrador de WhatsApp Flows para crear o editar formularios.
[Ver documentación de Meta sobre componentes de Flows](https://developers.facebook.com/docs/whatsapp/flows/reference/components/)
# Workflow
Source: https://docs.jelou.ai/guides/nodos/workflow
Redirige el flujo hacia otro workflow del mismo proyecto
El nodo **Workflow** te permite, desde dentro de un flujo, redirigir la conversación hacia otro workflow del mismo proyecto. Es útil para dividir automatizaciones grandes en flujos más pequeños y reutilizables.
No lo confundas con el nodo **[WhatsApp Flows](/guides/nodos/whatsapp-flows)** — ese nodo dispara un formulario nativo de WhatsApp (Meta Flow) dentro de la conversación, y no tiene relación con saltar a otro workflow del proyecto.
## Configuración
Selecciona el workflow destino desde un desplegable con todos los workflows del proyecto, ordenados alfabéticamente.
Haz clic sobre el nodo para abrir su panel de configuración y elige el workflow destino en el desplegable.
Usa el botón **"Ir al workflow"** para abrir directamente el canvas del workflow seleccionado y editarlo, sin salir del builder.
Mientras no hayas seleccionado un workflow, el nodo muestra el mensaje **"Haz clic para configurar tu workflow"**. Si el workflow configurado fue eliminado o ya no está disponible, el nodo muestra un aviso indicando que no está disponible.
## Enmascarado de datos (DLP)
El nodo incluye configuración de **Data Loss Prevention** para enmascarar información sensible en los logs generados por este nodo.
## Comportamiento del flujo
* El nodo tiene una única salida — no distingue entre éxito y error, porque redirige el flujo en lugar de hacer una llamada que pueda fallar.
* El contexto y las variables de la conversación se comparten automáticamente entre workflows; no necesitas mapear variables manualmente.
Este nodo no está disponible dentro de un **developer skill** o **pocket skill** (por ejemplo, tools) — no puedes insertar un salto a otro workflow en ese contexto.
# Observabilidad - Eventos de Nodo
Source: https://docs.jelou.ai/guides/observabilidad/eventos
Configura eventos de seguimiento en tus nodos para alimentar tus métricas y paneles de rendimiento
La **observabilidad** en Brain Studio te permite registrar eventos de seguimiento directamente desde la configuración de tus nodos, para entender cómo interactúan los usuarios en cada punto de tu workflow. Estos eventos alimentan tus métricas y paneles de rendimiento.
## ¿Cómo funciona?
Los paneles de configuración de nodos como **HTTP, SOAP, Código, Tool, Condicional, Skill y AI Agent**, entre otros, incluyen una pestaña **Eventos**. Desde ahí puedes registrar hasta **10 eventos por nodo**, cada uno con su propio nombre y propiedades personalizadas.
Cuando un nodo tiene eventos configurados, verás una insignia debajo de él en el canvas con un resumen de los eventos activos. Si no has configurado ninguno, la insignia muestra **"Sin eventos activos"**.
## Configurar un evento
Selecciona el nodo que quieres instrumentar y entra a su panel de configuración. Junto a la pestaña **General** encontrarás la pestaña **Eventos**, con el título **"Configuración de evento"**.
Haz clic en **"Agregar evento"** y completa el campo **"Nombre del evento"**. El nombre debe estar en **snake\_case** (por ejemplo, `payment_attempted` o `cita_agendada`); si no cumple el formato, verás el error **"Debe ser snake\_case"**.
En **"Propiedades del evento"**, usa **"Agregar campo"** para incluir hasta 5 pares de clave/valor. El valor puede ser un texto explícito (por ejemplo, `100` o `test`) o una variable evaluable siguiendo la sintaxis de variables de Jelou, como `{{$context.amount}}` o `{{$memory.nombre_cliente}}`.
Consulta las [Variables de Contexto](/guides/variables/context) disponibles para saber qué datos puedes incluir en el payload de tus eventos.
Límites por nodo: máximo **10 eventos**, cada uno con hasta **5 propiedades**. Los nombres de evento y de propiedad deben escribirse en snake\_case.
## Ejemplo práctico
Tienes un nodo Tool llamado **"Cobrar Pago"** que procesa un cobro. Configuras un evento así:
* **Nombre del evento**: `payment_attempted`
* **Propiedades del evento**:
* `monto`: `{{$context.amount}}`
* `metodo`: `{{$context.payment_method}}`
Cada vez que el nodo complete un cobro exitosamente, se registrará este evento con el monto y el método de pago usados, listos para analizarse en tus métricas.
Tienes un nodo API que consulta un sistema externo. Configuras un evento para detectar fallas:
* **Nombre del evento**: `consulta_externa_fallida`
* **Propiedades del evento**:
* `codigo_error`: `{{$context.statusCode}}`
Esto te permite identificar rápidamente cuántas veces y por qué falla esa integración.
Esta funcionalidad se está habilitando progresivamente para las empresas en Brain Studio.
## Artículos relacionados
Conoce todos los tipos de nodos disponibles y cómo configurarlos.
Aprende a usar `$context` y otras variables disponibles durante la ejecución.
Aprende a crear tu primer workflow desde cero.
# Observabilidad - Reporte de ejecuciones
Source: https://docs.jelou.ai/guides/observabilidad/reporte-ejecuciones
Descarga el detalle de las ejecuciones de tus workflows en una hoja de cálculo, respetando los filtros que tengas aplicados
Desde **Logs** puedes descargar el detalle de las ejecuciones de tus workflows en una hoja de cálculo. Lo que se descarga es exactamente lo que estás viendo en pantalla: el reporte respeta el rango de fechas y todos los filtros activos, no es un volcado completo.
Sirve para conservar el respaldo de lo que se te factura, o para revisar en frío qué pasó con un conjunto de ejecuciones sin abrirlas una por una.
## Cómo descargarlo
Entra a la sección **Logs** de tu proyecto y elige la pestaña **Workflows** o **Tools**, según lo que quieras exportar.
Ajusta el rango de fechas y los filtros que necesites: canal, estado, workflow, usuario. El archivo va a contener exactamente ese conjunto.
Está en la barra de filtros, a la derecha. La generación es asíncrona: se despacha el pedido y el archivo aparece en el **Centro de descargas** cuando está listo.
No necesitas quedarte en la pantalla esperando. Puedes seguir trabajando y volver al Centro de descargas más tarde.
Si el conjunto de filtros no devuelve resultados, el sistema te avisa que no hay data en lugar de entregarte un archivo vacío.
## Columnas del archivo
| Columna | Qué contiene |
| ----------------------- | ---------------------------------------------------- |
| `Id de ejecución` | Identificador único de la ejecución |
| `Id de ejecución padre` | La ejecución que la invocó, si es una ejecución hija |
| `Id de ejecución raíz` | La ejecución que originó toda la cadena |
| `Nivel` | `Padre` o `Hija` |
| `Nombre de usuario` | Nombre del usuario que originó la ejecución |
| `Id de usuario` | Identificador del usuario |
| `Workflow` | Nombre del workflow ejecutado |
| `Proyecto` | Proyecto al que pertenece, cuando corresponde |
| `Canal` | Canal por el que entró la conversación |
| `Estado` | Resultado de la ejecución |
| `Origen` | Qué la disparó |
| `Fecha de inicio` | Cuándo empezó |
| `Fecha de fin` | Cuándo terminó, vacía si la ejecución no finalizó |
Los encabezados vienen en tu idioma configurado: español, inglés o portugués.
## Jerarquía padre-hijo
Cuando un workflow invoca a otro, el reporte te permite distinguir quién llamó a quién con tres columnas que trabajan juntas.
`Nivel` te dice de un vistazo si la fila es una ejecución **padre** —arrancó por sí sola— o una **hija**, invocada desde otra. `Id de ejecución padre` te dice cuál la invocó, e `Id de ejecución raíz` te lleva al origen de toda la cadena, aunque haya varios niveles de anidamiento.
Las tres columnas pueden venir vacías si la información de jerarquía no está disponible para esa ejecución. El reporte deja la celda en blanco en vez de suponer un valor.
## Fechas y zona horaria
Las fechas del archivo se expresan en la **zona horaria configurada en tu perfil**, no en UTC del servidor. Si no tienes una configurada, se usa la de tu navegador.
El formato es `AAAA-MM-DD HH:MM:SS` en reloj de 24 horas. Ese orden es a propósito: en una hoja de cálculo ordena bien alfabéticamente y Excel lo reconoce como fecha.
La zona horaria del archivo y el rango de fechas del filtro son dos cosas distintas. El filtro define **qué ejecuciones entran**; la zona horaria define **cómo se muestran** las fechas de esas ejecuciones.
## Límite de tamaño
El reporte admite hasta **1.000.000 de filas**. Si tus filtros devuelven más, la descarga se rechaza con el mensaje *"Error al generar la descarga del informe, excede los límites de recursos"* y no se genera ningún archivo.
Si te topas con ese límite, acota el rango de fechas o agrega un filtro de workflow o canal.
Por debajo de ese tope, el archivo se arma solo. Si el conjunto es grande, se divide en varios archivos y los encuentras todos en el Centro de descargas.
## Casos de uso
Una cuenta que necesita justificar ante finanzas lo que se le factura: descarga el detalle del período completo y lo conserva como respaldo del consolidado que recibe.
Un equipo que vio muchas ejecuciones fallidas ayer: filtra por estado y por canal, descarga el conjunto y revisa los identificadores sin abrir cada ejecución en la interfaz.
Un workflow que invoca a otros dos, y uno de ellos falla: con `Id de ejecución raíz` se agrupan todas las filas de la misma cadena y con `Nivel` se distingue el padre de las hijas.
## Artículos relacionados
Qué cuenta como una ejecución y cómo impacta en tu facturación
Configura eventos de seguimiento para alimentar tus métricas
# Contexto
Source: https://docs.jelou.ai/guides/variables/context
Variables de contexto: datos temporales de la ejecución actual y variables del sistema
Usas el contexto para guardar variables que persisten únicamente durante la ejecución actual, es decir, solo viven dentro del flujo específico donde las creas. Si necesitas guardar datos que sobrevivan al final de la conversación (hasta 30 días de inactividad, renovado solo por escrituras, o menos según el TTL que asignes por variable), usa la [Memoria](/guides/variables/memory).
Piensa en el contexto como una pizarra temporal: cada vez que un usuario inicia una conversación, obtienes una pizarra nueva y limpia. Todo lo que escribas en ella está disponible para todos los nodos del flujo, pero cuando la conversación termina, la pizarra se borra.
## Variables del sistema
Al iniciar una ejecución, Brain Studio inyecta automáticamente variables del sistema en el contexto. Puedes acceder a ellas en todos los nodos sin necesidad de declararlas.
### executionId
Cada ejecución de un workflow recibe un identificador único llamado `executionId`. Brain Studio lo genera automáticamente al iniciar el flujo y permanece constante durante toda la ejecución, incluyendo derivaciones entre Workflows y llamadas a Tools.
```
{{$context.executionId}}
```
El `executionId` es de solo lectura. Brain Studio lo genera automáticamente al iniciar cada ejecución; no necesitas crearlo ni modificarlo.
#### Para qué sirve
El `executionId` te permite identificar de forma única cada ejecución de tu flujo. Esto es útil cuando necesitas:
* **Conectar sistemas externos**: Enviar el ID a tu backend para que pueda rastrear o correlacionar la conversación con tus registros internos.
* **Reanudar ejecuciones pausadas**: Cuando un AI Agent se pausa esperando una respuesta externa (por ejemplo, una aprobación), el sistema externo necesita el `executionId` para reanudar la ejecución correcta.
* **Auditoría y trazabilidad**: Registrar en tus bases de datos qué ejecución generó cada acción, facilitando el seguimiento y la resolución de problemas.
#### Ejemplo: Enviar a una API externa
Supongamos que tu flujo consulta un servicio externo y necesitas que ese servicio sepa a qué ejecución responder. En un nodo **API**, puedes incluir el `executionId` en el cuerpo de la petición:
```json theme={null}
{
"orderId": "{{$context.orderId}}",
"callbackExecutionId": "{{$context.executionId}}",
"action": "procesar_pago"
}
```
Tu servicio externo recibe el `callbackExecutionId` y lo usa para enviar la respuesta de vuelta a la ejecución correcta.
#### Ejemplo: Reanudar un AI Agent pausado
Cuando configuras un AI Agent con **pausa por reanudación externa**, el payload de reanudación requiere el `executionId` para identificar qué ejecución continuar:
```json theme={null}
{
"executionId": "{{$context.executionId}}",
"message": "Pago aprobado exitosamente",
"pauseInteraction": false
}
```
#### Ejemplo: Guardar en Datum para auditoría
En un nodo **Datum**, puedes guardar el `executionId` junto con los datos de la operación para tener trazabilidad completa:
```json theme={null}
{
"usuario": "{{$user.name}}",
"accion": "solicitud_credito",
"executionId": "{{$context.executionId}}",
"fecha": "{{$context.currentDate}}"
}
```
Así, si necesitas investigar qué pasó en una conversación específica, puedes buscar por `executionId` en tu base de datos y ver exactamente los pasos que se ejecutaron.
#### En nodos de código
Dentro de un nodo **Código**, accedes al `executionId` igual que a cualquier otra variable de contexto:
```js theme={null}
const executionId = $context.get('executionId')
$output.set('log', `Procesando ejecución: ${executionId}`)
```
## Variables personalizadas
Además de las variables del sistema, puedes crear tus propias variables de contexto usando nodos de **Variable**, nodos de **Código**, o cualquier nodo que guarde datos en contexto (como las respuestas de nodos interactivos).
### Leer variables
Dentro de cualquier nodo puedes acceder al contexto con la sintaxis `{{$context.nombreVariable}}`. Por ejemplo, si las variables en contexto son:
```json theme={null}
{
"nombre": "Juan",
"ultimoPedido": { "id": "PED-123", "estado": "en camino" }
}
```
Entonces:
* `{{$context.nombre}}` muestra `Juan`.
* `{{$context.ultimoPedido.estado}}` muestra `en camino`.
### Contexto en nodos de código
En los nodos de código, usas los métodos de contexto de forma diferente:
* `$context.get(key, [defaultValue])` — Obtiene un valor del contexto
* `$context.set(key, value)` — Guarda o actualiza un valor en el contexto
* `$context.getHttpResponse(key)` — Obtiene la respuesta completa de un nodo HTTP
* `$context.getHttpRequest(key)` — Obtiene la petición enviada por un nodo HTTP
Para obtener variables de contexto:
```js theme={null}
const nombre = $context.get('nombre')
const nombre = $context.get('nombre', 'Juan')
```
Para guardar variables dentro de un nodo código:
```js theme={null}
$context.set('ultimoPedido.estado', 'entregado')
const usuario = { nombre: 'Juan', plan: 'gold' }
$context.set('usuario', usuario)
const pedidos = ['PED-123', 'PED-456']
$context.set('pedidos', pedidos)
```
Puedes usar la sintaxis de objetos con el punto para acceder a llaves internas del contexto. Por ejemplo, `$context.get('ultimoPedido.estado')` devuelve `"en camino"` sin necesidad de obtener todo el objeto.
## Contexto vs Memoria
| Característica | Contexto (`$context`) | Memoria (`$memory`) |
| ------------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------- |
| **Duración** | Solo la ejecución actual | Hasta 30 días de inactividad (se renueva con cada escritura); TTL opcional por variable |
| **Alcance** | Todos los nodos del flujo | Todos los workflows del usuario |
| **Uso típico** | Datos temporales de la conversación | Datos que persisten entre conversaciones |
| **Variables del sistema** | `executionId` (inyectado automáticamente) | No tiene |
Si dudas entre contexto y memoria, hazte esta pregunta: **¿necesito este dato después de que termine la conversación?** Si la respuesta es sí, usa [Memoria](/guides/variables/memory). Si solo lo necesitas durante la conversación actual, usa Contexto.
# Guía rápida
Source: https://docs.jelou.ai/guides/variables/guia-rapida
Introducción rápida a Variables — qué son, cuándo usarlas y cómo.
Las variables son clave para programar aplicaciones conversacionales. Te permiten compartir información entre nodos, workflows o tools.
Principalmente, vas a tratar con los siguientes tipos de variables en Brain Studio:
* [Context](/guides/variables/context): Temporal, solo vive dentro del flujo específico en el que la creas.
* [Memory](/guides/variables/memory): Persiste durante 24h y entre diferentes workflows, tools e interacciones.
* [User](/guides/variables/user): Te permite acceder a datos del usuario como nombre, teléfono u otros.
* [Message](/guides/variables/message): Permite acceder al último mensaje enviado por el usuario.
* [Input/Output](/guides/variables/input-output): Útil para construir tools personalizados en la plataforma, exponiendo entradas que recibes del flujo y salidas que regresas al mismo.
Las variables mencionadas arriba están relacionadas al usuario actual de la conversación. Es decir, sus valores corresponden a los datos y contexto de ese usuario en particular.
A continuación, puedes leer las guías de cómo guardar, leer y actualizar variables en cada caso.
# Input/Output
Source: https://docs.jelou.ai/guides/variables/input-output
Variables - Input and Output
Las variables `Input` y `Output` son clave cuando construyes tools en Brain Studio. Te permiten recibir datos desde el flujo que invoca tu tool y regresar resultados al finalizar su ejecución.
## Inputs
Los valores de entrada se definen desde el administrador de inputs mientras configuras tu tool. Cada input que declares estará disponible en tiempo de ejecución a través de:
* `{{$input.nombre}}` dentro de cualquier nodo.
* `$input.get('nombre', [valorPorDefecto])` dentro de nodos de código.
Por ejemplo, si configuraste un input llamado `ciudad`:
```js theme={null}
// Obtiene input ciudad
const ciudad = $input.get('ciudad')
// Obtiene input ciudad
// Si no lo encuentra, setea el valor como 'Quito'
const ciudad = $input.get('ciudad', 'Quito')
```
## Outputs
Los outputs de una tool se definen desde un nodo de código usando `$output.set(clave, valor)`. Estos valores quedan disponibles para el flujo luego de que el tool termina.
```js theme={null}
const data = $memory.get('apiResponse')
$output.set('data', data)
```
Puedes establecer múltiples outputs llamando a `$output.set` varias veces (uno por cada clave).
Asegúrate de que los nombres que uses en `$output.set('clave', valor)` coincidan con los outputs que declaraste en el nodo `end` de la tool; así el flujo podrá consumirlos sin errores.
# Memory
Source: https://docs.jelou.ai/guides/variables/memory
Persiste datos entre conversaciones con control de tiempo de vida (TTL) y soporte para archivos
## Introducción
Memory te permite guardar variables que persisten entre conversaciones, workflows y nodos. A diferencia de Context (que solo está disponible durante la conversación actual), Memory mantiene los datos disponibles para futuras interacciones con el usuario.
**Una sola memoria por usuario, cuatro formas de acceder.** Memory es un único almacén por usuario que ves desde distintas superficies según dónde estés trabajando:
* **Builder** — placeholder `{{$memory.key}}` y métodos `$memory.set/get/...` en nodos de código.
* **Functions** — cliente `ctx.memory` (ver [guía de `ctx.memory`](/guides/functions/memoria)).
* **AI Agent** — tool nativa `memory_manager` (ver [Tools nativas](/guides/agentes-ia/tools-nativas)).
* **REST API** — endpoints [`POST https://gateway.jelou.ai/workflows/v2/memory/users/{get-all|find-one|set}`](/api/memoria/introduccion).
Cualquier escritura desde una superficie es visible inmediatamente desde las otras. No hay copias paralelas ni sincronización que hacer.
## Características principales
| Característica | Descripción |
| ------------------------------ | ------------------------------------------------------- |
| **TTL configurable** | Define el tiempo de vida de cada variable en segundos |
| **Múltiples tipos** | Soporta primitivos, JSON y archivos |
| **Almacenamiento de archivos** | Guarda imágenes, videos, audios y documentos hasta 10MB |
| **Métodos específicos** | API diferenciada para cada tipo de dato |
## Tipos de datos
| Tipo | Tamaño máximo | TTL por variable | TTL máximo por variable |
| ----------- | -------------- | ---------------- | ----------------------- |
| **String** | 255 caracteres | Opcional | - |
| **Number** | 15 dígitos | Opcional | - |
| **Boolean** | - | Opcional | - |
| **JSON** | 15KB | Requerido | 86.400s (1 día) |
| **File** | 10MB | Requerido | 604.800s (1 semana) |
**TTL (Time To Live):** Tiempo de vida en segundos de cada variable. Transcurrido el TTL, la variable se elimina automáticamente. Por ejemplo, `3600` = 1 hora, `86400` = 1 día.
## Cuánto dura Memory en total
Memory tiene **dos relojes independientes** funcionando a la vez:
| Reloj | Duración | Qué controla | Se renueva cuando |
| -------------------- | ---------------- | --------------------------------- | -------------------------------------------- |
| **hashTTL** | 30 días | La memoria del usuario **entera** | Cada vez que **escribes** cualquier variable |
| **TTL por variable** | Ver tabla arriba | Cada variable individual | No se renueva — expira desde el `set` |
**Regla mental:** si el usuario escribe algo en Memory al menos una vez cada 30 días, su memoria vive indefinidamente. Si pasa 30 días completos sin escribir, **toda** su memoria se borra. Cada variable puede caducar antes por su propio TTL sin afectar el resto.
Solo **escribir** (`set`, `setJson`, `setFile`) renueva el hashTTL. **Leer** (`get`, `getJson`, `getFile`) no cuenta.
## Guardar variables
### Usando el nodo Variable
Para guardar variables sin código, usa el nodo **Variable** dentro de la sección `Lógica`. En `Variable` coloca el nombre y en `Valor` lo que quieres guardar; puede ser texto plano, otra variable o un dato del contexto.
```md theme={null}
Variable: nombre
Valor: {{$user.names}}
```
El nodo Variable es ideal para guardar primitivos (string, number, boolean) de forma rápida y visual.
Cada nodo Variable permite un máximo de **20 variables**. Si necesitas más, usa varios nodos o guarda los valores desde un nodo de código.
### Usando nodos de código
Para mayor control sobre TTL y tipos de datos complejos, usa los métodos de `$memory` en nodos de código:
```javascript theme={null}
// Primitivos (string, number, boolean) - TTL opcional
$memory.set('nombre', 'Juan')
$memory.set('intentos', 3)
$memory.set('verificado', true)
// Primitivo con TTL (expira en 1 hora)
$memory.set('codigoTemporal', 'ABC123', 3600)
// JSON - TTL requerido (máximo 1 día)
$memory.setJson('preferencias', {
idioma: 'es',
notificaciones: true
}, 86400)
// Archivo - async, TTL requerido, MIME requerido (máximo 1 semana)
await $memory.setFile('comprobante', base64String, 604800, 'application/pdf')
```
## Leer variables
### En cualquier nodo
Dentro de cualquier nodo puedes acceder a Memory con la sintaxis `{{$memory.nombreVariable}}`. Por ejemplo, si las variables en memoria son:
```json theme={null}
{
"nombre": "Juan",
"ultimoPedido": { "id": "PED-123", "estado": "en camino" }
}
```
Entonces:
* `{{$memory.nombre}}` muestra `Juan`
* `{{$memory.ultimoPedido.estado}}` muestra `en camino`
### En nodos de código
Usa los métodos específicos según el tipo de dato:
```javascript theme={null}
// Primitivos
const nombre = $memory.get('nombre')
const nombre = $memory.get('nombre', 'Invitado') // con valor por defecto
// JSON
const prefs = $memory.getJson('preferencias')
const prefs = $memory.getJson('preferencias', {}) // con valor por defecto
// Archivos - devuelve FileHandle
const archivo = $memory.getFile('comprobante')
```
## Trabajar con archivos
Al guardar un archivo en Memory, debes proporcionar el contenido en base64, el TTL y el tipo MIME:
```javascript theme={null}
// Guardar un archivo
await $memory.setFile('documento', base64Content, 604800, 'application/pdf')
```
Al leer un archivo con `$memory.getFile()` obtienes un **FileHandle** con tres métodos para acceder al contenido:
| Método | Descripción | Async |
| ------------- | -------------------------------- | ----- |
| `.toUrl()` | URL temporal para descargar (S3) | No |
| `.toBase64()` | Contenido en string base-64 | Sí |
| `.toRaw()` | Buffer / Uint8Array | Sí |
```javascript theme={null}
// Obtener URL temporal
const url = $memory.getFile('comprobante').toUrl()
// Obtener contenido en base64
const base64 = await $memory.getFile('comprobante').toBase64()
// Obtener contenido del archivo
const contenido = await $memory.getFile('comprobante').toRaw()
```
### Tipos MIME permitidos
| Categoría | Tipos MIME |
| -------------- | ----------------------------------------------------------------------- |
| **Texto** | `text/plain` |
| **JSON** | `application/json` |
| **XML** | `application/xml`, `text/xml` |
| **Imágenes** | `image/jpeg`, `image/png`, `image/gif` |
| **Videos** | `video/mp4`, `video/ogg`, `video/webm`, `video/x-msvideo`, `video/mpeg` |
| **Audios** | `audio/mpeg`, `audio/wav`, `audio/ogg`, `audio/aac`, `audio/flac` |
| **Documentos** | `application/pdf` |
Los métodos `$memory.setFile()`, `$memory.getFile().toBase64()` y `$memory.getFile().toRaw()` son **asincrónicos**. Debes usar `await` al llamarlos.
## Eliminar variables
Puedes eliminar variables antes de que expire su TTL:
```javascript theme={null}
// Eliminar una variable
$memory.delete('temporal')
// Eliminar múltiples variables
$memory.delete(['cache', 'sesion', 'temporal'])
```
## Seguridad
**No almacenes datos sensibles en Memory:**
* Contraseñas o números de tarjeta de crédito
* Tokens de autenticación de larga duración
* Información de identificación personal (PII) altamente sensible
Para persistencia a largo plazo, usa **[Datum](/guides/nodos/datum)**.
# Memory vs Context
Source: https://docs.jelou.ai/guides/variables/memory-vs-context
Aprende a manejar el estado de la conversación combinando Memory y Context según cada caso.
Elegir entre `$memory` y `$context` depende de una pregunta simple: **¿necesitas este dato después de que termine la conversación?**
## Usa \$context cuándo
Usa `$context` cuando el dato solo es necesario durante la conversación actual.
**Casos de uso:**
* **Consultar disponibilidad de productos:** El usuario pregunta por un producto, consultas tu inventario y guardas el stock disponible en `$context.stock` para mostrarlo y validar la cantidad que quiere comprar en los siguientes nodos
* **Validar código de descuento:** El usuario ingresa un cupón, lo validas con tu API y guardas el porcentaje en `$context.descuento` para aplicarlo al calcular el total
* **Autenticación temporal:** Obtienes un token de tu API y lo guardas en `$context.token` para usarlo en las siguientes llamadas del mismo flujo
* **Cálculos intermedios:** El usuario selecciona productos, vas sumando el subtotal en `$context.subtotal` para mostrarlo antes de confirmar la compra
**Ejemplo:**
```txt theme={null}
1. Usuario pregunta: "¿Tienen la camisa azul en talla M?"
2. Nodo API → Consulta inventario y guarda en $context.stock = 5
3. Nodo Mensaje → "Tenemos {{$context.stock}} unidades disponibles"
4. Usuario compra → Validas que la cantidad no exceda $context.stock
```
## Usa \$memory cuándo
Usa `$memory` cuando el dato debe persistir entre conversaciones. Puedes configurar el tiempo de vida (TTL) de cada variable.
**Casos de uso:**
* Recordar el nombre del usuario para saludarlo personalmente
* Guardar la última dirección de envío para ofrecerla por defecto
* Almacenar preferencias que mejoran la experiencia en futuras interacciones
* Recordar que el usuario completó un paso de verificación
**Ejemplo:**
```txt theme={null}
Conversación 1: Usuario dice "Mi nombre es María"
→ Guardas en $memory.nombre = "María"
Conversación 2 (mismo día):
→ Usas {{$memory.nombre}} para saludar: "Hola María"
```
Para detalles sobre tipos de datos, TTL, archivos y métodos disponibles, consulta la [guía completa de Memory](/guides/variables/memory).
## Criterios de decisión rápida
**No** → Usa `$context`
**Sí** → Usa `$memory`
## Resumen
| Aspecto | \$context | \$memory |
| ------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| **Duración** | Solo durante la conversación | Hasta 30 días de inactividad del usuario (se renueva con cada escritura). TTL opcional por variable: hasta 1 día (JSON) o 1 semana (archivos) |
| **Tipos** | Cualquier valor JavaScript | Primitivos, JSON (15KB), Archivos (10MB) |
| **Uso** | Datos temporales de la conversación | Mejorar experiencia entre conversaciones |
| **Seguridad** | Ideal para datos sensibles temporales | No usar para datos sensibles |
## ¿Y si necesito guardar datos por más tiempo o compartidos entre usuarios?
`$memory` no es una base de datos: es una memoria de trabajo por usuario con vigencia limitada (30 días de inactividad, TTL máximo por variable de 1 día en JSON o 1 semana en archivos). Si tu caso pide **persistencia real, datos compartidos entre usuarios, consultas por campos o historial auditable**, usa [Datum](/guides/nodos/datum) en su lugar.
**Señales de que `$memory` no es la herramienta correcta:**
* Necesitas conservar los datos por más de 30 días, incluso si el usuario deja de escribir.
* Los datos son de negocio (pedidos, facturas, catálogos), no del turno del usuario.
* Varios usuarios o workflows del mismo proyecto tienen que leer el mismo registro.
* Necesitas filtrar, buscar por campo o auditar cambios.
| Aspecto | \$memory | Datum |
| ---------------- | ---------------------------------------------- | ----------------------------------------------------------- |
| **Alcance** | Por usuario | Global — compartido entre workflows y usuarios del proyecto |
| **Persistencia** | Hasta 30 días de inactividad; TTL por variable | Permanente hasta que lo borres |
| **Consultas** | Solo por `key` exacta | Filtros por campo, búsqueda, paginación |
| **Ideal para** | Estado de conversación, preferencias | Pedidos, catálogos, registros históricos |
Regla práctica: si el dato sigue teniendo valor cuando el usuario deja de hablarte por un mes, no vive en `$memory`. Vive en Datum.
# Message
Source: https://docs.jelou.ai/guides/variables/message
Variables - Message
La variable `Message` expone el último mensaje que envió el usuario y que activó tu flujo. Es ideal cuando necesitas reaccionar al texto, adjuntos o metadatos del mensaje entrante.
## Estructura del mensaje
El objeto cambia según el tipo de mensaje que recibas. A continuación encontrarás ejemplos reales devueltos por WhatsApp.
### Texto
```json theme={null}
{
"type": "TEXT",
"text": "Hola"
}
```
### Audio
> El audio se transcribe automáticamente cuando es posible. Recibirás tanto la URL del archivo como el texto reconocido.
```json theme={null}
{
"type": "AUDIO",
"mediaUrl": "https://cdn.jelou.ai/...mp3",
"contentType": "mp3",
"text": "Esto es un audio"
}
```
### Imagen
```json theme={null}
{
"type": "IMAGE",
"mediaUrl": "https://cdn.jelou.ai/....png",
"caption": "Esto es una imagen",
"width": 680,
"height": 462,
"length": 58137
}
```
### Video
```json theme={null}
{
"type": "VIDEO",
"mediaUrl": "https://cdn.jelou.ai/....mp4",
"caption": "Esto es un video"
}
```
### Ubicación
```json theme={null}
{
"type": "LOCATION",
"lat": "-2.1646540164948",
"lng": "-79.895797729492",
"url": "http://maps.google.com/maps..."
}
```
### Archivo
```json theme={null}
{
"type": "FILE",
"mediaUrl": "https://cdn.jelou.ai/...",
"mimeType": "application/pdf",
"caption": "Esto es un archivo"
}
```
## Acceder al último mensaje
Puedes acceder al mensaje desde cualquier nodo con la sintaxis `{{$message.propiedad}}`:
* `{{$message.type}}` indica el tipo (`TEXT`, `IMAGE`, `AUDIO`, etc.).
* `{{$message.text}}` devuelve el contenido del mensaje para tipos `TEXT` y la transcripción de `AUDIO`.
* `{{$message.mediaUrl}}` expone la URL del archivo adjunto (imagen, audio, video o documento).
* `{{$message.caption}}` muestra el texto adicional enviado junto al archivo.
* `{{$message.lat}}` y `{{$message.lng}}` entregan las coordenadas cuando el tipo es `LOCATION`.
## Message en nodos de código
Dentro de un nodo de código accede a cada propiedad con `$message.get('propiedad')`:
```js theme={null}
const primerMensaje = $message.get('text')
const tipoMensaje = $message.get('type')
const urlAdjunto = $message.get('mediaUrl')
```
`Message` siempre muestra el mensaje más reciente que el usuario haya enviado. Si es la primera vez que el usuario escribe, ese primer mensaje también será considerado el “último mensaje”.
# User
Source: https://docs.jelou.ai/guides/variables/user
Variables - User
El objeto `User` te permite consultar información del contacto que está interactuando con tu flujo, como su nombre, número de teléfono y metadatos del canal.
## Datos disponibles
Los datos disponibles dependen del canal, pero un ejemplo típico es:
```json theme={null}
{
"id": "593999999999",
"names": "Juan Pérez",
"phone": "593999999999"
}
```
## Usar variables de usuario
Puedes acceder a cualquier propiedad del usuario con la sintaxis `{{$user.propiedad}}`:
* `{{$user.id}}` muestra el identificador único del usuario para el canal.
* `{{$user.names}}` muestra el nombre del usuario.
* `{{$user.phone}}` muestra el número de teléfono del contacto. Es el campo que debes usar cuando necesitas el teléfono.
En canales como WhatsApp, el teléfono viene sin signos ni espacios. Por ejemplo, `+593-111-111-111` se representa como `593111111111`.
## El teléfono va en `phone`, no en `id`
WhatsApp dejó de garantizar el número telefónico como identificador estable y adopta un identificador propio, el BSUID. Por eso `{{$user.id}}` puede traer un BSUID en lugar de un número, y no debes usarlo como teléfono.
* **Para el teléfono, usa `{{$user.phone}}`.** Devuelve el número real del contacto, incluso cuando la conversación entra identificada por BSUID.
* **`{{$user.phoneNumber}}` es un alias de `{{$user.phone}}`** y devuelve el mismo valor. Se mantiene para los flujos que ya lo usan, pero recomendamos migrar a `phone`.
* **Ambos pueden venir vacíos** cuando no es posible resolver un teléfono para ese contacto. Contempla ese caso en tus flujos antes de usar el valor.
* **`{{$user.id}}` sigue siendo el identificador del contacto** en el canal, y es el que debes usar para referenciarlo. Solo dejó de ser siempre un teléfono.
## User en nodos de código
Dentro de los nodos de código, tienes disponible el objeto `User` a través de `$user`:
```js theme={null}
// Accede al id del usuario
const id = $user.get('id')
// Accede al nombre del usuario
const name = $user.get('names')
```
El objeto `User` es de solo lectura. Si quieres persistir información nueva del contacto, guarda esos datos en `Memory` o `Context`.
# Introducción
Source: https://docs.jelou.ai/index
Jelou: la plataforma para construir, operar y escalar agentes de IA con atención humana integrada.
## ¿Qué es Brain Studio?
Brain Studio es una plataforma de Jelou para crear agentes de IA, automatizar conversaciones y gestionar la atención al cliente desde un solo lugar. Combina un builder visual low-code con herramientas de operación para equipos, mensajería outbound y conexión multicanal.
En Jelou puedes:
* **Construir** workflows conversacionales y tools reutilizables con un editor visual de nodos.
* **Operar** la atención humana desde una bandeja unificada multicanal.
* **Escalar** tu comunicación con campañas de mensajería outbound por WhatsApp.
## Construir con IA
### Workflows
Los Workflows son flujos conversacionales que programas para interactuar con tus usuarios. Diseña diálogos, ramifica decisiones y orquesta pasos según el contexto de la conversación usando nodos visuales.
### Tools
Las Tools son funciones reutilizables. Ejecutan tareas —integrar APIs, realizar cálculos, consultar datos— y devuelven un resultado sin esperar respuestas del usuario. Invócalas desde cualquier Workflow para no duplicar lógica.
### Agentes de IA
Configura agentes inteligentes que procesan mensajes, toman decisiones y ejecutan acciones de forma autónoma. Cuando el agente necesita escalar, transfiere la conversación con todo el contexto al equipo humano.
## Operar la atención
Jelou centraliza las conversaciones de WhatsApp, Instagram, Facebook y correo electrónico en una bandeja única donde tu equipo puede:
* Atender múltiples canales desde un solo lugar.
* Consultar y actualizar datos del cliente con el CRM integrado.
* Transferir casos entre agentes con contexto completo.
* Configurar mensajes automáticos, horarios y reglas de derivación.
Cuando un agente de IA escala un caso, el equipo humano recibe la conversación con el historial completo.
## Mensajería outbound
No todas las conversaciones comienzan con un mensaje del cliente. Con el módulo de campañas puedes enviar mensajes proactivos por WhatsApp usando plantillas HSM aprobadas por Meta: recordatorios, promociones, notificaciones o seguimiento.
* Crea y gestiona plantillas (HSM).
* Configura envíos masivos a listas de contactos.
* Personaliza mensajes con variables dinámicas.
* Mide resultados: enviados, entregados, leídos, respondidos.
## Cómo funciona todo junto
Jelou conecta la automatización con la atención humana en un ciclo continuo:
1. Diseñas Workflows y configuras agentes de IA en el builder.
2. Lanzas campañas outbound para iniciar conversaciones.
3. Los agentes de IA gestionan las respuestas automatizables.
4. Tu equipo interviene desde la bandeja cuando es necesario.
Todo dentro de la misma plataforma.
## Explora las áreas clave
Diseña, entrena y coordina agentes inteligentes para automatizar tus procesos.
Conoce los bloques que dan forma a cada interacción de tus workflows conversacionales.
Aprende a capturar, almacenar y reutilizar información a lo largo de la conversación.
Descubre las herramientas que potencian tu ciclo de trabajo con IA.
## Siguientes pasos
* [Conoce el Jelou Agent](/guides/getting-started/jelou-agent)
* [Tu primer workflow](/guides/getting-started/tu-primer-workflow)
* [Tu primera campaña](/connect/empezando/tu-primera-campana)
# Buenas prácticas para plantillas de WhatsApp
Source: https://docs.jelou.ai/connect/campanas/buenas-practicas
Recomendaciones para opt-in, estructura, redacción y calidad de plantillas ante Meta y WhatsApp.
Para asegurar una **alta calidad de envío**, **aprobación** y **entrega** de tus plantillas en Meta, conviene seguir estas recomendaciones. Complementa con la [categorización de plantillas](/connect/campanas/tipos-plantillas) para alinear contenido y tipo de mensaje.
## 1. Obtén el consentimiento del usuario (opt-in)
Antes de enviar cualquier plantilla, debes contar con la **autorización del cliente**. Puedes obtener este consentimiento a través de:
* Sitio web
* Conversaciones previas en WhatsApp
* SMS o llamadas
* Formularios o firma física
**Informa con claridad:**
* El nombre de tu negocio
* El tipo de mensajes que recibirá
* Cómo puede dejar de recibirlos
Esto reduce bloqueos y mejora la calidad de tu cuenta.
## 2. Cuida la estructura de la plantilla
Al crear una plantilla, sigue estas reglas básicas:
* Usa **nombres en minúsculas** y con **guiones bajos** (por ejemplo: `recordatorio_cita_1`).
* **Máximo 1024 caracteres** en el cuerpo.
* Usa **variables dinámicas en orden**: `{{1}}`, `{{2}}`, `{{3}}`…
* **No** coloques variables al **inicio** ni al **final** del mensaje.
Puedes incluir, cuando aplique:
* Botones
* Enlaces
* Imágenes (máx. 20 MB)
## 3. Redacción clara y adecuada
Evita rechazos o pérdida de calidad:
* No uses lenguaje ofensivo.
* No solicites datos sensibles (contraseñas, tarjetas completas, etc.).
* Evita exceso de promociones o mensajes invasivos.
* Cuida la ortografía y la redacción.
WhatsApp prioriza la **experiencia del usuario**; el tono y la utilidad del mensaje cuentan para la aprobación y el engagement.
## 4. Monitorea la calidad de tus plantillas
Meta evalúa de forma continua:
* Interacción de los usuarios
* Bloqueos o reportes
* Nivel de respuesta
**Estados habituales:**
* **Pendiente:** recién creada.
* **Alta / media / baja calidad:** según el comportamiento de los usuarios.
## 5. Evita pausas o bloqueos
Si una plantilla **baja de calidad**, puede **pausarse** temporalmente:
| Ocurrencia | Efecto |
| :--------- | :--------------------------- |
| 1.ª vez | Pausa de **3 horas** |
| 2.ª vez | Pausa de **6 horas** |
| 3.ª vez | **Desactivación indefinida** |
Si una plantilla se pausa, **detén los flujos automáticos** que la utilicen hasta revisar el contenido y el opt-in.
## 6. Usa botones para iniciar conversaciones
Cuando quieras **iniciar una conversación** y abrir la ventana de **24 horas**:
* Usa plantillas con **botones interactivos** (hasta **10** opciones).
Esto favorece:
* Mayor tasa de respuesta
* Mejor experiencia para el usuario
* Más probabilidades de continuar la conversación
**Recomendación final:** cuanto más **claras**, **útiles** y **esperadas** sean tus plantillas, mejor serán su **aprobación**, su **entrega** y la **interacción** con tus clientes.
# Recomendaciones multimedia para plantillas
Source: https://docs.jelou.ai/connect/campanas/dimensiones-plantillas
Dimensiones, formatos y buenas prácticas de diseño para imágenes, videos y documentos en plantillas de WhatsApp.
Para asegurar una correcta visualización de las plantillas en WhatsApp, Meta recomienda utilizar las siguientes dimensiones y formatos según el tipo de contenido.
## Imagen
| Tipo | Dimensiones recomendadas | Relación de aspecto |
| -------- | ------------------------ | ------------------- |
| Estándar | 800 × 418 px | 1.91:1 |
| Carrusel | 800 × 800 px | 1:1 |
* **Peso máximo:** 5 MB
* **Formatos permitidos:** JPG y PNG
## Video
* **Relación recomendada:**
* 16:9 (horizontal)
* 9:16 (vertical)
* **Peso máximo:** 15 MB
* **Formato permitido:** MP4
## Documento
Los documentos no utilizan dimensiones específicas, ya que WhatsApp muestra únicamente el ícono y nombre del archivo.
* **Peso máximo:** 15 MB
* **Formato permitido:** PDF
***
## Recomendaciones de diseño
### Mantén una zona segura
Evita colocar textos, logos o información importante demasiado cerca de los bordes de la imagen, ya que algunos dispositivos pueden aplicar recortes automáticos.
### Usa imágenes livianas
Aunque Meta permite archivos de hasta 5 MB, se recomienda optimizar las imágenes para mejorar la velocidad de carga y entrega de campañas.
### Evita exceso de texto
Las imágenes con demasiado texto suelen generar menor interacción y pueden afectar la experiencia visual del usuario.
### Mantén consistencia visual
En plantillas tipo carrusel se recomienda:
* Utilizar el mismo tamaño en todas las imágenes
* Mantener una línea gráfica consistente
* Evitar mezclar imágenes horizontales y verticales
### Considera el modo oscuro
Algunos usuarios utilizan WhatsApp en modo oscuro, por lo que se recomienda:
* Evitar textos oscuros sobre fondos transparentes
* Mantener buen contraste
* Validar que logos e íconos sigan siendo visibles
# Envío de campañas
Source: https://docs.jelou.ai/connect/campanas/envio-campanas
Crea y envía una campaña de WhatsApp paso a paso: qué pide cada formato de plantilla, cómo cargar destinatarios, emparejar parámetros y programar el envío.
Una campaña envía una plantilla aprobada a una lista de contactos por WhatsApp. Puedes iniciarla desde **Campañas**, desde **Plantillas** o desde la **bandeja de entrada**: las tres abren el mismo asistente de cinco pasos.
Si entras desde Plantillas, el canal y la plantilla llegan ya seleccionados y, si cancelas, vuelves a la lista de plantillas.
**Cada formato de plantilla pide cosas distintas** en el primer paso y algunos obligan a una configuración en el paso Avanzado.
## Antes de empezar
* **Un canal de WhatsApp activo.** El selector solo lista canales de WhatsApp activos de tu empresa.
* **Una plantilla aprobada.** El selector solo trae plantillas en estado **Aprobada**. Si la creaste hace poco y sigue en revisión, todavía no aparece.
***
## Crear la campaña
Subes un CSV con la lista de contactos y eliges si la campaña se envía ahora o queda programada: promociones, avisos o recordatorios a muchos destinatarios a la vez.
Se completa en cinco pasos.
Canal, plantilla y todo lo que el formato necesite: archivo del encabezado, sucursal o producto de tu tienda.
Un archivo CSV con la lista de contactos, más el nombre de la campaña.
Empareja los `{{1}}`, `{{2}}`… de la plantilla con columnas del CSV.
Qué pasa después de que el contacto recibe el mensaje: workflow, botones, parámetros adicionales y CRM.
Envío inmediato o programado. Antes de disparar verás una pantalla de confirmación con el resumen completo.
El botón **Siguiente** permanece bloqueado hasta que el paso esté completo, y las reglas de bloqueo cambian según el formato de la plantilla.
### Cómo se completa cada paso
Subes un **CSV** con la lista de contactos y le pones **nombre a la campaña** (entre 5 y 80 caracteres). Puedes descargar una plantilla de CSV de ejemplo.
Los números deben incluir el código internacional del país, sin `+` ni espacios. Sin archivo o sin nombre válido, el paso queda bloqueado.
Cada parámetro se **empareja con una columna del CSV**.
Los parámetros se agrupan por dónde viven: encabezado, contenido, botones y, si es carrusel, un grupo por tarjeta. Ninguno puede quedar vacío.
Si la plantilla no tiene parámetros verás el aviso "La plantilla escogida no tiene parámetros para emparejar" y puedes seguir de largo.
Para armar el CSV y ver buenas prácticas, revisa [Parámetros de campañas](/connect/campanas/parametros-campanas).
Aquí defines qué pasa **después** de que el contacto recibe el mensaje.
| Configuración | Cuándo aparece | Qué define |
| ------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| **Workflow** | Siempre | El workflow que atiende la respuesta del contacto. |
| **Botones** | Plantillas con botones de respuesta rápida | Qué workflow dispara cada botón, si el botón es de un solo uso y si la respuesta es obligatoria. |
| **Workflow post-compra** | Plantillas de Jelou Shop | Workflow al responder y workflow al completar la compra en la tienda. |
Puedes reutilizar una configuración guardada o crear una nueva; el nombre pide mínimo 5 caracteres. También puedes agregar **parámetros adicionales** y **parámetros de CRM**, que viajan como datos del mensaje.
En los formatos con configuración obligatoria (botones de respuesta rápida y Jelou Shop) el paso queda bloqueado hasta elegir o crear una configuración válida para esa plantilla.
* **Enviar ahora**: la campaña se dispara de inmediato.
* **Programar**: eliges fecha y hora. Se guarda con la zona horaria de tu usuario. Sin fecha y hora, el paso queda bloqueado.
Antes de disparar el envío verás un resumen con el canal y la plantilla, la cantidad de destinatarios, cuántos parámetros se emparejaron, qué configuración quedó asociada y cuándo se envía.
Si no tienes créditos suficientes, aparece un aviso antes de continuar.
### Qué pide cada formato de plantilla
Los formatos son los mismos que eliges al crear la plantilla. Lo que cambia en la campaña es qué te pide cada uno antes de dejarte avanzar.
| Formato | Qué pide el paso General | ¿Obliga configuración en Avanzado? |
| ------------------------------------------------- | ------------------------------------------------- | ----------------------------------------------- |
| Mensaje personalizado (texto) | Nada extra | No |
| Mensaje personalizado (imagen, video o documento) | Subir el archivo del encabezado | No |
| Carrusel | Imagen o video por cada tarjeta | No |
| Mensaje de Jelou Shop | Sucursal, y producto o categoría según el formato | **Sí** — workflow post-compra |
| Oferta por tiempo limitado | Imagen o video del encabezado | No, pero pide fecha de expiración en Parámetros |
| WhatsApp Flow | Archivo del encabezado, si la plantilla lo tiene | No |
| Autenticación | Nada extra | No |
Además, **cualquier** formato cuyos botones sean de respuesta rápida obliga a una configuración de botones en el paso Avanzado.
### Guías por formato
**Solo texto.** El caso más simple: eliges canal y plantilla y sigues de largo. Si la plantilla tiene parámetros, se emparejan en el paso 3.
**Con multimedia.** Si el encabezado es imagen, video o documento, aparece un cargador de archivo. Es obligatorio: sin archivo no avanzas.
| Encabezado | Formatos | Peso máximo |
| ---------- | ----------------- | ----------- |
| Imagen | .jpg, .jpeg, .png | 5 MB |
| Video | .mp4 | 15 MB |
| Documento | .pdf | 15 MB |
El archivo se sube por campaña, no queda guardado en la plantilla. Cada envío puede llevar una imagen distinta. Revisa las [recomendaciones multimedia](/connect/campanas/dimensiones-plantillas) para las dimensiones sugeridas.
La plantilla ya define cuántas tarjetas trae y de qué tipo es su encabezado. Por cada tarjeta debes **subir su archivo**: imagen (.jpg, .jpeg, .png) hasta 5 MB o video (.mp4) hasta 15 MB.
Todas las tarjetas deben tener su archivo para poder avanzar. Cada tarjeta puede además tener sus propios parámetros, que se emparejan por separado en el paso 3.
La plantilla se aprueba una sola vez y **el producto, la categoría o la sucursal se eligen aquí**, al crear la campaña. En el paso General aparece una sección propia:
* **Sucursal**: si la tienda tiene una sola, se autoselecciona y no se muestra. Con dos o más es obligatoria. **Cambiar de sucursal borra el producto elegido**, porque el catálogo y los precios cambian.
* **Producto** (formato *Un solo producto*): botón **Seleccionar producto** que abre un panel con búsqueda y filtro por categoría.
* **Categoría** (formato *Categoría de productos*): selector obligatorio, se carga según la sucursal.
* **Catálogo completo**: no pide nada más allá de la sucursal.
El nombre y el precio del producto los resuelve Jelou en el momento del envío: no aparecen en el paso de Parámetros ni en la confirmación.
En el paso Avanzado es **obligatorio** configurar el **workflow post-compra**.
Si tu empresa no tiene Jelou Shop configurado, verás un banner rojo y el paso queda bloqueado.
En el paso General se comporta como una plantilla con encabezado multimedia: subes la imagen o el video y sigues.
Lo particular está en el **paso 3**: aparece un bloque de **fecha y hora de expiración** de la oferta. Es obligatorio y la fecha tiene que ser **futura**; con una fecha pasada el paso no deja avanzar.
Si el encabezado es multimedia, subes el archivo (imagen 5 MB, video 15 MB, documento 15 MB).
El **flujo asociado se muestra en un selector deshabilitado**: viene fijo desde la plantilla. Para cambiarlo tienes que editar la plantilla en **Campañas → Plantillas**.
No pide nada especial en el paso General. Suele traer un parámetro con el código, que se empareja en el paso 3.
### Reenviar una campaña
Desde el detalle de una campaña ya enviada puedes reenviarla. La configuración llega **en modo lectura** —canal, plantilla, parámetros y avanzado— y solo subes un CSV nuevo y eliges cuándo enviar.
El CSV debe traer las mismas columnas que la campaña original.
En campañas de Jelou Shop hay dos excepciones: si el producto ya no existe en el catálogo el reenvío se bloquea, y si el catálogo no responde la campaña se reenvía con los datos originales y verás un banner amarillo de aviso.
***
Cómo se crea cada formato de plantilla antes de poder enviarlo.
Entregados, leídos y respondidos después del envío.
Qué revisar cuando una campaña falla o no llega.
# Errores frecuentes en campañas
Source: https://docs.jelou.ai/connect/campanas/errores-frecuentes-campanas
Errores más comunes al enviar campañas de WhatsApp y cómo resolverlos.
Al enviar campañas con plantillas de WhatsApp, algunos mensajes pueden no enviarse o no entregarse. A continuación te explicamos los errores más comunes y qué significa cada uno.
## El número del usuario es parte de un experimento
Este error ocurre cuando el número del usuario forma parte de un **experimento de Meta** relacionado con mensajes de marketing.
Durante estos experimentos, algunos usuarios no pueden recibir mensajes de marketing, incluso si el número es válido.
**Qué puedes hacer:**
* Pedir al usuario que inicie la conversación enviando un mensaje primero.
* Una vez que el usuario escriba, tendrás **24 horas** para responder dentro de la ventana de atención.
## El mensaje no se puede enviar
Este error indica que WhatsApp no pudo enviar el mensaje al destinatario.
Las razones más comunes son:
* El número de teléfono no tiene WhatsApp.
* El usuario no aceptó las Condiciones del servicio y Política de privacidad más recientes.
* El usuario utiliza una versión antigua de WhatsApp.
**Qué puedes hacer:**
* Confirmar que el número tenga WhatsApp activo.
* Pedir al usuario que actualice la aplicación a la última versión.
* Verificar que el usuario aceptó los términos y políticas de WhatsApp.
## Meta decidió no entregar el mensaje
Meta puede decidir no entregar un mensaje para proteger la experiencia del usuario en el ecosistema de WhatsApp.
Esto suele ocurrir cuando:
* El usuario ha recibido muchos mensajes de marketing recientemente.
* Meta detecta que el mensaje podría afectar la calidad de interacción.
**Qué puedes hacer:**
* Esperar al menos **24 horas** antes de intentar enviar el mensaje nuevamente.
* Evitar enviar campañas repetidas al mismo usuario en poco tiempo.
## El usuario dejó de recibir mensajes de marketing
Este error aparece cuando el usuario decidió dejar de recibir mensajes de marketing de tu empresa.
Cuando esto sucede:
* No podrás enviar plantillas de marketing a ese número.
* Los mensajes de campaña no serán entregados.
**Qué puedes hacer:**
* No volver a intentar enviar campañas a ese usuario.
***
**Recomendación para mejorar la entrega de tus campañas:**
* Mantén tu base de contactos actualizada.
* Envía campañas solo a usuarios que aceptaron recibir comunicaciones.
* Evita enviar demasiados mensajes de marketing en poco tiempo.
# Formatos de plantillas
Source: https://docs.jelou.ai/connect/campanas/formatos-plantillas
Elige el formato correcto para tu plantilla de WhatsApp: mensaje personalizado, carrusel, mensaje de Jelou Shop, oferta por tiempo limitado, WhatsApp Flow y autenticación.
Las plantillas son los mensajes que WhatsApp debe aprobar antes de que puedas enviarlos a tus clientes en una campaña o desde la Bandeja de entrada.
El formato de una plantilla se define con dos decisiones que tomas en el **paso 2 (Tipo de mensaje)** del asistente: la **categoría** y el **subformato**, que determina cómo se ve el mensaje.
Esta guía cubre los subformatos. Para elegir la categoría y entender cómo WhatsApp la valida, revisa [Categorización de plantillas](/connect/campanas/tipos-plantillas).
## Los pasos comunes
Todos los formatos se crean con el mismo asistente. El botón para avanzar permanece bloqueado mientras falten campos obligatorios del paso actual, y a la derecha siempre tienes una **vista previa** que se actualiza mientras escribes.
Nombre, idioma, canal y visibilidad. Aquí también está disponible **Crear con IA**, que rellena los campos a partir de una descripción de lo que quieres enviar.
Categoría y subformato.
Cambia según el formato. Es lo que detallan las guías de abajo.
Cambian según el formato. Al terminar este paso, el contenido se revisa contra las políticas de WhatsApp: si se detectan incumplimientos, se muestran en pantalla y puedes volver al paso de contenido a corregirlos.
Revisión final y envío a WhatsApp. La plantilla queda **en revisión** hasta que la aprueben o rechacen; el estado se ve en el listado de plantillas.
***
## Guías por formato
Disponible en **Marketing** y en **Utilidad**. Es el formato más flexible y el punto de partida recomendado.
**Para qué se usa**
* Marketing: anuncio de una promoción, lanzamiento de producto, cupón de descuento, invitación a un evento, reactivación de clientes inactivos.
* Utilidad: confirmación de compra, aviso de despacho, recordatorio de cita, vencimiento de factura, cambio de estado de un ticket.
**Paso a paso**
1. En el paso 2 elige la categoría (Marketing o Utilidad) y luego **Mensaje personalizado**.
2. En el paso 3, elige el **tipo de mensaje**: Texto, Imagen, Video o Documento.
* Con **Texto** puedes agregar un **encabezado** de texto (opcional, hasta 60 caracteres, admite 1 parámetro).
* Con **Imagen, Video o Documento** debes subir el archivo. Esa pieza es la que verá el cliente como encabezado.
3. Escribe el **cuerpo del mensaje** (obligatorio). Usa **Agregar parámetro** para insertar valores que cambian por contacto (nombre, número de pedido, monto). Por cada parámetro completa su etiqueta y un ejemplo.
4. Opcionalmente escribe un **pie de página** (hasta 60 caracteres). Sirve para avisos cortos tipo "Responde SALIR para no recibir más mensajes".
5. En el paso 4 agrega los botones que necesites.
6. Revisa la vista previa y confirma.
| Tipo | Máximo | Para qué sirve |
| --------------------- | ------ | --------------------------------------------------------------------------------- |
| Derivación a workflow | 10 | El cliente responde con un toque y puedes derivarlo a un workflow. |
| URL | 2 | Abre una página web. Puede ser fija o con una parte variable por contacto. |
| Contacto | 1 | Llama a un número de teléfono del negocio. No se combina con el botón de llamada. |
| Llamada | 1 | No se combina con el botón de contacto. |
Categoría **Marketing**. Un mensaje de texto seguido de un conjunto de tarjetas deslizables, cada una con su propia imagen o video y sus botones.
**Para qué se usa**
* Mostrar varias promociones a la vez (por categoría, por sucursal, por talla).
* Presentar los productos más vendidos de la semana.
* Mostrar varios planes o paquetes para que el cliente compare y elija.
* Catálogo visual cuando **no** tienes catálogo conectado a Meta.
**Paso a paso**
1. En el paso 2 elige **Marketing** y luego **Carrusel**.
2. En el paso 3, escribe el **cuerpo del mensaje**: el texto que aparece arriba de las tarjetas (obligatorio, hasta 900 caracteres, admite hasta 9 parámetros). Es el gancho del mensaje.
3. Elige el **tipo de encabezado para tarjetas**: **Imagen** o **Video**. Todas las tarjetas comparten el mismo tipo; no puedes mezclar.
4. Presiona **Agregar tarjeta** por cada tarjeta que necesites. Puedes crear **de 1 a 10**; el botón se deshabilita al llegar a 10.
5. En cada tarjeta escribe su **contenido** (obligatorio, hasta 120 caracteres). Dentro de una tarjeta **no se permiten saltos de línea**; si necesitas separar ideas, usa otra tarjeta.
6. Si una tarjeta necesita valores variables, agrégale sus propios parámetros con etiqueta y ejemplo. Cada tarjeta maneja los suyos, independientes del cuerpo.
7. Usa la **X** de la esquina de una tarjeta para eliminarla.
8. En el paso 4 configura los botones: **los botones que añadas se aplican a todas las tarjetas del carrusel**, y cada tarjeta debe tener mínimo uno y máximo dos. Solo cambia el destino por tarjeta (por ejemplo, la URL de cada producto); el tipo y el nombre son comunes.
9. Revisa la vista previa deslizando las tarjetas y confirma.
La imagen o el video de cada tarjeta se muestra con una pieza de ejemplo mientras creas la plantilla. El archivo real de cada tarjeta se define al momento de crear la campaña.
Categoría **Marketing**. Lleva al cliente directo a tu tienda web de [Jelou Shop](/guides/integraciones/e-commerce/jelou-shop) con un botón, sin necesidad de tener un catálogo sincronizado con Meta.
Lo que WhatsApp aprueba es una plantilla estándar (encabezado, cuerpo y botón de URL), así que **una sola plantilla aprobada sirve para todo tu catálogo**: **el producto, la categoría o la sucursal se eligen al crear la campaña**, no al crear la plantilla. No tienes que pedir una aprobación nueva por cada producto ni queda el precio congelado desde el día en que creaste la plantilla.
**Requisitos**
* Jelou Shop configurado en tu empresa y con productos cargados. Si la empresa no lo tiene, la opción aparece deshabilitada con el aviso "Esta empresa no tiene Jelou Shop configurado, así que no se puede usar este formato."
* Al menos un canal de WhatsApp conectado.
* Si tu tienda maneja sucursales, tenerlas creadas: el envío las usa para filtrar el catálogo.
**Para qué se usa**
* Promocionar un producto puntual con su nombre y su precio actual, sin crear una plantilla por producto.
* Llevar tráfico a una categoría completa: "Nueva colección de verano", "Todo en electrodomésticos".
* Invitar a recorrer la tienda entera en campañas de temporada o de reactivación.
**Los tres formatos**
| Formato | Qué abre el botón | Encabezado |
| -------------------------- | ---------------------------------------------------------------- | -------------------------------------------------- |
| **Un solo producto** | La tienda web mostrando el producto que elijas en la campaña | Imagen del producto, resuelta al momento del envío |
| **Categoría de productos** | La tienda web filtrada por la categoría que elijas en la campaña | Texto, imagen, video o documento |
| **Catálogo completo** | La tienda web con todo el catálogo | Texto, imagen, video o documento |
**Paso a paso**
1. En el paso 2 elige **Marketing** y luego **Mensaje de Jelou Shop**. El asistente arranca con el formato **Un solo producto** y el botón *Ver producto* ya preparado.
2. En el paso 3, elige el **Formato** (obligatorio) entre las tres opciones. Cambiar de formato limpia el cuerpo, el encabezado y el botón, así que decídelo antes de redactar.
3. Con **Categoría de productos** o **Catálogo completo**, elige el tipo de contenido (Texto, Imagen, Video o Documento). Si eliges uno multimedia, sube el archivo con los límites habituales: 5 MB en imagen, 15 MB en video y 15 MB en PDF. El cuerpo es un solo campo, sin bloque de producto ni texto de cierre.
4. Con **Un solo producto**, el cuerpo se arma en tres partes:
| Parte | ¿Editable? | Límite | Parámetros |
| ----------------------------------------- | ---------------------- | -------------- | ---------- |
| **Contenido inicial del mensaje** | Sí | 600 caracteres | Hasta 7 |
| **Producto** (nombre en negrita y precio) | No, lo arma el sistema | — | — |
| **Contenido de cierre del mensaje** | Sí | 280 caracteres | No admite |
El bloque **Producto** se muestra como referencia con datos de ejemplo (por ejemplo, *Camiseta de algodón · \$19.90*) y la nota "Datos de ejemplo. Seleccionarás el producto al enviar la campaña." En el mensaje real, Jelou reemplaza nombre y precio por los del producto que elijas en la campaña.
5. Escribe el **pie de mensaje** si lo necesitas: es opcional, como en el resto de plantillas.
6. En el paso 4 no configuras un botón: ya viene uno de URL y **solo puedes cambiar su etiqueta** (hasta 24 caracteres, sin comillas). Los textos por defecto son *Ver producto*, *Ver categoría* y *Ver catálogo*. La URL la arma Jelou —es un enlace dinámico a tu tienda web— y no es editable.
7. Confirma. La imagen del encabezado que viaja en la solicitud a Meta es solo una **muestra para el revisor**; la imagen real del producto se adjunta en cada envío.
Categoría **Marketing**. Muestra un título de oferta con un temporizador de cuenta regresiva y, opcionalmente, un código promocional que el cliente copia con un toque.
**Para qué se usa**
* Flash sales y ofertas de pocas horas.
* Cupones con fecha de vencimiento.
* Campañas de urgencia: "últimas horas", "solo hoy".
**Paso a paso**
1. En el paso 2 elige **Marketing** y luego **Oferta por tiempo limitado**.
2. En el paso 3, elige el **tipo de mensaje**: Imagen o Video. Este formato no admite solo texto ni documento.
3. Escribe el **título de la oferta** (obligatorio, máximo 16 caracteres). Es el texto que acompaña al temporizador; por ejemplo `50% OFF hoy`.
4. Marca **Añadir periodo de caducidad a esta oferta** si quieres mostrar la cuenta regresiva. La vista previa muestra una fecha de ejemplo: **la fecha real se configura al crear la campaña**.
5. Escribe el **cuerpo del mensaje** (obligatorio, hasta **600** caracteres en este formato).
6. En el paso 4 configura los botones:
* **Botón de URL**: obligatorio. Lleva a la tienda o a la página de la oferta. Puede ser fija o con una parte variable por contacto; en ese caso debes dar un ejemplo de URL completa.
* **Copiar código**: opcional. Agrega un código promocional que el cliente copia con un toque; debes ingresar un código de ejemplo.
7. Confirma.
El nombre del botón de URL no puede ser igual al del botón de copiar código.
Disponible en **Marketing** y en **Utilidad**. Añade un botón que abre un flujo de WhatsApp, un formulario o proceso guiado dentro del chat. Solo disponible en canales de **WhatsApp Cloud**.
**Para qué se usa**
* Marketing: registro a un evento, encuesta de interés, solicitud de cotización.
* Utilidad: agendamiento de citas, actualización de datos, encuesta de satisfacción posventa.
**Paso a paso**
1. En el paso 2 elige **Marketing** o **Utilidad** y luego **WhatsApp Flow**.
2. En el paso 3, elige el **tipo de mensaje**: Texto, Imagen, Video o Documento. Con Texto puedes agregar encabezado; con los demás debes subir el archivo.
3. Escribe el **cuerpo del mensaje** (obligatorio) y, si quieres, el **pie de página**.
4. En el paso 4 configura el botón de flujo:
* Escribe el **nombre del botón** (entre 2 y 24 caracteres).
* Elige el **flujo** de la lista de flujos del canal.
* Elige la **pantalla** con la que se abrirá el flujo. La lista se carga después de elegir el flujo.
5. Confirma.
Categoría **Autenticación**. Envía un código de un solo uso. Es el único formato cuyo contenido no se escribe: se genera automáticamente.
**Para qué se usa**
* Verificación de inicio de sesión.
* Confirmación de una transacción o un cambio de datos sensibles.
* Recuperación de contraseña.
**Requisito**: el portafolio de la cuenta debe estar verificado en Meta.
**Paso a paso**
1. En el paso 2 elige **Autenticación**. No hay subformatos que seleccionar.
2. En el paso 3 verás el aviso de que el contenido no es editable. El cuerpo se arma según el idioma elegido en el paso 1:
* Español: `Tu código de verificación es {{1}}.`
* Inglés: `{{1}} is your verification code.`
* Portugués: `Seu código de verificação é {{1}}.`
3. Marca las opciones adicionales que quieras:
* **Agregar recomendación de seguridad**: añade "Por tu seguridad, no lo compartas."
* **Agrega el tiempo de caducidad para el código**: añade al pie "Este código caduca en N minutos". El valor debe estar **entre 1 y 90**; fuera de ese rango verás "Ingresa un valor entre 1 y 90".
4. En el paso 4 verás el botón **Copiar código**, creado automáticamente. Puedes cambiarle el texto.
5. Confirma.
***
## Cambiar de formato
Cambiar la categoría o el subformato **borra el contenido ya cargado**: encabezado, cuerpo, pie, multimedia, parámetros, tarjetas y botones. Elige el formato antes de escribir el mensaje.
En una plantilla ya enviada a WhatsApp que esté **pendiente de aprobación no se puede modificar la categoría**. Verás el aviso "No es posible modificar la categoría de una plantilla aprobada."
Meta gestiona automáticamente el cambio de categoría de la plantilla cuando la considera incorrecta, para poder aprobarla.
## Notas
* Los parámetros no pueden ir al inicio ni al final del texto, y no se permiten varios saltos de línea seguidos.
* Los nombres de los botones no pueden repetirse dentro de una misma plantilla.
* Solo puedes usar en campañas las plantillas **aprobadas**.
* En el listado de plantillas, las de Jelou Shop aparecen con el tipo *Shop - Producto*, *Shop - Categoría* o *Shop - Catálogo*.
Dimensiones, pesos y buenas prácticas de diseño para imágenes, videos y documentos.
Cómo WhatsApp valida la categoría y qué hacer si te la recategorizan.
Usa una plantilla aprobada para enviar tu primer envío masivo.
# Métricas de campañas
Source: https://docs.jelou.ai/connect/campanas/metricas-de-campanas
Aprende a leer correctamente las métricas y tomar mejores decisiones en tus próximos envíos.
Analizar una campaña no es solo revisar números, sino entender qué ocurrió después del envío y cómo cada métrica, vista en conjunto, explica el comportamiento real de los usuarios.
## Entregado vs Entregado al usuario
### ✓ Mensaje entregado
Indica que WhatsApp aceptó el mensaje correctamente.
Es una validación técnica: el envío salió bien desde tu cuenta.
***
### ✓✓ Mensaje entregado al usuario
Confirma que el mensaje llegó a la bandeja de entrada del usuario.
Si este número es bajo, puede indicar:
* Números inválidos
* Usuarios sin WhatsApp activo
* Restricciones por calidad o comportamiento percibido como spam
Si la diferencia entre “entregado” y “entregado al usuario” es alta, revisa la calidad de tu base de datos.
***
## Confirmación de lectura
### ✓✓ Lectura
Se registra solo si el usuario tiene activada la confirmación de lectura (vistos azules).
No debe usarse como métrica principal.
Un bajo porcentaje de lectura no significa que la campaña falló.
Muchos usuarios desactivan esta opción, por lo que no es un indicador absoluto.
***
## Mensajes respondidos: la métrica clave
Esta es la métrica más relevante.
Cuenta la primera interacción del usuario, incluyendo:
* Respuestas de texto
* Clics en botones que activan flujos internos
⚠️ No incluye clics en botones externos (por ejemplo, enlaces a páginas web).
Esta métrica mide:
* Interés real
* Claridad del mensaje
* Efectividad del llamado a la acción (CTA)
***
## Cómo leer los resultados en conjunto
No analices cada métrica por separado. Observa patrones.
**Alto entregado + bajo respondido**\
→ El mensaje llegó, pero no generó interés.\
Revisa el contenido o el CTA.
**Alto entregado + alto respondido**\
→ Mensaje y acción están alineados.\
La campaña funcionó.
**Baja entrega general**\
→ Revisa la lista de contactos o el estado de la plantilla.
***
## Próximos pasos recomendados
Después de cada campaña:
* Ajusta el texto o el CTA
* Prueba diferentes versiones de mensaje
* Optimiza botones y llamadas a la acción
* Replica lo que funcionó mejor
Cada campaña es una oportunidad de aprendizaje, y la mejora real ocurre cuando ajustas tus decisiones con base en datos, no en suposiciones.
# Parámetros de campañas
Source: https://docs.jelou.ai/connect/campanas/parametros-campanas
Personaliza tus mensajes masivos usando parámetros dinámicos en tus plantillas.
Los parámetros te permiten convertir una plantilla genérica en un mensaje personalizado.
En lugar de enviar el mismo texto a todos, puedes adaptar partes del mensaje con datos específicos como nombre, pedido o fecha.
## ¿Qué son los parámetros?
Son espacios dentro de la plantilla que se completan automáticamente con información dinámica.
Se escriben entre doble llave y con numeración:
`{{1}}`, `{{2}}`, `{{3}}`
Pueden representar:
* Nombre del cliente
* Número de pedido
* Fecha
* Ciudad
* Cualquier dato relevante para el mensaje
## Ejemplo
**Plantilla:**
Hola `{{1}}`, tu pedido `{{2}}` fue enviado y llegará el `{{3}}`.
**Mensaje final recibido:**
Hola Ana, tu pedido 5678 fue enviado y llegará el 21 de abril.
Un mismo mensaje puede adaptarse automáticamente a cada destinatario.
***
## Cómo usar parámetros en Jelou
Para configurar correctamente una plantilla con parámetros:
1. Escribe el texto del mensaje.
2. Inserta los parámetros usando doble llave y numeración: `{{1}}`, `{{2}}`, `{{3}}`
3. Asegúrate de que el orden y la cantidad coincidan exactamente con los datos que enviarás.
Si el orden o la cantidad no coinciden, la plantilla puede fallar o ser rechazada.
## ¿De dónde salen los valores?
Cuando envías una campaña, los valores provienen de:
### Archivo CSV
* La primera columna debe ser el identificador del destinatario: un número de teléfono (con código de país) o un BSUID.
* Las columnas siguientes deben corresponder a los parámetros.
* Cada fila representa un destinatario.
### Configuración dentro de Jelou
Al crear la campaña, relacionas cada parámetro con una columna del archivo CSV.
Jelou reemplaza automáticamente los valores en el mensaje final.
El CSV debe tener la misma cantidad de parámetros que la plantilla.
## Buenas prácticas
Mantén el mensaje claro y enfocado.
✔️ Ejemplo recomendado:
Hola `{{1}}`, tu cita es el `{{2}}` a las `{{3}}`.
❌ Ejemplo poco claro:
`{{1}}`, tu info: `{{2}}`, `{{3}}`, `{{4}}`, `{{5}}`.
Además:
* No uses parámetros consecutivos sin texto intermedio.
* Evita dejar campos vacíos.
* Siempre prueba antes de enviar.
## ¿Cuántos parámetros puedes usar?
Puedes usar hasta **9 parámetros por mensaje**.
Pero más no significa mejor. La clave es usarlos con intención.
✔️ Ejemplo correcto:
Hola `{{1}}`! Este es tu número de boleto: `{{2}}`.\
Tu vuelo a `{{3}}` está programado para el `{{4}}` a las `{{5}}`. ¡Buen viaje!
❌ Ejemplo incorrecto:
`{{1}}`, aquí hay información sobre tu boleto: `{{2}}`.\
`{{3}}` `{{4}}` `{{5}}`
La claridad siempre es prioridad.
## ¿Qué pasa si un parámetro está vacío?
Si un parámetro no tiene valor:
* El mensaje puede salir incompleto.
* Puede verse poco profesional.
* Puede afectar la experiencia del usuario.
Por eso es importante validar antes de enviar.
## Cómo probar tus parámetros
Antes de lanzar una campaña:
* Usa la vista previa en Jelou.
* Envíate un mensaje de prueba.
* Verifica que todos los valores se reemplacen correctamente.
* Corrige cualquier error antes de enviar.
Personalizar bien tus mensajes mejora la experiencia y aumenta la probabilidad de respuesta.
La clave no es usar más parámetros, sino usarlos con intención.
# Categorización de plantillas
Source: https://docs.jelou.ai/connect/campanas/tipos-plantillas
Cómo se categorizan las plantillas de WhatsApp y su impacto en precio y aprobación.
Al crear o administrar una plantilla de WhatsApp, es fundamental comprender cómo se categorizan, ya que la categoría impacta directamente en el **precio** y en la **aprobación**.
Antes de crear una plantilla:
* Revisa las normas de categorización
* Verifica que el contenido cumpla con los criterios
* Da seguimiento al estado de aprobación
* Mantente atento a posibles recategorizaciones automáticas
## Tipos de categorías de plantillas
WhatsApp clasifica las plantillas en **3 categorías principales**:
### 1. Plantillas de Marketing
Son las más flexibles y buscan:
* Generar reconocimiento
* Impulsar ventas
* Hacer retargeting
* Promover descargas de app
* Fortalecer la relación con clientes
También se consideran marketing:
* Plantillas con contenido mixto (utilidad + promoción)
* Mensajes ambiguos como "¡Felicidades!" o solo un parámetro vacío
**Ejemplos comunes:**
* `Disfruta un 15% de descuento con el código PROMO`
* `Dejaste productos en tu carrito. Finaliza tu compra`
* `Renueva tu suscripción antes del 01/04/2025`
* `Descarga nuestra app para acceder a beneficios exclusivos`
***
### 2. Plantillas de Utilidad
Son mensajes no promocionales que:
* Se activan por acción del usuario
* Son específicos para su cuenta/pedido
* Son esenciales o críticos para el usuario
Deben cumplir **dos condiciones**:
1. No tener intención promocional
2. Ser específicas para el usuario o críticas para su seguridad
**Ejemplos comunes:**
* `Tu pedido #1234 ha sido confirmado y será enviado el 15/03.`
* `Tu factura #5678 vence el 20/03 por un valor de $50.00.`
* `Hemos recibido tu pago por $120.00. Gracias.`
* `Tu turno está confirmado para el 10/04 a las 14:00.`
***
### 3. Plantillas de Autenticación
Son las más restrictivas. Se usan exclusivamente para:
* Enviar códigos OTP
* Verificación de identidad
* Recuperación de cuenta
* Validación de transacciones
**Ejemplo:**
* `Tu código de verificación es 123456`
Solo esta categoría puede enviar códigos de acceso.
Para crear plantillas de autenticación, tu negocio debe tener el **portafolio verificado** en Meta Business Manager. Sin esta verificación, no podrás crear ni enviar este tipo de plantillas.
Consulta la [guía paso a paso para verificar tu negocio en Facebook Business Manager](https://help.jelou.ai/es/articles/11118021-guia-paso-a-paso-como-verificar-tu-negocio-en-facebook-business-manager).
## Cómo WhatsApp asigna la categoría
Cuando creas una plantilla:
1. Seleccionas la categoría.
2. WhatsApp valida el contenido.
3. Se asigna un estado.
Desde el **9 de abril de 2025**: si seleccionas **Utilidad** pero el contenido parece marketing, se aprobará automáticamente como **Marketing**. Puedes solicitar revisión hasta 60 días después del cambio.
## Estados de aprobación
| Estado | Descripción |
| ------------- | --------------------------------------------------------------------------------------------- |
| **Aprobada** | WhatsApp acepta la categoría elegida. La plantilla puede usarse. |
| **Pendiente** | Está en proceso de revisión. |
| **Rechazada** | WhatsApp no coincide con la categoría seleccionada. El motivo puede ser `INCORRECT_CATEGORY`. |
Si tu plantilla es **rechazada**, puedes:
* Crear una nueva plantilla
* Editar la categoría y reenviar
* Solicitar revisión desde el Business Manager
***
**Recomendaciones clave:**
* Si incluye promoción → probablemente es **Marketing**.
* Si informa sobre una acción del usuario → puede ser **Utilidad**.
* Si envía un código OTP → debe ser **Autenticación**.
* Evita mezclar promoción con mensajes operativos si quieres que sea utilidad.
* Revisa siempre antes de enviar a aprobación.
Ya elegiste la categoría: revisa los subformatos disponibles y cómo se arma el contenido de cada uno.
# Tu primera plantilla
Source: https://docs.jelou.ai/connect/empezando/crea-tu-primera-plantilla
Diseña y envía tu primera plantilla (HSM) a revisión de Meta desde Jelou.
Las plantillas de WhatsApp te permiten enviar mensajes proactivos fuera de la ventana de 24 horas.\
En esta guía vas a crear tu primera plantilla en Jelou y dejarla lista para revisión por Meta.
## Antes de empezar
Asegúrate de tener:
* Un canal de WhatsApp ya conectado
* Definido el objetivo del mensaje (informativo, promocional o autenticación)
***
Ve a `Campañas` → `Plantillas`.
Haz clic en `+ Crear`.
Aquí podrás ver todas tus plantillas y su estado:
* Pendiente
* Aprobada
* Rechazada
Define los datos básicos de la plantilla:
**Nombre**\
Usa un nombre claro y descriptivo (ej.: `recordatorio_cita`).
El identificador técnico se genera automáticamente según las reglas de Meta.
**Idioma**\
Selecciona el idioma del mensaje:
* Español
* Inglés
* Portugués
**Canal asociado**\
Define qué canal o flujo utilizará esta plantilla para iniciar o continuar la conversación.
\*\*Visibilidad en Connect \*\*\
Decide si los asesores podrán usar esta plantilla manualmente desde la Bandeja de entrada.
Elige la categoría según el objetivo del mensaje:
* **Utilidad**: mensajes operativos o transaccionales (confirmaciones, recordatorios).
* **Marketing**: promociones, campañas, anuncios.
* **Autenticación**: envío de códigos de seguridad (OTP).
Si la categoría no coincide con el contenido, Meta puede reclasificarla automáticamente.
Elige el formato:
* Solo texto
* Imagen
* Video
* Documento
Luego redacta el mensaje principal.
Si necesitas personalización, agrega **parámetros** (ej.: nombre, número de pedido, fecha).
Por cada parámetro debes:
* Indicar cuántos usas
* Proporcionar un ejemplo
El pie de página es opcional.
Las plantillas de Autenticación (OTP) no permiten este formato libre.
Buenas prácticas:
* No uses parámetros consecutivos.
* Separa siempre parámetros con texto.
* Mantén el mensaje enfocado en una sola intención.
Según el tipo de plantilla, puedes incluir botones:
* Redirección a un flujo o flujo
* URL (estática o dinámica)
* Llamada telefónica
La cantidad y tipo de botones permitidos depende de la categoría seleccionada.
Antes de enviar:
* Revisa la vista previa
* Verifica parámetros y botones
* Confirma la creación
Una vez enviada, la plantilla entrará en revisión por Meta.
**Tiempo de aprobación:** Meta puede tardar hasta 48 horas en revisar la plantilla, aunque normalmente el proceso es más rápido.
***
## Listo
Con tu plantilla aprobada podrás:
* Iniciar conversaciones fuera de la ventana de 24 horas
* Lanzar campañas masivas
* Permitir que asesores usen plantillas desde Connect
* Automatizar notificaciones operativas o promocionales
Así construyes la base de tu comunicación proactiva en WhatsApp.
# Tu Primera Campaña
Source: https://docs.jelou.ai/connect/empezando/tu-primera-campana
Las campañas te permiten enviar mensajes masivos por WhatsApp usando plantillas aprobadas y una base de datos de contactos.
En esta guía vas a crear tu primera campaña en Jelou: elegirás una plantilla, cargarás destinatarios y enviarás el mensaje ahora o programado.
***
Desde el dashboard, haz clic en `Campañas`.
Selecciona el botón `+Enviar Campañas` .
Escribe un nombre claro y fácil de identificar.
El nombre puede tener hasta 80 caracteres.\
Usa algo descriptivo como: `Promo_Marzo_ClientesVIP`.
Elige:
* El canal asociado
* La plantilla aprobada por WhatsApp
Si la plantilla incluye multimedia, adjunta el archivo correspondiente.
Revisa la vista previa antes de continuar.
Las plantillas pueden incluir parámetros como `{{nombre}}` para personalizar el mensaje.
Sube un archivo **CSV** con tu base de contactos.
Reglas básicas:
* La primera columna debe ser el número de teléfono con código de país.
* Puedes incluir columnas adicionales para personalización.
Asegúrate de que las columnas del CSV coincidan con los parámetros de la plantilla.
Asocia cada parámetro de la plantilla con una columna del CSV.
Esto permite que cada persona reciba su mensaje con datos personalizados.
Puedes:
* Usar una configuración guardada
* Crear una nueva desde cero
Aquí puedes definir:
* Comportamiento de botones
* Precarga de datos en CRM
* Reglas para interacción con atención humana
Si guardas la configuración, podrás reutilizarla como preset en futuras campañas.
Elige si quieres:
* Enviar la campaña de inmediato
* Programarla para una fecha y hora específica
Los mensajes se enviarán progresivamente según las reglas de WhatsApp.
Aproximadamente una hora después del envío podrás ver métricas en el dashboard.
Métricas disponibles:
* Entregados
* Recibidos
* Leídos
* Respondidos
Asegúrate de que tus campañas cumplan con las políticas de mensajería de WhatsApp para evitar rechazos o bloqueos.
# Aplicación móvil
Source: https://docs.jelou.ai/connect/panel-multi-agente/aplicacion-movil
Gestiona conversaciones desde cualquier lugar con la app móvil de la Bandeja de entrada.
Puedes descargar el aplicativo desde:
* [Google Play Store](https://play.google.com/store/search?q=jelou+connect\&c=apps)
* [Apple App Store](https://apps.apple.com/us/app/jelou-connect/id6472668771)
La app está diseñada para que los operadores puedan gestionar conversaciones desde cualquier lugar, manteniendo las funcionalidades clave de la Bandeja de entrada.
## Chats
En esta sección podrás visualizar todas las conversaciones que se te asignen:
* Asignación directa
* Asignación por cola de atención
También podrás:
* Buscar conversaciones por nombre o número celular
* Visualizar el estado de cada conversación
### Funcionalidades dentro del chat
Dentro de cada conversación podrás:
* Enviar imágenes y videos en tiempo real
* Enviar archivos desde la galería
* Enviar audios
* Escuchar audios en velocidad x2
* Visualizar y completar el CRM
* Agregar y consultar tags
* Enviar mensajes rápidos configurados previamente
## Archivados
Aquí podrás ver todas las conversaciones anteriores que hayas gestionado.
* Si la sesión está activa, podrás recontactar al cliente.
* La conversación se moverá automáticamente a la sección **Chats**.
Adicionalmente, en la sección de archivados podrás:
* Visualizar la información registrada en el CRM
* Consultar los tags asociados a la conversación
## Configuraciones
En esta sección encontrarás:
* **Tema oscuro y claro** para personalizar la interfaz.
* **Acerca de**, donde podrás:
* Ver la versión de la app
* Consultar términos y política de privacidad
* **Gestión del estado del operador** (disponible, ocupado, etc.)
***
La aplicación móvil te permite mantener la continuidad de atención, gestionar conversaciones en tiempo real y acceder a toda la información relevante del cliente, sin depender de un escritorio.
# Integración con Genesys
Source: https://docs.jelou.ai/connect/panel-multi-agente/genesys
Conecta Jelou con Genesys para gestionar conversaciones y habilitar la interoperabilidad entre ambas plataformas.
Jelou integra los canales de comunicación con Genesys, permitiendo a los usuarios:
* Gestionar conversaciones desde Genesys utilizando los canales de comunicación conectados en Jelou.
* Transferir conversaciones y contexto de interacción entre Jelou y Genesys para garantizar una atención continua y eficiente.
* Escalar conversaciones hacia agentes de Genesys cuando sea necesario.
* Centralizar la gestión de conversaciones en un ecosistema integrado entre ambas plataformas.
## Requisitos previos
Para utilizar la integración con Genesys, necesitas:
* Una cuenta **Enterprise** activa en Jelou.
* El nodo de **Genesys** habilitado en la cuenta.
* Una cuenta activa de **Genesys Cloud** con permisos administrativos.
* Un cliente **OAuth** con permisos para consumir las APIs de Genesys Cloud.
* Los canales y flujos que participarán en la interoperabilidad previamente definidos.
***
## Instala la aplicación
Inicia sesión en Jelou. Dirígete al módulo **Brain** y selecciona la sección **Canvas**.
Dentro del Canvas, crea un nuevo flujo según tu necesidad. Para este ejemplo:
1. Agrega un **nodo de texto** y configura un mensaje de bienvenida para el canal.
2. Desde la barra lateral de nodos, arrastra el **nodo de Genesys** hacia el flujo.
Se recomienda agregar:
* Un mensaje de confirmación para transferencias exitosas.
* Un mensaje de manejo de errores en caso de falla de integración.
Antes de configurar la integración, valida que existan los roles necesarios para el cliente OAuth.
Ruta: **Menu > User Management > Roles and Permissions**
Los roles permitirán que el cliente OAuth tenga acceso a los recursos y APIs utilizados por la integración.
Selecciona el rol correspondiente para revisar los permisos habilitados.
Ruta: **Menu > User Management > Roles and Permissions > \[Rol] > Edit Role**
El rol **Developer** puede utilizarse con los permisos predeterminados de Genesys Cloud.
Dirígete a: **Menu > IT and Integrations > OAuth**
Crea un cliente OAuth que permita autenticar el middleware o servicio externo encargado de la interoperabilidad con Genesys Cloud.
Una vez creado, abre la aplicación para revisar su configuración.
Ruta: **Menu > IT and Integrations > OAuth > \[Cliente OAuth] > Edit Application**
Verifica la siguiente información:
* Nombre de la aplicación.
* Tipo de autenticación.
* Grant Type.
* Configuración general.
Selecciona la pestaña **Roles** del cliente OAuth.
Ruta: **Menu > IT and Integrations > OAuth > \[Cliente OAuth] > Roles**
Asigna los roles previamente configurados para que el cliente OAuth pueda consumir las APIs requeridas por la integración.
Verifica que las divisiones asociadas a los roles correspondan a todas las interacciones que utilizará el conector. Una configuración incorrecta puede generar errores de permisos durante la operación.
Dirígete a: **Menu > Digital and Telephony > Message > Platform Integrations**
Crea una integración de tipo **Open Messaging** para permitir el intercambio de mensajes entre Genesys Cloud y Jelou.
Abre la integración creada para revisar su configuración.
Una vez creada la integración Open Messaging, identifica el **Integration ID**.
Este identificador corresponde al valor único de la integración y será utilizado posteriormente para configurar el Trigger.
El **Integration ID** se encuentra al final de la URL cuando accedes al detalle de la integración Open Messaging.
Dirígete a: **Menu > Orchestration > Triggers**
Crea un Trigger que escuche los eventos asociados a las conversaciones de Open Messaging. Este Trigger será el encargado de ejecutar automáticamente el Workflow cuando ocurra el evento configurado.
Abre el Trigger creado y configura una condición utilizando el **Integration ID** obtenido anteriormente.
Esta condición garantiza que únicamente se ejecuten los eventos correspondientes a la integración configurada.
El Trigger debe ejecutar el Workflow de Architect encargado de notificar a Jelou que la atención en Genesys finalizó.
Dirígete a: **Menu > Orchestration > Architect > Flows**
Crea un Workflow que reciba la información enviada por el Trigger. Este Workflow será responsable de procesar los datos de la conversación y ejecutar el Data Action que notifica la finalización a Jelou.
Dentro del Workflow, configura las variables que recibirán la información enviada desde el Trigger.
Estas variables permitirán identificar la conversación y construir la solicitud hacia el Data Action.
Dentro del Workflow, agrega una tarea para ejecutar el **Data Action** encargado de notificar a Jelou que la atención en Genesys terminó.
Dirígete a: **Menu > IT and Integrations > Data Actions**
Crea un nuevo Data Action que será utilizado por el Workflow.
Abre el Data Action creado y configura la petición hacia el webhook de Jelou.
Este Data Action notifica a Jelou que la atención en Genesys Cloud terminó. Al recibirlo, Jelou retoma el control y el canal continúa atendiendo al usuario final, sin que este perciba el cambio.
Ruta: **Menu > IT and Integrations > Data Actions > \[Data Action] > Configuration**
**Método y endpoint**
| Campo | Valor |
| -------------------- | --------------------------------------------- |
| Método HTTP | `POST` |
| Request URL Template | `https://chatbot.jelou.ai/v1/genesys/webhook` |
**Headers**
| Header | Valor |
| -------------- | ------------------ |
| `Content-Type` | `application/json` |
**Body del request**
El webhook espera un evento de Open Messaging con la siguiente estructura:
```json theme={null}
{
"id": "EVENT_ID",
"type": "Event",
"direction": "Outbound",
"conversationId": "CONVERSATION_ID",
"channel": {
"from": { "id": "FROM_ID" },
"to": { "id": "CUSTOMER_ID", "idType": "Phone" }
},
"events": [
{ "eventType": "CustomerEnd" }
]
}
```
**Descripción de los campos**
| Campo | Tipo | Descripción |
| -------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | Identificador único del evento enviado. |
| `type` | string | Tipo de payload. Para notificar la finalización siempre es `Event`. |
| `direction` | string | Dirección del evento respecto a Genesys Cloud. Debe ser `Outbound` (sale de Genesys hacia Jelou). |
| `conversationId` | string | Identificador de la conversación en Genesys Cloud. Es el valor con el que Jelou identifica la transferencia activa. |
| `channel.from.id` | string | Identificador del participante de Genesys que emite el evento (por ejemplo, el agente o el flujo de Architect). |
| `channel.to.id` | string | Identificador del usuario final en el canal. En WhatsApp es el número de teléfono en formato E.164. |
| `channel.to.idType` | string | Tipo de identificador del usuario final. Para canales de WhatsApp usa `Phone`. |
| `events[].eventType` | string | Evento a notificar. Usa `CustomerEnd` para indicar que la atención en Genesys Cloud finalizó y el control vuelve al canal. |
El campo `direction` se nombra desde el punto de vista de Genesys Cloud: `Outbound` significa que el evento **sale de Genesys** hacia Jelou. El webhook rechaza los payloads marcados como `Inbound`.
**Mapeo de variables**
En la sección **Input Contract** declara las variables que recibirá el Data Action desde el Workflow y referéncialas en el body mediante la sintaxis `${input.nombreVariable}`. Los nombres del ejemplo son ilustrativos: usa los que hayas definido en tu Workflow.
```json theme={null}
{
"id": "${input.eventId}",
"type": "Event",
"direction": "Outbound",
"conversationId": "${input.conversationId}",
"channel": {
"from": { "id": "${input.fromId}" },
"to": { "id": "${input.customerId}", "idType": "Phone" }
},
"events": [
{ "eventType": "CustomerEnd" }
]
}
```
El `conversationId` debe ser el mismo que Genesys Cloud asignó a la conversación transferida desde Jelou. Si no corresponde a una transferencia activa, Jelou no podrá identificarla y el control no se devolverá al canal.
***
## Configura la aplicación
Después de completar la configuración en Genesys Cloud:
Regresa al módulo **Brain** y selecciona el nodo de **Genesys** dentro del flujo.
Completa la configuración del nodo con los parámetros correspondientes de la integración.
Verifica que la conexión con Genesys Cloud sea exitosa antes de continuar.
Cuando la configuración esté lista, **guarda y publica** el flujo.
***
## Usa la aplicación
Una vez configurada la integración:
* Las conversaciones podrán transferirse automáticamente entre Jelou y Genesys Cloud.
* Los canales creados en Jelou automatizarán la atención inicial de los clientes.
* Los agentes podrán continuar la conversación directamente desde Genesys Cloud.
* Cuando la atención finalice en Genesys Cloud, el Trigger y el Workflow notificarán a Jelou y el canal retomará la conversación con el usuario final.
* Los eventos de conversación serán procesados automáticamente mediante Open Messaging, Trigger, Workflow y Data Actions.
No se requieren acciones manuales adicionales una vez publicada la automatización.
# Gestión Consolidada de Casos
Source: https://docs.jelou.ai/connect/panel-multi-agente/gestion-consolidada-de-casos
Visualiza y analiza de forma centralizada todos los casos gestionados durante un período determinado.
A diferencia del Monitoreo en Vivo, que se enfoca en la operación actual, esta sección ofrece una visión completa de la actividad ocurrida dentro del rango de fechas seleccionado, facilitando el análisis de tendencias, la evaluación de resultados y la toma de decisiones basada en datos históricos.
## Indicadores generales
En la parte superior de la pantalla se presenta un conjunto de indicadores que permiten conocer rápidamente el comportamiento general de la operación.
Representa la cantidad total de casos registrados durante el período seleccionado, independientemente de su estado o resultado final.
Corresponde a los casos que recibieron al menos una intervención por parte de un operador.
Representa los casos que fueron transferidos entre operadores o equipos durante el proceso de atención.
Corresponde a los casos que finalizaron sin haber recibido ninguna intervención humana.
Muestra el tiempo promedio que tardaron los operadores en emitir la primera respuesta al usuario una vez iniciado el proceso de atención.
Representa el tiempo promedio transcurrido entre los mensajes enviados por los usuarios y las respuestas realizadas por los operadores durante toda la conversación.
Muestra el tiempo promedio que transcurrió desde el inicio hasta el cierre de los casos gestionados durante el período seleccionado.
## Clasificación de casos
Los casos se agrupan en dos categorías principales según la forma en que llegaron al equipo humano.
Son aquellos casos que fueron escalados por el workflow hacia un operador humano debido a una solicitud del usuario, una regla de negocio o una condición configurada dentro del flujo conversacional.
Corresponden a conversaciones donde el workflow determinó que debía existir atención humana, pero no fue posible realizar la asignación debido a que no existían operadores disponibles en ese momento o el horario de atención había finalizado.
Esta clasificación permite identificar oportunidades de mejora en los flujos de atención y en la cobertura operativa.
***
## Información disponible por caso
Cada registro muestra información detallada que permite conocer el contexto y trazabilidad completa de la atención.
| Campo | Descripción |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Identificador del cliente | Identificador único del usuario asociado al caso. Dependiendo del canal, puede corresponder a un número telefónico, correo electrónico u otro username. |
| Canal de origen | Canal que recibió inicialmente la interacción y que posteriormente derivó la conversación hacia atención humana. |
| Operador asignado | Operador responsable de la gestión actual del caso. En caso de transferencias, se visualizará el operador que tiene asignada la conversación en ese momento. |
| Equipo | Equipo de atención responsable de gestionar el caso. |
| Hora de inicio | Momento exacto en que comenzó la atención humana del caso. Esta fecha puede ser diferente al inicio de la conversación. |
| Tiempo de primera respuesta | Tiempo específico que tardó el operador en emitir la primera respuesta al usuario. |
| Marca de gestión | Indica si el caso recibió una intervención efectiva por parte del operador. Se actualiza automáticamente según las acciones realizadas durante la atención. |
| Duración total | Tiempo transcurrido entre el inicio y cierre de la conversación. Para casos activos, este valor continuará actualizándose hasta que la atención finalice. |
### Estado del caso
Permite identificar la situación actual de la conversación.
| Estado | Descripción |
| ------- | ------------------------------------- |
| Activo | La conversación se encuentra en curso |
| Cerrado | La conversación fue finalizada |
### Finalizada por
Indica quién o qué acción provocó el cierre de la conversación.
* Cierre realizado por el operador.
* Cierre automático por inactividad.
* Cierre asociado a una transferencia.
### Origen del caso
Permite identificar cómo se generó la atención.
| Origen | Descripción |
| -------------------------- | ------------------------------------------------------------ |
| Orgánico | La conversación fue iniciada de forma natural por el usuario |
| Inducido por administrador | La atención fue generada por un administrador |
| Inducido por operador | La atención fue generada por un operador |
| Inducido por sistema | La atención fue generada automáticamente por el sistema |
### Acciones disponibles
Cada caso dispone de accesos rápidos que permiten consultar el detalle completo de la conversación y revisar el historial asociado a la gestión realizada.
***
## Búsqueda y filtros
Para facilitar el análisis de grandes volúmenes de información, la plataforma incorpora herramientas de búsqueda y filtrado.
Es posible buscar casos mediante:
* Canal
* Operador
* Equipo
* Rango de fechas
Estos filtros permiten realizar análisis específicos y localizar rápidamente conversaciones dentro de la operación.
# Historial de Conversaciones
Source: https://docs.jelou.ai/connect/panel-multi-agente/historial-de-conversaciones
Consulta, revisa y audita todas las interacciones gestionadas dentro de la plataforma con trazabilidad completa.
Cada conversación queda registrada con su contexto completo: mensajes intercambiados, eventos operativos, transferencias y cierres. Esto permite reconstruir qué ocurrió durante la atención, quién intervino y cuándo sucedió cada evento.
## Tipo de atención
La información puede visualizarse según el tipo de atención:
Conversaciones provenientes de canales de mensajería.
Interacciones gestionadas desde la bandeja de correo integrada.
Comentarios y respuestas realizadas sobre publicaciones de Facebook, Instagram y X (antes Twitter).
Esta segmentación permite consultar cada modalidad de atención de forma independiente y facilita la búsqueda y análisis de conversaciones.
## Búsqueda de conversaciones
La forma más rápida de localizar una conversación es utilizando el identificador del cliente.
Dependiendo del tipo de atención, este identificador puede corresponder a:
* Número de teléfono
* Correo electrónico
* Identificador único del contacto
La búsqueda mostrará todas las conversaciones asociadas al cliente para que puedas revisar su historial completo de interacciones.
## Filtros disponibles
Para facilitar el análisis de la información, el historial cuenta con filtros avanzados que permiten encontrar conversaciones específicas.
Los filtros disponibles son:
* Rango de fechas
* Operador
* Equipo
* Canal de origen
Los filtros son acumulativos, por lo que es posible combinar varios criterios para obtener resultados más precisos.
## Lista de conversaciones
Una vez aplicados los filtros, se mostrará el listado de conversaciones que cumplen con los criterios seleccionados.
Para cada conversación podrás visualizar:
* Cliente
* Operador asignado
* Fecha de atención
Esta vista permite identificar rápidamente conversaciones específicas sin necesidad de ingresar al detalle de cada una.
***
## Detalle de la conversación
Al ingresar a una conversación se visualizará el historial completo de mensajes en orden cronológico.
Para cada interacción se mostrará:
* Autor del mensaje (cliente, canal u operador)
* Contenido enviado
* Fecha y hora exacta
* Estado del mensaje cuando aplique
Esto permite reconstruir completamente el contexto de la atención realizada.
### Información general del caso
En la parte superior de la conversación se visualizarán los principales atributos asociados al caso:
* Estado del caso
* Origen del caso
* Indicador de gestión
Esta información permite comprender rápidamente el contexto general de la atención.
### Eventos del caso
Además de los mensajes intercambiados, el historial incorpora eventos relevantes ocurridos durante el ciclo de vida del caso.
Algunos ejemplos son:
* Asignación de operador
* Transferencia entre operadores
* Transferencia entre equipos
* Recuperación de casos
* Cambios de estado
* Cierre de conversación
Estos eventos permiten comprender las acciones operativas realizadas durante la gestión.
### Navegación entre conversaciones
Cuando un cliente posee múltiples conversaciones registradas, es posible navegar entre ellas para revisar el historial completo de atención sin necesidad de regresar al listado principal.
Esta funcionalidad facilita el análisis de casos recurrentes y el seguimiento histórico de cada cliente.
***
## Trazabilidad y auditoría
Toda la información almacenada en el historial queda registrada para fines de seguimiento y auditoría.
Esto permite:
* Revisar decisiones tomadas durante una gestión.
* Analizar conversaciones históricas.
* Dar seguimiento a procesos de atención.
* Realizar controles de calidad operativa.
La información registrada sirve como respaldo para supervisores, administradores y equipos de auditoría.
## Exportación de información
El historial permite exportar la información consultada para análisis o seguimiento externo.
La exportación respetará los filtros aplicados al momento de generar el archivo e incluirá la información principal asociada a cada conversación.
Esto facilita la elaboración de reportes, auditorías y análisis operativos fuera de la plataforma.
# Integración con HubSpot
Source: https://docs.jelou.ai/connect/panel-multi-agente/hubspot
Conecta Jelou con HubSpot para gestionar conversaciones, automatizar flujos y centralizar la atención al cliente.
Jelou integra los canales de comunicación y automatizaciones conversacionales con HubSpot, permitiendo a los usuarios:
* Gestionar conversaciones desde HubSpot utilizando canales de comunicación conectados en Jelou.
* Automatizar flujos conversacionales mediante canales configurados desde el módulo Brain.
* Transferir conversaciones y atención de clientes hacia Inbox de HubSpot o canales personalizados.
* Centralizar la atención al cliente y automatizaciones en una única plataforma.
## Requisitos previos
Para utilizar la integración con HubSpot, necesitas:
* Una cuenta **Enterprise** activa en Jelou.
* El nodo de **HubSpot** habilitado en la cuenta.
***
## Instala la aplicación
Inicia sesión en Jelou. Dirígete al módulo **Brain** y selecciona la sección **Canvas**.
Dentro del Canvas, crea un nuevo flujo según tu necesidad. Para este ejemplo:
1. Agrega un **nodo de texto** y configura un **mensaje de bienvenida** para el canal de prueba.
2. Desde la barra lateral de nodos, arrastra el **nodo de HubSpot** hacia el flujo.
Se recomienda agregar:
* Un mensaje de confirmación para asignaciones exitosas.
* Un mensaje de manejo de error en caso de falla de integración.
Dentro del nodo de HubSpot, haz clic en **Continuar configuración en HubSpot**. La plataforma redireccionará automáticamente a HubSpot para completar la instalación y autorización de la aplicación.
HubSpot te pedirá seleccionar la cuenta que deseas conectar con Jelou. Elige la cuenta correspondiente y haz clic en **Elegir cuenta**.
A continuación, revisa los permisos que la aplicación solicita para enviar y recibir mensajes, gestionar conversaciones y acceder a la información de tu cuenta.
Haz clic en **Conectar aplicación**. Una vez finalizada la autorización, regresarás automáticamente a Jelou.
***
## Configura la aplicación
Después de instalar la integración:
Regresa al módulo **Brain** y selecciona el **nodo de HubSpot** dentro de tu flujo.
Configura el canal de comunicación que utilizarás:
* **Inbox de HubSpot** — para gestionar conversaciones directamente desde HubSpot.
* **Canal personalizado** — para canales configurados de forma independiente.
Configura las acciones deseadas para la atención de clientes y automatizaciones dentro del flujo.
Cuando la configuración esté lista, **guarda y publica** el flujo.
***
## Usa la aplicación
Una vez configurada la integración:
* Las conversaciones recibidas desde los canales conectados podrán ser transferidas y gestionadas desde HubSpot.
* Los canales creados en Jelou podrán automatizar la atención inicial de clientes.
* Los agentes podrán continuar la conversación directamente desde HubSpot Inbox o desde el canal configurado.
* Las sincronizaciones y transferencias de conversaciones se realizan automáticamente según la configuración establecida en el flujo.
No se requieren acciones manuales adicionales una vez publicada la automatización.
***
## Desinstala la aplicación
Para desinstalar la aplicación de Jelou desde HubSpot:
Inicia sesión en tu cuenta de HubSpot. Haz clic en el ícono de **configuración** ubicado en la barra de navegación superior.
En el menú lateral izquierdo, dirígete a **Integraciones > Aplicaciones conectadas**. Ubica la aplicación de **Jelou**.
Haz clic en **Acciones** y luego selecciona **Desinstalar**. En la ventana de confirmación, escribe `desinstalar` y haz clic en **Desinstalar** para confirmar la acción.
Una vez desinstalada la aplicación:
* Jelou dejará de tener acceso a la cuenta de HubSpot.
* Las automatizaciones y transferencias configuradas mediante el nodo de HubSpot dejarán de funcionar.
* Para eliminar completamente la integración del flujo, debes borrar el nodo de HubSpot dentro del módulo Brain en Jelou.
# Mensajes Rápidos
Source: https://docs.jelou.ai/connect/panel-multi-agente/mensajes-rapidos
Crea respuestas predefinidas para que los operadores respondan de forma más rápida, consistente y eficiente.
En lugar de escribir las mismas respuestas una y otra vez, los operadores pueden seleccionar mensajes predefinidos directamente desde la Bandeja de entrada. Esto reduce los tiempos de atención, estandariza la comunicación y mejora la experiencia tanto para los operadores como para los clientes.
## ¿Para qué sirven los mensajes rápidos?
En la operación diaria existen respuestas que se utilizan constantemente, como:
* Saludos iniciales.
* Solicitud de información adicional.
* Confirmación de recepción de una solicitud.
* Respuestas a preguntas frecuentes.
* Mensajes de seguimiento.
* Cierres de conversación.
Los Mensajes Rápidos permiten reutilizar estos textos sin necesidad de escribirlos nuevamente en cada interacción.
## Crear un Mensaje Rápido
Para crear un nuevo Mensaje Rápido se deben completar los siguientes campos:
Será utilizado por los operadores para localizar rápidamente el mensaje dentro de la Bandeja de entrada.
Texto predefinido que se enviará al cliente durante la conversación.
Selecciona en qué canales estará disponible el mensaje.
Define qué equipos podrán utilizar este mensaje rápido.
## Administración de Mensajes Rápidos
Los Mensajes Rápidos pueden administrarse durante todo su ciclo de vida.
Las acciones disponibles son:
| Acción | Descripción |
| --------- | ----------------------------------------------------------------- |
| Crear | Agregar un nuevo mensaje rápido a la biblioteca |
| Editar | Modificar el contenido, canales o equipos de un mensaje existente |
| Inactivar | Deshabilitar temporalmente un mensaje sin eliminarlo |
| Eliminar | Remover permanentemente un mensaje de la biblioteca |
## Búsqueda y filtros
Para facilitar la administración de bibliotecas con una gran cantidad de mensajes, se dispone de herramientas de búsqueda y filtrado.
Puedes buscar por:
* Nombre
* Canal
* Equipo
Esto permite localizar rápidamente los mensajes necesarios para su administración.
***
## Buenas prácticas
Para mantener una biblioteca organizada y fácil de utilizar se recomienda:
* Utilizar nombres descriptivos y fáciles de identificar.
* Agrupar mensajes por categorías funcionales.
* Revisar periódicamente los mensajes existentes.
* Eliminar o inactivar mensajes obsoletos.
* Actualizar enlaces, procesos o información cuando sea necesario.
* Evitar duplicar mensajes con contenido similar.
Una biblioteca bien administrada permite que los operadores encuentren rápidamente la respuesta adecuada y mantengan una experiencia consistente durante toda la atención.
# Monitoreo en Vivo
Source: https://docs.jelou.ai/connect/panel-multi-agente/monitoreo-en-vivo
Supervisa en tiempo real el estado de tu operación, los tiempos de atención y los casos que requieren acción inmediata.
Desde este módulo los supervisores pueden detectar cuellos de botella, redistribuir la carga de trabajo y actuar sobre conversaciones críticas antes de que impacten la calidad del servicio.
Su objetivo es brindar visibilidad sobre:
* Cuántas conversaciones se encuentran activas.
* Qué tan rápido están respondiendo los operadores.
* Qué casos requieren seguimiento o intervención inmediata.
## Segmentación por tipo de interacción
La información puede visualizarse según el tipo de interacción que gestionan los operadores:
Incluyen conversaciones provenientes de canales de mensajería como WhatsApp, Facebook Messenger, Instagram Direct y X (Twitter).
Incluyen todas las interacciones gestionadas desde la bandeja de correo integrada.
Incluyen comentarios y respuestas realizadas sobre publicaciones de Facebook e Instagram.
Esta segmentación permite analizar de forma independiente cada tipo de interacción con los clientes, ya que cada una posee volúmenes, tiempos de respuesta y dinámicas operativas diferentes.
De esta forma es posible supervisar el desempeño específico de cada tipo de interacción sin afectar la operación en curso.
## Indicadores de Conversaciones
Los indicadores muestran el comportamiento general de las conversaciones dentro del período seleccionado.
Cantidad total de conversaciones registradas durante el período consultado, independientemente de su estado.
Conversaciones que se encuentran activas y requieren atención de un operador.
Conversaciones que recibieron al menos una acción por parte de un operador, como responder, transferir o cerrar el caso.
Conversaciones derivadas a atención humana que no recibieron ninguna interacción por parte de un operador.
Conversaciones donde el cliente envió un mensaje y todavía no ha recibido una primera respuesta por parte del operador asignado.
Cantidad de conversaciones que fueron transferidas entre operadores o equipos durante el período seleccionado.
## Indicadores de Tiempo
Estas métricas permiten medir la eficiencia de atención de los equipos.
Tiempo promedio transcurrido entre la asignación de una conversación y la primera respuesta enviada por el operador.
Tiempo promedio que tardan los operadores en responder los mensajes de los clientes durante una conversación activa.
Tiempo promedio que transcurre desde la apertura hasta el cierre de una conversación.
***
## Gestión en Vivo de Casos
Permite visualizar y gestionar las conversaciones de manera individual.
### Casos Actuales
Muestra las conversaciones que se encuentran activas y asignadas a un operador.
La información disponible incluye:
* Operador asignado
* Cliente
* Equipo
* Origen de la conversación
* Hora de inicio de atención
* Estado de gestión
Desde esta vista los supervisores pueden transferir conversaciones a otros operadores o equipos cuando sea necesario.
### Casos por Recuperar
Corresponde a conversaciones que quedaron sin seguimiento debido a situaciones como:
* Atención fuera de horario
* Operadores no disponibles
* Conversaciones sin respuesta dentro de la bandeja del operador
Estos casos pueden recuperarse antes de que expire la ventana de atención. Al recuperar un caso, este vuelve a estado activo y puede ser reasignado a un operador para continuar la gestión.
### Casos en Cola
Corresponde a conversaciones que todavía no tienen un operador asignado y se encuentran esperando atención dentro de la cola del equipo correspondiente.
Cada caso en cola representa un cliente pendiente de atención.
# Supervisión de Operadores
Source: https://docs.jelou.ai/connect/panel-multi-agente/supervision-de-operadores
Monitorea el estado, desempeño y actividad de los operadores, y ejecuta acciones de gestión cuando sea necesario.
Desde aquí podrás identificar quién está conectado, cuántas conversaciones gestiona cada operador, impersonar sesiones para soporte interno y transferir conversaciones cuando la situación lo requiera.
## Vista general de operadores
La vista principal muestra todos los operadores habilitados dentro de la compañía.
Para cada operador podrás visualizar:
* Nombre y correo electrónico.
* Equipos a los que pertenece.
* Estado actual de conexión.
* Fecha y hora de inicio de sesión.
* Cantidad de conversaciones activas.
* Conversaciones pendientes por responder.
* Acciones rápidas disponibles.
Esta información permite identificar rápidamente la carga de trabajo y disponibilidad de cada operador.
## Acciones rápidas
Dependiendo de los permisos asignados, los supervisores podrán ejecutar acciones sobre los operadores como:
* Impersonar operador.
* Cambiar el estado del operador.
* Transferir conversaciones a otros operadores o equipos.
Estas acciones permiten mantener la continuidad operativa y distribuir la carga de trabajo de manera eficiente.
## Estados del operador
Cada operador cuenta con un indicador visual que muestra su disponibilidad dentro de la plataforma.
| Estado | Descripción | Recibe nuevas conversaciones |
| ------------- | ----------------------------------------------- | ---------------------------- |
| Conectado | Disponible para atender conversaciones | Sí |
| No Disponible | No recibirá nuevas conversaciones temporalmente | No |
| Desconectado | No tiene una sesión activa en la plataforma | No |
## Impersonación
La funcionalidad de Impersonación permite que usuarios autorizados accedan temporalmente a la plataforma utilizando la vista de otro operador.
Algunos casos de uso comunes son:
* Soporte interno ante incidencias reportadas por operadores.
* Continuidad de atención cuando un operador no puede finalizar una gestión.
* Procesos de capacitación y aseguramiento de calidad.
Todas las sesiones de impersonación quedan registradas para fines de auditoría y seguridad.
## Búsqueda y filtros
Para facilitar la gestión de equipos grandes, es posible buscar operadores por:
* Nombre
* Correo electrónico
También se pueden aplicar filtros por:
* Estado
* Equipo
Los filtros ayudan a identificar rápidamente operadores específicos o analizar grupos de trabajo concretos.
## Exportación de información
La información de los operadores puede exportarse para análisis o seguimiento externo.
La exportación respetará los filtros aplicados en pantalla al momento de generar el archivo.
***
## Detalle del operador
Al ingresar a un operador específico podrás visualizar información detallada sobre su actividad.
### Resumen de gestión
Muestra los indicadores asociados a la atención realizada por el operador durante el período seleccionado:
* Total de conversaciones
* Conversaciones activas
* Conversaciones atendidas
* Conversaciones no atendidas
* Conversaciones pendientes
* Tiempo promedio de primera respuesta
* Tiempo promedio de respuesta
* Tiempo promedio de duración de las conversaciones
Adicionalmente, podrás visualizar el listado de conversaciones gestionadas por el operador y consultar información histórica mediante filtros de fecha.
### Historial de conexiones
Permite revisar la actividad de acceso del operador dentro de la plataforma.
Para cada registro se visualizará:
| Campo | Descripción |
| ------------------- | -------------------------------------------------------------- |
| Tipo de evento | Acción registrada (inicio de sesión, cierre, cambio de estado) |
| Fecha y hora | Momento exacto del evento |
| Dirección IP | IP desde la que se realizó la conexión |
| Navegador utilizado | Navegador web del operador |
| Sistema operativo | Sistema operativo del dispositivo |
Esta información resulta útil para auditoría, seguimiento operativo y análisis de actividad.
# Transferencia de casos
Source: https://docs.jelou.ai/connect/panel-multi-agente/transferencia-de-casos
Deriva conversaciones entre operadores o equipos desde Connect.
La transferencia de casos te permite mover una conversación de un operador o equipo a otro dentro de Connect. Así te aseguras de que cada cliente sea atendido por la persona correcta, sin perder el historial ni el contexto de la conversación.
## ¿Para qué sirve?
La transferencia de casos facilita la continuidad de la atención y mejora la asignación interna.
Con esta funcionalidad puedes:
* Derivar casos entre operadores.
* Transferir conversaciones por equipo, según especialidad o tipo de atención.
* Mantener el historial completo durante la transferencia.
El cliente no pierde contexto y el equipo no empieza desde cero.
## Configuración según tu plan
### Self Service
* La transferencia es una configuración general.
* Se aplica automáticamente a todos los canales.
* No requiere ajustes individuales por canal.
Es una configuración simple y centralizada.
### Enterprise
* La transferencia puede configurarse por canal.
* Permite definir reglas distintas para WhatsApp, Web u otros canales.
* Se adapta a operaciones más complejas.
Ideal cuando cada canal tiene su propia lógica de atención.
***
## Buenas prácticas
Configurar correctamente la transferencia de casos te ayuda a:
* Reducir tiempos de atención.
* Evitar reprocesos.
* Mejorar la experiencia del cliente.
* Asegurar que cada conversación sea atendida por el equipo adecuado desde el primer contacto.
Una derivación bien definida es parte clave de una operación eficiente.
# Autenticación y perfiles
Source: https://docs.jelou.ai/guides/cli/autenticacion
Inicia sesión, gestiona múltiples cuentas como perfiles, cambia de empresa y entiende el orden de resolución de credenciales del CLI de Jelou.
El CLI guarda credenciales en `~/.jelou/credentials.json` con soporte
**multi-perfil**: varias cuentas (y empresas) coexisten como perfiles con nombre.
## `jelou login`
Autentícate con tu **API key de Jelou**. `jelou login` la guarda como un perfil
con nombre y lo deja como el activo. Obtén tu API key en
[apps.jelou.ai/settings/api-keys](https://apps.jelou.ai/settings/api-keys).
```bash theme={null}
jelou login # interactivo: pide pegar la API key
# ? Paste your API key: ****
jelou login --token $JELOU_TOKEN --profile prod # no interactivo (CI)
```
| Flag | Descripción |
| ------------------ | ------------------------------------- |
| `--token ` | API key para uso no interactivo (CI) |
| `--profile ` | Nombre del perfil a guardar |
| `--skip-skills` | No auto-instalar skills tras el login |
En el **primer login exitoso**, `jelou` instala automáticamente las skills de
agente de forma global en los editores de IA detectados (Claude Code, Cursor,
Codex…), para que las sesiones nuevas conozcan `jelou`. Ver
[Skills](/guides/cli/skills).
## `jelou whoami`
Verifica la identidad actual y la URL de API.
```bash theme={null}
jelou whoami
jelou whoami --json
```
## `jelou logout`
Elimina credenciales almacenadas.
```bash theme={null}
jelou logout # olvida el perfil activo
jelou logout --profile prod # olvida un perfil específico (sin cambiarte a él)
jelou logout --all # olvida todos los perfiles
```
Las credenciales no se recuperan: tras `logout`, tendrás que volver a
`jelou login`, y las skills que se auto-instalaron en el login no se reinstalan
solas. `logout --all` (o `logout` cuando solo queda un perfil) es una operación
destructiva — confirma antes de ejecutarla.
## Perfiles
Cada perfil mapea normalmente 1:1 con una empresa. Cambiar de perfil cambia la
empresa activa: `jelou project list` devuelve los proyectos de la nueva empresa,
`jelou databases list` sus bases de datos, etc.
```bash theme={null}
jelou login --token --profile staging # añade un segundo perfil
jelou profiles # lista todos (alias de `auth list`)
jelou auth list # lista todos los perfiles
jelou auth switch staging # cambia el perfil activo
jelou auth remove staging # elimina un perfil
jelou functions deploy --profile production # override de un solo uso (no persiste)
```
Para una consulta puntual contra otra empresa, prefiere `--profile ` en
ese comando en lugar de `auth switch`. El perfil activo es **pegajoso**: si
cambias a "staging" para leer un valor y olvidas volver, el siguiente deploy,
borrado o rotación de secret apuntará a la empresa equivocada.
### Orden de resolución de credenciales
El CLI resuelve qué credencial usar en este orden (de mayor a menor prioridad):
1. Variable de entorno `JELOU_TOKEN`
2. Flag `--profile `
3. Variable de entorno `JELOU_PROFILE`
4. `.jelou/state.json:profile` — vínculo por directorio que `jelou link`
escribe al enlazar el proyecto, usando el perfil activo en ese momento (o
el que indiques con el flag global `--profile ` en ese mismo
comando)
5. `activeProfile` en el archivo de credenciales
Cuando ves "empresa equivocada", revisa este orden antes de asumir que el
archivo está mal.
## Salud y diagnóstico
Cuando algo se ve raro (auth, red, "empresa equivocada", drift de perfil),
ejecuta primero `jelou doctor`:
```bash theme={null}
jelou doctor # reporte legible
jelou doctor --json # estructurado para agentes/CI
```
Verifica: validez del token, alcance del gateway, sanidad del perfil,
integridad del lockfile (si hay `jelou.lock` en el directorio) y frescura de la
versión del CLI. Códigos de salida: `0` sano, `4` falla de auth, `6` gateway
inalcanzable, `1` otro fallo.
## Seguridad de credenciales
* Las API keys en `~/.jelou/credentials.json` son credenciales sensibles — nunca
las publiques en un repo ni las pegues en un chat.
* Las credenciales v1 (formato antiguo) se migran automáticamente a v2 dejando
un respaldo `.bak`. No borres ese `.bak`: es el único fallback si el archivo
v2 se corrompe.
# Connect
Source: https://docs.jelou.ai/guides/cli/connect
Consulta los operadores y equipos de Connect desde el CLI para enrutar los nodos de atención humana de tus workflows.
`jelou connect` lista los **operadores** y **equipos** de Connect, la atención
humana de Jelou. Es de solo lectura: sirve para obtener los ids que necesitas al
configurar un nodo de atención humana en un workflow.
Los nodos de atención humana enrutan a un id de operador, o a un id de equipo
cuando usan `assignmentBy: "TEAM"`. Estos comandos son la forma de conocer esos
ids sin entrar al Studio.
## Operadores
```bash theme={null}
jelou connect operators list # solo activos (default)
jelou connect operators list --include-inactive # incluye inactivos y eliminados
jelou connect operators list --json
```
| Flag | Descripción |
| -------------------- | --------------------------------------------- |
| `--include-inactive` | Incluye operadores inactivos o eliminados |
| `--out ` | Escribe el JSON a un archivo además de stdout |
Por defecto solo devuelve operadores **activos**. Los inactivos siguen existiendo
en la organización, pero enrutarles una conversación no sirve.
## Equipos
```bash theme={null}
jelou connect teams list # solo activos (default)
jelou connect teams list --include-inactive
jelou connect teams list --json
```
| Flag | Descripción |
| -------------------- | --------------------------------------------- |
| `--include-inactive` | Incluye equipos inactivos |
| `--out ` | Escribe el JSON a un archivo además de stdout |
## Capturar el listado a un archivo
`--out` escribe el mismo JSON a disco, útil para guardar los ids antes de editar
varios workflows:
```bash theme={null}
jelou connect operators list --json --out operators.json
jelou connect teams list --json --out teams.json
```
El directorio destino de `--out` debe existir; el CLI no lo crea.
# Bases de datos
Source: https://docs.jelou.ai/guides/cli/databases
Provisiona y opera bases de datos gestionadas por Jelou (Datum): colecciones, API keys con habilidades granulares y triggers webhook, desde el CLI.
`jelou databases` provisiona y opera las bases de datos gestionadas de Jelou. El
alias `jelou datum` funciona en todos lados donde funciona `jelou databases`:
mismos subcomandos, mismos flags, mismos exit codes.
Existen **dos productos con el nombre "Datum"**. El alias `jelou datum` apunta al
producto actual de **Bases de datos de Jelou** (el que gestiona este CLI). Hay
un producto de base de datos legacy separado, accesible solo desde el Studio,
que este CLI no puede administrar.
## Bases de datos
| Subcomando | Descripción |
| -------------------------- | -------------------------------------------------------- |
| `list` | Lista las bases de datos de la organización |
| `create --name ""` | Provisiona una base de datos nueva |
| `show ` | Detalles, URLs y estado (acepta id, slug o nombre) |
| `update --name ""` | Renombra la base de datos |
| `delete --yes` | Elimina la base de datos (irreversible) |
| `plans` | Lista los planes de máquina y almacenamiento disponibles |
```bash theme={null}
jelou databases plans
jelou databases list --json
jelou databases create --name "ordenes"
jelou databases show 01H2XCEJQTG2H5V5NKCYW3J7Z2
jelou databases update 01H2XCEJQTG2H5V5NKCYW3J7Z2 --name "ordenes-v2"
jelou databases delete 01H2XCEJQTG2H5V5NKCYW3J7Z2 --yes
```
`databases delete` es **irreversible**: cada colección, registro y archivo
desaparece, sin papelera ni rollback. Ejecuta `databases show` primero y
confirma el conteo con el usuario.
## Colecciones
| Subcomando | Descripción |
| -------------------------------------------------- | ----------------------------------- |
| `collections list ` | Lista las colecciones |
| `collections show ` | Muestra el esquema de una colección |
| `collections create --name ""` | Crea una colección |
| `collections update --name ""` | Renombra |
| `collections delete --yes` | Elimina (irreversible) |
`collections create` acepta `--field ":"` (repetible, define las
columnas) y `--type base|view` (tipo de colección; por defecto `base`).
```bash theme={null}
jelou databases collections list 01H2XCEJQTG2H5V5NKCYW3J7Z2
jelou databases collections create 01H2XCEJQTG2H5V5NKCYW3J7Z2 --name "ordenes" \
--field "total:number" --field "estado:select:values=pendiente|pagado"
```
`collections create` es *get-or-create*: si ya existe una colección con ese
nombre y el mismo esquema, la reutiliza y responde con `reused: true` en vez
de fallar. Si existe una con ese nombre pero un esquema **distinto**, rechaza
la operación con exit code 2 y un diff campo por campo — nunca la modifica en
silencio.
## API keys
Cada key se crea con **habilidades granulares**, combinables por coma:
`records:read`, `records:write`, `records:delete`, `files:read`, `files:write`.
Presets comunes: `read_only`, `read_write`, `all`.
```bash theme={null}
jelou databases api-keys list 01H2XCEJQTG2H5V5NKCYW3J7Z2
jelou databases api-keys create 01H2XCEJQTG2H5V5NKCYW3J7Z2 --name "solo-lectura" --abilities "records:read,files:read"
jelou databases api-keys create 01H2XCEJQTG2H5V5NKCYW3J7Z2 --name "completo" --abilities "all"
jelou databases api-keys regenerate 01H2XCEJQTG2H5V5NKCYW3J7Z2 key_01H2XCEJQ --yes
jelou databases api-keys delete 01H2XCEJQTG2H5V5NKCYW3J7Z2 key_01H2XCEJQ --yes
```
Repetir `api-keys create` con un nombre que ya existe **no crea una segunda
key**: falla con exit code 2 y `ALREADY_EXISTS`, e incluye
`details.never_used` para saber si la key existente se llegó a usar — así
sabes si rotarla con `api-keys regenerate` es seguro.
`files:write` requiere `records:write` (los archivos viven dentro de
registros) — el CLI rechaza la combinación localmente. Los tokens llevan prefijo
`db_` y se muestran **una sola vez** al crearse: guárdalos enseguida.
`api-keys regenerate` rota el token de inmediato; cualquier servicio con el token
viejo recibirá 401 en la siguiente petición. Por eso pide confirmación antes de
ejecutarse; en CI o con `--agent` hay que pasar `--yes` explícitamente.
## Triggers
Webhooks que se disparan ante eventos de una colección
(`create`, `update`, `delete`).
| Subcomando | Descripción |
| ------------------------------------------ | ------------------------------- |
| `triggers list ` | Lista los triggers |
| `triggers create …` | Crea un trigger webhook |
| `triggers show ` | Detalles del trigger |
| `triggers update --url ""` | Actualiza el trigger |
| `triggers delete --yes` | Elimina el trigger |
| `triggers pause ` | Pausa (deja de enviar webhooks) |
| `triggers resume ` | Reanuda un trigger pausado |
`triggers create`/`update` también aceptan `--method ""` (verbo HTTP,
por defecto `POST`) y, para eventos `update`, `--update-scope fields --columns
""` para disparar solo cuando cambian esas columnas (el scope por
defecto es `all`).
```bash theme={null}
jelou databases triggers create 01H2XCEJQTG2H5V5NKCYW3J7Z2 \
--name "webhook-ordenes" \
--collection-id col_01H2XCEJQ \
--url "https://example.com/webhook" \
--events "create,update,delete"
jelou databases triggers create 01H2XCEJQTG2H5V5NKCYW3J7Z2 \
--name "webhook-plan-cambiado" \
--collection-id col_01H2XCEJQ \
--url "https://example.com/webhook" \
--events "update" \
--update-scope fields --columns "plan,email"
jelou databases triggers pause 01H2XCEJQTG2H5V5NKCYW3J7Z2 trig_01H2XCEJQ
jelou databases triggers resume 01H2XCEJQTG2H5V5NKCYW3J7Z2 trig_01H2XCEJQ
```
`triggers pause` causa **pérdida de datos silenciosa**: el destino deja de
recibir eventos sin que se reporte error aguas arriba. Menciona siempre
`resume` como ruta de recuperación.
# Jelou CLI
Source: https://docs.jelou.ai/guides/cli/index
El CLI de Jelou (jelou): un único binario para construir y operar Functions, proyectos, canales, bases de datos, WhatsApp, marketplace y más — desde la terminal o desde tu editor de IA.
`jelou` es el punto de entrada a toda la plataforma de Jelou desde la terminal.
Un solo binario reúne varias superficies de producto: **Functions** (funciones
serverless), **proyectos** (asistentes de IA + canales), **bases de datos**
(Datum), **WhatsApp** (bots, plantillas y campañas), **marketplace** y
**secrets** de organización.
El CLI es **agente-nativo**: está diseñado para que un agente de IA (Claude
Code, Cursor, Codex, Windsurf…) lo conduzca, y para que un humano lo use desde
la terminal. Al hacer `jelou login`, el CLI **instala automáticamente skills**
en los editores de IA detectados, de modo que cualquier sesión nueva ya sabe
operar `jelou`. Ver [Skills](/guides/cli/skills).
## Instalación
```bash theme={null}
npm install -g @jelou/cli
```
También puedes instalarlo con Deno:
```bash theme={null}
deno install -A -f -n jelou --global jsr:@jelou/cli
```
Deno escribe el binario en `~/.deno/bin/`. Si ese directorio no está en tu
`PATH`:
```bash theme={null}
echo 'export PATH="$HOME/.deno/bin:$PATH"' >> ~/.bashrc
```
Verifica la instalación:
```bash theme={null}
jelou --version
```
¿No tienes cuenta de Jelou todavía? [Crea una gratis](https://apps.jelou.ai/signup?plan=brain-free\&source=cli_docs\&lang=es).
## Quickstart
```bash theme={null}
jelou login # las skills se auto-instalan en tus editores de IA
mkdir mi-proyecto && cd mi-proyecto
```
Abre una sesión nueva en Claude Code, Cursor, Codex o Windsurf desde esa carpeta
y pide lo que necesites en lenguaje natural — *"construye un flujo de soporte"*,
*"despliega un webhook de Stripe"*, *"envía una campaña de WhatsApp"*. El agente
elige el comando `jelou` correcto y lo ejecuta.
¿Prefieres la terminal? `jelou --help` recorre todo el árbol de comandos.
## Mapa de comandos
| Quieres… | Usa | Página |
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------ |
| Autenticarte, gestionar perfiles/empresas y cerrar sesión | `jelou login`, `jelou whoami`, `jelou logout`, `jelou auth …`, `jelou profiles` | [Autenticación](/guides/cli/autenticacion) |
| Instalar/actualizar skills en editores de IA | `jelou agent install`, `jelou skills …` | [Skills](/guides/cli/skills) |
| Crear proyectos, subir conocimiento, conectar canales | `jelou project …`, `jelou channels …` | [Proyectos y canales](/guides/cli/project) |
| Ver todo el workspace en una sola llamada (identidad, drift, modelos, secrets, DBs) | `jelou context` | [Proyectos y canales](/guides/cli/project) |
| Sincronizar workflows local ↔ servidor y resolver conflictos | `jelou link`, `jelou pull`, `jelou status`, `jelou push`, `jelou incoming` | [Proyectos y canales](/guides/cli/project) |
| Gestionar tools reutilizables entre proyectos de la app | `jelou tool …`, `jelou test tool …` | [Proyectos y canales](/guides/cli/project) |
| Provisionar y operar bases de datos | `jelou databases …` (alias `jelou datum …`) | [Bases de datos](/guides/cli/databases) |
| Construir, probar y depurar workflows | `jelou workflow …`, `jelou test …`, `jelou logs …`, `jelou users …` | [Workflows y pruebas](/guides/cli/workflows) |
| Gestionar plantillas y campañas de WhatsApp | `jelou template …`, `jelou campaign …` | [WhatsApp](/guides/cli/whatsapp) |
| Enrutar atención humana (operadores y equipos) | `jelou connect operators …`, `jelou connect teams …` | [Connect](/guides/cli/connect) |
| Descubrir y validar modelos de IA | `jelou models …` | [Modelos de IA](/guides/cli/models) |
| Gestionar el servicio de voz: llamadas, agentes, contactos, campañas, facturación | `jelou voice …` | [Voz](/guides/cli/voice) |
| Administrar la tienda: catálogo, pedidos, cupones | `jelou shop …` | [Shop](/guides/cli/shop) |
| Instalar integraciones del marketplace | `jelou marketplace …` | [Marketplace e integraciones](/guides/cli/marketplace) |
| Gestionar secrets de organización o de un proyecto | `jelou secret …` | [Secrets](/guides/cli/secret) |
| Desplegar funciones serverless | `jelou functions …` | [CLI de Functions](/guides/functions/cli) |
| Consultar la analítica de la empresa: catálogo, una métrica o un tablero | `jelou metrics list`, `jelou metrics get`, `jelou metrics dashboard` | [Métricas](/guides/cli/metrics) |
| Actualizar el CLI, diagnosticar problemas, ver notas de versión y enviar feedback | `jelou update`, `jelou doctor`, `jelou changelog`, `jelou feedback` | [Referencia](/guides/cli/referencia) |
| Flags globales, modos de salida y exit codes | `--agent`, `--json`, `--describe` | [Referencia](/guides/cli/referencia) |
La [CLI de Functions](/guides/functions/cli) documenta en detalle el subcomando
`jelou functions` (desarrollo local, despliegue, secrets y cron de funciones).
## Modos de salida
Todo comando soporta salida pensada para agentes y para CI:
* `--json` — salida estructurada `{ ok, data }` / `{ ok, error }` en stdout.
* `--agent` — modo agente: implica `--json`, `--no-input`, `--compact` y
`NO_COLOR`.
* `--describe` — emite el esquema del comando como JSON, sin ejecutarlo.
Cuando stdout no es una terminal (por ejemplo `jelou whoami | jq`), el CLI pasa
a JSON automáticamente. Detalles en [Referencia](/guides/cli/referencia).
```bash theme={null}
jelou whoami --json
jelou project list --agent
jelou databases create --describe
```
# Marketplace e integraciones
Source: https://docs.jelou.ai/guides/cli/marketplace
Explora e instala apps del marketplace (OAuth), lista operadores y equipos de Connect, y administra el catálogo de Jelou Shop desde el CLI.
## `jelou marketplace`
Explora, busca e instala apps OAuth del marketplace (Shopify, HubSpot, Slack…)
para tu organización.
| Subcomando | Descripción |
| ---------------- | ----------------------------------------------------------------- |
| `list` | Explora las apps disponibles (busca o filtra a instaladas) |
| `install ` | Instala una app por slug (abre el navegador para autorizar OAuth) |
```bash theme={null}
jelou marketplace list
jelou marketplace list --search "shopify" --json
jelou marketplace list --installed-only
jelou marketplace install hubspot
jelou marketplace install slack --print-url # flujos headless/SSH
jelou marketplace install shopify --metadata '{"shopify":{"shop":"mitienda.myshopify.com"}}'
```
| Flag | Descripción |
| ------------------- | ----------------------------------------------------------------------------------------------- |
| `--search ` | Filtra por nombre o descripción |
| `--installed-only` | Solo apps ya instaladas |
| `--print-url` | Imprime la URL OAuth en lugar de abrir el navegador |
| `--metadata ` | Metadata requerida por algunas apps (ej. la tienda de Shopify) |
| `--timeout ` | Timeout del sondeo OAuth (default 120) |
| `--limit ` | Máximo de resultados (default 20) |
| `--wait` | En modo `--json`, espera hasta que la app quede instalada (o `--timeout`) e informa `installed` |
Instalar otorga scopes OAuth de larga duración sobre los datos de tu
organización; revocar es un proceso de varios pasos en el dashboard (no por
CLI). Si un slug coincide con varias apps, el CLI pide elegir. Algunas apps
(Shopify, etc.) requieren `--metadata` específico de la app.
## `jelou connect`
Lista operadores y equipos del producto Connect (flujos de derivación a
humanos).
```bash theme={null}
jelou connect operators list # operadores activos
jelou connect operators list --include-inactive # incluye eliminados/inactivos
jelou connect teams list --json
jelou connect operators list --json --out operadores.json
```
* **Operadores** son usuarios individuales con rol de operador.
* **Equipos** son grupos de operadores para enrutamiento por equipo.
* El `id` del operador es el que se referencia en un nodo CONNECT de un workflow
(`configuration.operatorId`).
## `jelou shop`
Administra Jelou Shop: activación por empresa, configuración, sucursales,
categorías, productos e importaciones de catálogo.
| Grupo | Subcomandos |
| ---------------- | --------------------------------------------------------------- |
| Setup | `info`, `activate`, `settings` |
| Sucursales | `branches list/show/create/update/delete` |
| Categorías | `categories list/create/update/delete` |
| Productos | `products list/show/upsert/update/status/delete` |
| Catálogo externo | `accounts`, `syncs `, `imports template/upload/show` |
```bash theme={null}
jelou shop activate --name "Mi Tienda" --contact-mail "soporte@example.com"
jelou shop settings --currency USD --tax-value 15
jelou shop products list --stock in_stock --search "zapato" --json
jelou shop products upsert --file productos.json --sync
jelou shop imports template --lang es --output plantilla.xlsx
jelou shop imports upload --file productos.xlsx --uploaded-by "alex"
```
`shop activate` usa la credencial de tu `jelou login` (tu API key de Jelou) — no
requiere una clave de bootstrap aparte. Solo se permite **una activación por empresa** y se niega a
sobrescribir una activación existente bajo `--no-input`. `products upsert` crea
o actualiza productos por SKU; usa `--sync` para esperar el resultado.
# Métricas
Source: https://docs.jelou.ai/guides/cli/metrics
Consulta la analítica de tu empresa desde la terminal: catálogo completo por categoría, una métrica puntual o un tablero entero, con filtros, ventanas de tiempo y plantillas.
`jelou metrics` lee el mismo catálogo de analítica que ven los dashboards de
Studio — ocho categorías: `inbox`, `ecommerce`, `payments`, `voice`,
`biometrics`, `brain`, `ai`, `general`. El catálogo es propio de cada empresa y
se sirve en vivo, así que **descubre antes de pedir** — nunca asumas una `key`
de memoria.
```bash theme={null}
jelou metrics list --agent # catálogo completo
jelou metrics list --category ecommerce --agent # una categoría
jelou metrics get ecommerce_sales_kpis --last 30d --agent # una métrica
jelou metrics dashboard --template ecommerce --agent # un tablero entero
jelou metrics dashboard --agent # el tablero principal
```
## Listar el catálogo
```bash theme={null}
jelou metrics list # todo, legible
jelou metrics list --category payments # solo una categoría
jelou metrics list --json # incluye filtros y forma de cada métrica
```
| Flag | Descripción |
| ------------------- | --------------------------------------------------------------------------------- |
| `--category ` | `inbox`, `ecommerce`, `payments`, `voice`, `biometrics`, `brain`, `ai`, `general` |
| `--origin ` | `default`, `custom` o `datum` (por defecto se mezclan los tres) |
`list` combina tres orígenes de catálogo. Si uno falla, el comando de todas
formas sale con código `0` con las métricas que sí pudo leer y nombra las
demás en `errors[]` — revisa `failed` antes de asumir que una `key` no existe.
Las siete `keys` heredadas de antes de v2 (`dau_total`, `dau_ai_total`,
`unique_users_total`, `unique_users_per_day`, `brain_sessions`,
`bic_billing_sessions`, `hsm_by_sent_status`) siguen resolviendo y devuelven su
envelope de siempre — aparecen marcadas con `legacy: true` en `--json`.
## Traer una métrica
```bash theme={null}
jelou metrics get ecommerce_sales_kpis --last 30d
jelou metrics get payments_success_rate --last 7d --agent
jelou metrics get brain_top_words --bot-id --last 30d
jelou metrics get dau_ai_total --period currentYear # slug heredado, ventana heredada
```
Las `keys` no distinguen mayúsculas/minúsculas, y el final del `invocation_name`
funciona cuando no es ambiguo. En terminal interactiva se renderiza como
tarjeta o gráfico; en modo `--json`/`--agent` (o con stdout redirigido) emite
el envelope JSON.
### Ventanas de tiempo
| Flag | Descripción |
| ---------------------------------------------------------- | -------------------------------------------------- |
| `--last <7d\|30d\|90d\|12m>` | Ventana relativa. Prefiérela. |
| `--period ` | Ventana de calendario (por defecto `currentMonth`) |
| `--start --end ` | Rango a medida; van los dos juntos o ninguno |
Precedencia: `--start`+`--end` gana sobre `--last`, que gana sobre `--period`.
### Filtros
Cada métrica declara qué filtros acepta — pasar uno que no declara sale con
código `2` y te dice cuáles sí acepta. Revisa `filters` en
`jelou metrics list --json` antes de llamar una métrica nueva.
| Filtro | Flag | Notas |
| ------------------- | --------------------------- | ------------------------------------------------------- |
| `botId` | `--bot-id` | repetible; ids desde `jelou channels list` |
| `teamId` | `--team-id` | repetible |
| `provider` | `--provider` | repetible (pagos) |
| `currency` | `--currency` | repetible (pagos) |
| `environment` | `--environment PROD\|DEV` | pagos |
| `typeBiometric` | `--type-biometric` | biometría |
| `skillName` | `--skill-name` | repetible; costo de IA por workflow |
| `nodeId` | `--node-id` | repetible; evaluaciones de agente |
| `skillId` | `--skill-id` | repetible; evaluaciones de agente |
| `criterionName` | `--criterion-name` | repetible; frecuencia de evaluación de agente |
| — | `--app-id` | solo ecommerce, repetible; sin este flag es cada tienda |
| `startAt` / `endAt` | los flags de ventana arriba | siempre se aceptan, estén declarados o no |
## Un tablero completo
```bash theme={null}
jelou metrics dashboard # tablero principal
jelou metrics dashboard --template ecommerce # tablero oficial
jelou metrics dashboard "Operaciones" # tablero guardado, por nombre
jelou metrics dashboard --template payments --last 7d --agent
```
| Flag | Descripción |
| ------------------------------------------- | ----------------------------------------------------------------- |
| `--template ` | `ecommerce`, `payments`, `brain`, `ai`, `biometrics`, `operators` |
| `--last` / `--period` / `--start` / `--end` | mismas ventanas que `get` |
Las métricas de un tablero se piden en paralelo — una que falle no tumba el
tablero completo: la respuesta `--json` trae `failed` y `errors[]`, y el resto
se renderiza igual. Sin nombre ni `--template`, `dashboard` muestra el tablero
principal de la empresa; el envelope de ese comando lista en `workspaces` los
nombres de tableros guardados que encontró.
## Códigos de salida
| Código | Significado |
| ------ | ------------------------------------------------------------------------------------------------- |
| 0 | Éxito |
| 1 | Genérico / desconocido |
| 2 | Input — key ambigua, fecha inválida, `--start`/`--end` suelto, o un flag que la métrica no acepta |
| 3 | No encontrado — key, tablero o template desconocido |
| 4 | Auth / prohibido — token faltante, expirado o rechazado |
| 6 | Transitorio / API — 5xx o timeout de red, reintentable |
Los números que devuelve este comando tienen matices reales, no son verdad
contable:
* Los agregados de ecommerce vienen de tablas de rollup refrescadas cada \~10
min (solo hoy/ayer + días recientemente tocados); el detalle (`detail`) es en
vivo y puede no coincidir con los resúmenes.
* `total_sales` excluye modificadores de producto — es una estimación de GMV,
no ingreso contable. `AOV` hereda esta limitación.
* `cart_conversion` es una cohorte por fecha de creación sin TTL de abandono —
las ventanas recientes se subestiman; `breakdown.abandoned` suele ser `0`.
* `sales_by_category` cuenta doble (relación M:N) — no va a sumar igual a
`total_sales`.
* La familia de métricas de catálogo ignora `from`/`to` por completo (es una
foto global).
* `ai_usage_cost` en las métricas de búsqueda es costo interno de Jelou.
* Los totales de términos de búsqueda excluyen consultas de navegación/sentinel,
pero `total_searches` sí las incluye — las listas no van a sumar igual.
* Se necesita el scope `analytics:read` (+ `ecommerce:access` para shop). No lo
valides de antemano — deja que el 401/403 de la API llegue tal cual.
Si tu agente ya va a pedir el resto del contexto del workspace, `jelou context --agent` no incluye métricas — este comando sigue siendo la única puerta de
entrada a la analítica de la empresa.
# Modelos de IA
Source: https://docs.jelou.ai/guides/cli/models
Descubre y valida los modelos de IA disponibles para los nodos AI Agent y AI_TASK: filtros por proveedor y capacidad, validación y caché del catálogo.
`jelou models` consulta el catálogo de modelos de IA disponibles para tu
organización — los que puedes usar en los nodos **AI Agent** y **AI\_TASK**.
Sirve para dos cosas: descubrir qué modelos tienes y validar que el nombre que
vas a escribir en un workflow existe.
Si tu agente ya necesita otro contexto del workspace (identidad, drift del
proyecto, secretos, bases de datos), `jelou context --agent` trae todo eso en
una sola llamada, incluyendo el mismo listado de modelos permitidos en
`data.models` (con `source`, `fetchedAt`, `count`, `allowed[]`, `deprecated[]`
y `byProvider{}`). Es un atajo útil cuando de todas formas vas a pedir ese
contexto más amplio.
## Listar
```bash theme={null}
jelou models list # todos
jelou models list --provider anthropic # de un proveedor
jelou models list --capability vision --json # que soporten imágenes
```
| Flag | Descripción |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `--provider ` | Filtra por proveedor |
| `--capability ` | `vision`, `json`, `pdf`, `reasoning`, `streaming`, `audio`, `system`, `tools`, `structured` |
| `--type ` | `platform` (de la plataforma) o `custom` (tuyos) |
| `--include-deprecated` | Incluye modelos con fecha de fin de vida |
| `--no-cache` | Consulta fresca, ignora la caché local |
| `--sort ` | Orden de los resultados (por defecto `catalog`) |
| `--max-input-cost ` | Solo modelos con costo de entrada por 1M tokens igual o menor al indicado |
| `--max-output-cost ` | Solo modelos con costo de salida por 1M tokens igual o menor al indicado |
Los modelos deprecados **no aparecen** salvo que pases `--include-deprecated`.
## Ver detalle
Acepta el nombre corto, el id calificado o el id numérico:
```bash theme={null}
jelou models show gpt-4.1
jelou models show openai/gpt-4.1
```
## Validar
Confirma que un modelo existe antes de escribirlo en un workflow. Es el comando
pensado para CI y para agentes:
```bash theme={null}
jelou models validate openai/gpt-4.1 # forma canónica
jelou models validate gpt-4.1 # forma corta, se expande sola
jelou models validate gpt-4o --provider openai
```
| Código de salida | Significado |
| ---------------- | --------------------------------------------------------- |
| `0` | Válido y no deprecado |
| `2` | Deprecado, o el nombre corto es ambiguo entre proveedores |
| `3` | No existe en el catálogo |
| Flag | Descripción |
| --------------------- | -------------------------------------------------------------- |
| `--provider ` | Exige que el modelo pertenezca a ese proveedor |
| `--allow-deprecated` | Trata los deprecados como válidos (salida `0` con advertencia) |
`--provider` es útil para validar el par modelo+proveedor de un `fallbackModel`
antes de publicar un workflow.
## Caché del catálogo
El catálogo se guarda en disco para no consultar la API en cada llamada. Si
acabas de habilitar un modelo y no aparece:
```bash theme={null}
jelou models refresh # consulta fresca y reescribe la caché
jelou models refresh --clear # borra la caché del perfil activo
jelou models refresh --clear --all-profiles # borra la de todos los perfiles
```
También puedes pasar `--no-cache` a `list`, `show` y `validate` para una consulta
puntual sin tocar la caché.
# Proyectos y canales
Source: https://docs.jelou.ai/guides/cli/project
Crea y administra proyectos (asistentes de IA), sube archivos de conocimiento, conecta canales (Web, WhatsApp, Facebook, Instagram) y sincroniza los workflows de un proyecto entre tu máquina y el servidor.
Un **proyecto** es un asistente de IA con sus archivos de conocimiento, sus
canales y sus workflows. El subcomando `jelou project` administra el ciclo de
vida del proyecto; `jelou channels` conecta el proyecto a canales de
mensajería; y los comandos de sincronización (`link`, `pull`, `status`, `push`,
`incoming`) llevan los workflows del proyecto a archivos locales y de vuelta.
"Brain" es el nombre legacy de "project". `--brain` se acepta como alias de
`--project` en `jelou link` y `jelou channels`, y varios campos a nivel de API
conservan el nombre `brain` — pero el nombre canónico es **project**.
## `jelou project`
| Subcomando | Descripción |
| --------------------------------- | ------------------------------------------------------- |
| `list` | Lista todos los proyectos (paginado) |
| `show ` | Muestra detalles del proyecto y conteo de archivos |
| `create ""` | Crea un proyecto nuevo |
| `update ` | Actualiza nombre o descripción |
| `delete ` | Elimina un proyecto (en cascada, irreversible) |
| `publish ` | Publica el borrador a una rama de producción |
| `history ` | Historial de versiones / commits del proyecto |
| `restore ` | Restaura a un commit anterior o lo carga en el borrador |
| `knowledge list ` | Lista los archivos de conocimiento |
| `knowledge upload ` | Sube un archivo de conocimiento |
| `knowledge delete ` | Elimina un archivo de conocimiento |
```bash theme={null}
jelou project list --page 2 --limit 5 --json
jelou project show 01H2XCEJQTG2H5V5NKCYW3J7Z2
jelou project create "Soporte de Producto" --description "IA de atención al cliente"
jelou project update 01H2XCEJQTG2H5V5NKCYW3J7Z2 --name "Soporte v2"
jelou project delete 01H2XCEJQTG2H5V5NKCYW3J7Z2 --yes
```
### Conocimiento
Sube documentos para que el asistente los use como base de conocimiento.
Formatos soportados: PDF, TXT, CSV (máx. 2 MB por archivo).
```bash theme={null}
jelou project knowledge upload 01H2XCEJQTG2H5V5NKCYW3J7Z2 ./catalogo.pdf
jelou project knowledge upload 01H2XCEJQTG2H5V5NKCYW3J7Z2 ./faq.txt --name "FAQ"
jelou project knowledge list 01H2XCEJQTG2H5V5NKCYW3J7Z2
jelou project knowledge delete 01H2XCEJQTG2H5V5NKCYW3J7Z2 file_01H2XCEJQ --yes
```
### Publicar y restaurar
```bash theme={null}
jelou project publish 01H2XCEJQTG2H5V5NKCYW3J7Z2 --branch master --commit-name "Lanzamiento v2" --yes
jelou project history 01H2XCEJQTG2H5V5NKCYW3J7Z2 --detailed --limit 50
jelou project restore 01H2XCEJQTG2H5V5NKCYW3J7Z2 abc123def456 --into-draft --yes
```
`project delete` es **en cascada e irreversible**: elimina el proyecto, su
workspace, su skill por defecto y su canal sandbox. `publish` y `restore`
afectan producción de inmediato, y borrar un archivo de conocimiento dispara un
re-entrenamiento (las respuestas pueden cambiar). Ejecuta `project show` y
confirma antes de operaciones destructivas.
## `jelou channels`
Conecta un proyecto a canales de mensajería.
| Subcomando | Descripción |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `list --project ` | Lista los canales de un proyecto (filtra por `--type`) |
| `show ` | Muestra el detalle completo de un canal, incluido el `pageId` de Meta para Facebook/Instagram |
| `activate whatsapp` | Conecta un número de WhatsApp mediante el flujo de inicio de sesión de Meta (sin pegar credenciales) |
| `create --project --type web --name ""` | Crea un canal (por CLI, solo `--type web`) |
| `connect --to --id ` | Enruta el canal a un bot, agente o Bandeja de entrada |
| `flows --bot-id ` | Lista los WhatsApp Flows disponibles para un bot |
| `testers` | Administra la whitelist de números de prueba del sandbox de WhatsApp |
Por CLI, `channels create` solo soporta `--type web` (widget web). Los canales
de WhatsApp, Facebook e Instagram requieren OAuth: WhatsApp se conecta con
`jelou channels activate` (inicio de sesión de Meta, sin pegar credenciales),
mientras que Facebook e Instagram solo se pueden crear desde el dashboard.
```bash theme={null}
jelou channels list --project 01H2XCEJQTG2H5V5NKCYW3J7Z2 --exclude-sandbox
jelou channels list --type Facebook_Feed # bandeja de comentarios de la página, no el Messenger
jelou channels create --project 01H2XCEJQTG2H5V5NKCYW3J7Z2 --type web --name "Widget del sitio"
jelou channels activate whatsapp --project 01H2XCEJQTG2H5V5NKCYW3J7Z2
jelou channels connect 01H2XCEJQTG2H5V5NKCYW3J7Z2 --to bot --id bot_01H2XCEJQ
```
`--type` es exacto: `Facebook` son solo los DMs de Messenger, `Facebook_Feed` es
la bandeja de comentarios de la página (antes invisible para el CLI); mismo
patrón para `Instagram`/`Instagram_Feed`. Conectar una página de Facebook es
solo la mitad del trabajo — cada skill necesita su propio canal de Facebook,
agregado desde Studio, o el motor no encuentra a qué workflow enrutar ese
tráfico y nada responde.
`channels connect` **sobrescribe sin avisar**: si el canal ya tenía una
conexión, re-apuntarlo enruta usuarios reales al nuevo destino en el siguiente
mensaje entrante. Verifica ambos destinos antes de conectar.
## Sincronizar workflows (local ↔ servidor)
Para editar los workflows de un proyecto como archivos locales, primero vincula
el directorio al proyecto con `jelou link`, luego baja (`pull`), revisa
(`status`), edita y sube (`push`).
### `jelou link`
```bash theme={null}
jelou link # interactivo
jelou link --project 01H2XCEJQTG2H5V5NKCYW3J7Z2
jelou link --project 01H2XCEJQTG2H5V5NKCYW3J7Z2 --authoring ts # fija el modo de authoring (ts|json) en jelou.yml
jelou link --status --json # muestra el vínculo sin llamar a la API
```
Escribe `jelou.yml` (vínculo compartido por el equipo — versiónalo en git) y
`.jelou/state.json` (estado local — añádelo a `.gitignore`, el CLI lo hace
automáticamente).
`jelou link`, `pull` y `push` escriben archivos locales. Requieren un worktree
git limpio o un directorio vacío. Si el directorio está sucio o no versionado,
el CLI se detiene; usa `--allow-dirty` solo tras confirmar.
### `pull`, `status`, `push`
```bash theme={null}
jelou pull # baja todos los workflows a workflows/..json
jelou pull --check --json # dry-run: resumen sin escribir
jelou pull --workflow saludo # un solo workflow
jelou pull --quiet --json # sin ruido informativo en stderr (uso por agentes)
jelou status # diff local vs lockfile
jelou status --check-remote # además compara contra el servidor (detecta drift)
jelou status --quiet --json # solo muestra entradas no-limpias (CI-friendly)
jelou push # sube las ediciones locales
jelou push --dry-run --json # solo muestra el diff, no despacha
jelou push --workflow saludo # un solo workflow
jelou push --force # omite la verificación de drift del lockfile (capa 1)
jelou push --repair-from .jelou/journals/2026-05-03T20-45-52-000Z.json # reintenta un journal incompleto o fallido
```
¿Necesitas el panorama completo, no solo el estado de sync? `jelou context --agent`
entrega en una sola llamada la misma información de drift que `status` más
identidad, allowlist de modelos de IA, nombres de secretos, inventario de
bases de datos y el catálogo de tipos de nodo — reemplaza la secuencia
`status` + `secret list` + `models list` + `databases list` +
`workflow node-spec --list` para agentes que quieren todo el contexto de
arranque de una sola vez.
### `jelou incoming` — resolver ediciones concurrentes
Si un `pull` detecta que el servidor cambió un workflow que también editaste
local, preserva la versión del servidor bajo `.jelou/incoming/` y sale con
código 7. Resuelve cada artefacto antes de volver a hacer `pull`:
```bash theme={null}
jelou incoming list
jelou incoming diff --workflow wf_01H2XCEJQ
jelou incoming accept-server --workflow wf_01H2XCEJQ --channel default # adopta el servidor
jelou incoming accept-local --workflow wf_01H2XCEJQ --channel default # sube lo local (force-with-lease)
jelou incoming mark-resolved --workflow wf_01H2XCEJQ # limpia sin aplicar ninguno
```
`accept-local` hace una verificación CAS contra el hash del servidor registrado:
si el servidor divergió desde que se capturó el artefacto, se niega con
`NEW_REMOTE_DRIFT`. Ejecuta `mark-resolved` y luego `pull` para ver la nueva
divergencia.
# Ramas
Source: https://docs.jelou.ai/guides/cli/ramas
Trabaja en una rama sin tocar el borrador del proyecto: publica commits inmutables, decide qué rama corre cada canal y promueve tu trabajo a producción cuando esté listo.
Por defecto, tu directorio local lee el **borrador** del proyecto: las mismas
filas que edita Studio, vivas y mutables. Con ramas puedes trabajar contra una
versión publicada en su lugar, probar en un canal aparte y llevar el resultado a
producción cuando esté listo, sin que nadie más vea el trabajo a medias.
Una **rama** guarda commits. Un **commit** es una foto inmutable de todos los
workflows del proyecto, y es lo que un canal sirve a tus usuarios.
Esta página asume que ya vinculaste el directorio con `jelou link` y bajaste los
workflows con `jelou pull`. Si aún no lo hiciste, empieza por
[Proyectos y canales](/guides/cli/project).
## Los dos orígenes
Tu directorio lee de un solo lugar a la vez, y `jelou status` lo dice en la
primera línea.
| Origen | Qué es | Quién lo ve |
| ------------ | -------------------------------- | ------------------------------------------- |
| **Borrador** | Las filas vivas que edita Studio | Solo se publica con `jelou project publish` |
| **Rama** | El commit de cabeza de esa rama | Los canales apuntados a esa rama |
El borrador es el origen por defecto y es lo que describe el resto de la
documentación del CLI. Todo lo de abajo es lo que cambia cuando trabajas en una
rama.
## Moverte entre orígenes
```bash theme={null}
jelou checkout dev # lee la rama dev
jelou checkout draft # vuelve al borrador
jelou checkout -b nueva-rama # crea una rama desde donde estás
jelou branch list # qué ramas existen y cuál lees
```
`jelou checkout ` reescribe tus archivos con lo que esa rama sirve. Por
eso se niega si tienes ediciones sin publicar: súbelas primero, o descártalas
con `jelou pull --accept-server`.
`jelou checkout -b ` es la excepción y no toca ningún archivo. Crea la
rama desde el commit que estás leyendo y se lleva tu trabajo en progreso, así
que es la forma de decir "esto que tengo a medias va a ser una rama".
`draft` es un nombre reservado, para que `jelou checkout draft` no sea ambiguo.
No puedes crear una rama con ese nombre.
## Publicar en una rama
Estando en una rama, `jelou push` cambia de significado: en lugar de escribir el
borrador, **publica un commit** y mueve la rama hacia él.
```bash theme={null}
jelou push -m "menú de pedidos" # publica los archivos modificados
jelou push --dry-run # muestra qué publicaría
```
Tres cosas que conviene saber:
* **El borrador no se toca.** Nada de lo que publiques en una rama aparece en
Studio hasta que promuevas a la rama que Studio lee.
* **Solo se envía lo modificado.** Los workflows y canales que no tocaste
conservan la versión que la rama ya servía, así que publicar un archivo
produce un commit completo igual.
* **El nombre del commit es obligatorio.** Úsalo para saber qué contiene cuando
lo veas en el historial.
`jelou push` se niega si la rama avanzó desde tu último `pull`. Alguien más
publicó mientras tanto y tu commit se construiría encima de trabajo que no has
visto. Haz `jelou pull`, revisa y vuelve a intentar.
### Conversaciones en curso
Publicar mueve las conversaciones que están a mitad de camino a la versión
nueva en su siguiente turno. Si prefieres que terminen con la versión con la que
empezaron, publica con `--keep-pinned`.
```bash theme={null}
jelou push -m "hotfix" --keep-pinned
```
Solo aplica al publicar a `master`, porque el ajuste es del proyecto entero y no
de una rama en particular.
## Llevar el trabajo a otra rama
```bash theme={null}
jelou promote master # master sirve el commit que estás leyendo
jelou promote master -m "Release 12"
```
`jelou promote` hace que otra rama sirva el commit en el que estás. La
promoción aparece en el historial de la rama destino con su propio identificador,
y tu directorio no cambia: sigues en tu rama, en tu commit.
El commit del destino recibe un identificador distinto al de origen. Es una
copia, no el mismo commit apuntado desde dos lados, y por eso queda registrado
en la historia de ambas ramas.
Se niega si tienes trabajo local que el commit no contiene — estarías
promoviendo algo distinto de lo que ves en pantalla. Publica primero, o usa
`--allow-dirty` si de verdad quieres promover lo publicado y dejar tus
ediciones donde están.
## Qué rama corre cada canal
Esto es independiente de lo que lee tu directorio. Un canal puede estar
sirviendo `master` mientras tú trabajas en `dev`.
```bash theme={null}
jelou channels list --project 01H2XCEJQTG2H5V5NKCYW3J7Z2
jelou channels set-branch 01H2XCEJQTG2H5V5NKCYW3J7Z2 dev
```
La columna **Branch** del listado muestra qué corre cada canal. En gris
significa que nunca se fijó, así que sigue `master`.
`set-branch` surte efecto de inmediato en las conversaciones nuevas. Se niega si
la rama no tiene nada publicado todavía.
Apunta un canal de pruebas a tu rama y deja los de producción en `master`. Así
pruebas con mensajes reales sin que ningún usuario vea el cambio.
## Un ciclo completo
La rama sale del commit que estás leyendo y se lleva tu trabajo en progreso.
```bash theme={null}
jelou checkout -b promo-navidad
# edita workflows/*.json
jelou workflow validate
```
```bash theme={null}
jelou push -m "promoción de navidad"
```
El borrador no se toca: nada de esto aparece en Studio todavía.
```bash theme={null}
jelou channels set-branch 01H2XCEJQTG2H5V5NKCYW3J7Z2 promo-navidad
```
Escríbele al canal y verifica el comportamiento con mensajes reales. Repite
los pasos anteriores hasta que quede como quieres.
```bash theme={null}
jelou promote master -m "Promo navidad"
```
Ahora `master` sirve el mismo commit que probaste.
```bash theme={null}
jelou channels set-branch 01H2XCEJQTG2H5V5NKCYW3J7Z2 master
```
Así vuelve a seguir producción y queda libre para la siguiente rama.
## Una rama sin nada publicado
Un proyecto nuevo nace con `master` vacía, y crear una rama desde ahí es
perfectamente válido. Tu directorio queda apuntando a una rama sin commits —
`jelou status` lo llama *nothing published yet* — y el primer `jelou push`
escribe su commit inicial.
Leer una rama vacía no es un error. Distinto es pedir una rama que no existe,
que sí falla.
## Qué necesita el borrador
Estos comandos escriben el borrador, así que se niegan mientras leas una rama.
Vuelve con `jelou checkout draft` para usarlos.
| Comando | Por qué |
| ------------------------------ | ------------------------------------------------------------------------------------------- |
| `jelou project publish` | Publica el **borrador**, no tus archivos: reemplazaría lo que acabas de publicar en la rama |
| `jelou incoming accept-local` | Despacha tu archivo contra el borrador |
| `jelou incoming accept-server` | Resuelve un conflicto capturado contra el borrador |
| `jelou status --check-remote` | Solo esta bandera; `jelou status` a secas funciona en ambos orígenes |
Después de un `jelou push` en una rama no hay nada más que ejecutar: el commit
existe y la rama ya apunta a él.
Al revés también aplica: `jelou promote` necesita estar en una rama, porque el
borrador no tiene ningún commit que promover.
## Workflows en TypeScript
Si adoptaste un workflow a TypeScript con `jelou workflow adopt`, ese archivo es
la fuente y el CLI nunca escribe JSON encima, ni siquiera al cambiar de rama.
Cuando el `.ts` difiere de lo que la rama sirve, `jelou pull` te lo dice y
`jelou status` lo marca como modificado. A partir de ahí decides:
* **Publicar el tuyo** — `jelou push`, que lo sube como un commit nuevo.
* **Quedarte con el de la rama** — borra el `.ts` y vuelve a hacer `jelou pull`.
# Referencia: flags, modos y exit codes
Source: https://docs.jelou.ai/guides/cli/referencia
Flags globales, modos de salida para agentes y CI, introspección con --describe, códigos de salida, variables de entorno y comandos de mantenimiento del CLI de Jelou.
## Flags globales
Disponibles en cualquier comando:
| Flag | Descripción |
| ------------------ | ------------------------------------------------------------------------------------------------ |
| `--json` | Salida JSON estructurada en stdout. Implica `--no-input`. |
| `--agent` | Modo agente: implica `--json`, `--no-input`, `--compact`, `NO_COLOR=1`, `JELOU_NO_SPINNERS=1`. |
| `--compact` | JSON sin espacios (ahorra \~30-40% de tokens). |
| `--human` | Fuerza salida legible aunque stdout esté redirigido (anula el auto-JSON). |
| `--no-input` | Desactiva prompts interactivos; las operaciones destructivas fallan en duro en vez de preguntar. |
| `--profile ` | Usa un perfil de auth específico para ese comando. |
| `--describe` | Emite el esquema del comando como JSON, sin ejecutarlo. |
## Modos de salida para agentes
**Auto-JSON al redirigir:** si stdout no es una terminal (ej. `jelou whoami |
jq`), el CLI pasa a JSON automáticamente. Fuérzalo a legible con `--human` o
`JELOU_FORCE_HUMAN_OUTPUT=1`.
**Forma de la respuesta:**
```json theme={null}
// Éxito
{ "ok": true, "data": { } }
// Error
{ "ok": false, "error": { "code": "...", "message": "...", "details": {} } }
```
Códigos de error: `AUTH_ERROR`, `FORBIDDEN`, `NOT_FOUND`, `VALIDATION_ERROR`,
`API_ERROR`, `MISSING_FUNCTIONS_PROJECT`, `MISSING_WORKFLOW_PROJECT`,
`MISSING_AUTH`, `INPUT_ERROR`, `UNKNOWN_ERROR`, `NETWORK_ERROR`,
`TRANSIENT_ERROR`, `MISSING_LOCKFILE`.
El objeto `error` también puede incluir un campo `retryable` (booleano) que
indica si vale la pena reintentar el comando tal cual, sin cambiar nada:
```json theme={null}
{ "ok": false, "error": { "code": "NOT_FOUND", "message": "Not found.", "retryable": false } }
```
Las respuestas `--json` de comandos de acción incluyen un array `breadcrumbs`
con los siguientes comandos sugeridos. Los logs en streaming emiten NDJSON (un
objeto JSON por línea).
Los flags de modo son **solo de salida**, nunca de seguridad. La preservación de
colisiones, el rechazo de `push` ante incoming sin resolver, la validación de
esquema y los chequeos de estado sucio siempre corren, con o sin `--agent`. Las
operaciones destructivas requieren `--yes` en modo JSON/agente — y ese flag es
para CI, no un atajo para saltarse confirmaciones.
## Introspección con `--describe`
`--describe` recorre el árbol de comandos y emite su firma como JSON, sin
ejecutar nada. Es de solo lectura — úsalo para planear.
```bash theme={null}
jelou --describe # comando raíz: lista subcomandos top-level
jelou project --describe # subcomandos de project
jelou databases create --describe # argumentos y opciones de un comando
```
Devuelve `{ command, description, arguments, options, examples, subcommands }`.
Los flags globales se filtran para que solo veas la superficie específica del
comando.
## Exit codes
El CLI usa códigos de salida granulares para que un agente ramifique sin parsear
stderr:
| Código | Significado | Causa típica |
| ------ | ---------------------------- | -------------------------------------------------------------------------------- |
| 0 | Éxito | — |
| 1 | Genérico / desconocido | Fallos de build, errores sin categoría |
| 2 | Input / validación | Flag inválido, argumento faltante, esquema no coincide |
| 3 | No encontrado | id, slug o nombre desconocido |
| 4 | Auth / prohibido | Token faltante o expirado, scope insuficiente |
| 5 | DB no lista | Base de datos aún provisionándose |
| 6 | Transitorio / API | 5xx, timeout de red — seguro reintentar |
| 7 | Conflicto de estado | Drift de lockfile, incoming sin resolver, push parcial |
| 8 | Secret detectado (reservado) | El escáner de secrets existe pero todavía no está conectado en producción |
| 9 | Lock ocupado | Otro `jelou pull`/`push` en curso |
| 10 | Desajuste de esquema local | El esquema de `qa.db` es incompatible (`QA_SCHEMA_MISMATCH`) — actualiza `jelou` |
Los códigos 7-9 son específicos de `jelou pull`/`push`/`incoming` (el 8 está
reservado y aún no se emite en rutas de producción). El código 10 aparece si
el toolchain local quedó desactualizado respecto al esquema que espera el CLI.
En modo `--json`, el mismo código aparece en `error.code` con un campo `hint`
que sugiere el siguiente comando.
## Variables de entorno
| Variable | Descripción |
| -------------------------- | ------------------------------------------------------------------------- |
| `JELOU_TOKEN` | Token de autenticación (máxima prioridad; sin necesidad de `jelou login`) |
| `JELOU_PROFILE` | Perfil por defecto |
| `JELOU_NO_INPUT` | `1` para modo no interactivo |
| `JELOU_FORCE_HUMAN_OUTPUT` | `1` para forzar salida legible aunque stdout esté redirigido |
| `NO_COLOR` | `1` para desactivar colores ANSI |
| `JELOU_NO_SPINNERS` | `1` para desactivar los spinners de progreso (implícito con `--agent`) |
| `CI` | Detectado automáticamente para modo no interactivo |
## Comandos de mantenimiento
```bash theme={null}
jelou update --agent # ¿hay una versión nueva? (solo lectura)
jelou doctor --json # chequeo de salud (api, auth, perfil, lockfile, proyecto, authoring, skills, versión)
jelou changelog # notas de versión
jelou feedback # envía un reporte (siempre pregunta primero)
```
Para actualizar el CLI:
```bash theme={null}
npm install -g @jelou/cli@latest
jelou --version
jelou agent install --global --all-targets --yes --no-input # refresca las skills
```
Para consultar la analítica de la empresa (catálogo, una métrica o un tablero
completo) usa `jelou metrics` — tiene su propia página en
[Métricas](/guides/cli/metrics).
# Secrets de organización
Source: https://docs.jelou.ai/guides/cli/secret
Administra los secrets a nivel de organización (variables de entorno cifradas) que se inyectan en workflows y funciones en tiempo de ejecución.
`jelou secret` administra los secrets de organización: variables de entorno
cifradas que se inyectan en **workflows y funciones** en tiempo de ejecución.
Por defecto, `list`, `set` y `delete` operan sobre toda la organización; con
`--project` (alias `--brain`) puedes limitar cualquiera de los tres a un solo
proyecto.
| Subcomando | Descripción |
| ---------------------- | ----------------------------------------------------- |
| `list` | Lista los nombres de secret de la organización activa |
| `set [valor]` | Crea o actualiza un secret (upsert) |
| `delete ` | Elimina un secret |
```bash theme={null}
jelou secret list --json
jelou secret set OPENAI_API_KEY # interactivo: pide el valor
jelou secret set OPENAI_API_KEY sk-proj-abc123 # inline
echo "$GITHUB_TOKEN" | jelou secret set GITHUB_TOKEN --from-stdin
jelou secret delete OPENAI_API_KEY --yes
jelou secret list --project 01H2XCEJQTG2H5V5NKCYW3J7Z2 # solo los secrets de ese proyecto
```
| Flag | Descripción |
| --------------------------- | ----------------------------------------------------------------------------------- |
| `--from-stdin` | Lee el valor desde stdin (ideal para CI) |
| `--yes`, `-y` | Omite la confirmación al borrar |
| `--project`, `--brain ` | Limita `list`/`set`/`delete` a un proyecto puntual en lugar de toda la organización |
Los nombres deben ser `UPPER_SNAKE_CASE` (ej. `MY_API_KEY`) y son sensibles a
mayúsculas/minúsculas.
`secret list` devuelve nombres y metadata pero **nunca el valor**. `set` es un
upsert sin diff: al sobrescribir, el valor anterior se pierde sin recuperación.
Al borrar un secret, los workflows y funciones que lo leen reciben `undefined`
en la siguiente ejecución — suele causar degradación silenciosa, no errores.
Revisa los consumidores conocidos antes de borrar.
Si necesitas secrets por función (no de toda la organización), usa
`jelou functions secrets set ` — ver la
[CLI de Functions](/guides/functions/cli).
# Shop
Source: https://docs.jelou.ai/guides/cli/shop
Administra Jelou Shop desde el CLI: catálogo, sucursales, categorías, cupones, pedidos, importaciones, sincronización de catálogos externos y métricas.
`jelou shop` administra la tienda de tu empresa: catálogo, pedidos, cupones y la
configuración de la app.
## Empezar
```bash theme={null}
jelou shop info # muestra la app Shop actual
jelou shop activate # habilita Shop para la empresa activa
jelou shop settings # actualiza la configuración de la app
jelou shop usage # uso del plan y totales del catálogo
```
`jelou shop usage` es la forma rápida de ver cuántos productos tienes contra el
límite de tu plan antes de una importación grande.
## Productos
| Subcomando | Descripción |
| ----------------------------- | ------------------------------------------------------------------------------------------- |
| `products list` | Lista y busca productos del catálogo |
| `products search` | Búsqueda de descubrimiento (la que usa el storefront) |
| `products show ` | Detalle de un producto |
| `products upsert` | Crea o actualiza según exista |
| `products update` | Aplica un parche masivo por id (`--file`) o a un solo producto (`--id --status\|--patch`) |
| `products status` | Cambia el estado de varios productos a la vez (`--ids p1,p2 --status true\|false`) |
| `products delete` | Elimina varios productos a la vez (`--ids p1,p2`), con confirmación (`--yes` para omitirla) |
| `products bulk` | Operaciones masivas |
| `products images add\|delete` | Gestiona las imágenes del producto (por SKU) |
```bash theme={null}
jelou shop products list --json
jelou shop products search "camiseta negra"
jelou shop products images add SKU-123 --file product.jpg --json
# Actualizar productos
jelou shop products update --file product-patch.json # parche masivo por id
jelou shop products update --id prod_123 --status false # un solo producto
# Cambiar estado o eliminar en bloque
jelou shop products status --ids p1,p2 --status false
jelou shop products delete --ids p1,p2 --yes
```
`products list` y `products search` no son lo mismo: `list` recorre tu catálogo
como administrador, `search` usa el motor de descubrimiento del storefront, que
es el que ve el cliente final.
## Sucursales y categorías
```bash theme={null}
jelou shop branches list
jelou shop branches show
jelou shop branches create --name "Sucursal Norte"
jelou shop branches update --name "Sucursal Centro"
jelou shop branches delete
jelou shop categories list
jelou shop categories create --name "Zapatos"
jelou shop categories update --name "Calzado"
jelou shop categories delete
```
## Cupones
| Subcomando | Descripción |
| --------------------- | --------------------------------------- |
| `coupons list` | Lista los cupones |
| `coupons show ` | Detalle del cupón, por código |
| `coupons create` | Crea un cupón |
| `coupons update ` | Actualiza un cupón |
| `coupons delete ` | Elimina un cupón |
| `coupons uses ` | Historial de usos del cupón, por código |
```bash theme={null}
jelou shop coupons list --json
jelou shop coupons show WELCOME10 --json
jelou shop coupons uses WELCOME10 --json
```
## Pedidos
```bash theme={null}
jelou shop orders list
jelou shop orders show ord_01h2xcejq
```
## Importaciones
Carga masiva de productos desde un archivo XLSX:
```bash theme={null}
jelou shop imports template # descarga la plantilla XLSX
jelou shop imports upload --file productos.xlsx --uploaded-by alex --json
jelou shop imports show imp_01h2xcejq --json # estado de una importación
```
Revisa `jelou shop usage` antes de una importación grande: si el archivo excede
el límite de productos de tu plan, la carga falla a medio camino.
## Catálogos externos
Cuando conectas un catálogo externo (por ejemplo Shopify desde el
[marketplace](/guides/cli/marketplace)), estos comandos muestran las cuentas
conectadas y el historial de sincronización:
```bash theme={null}
jelou shop accounts # cuentas de catálogo conectadas
jelou shop syncs # corridas de sincronización de esa cuenta
```
Úsalos para responder "¿por qué este producto no aparece en la tienda?": revisa
si la última sincronización de la cuenta corrió y con qué resultado.
## Actividad
```bash theme={null}
jelou shop activity list --json
```
Lista el registro de actividad de la tienda, agrupado por entidad (`app`,
`product`, `product_variation`, `branch`, `category`, `coupon`). Útil para
auditar qué cambió y cuándo.
## Métricas
```bash theme={null}
jelou shop metrics overview # resumen compuesto: ventas, búsquedas, catálogo y clientes
jelou shop metrics sales # ventas sobre carritos completados, AOV, conversión
jelou shop metrics search # búsquedas totales, tasa de resultados vacíos, consultas más frecuentes
jelou shop metrics catalog # foto del inventario: productos activos, sin stock, variaciones
jelou shop metrics customers # clientes nuevos vs. recurrentes, top compradores
```
Analítica de la tienda a nivel de empresa: ventas, búsquedas, catálogo y
clientes.
# Skills para editores de IA
Source: https://docs.jelou.ai/guides/cli/skills
El CLI de Jelou instala skills (paquetes de capacidades en markdown) en tus editores de IA para que un agente sepa operar la plataforma. Instálalas, actualízalas y gestiona sus versiones.
El CLI de Jelou es **agente-nativo**: además de ejecutarse desde la terminal,
empaqueta **skills** — paquetes de capacidades en markdown — que se instalan en
tus editores de IA. Una vez instaladas, un agente (Claude Code, Cursor, Codex,
Windsurf, etc.) sabe qué comandos `jelou` existen, qué hace cada área de
producto y cómo encadenarlos, así que puedes pedirle en lenguaje natural
*"despliega esta función"* o *"construye un flujo de soporte"* y elige el
comando correcto.
## Cómo funcionan
* En tu **primer `jelou login` exitoso**, el CLI detecta los editores de IA
instalados y les instala las skills globalmente. Las sesiones nuevas en esos
editores ya conocen `jelou`.
* Cada skill se escribe como markdown bajo un directorio de skills del editor.
El CLI escribe una copia canónica en `/.agents/skills//` y luego
crea symlinks (o copias, en sistemas que bloquean symlinks) hacia el
directorio de cada agente detectado.
* Todas las skills derivan su versión de la versión del binario del CLI: cuando
actualizas `jelou`, todas las skills se reportan como actualizadas.
"Skill" aquí significa el **paquete markdown** que `jelou agent install` escribe
en los editores de IA. No lo confundas con el sentido legacy de "skill" como
*workflow* dentro de un proyecto.
## `jelou agent install`
Instala (o reinstala) las skills manualmente. En una terminal interactiva
pregunta por agentes destino, alcance, método de instalación y confirmación. En
CI, modo JSON, modo agente o con stdout redirigido nunca pregunta.
```bash theme={null}
jelou agent install # interactivo
jelou agent install --global --targets codex --yes --no-input # un agente
jelou agent install --global --all-targets --yes --no-input # todos
jelou agent install --global --only project,whatsapp --targets codex,claude
```
| Flag | Descripción |
| -------------------------- | ------------------------------------------------------------------------------------ |
| `--global` | Instala en el home del usuario (todos los proyectos) en vez de en el proyecto actual |
| `--targets ` | Lista de agentes destino específicos |
| `--all-targets` | Instala en todos los agentes detectados |
| `--only ` | Instala solo las skills indicadas |
| `--method ` | Método de instalación (`symlink` por defecto: una sola fuente de verdad) |
| `--api` | Instala la variante del SDK enfocada en API cuando está disponible (solo Functions) |
| `--yes` | Omite la confirmación (para CI/no interactivo) |
### Editores de IA soportados
El CLI detecta y soporta estos destinos:
| Universal (`.agents/skills`) | Específicos |
| --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Amp, Codex, Gemini CLI, GitHub Copilot, OpenCode, Kimi Code CLI | Claude Code (`.claude/skills`), Cursor (`.cursor/skills`), Windsurf, Cline, Continue, Roo Code |
## `jelou skills`
Inspecciona y gestiona las versiones de las skills instaladas.
```bash theme={null}
jelou skills list # skills empaquetadas vs. instaladas
jelou skills check --skill jelou-functions # detecta si una skill está desactualizada
jelou skills acknowledge --skill jelou-functions # marca su versión como vista
jelou skills ack-update --version 1.53.0 # descarta el aviso de update del CLI
```
| Subcomando | Descripción |
| ---------------------------- | ----------------------------------------------------------------------------------------------------- |
| `list` | Lista todas las skills con su versión empaquetada e instalada |
| `check --skill ` | Detecta drift y emite marcadores de versión (`CLI_OUTDATED`, `CLI_UPDATE_AVAILABLE`, `SKILL_UPDATED`) |
| `acknowledge --skill ` | Marca la versión de una skill como vista; limpia el flag `SKILL_UPDATED` |
| `ack-update --version ` | Descarta el aviso `CLI_UPDATE_AVAILABLE` para esa versión |
## Skills disponibles
El CLI empaqueta estas skills (varias incluyen variantes y archivos de
referencia adicionales):
| Skill | Cubre |
| -------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `jelou` | Instalación, auth, perfiles, modos de salida y navegación del CLI |
| `jelou-functions` | Funciones serverless (variantes `cli` y `api`) |
| `jelou-databases` | Bases de datos gestionadas (alias `jelou-datum`) |
| `jelou-project` | Proyectos, archivos de conocimiento y canales |
| `jelou-whatsapp` | Bots, plantillas y campañas de WhatsApp |
| `jelou-shop` | Catálogo, productos, pedidos y cupones de Jelou Shop (ecommerce) |
| `jelou-debug-conversation` | Triaje de conversaciones de producción (logs) |
| `jelou-insights` | Analítica de conversaciones: preguntas frecuentes, fricción, sentimiento y oportunidades de automatización |
| `jelou-feedback` | Enviar reportes desde el CLI |
Además, los **bundles BrainOps** son paquetes instalables más completos (con
ejecutables, referencias y hooks) para construir y probar workflows:
`jelou-build-workflows` (construcción de workflows), `jelou-test-workflow`
(QA de un workflow) y `jelou-audit-workflows` (auditorías read-only).
## Mantener las skills al día
Tras actualizar el CLI, refresca las skills instaladas para que las sesiones
nuevas vean los cambios:
```bash theme={null}
npm install -g @jelou/cli@latest
jelou --version
jelou agent install --global --all-targets --yes --no-input
```
Si un agente no parece conocer `jelou`, ejecuta
`jelou agent install --global` y abre una **sesión nueva** del editor — las
skills se cargan al inicio de la sesión.
# Voz
Source: https://docs.jelou.ai/guides/cli/voice
Administra el servicio de voz completo desde el CLI: llamadas, números, agentes, contactos, campañas de llamadas, facturación, reportes y webhooks.
`jelou voice` cubre todo el servicio de voz de tu organización: **llamadas**
(consulta, grabaciones, estadísticas), **números telefónicos**, **agentes de
voz** (ElevenLabs y otros proveedores), **contactos** y **listas de
contactos**, **campañas de llamadas** (schedules), **facturación**,
**reportes de campaña** y **webhooks** de eventos.
Todo lo que marca o cuesta dinero pide confirmación antes de ejecutarse:
activar una campaña, una llamada de prueba, una llamada saliente y cualquier
`delete`. Usa `--yes` en CI o scripts no interactivos.
## Llamadas
```bash theme={null}
jelou voice calls list # últimos 30 días (default)
jelou voice calls list --start-date 2026-07-01 --end-date 2026-07-17
jelou voice calls list --direction inbound --status completed
```
| Flag | Descripción |
| --------------------------- | ----------------------------------------------- |
| `--start-date ` | Inicio del rango |
| `--end-date ` | Fin del rango |
| `--page ` | Página (default: 1) |
| `--limit ` | Resultados por página (default: 20) |
| `--provider ` | Filtra por proveedor (por ejemplo `elevenlabs`) |
| `--direction ` | `inbound` o `outbound` |
| `--status ` | Filtra por estado de la llamada |
El resultado está paginado: sin `--start-date` y `--end-date` obtienes los
últimos 30 días.
### Ver una llamada
```bash theme={null}
jelou voice calls get call_01h2xcejq
```
### Descargar la grabación
```bash theme={null}
jelou voice calls recording call_01h2xcejq --out llamada.mp3
```
Sin `--out` el archivo se guarda con un nombre por defecto en el directorio
actual.
### Estadísticas, exportación y agentes
```bash theme={null}
# Resumen agregado del rango (default: últimos 30 días)
jelou voice calls stats --start-date 2026-07-01 --end-date 2026-07-31
# Exportar a CSV o XLSX (--format csv|xlsx, default: csv)
jelou voice calls export --start-date 2026-07-01 --end-date 2026-07-31 --out llamadas.csv
jelou voice calls export --start-date 2026-07-01 --end-date 2026-07-31 --format xlsx --out llamadas.xlsx
# Una métrica puntual por nombre (catálogo v2)
jelou voice calls metrics --start-date 2026-07-01
# Agentes que han atendido llamadas
jelou voice calls agents
```
### Llamada saliente puntual
```bash theme={null}
jelou voice calls outbound --phone +593999999999 --agent-id ag_1
jelou voice calls outbound --phone +593999999999 --agent-id ag_1 --phone-number-id num_1 --pbx sip-trunk
```
`jelou voice calls outbound` marca un número real de inmediato y factura la
cuenta. Pide confirmación salvo que pases `--yes`.
## Números telefónicos
```bash theme={null}
jelou voice numbers list # los de tu organización (ElevenLabs por defecto)
jelou voice numbers list --provider # otro proveedor
jelou voice numbers list --all # todos los números del proveedor
```
| Flag | Descripción |
| --------------------- | --------------------------------------------------------------------- |
| `--provider ` | Proveedor a consultar (default: `elevenlabs`) |
| `--all` | Lista todos los números del proveedor, no solo los de tu organización |
## Agentes de voz
Un agente de voz es quien "habla" en la llamada: define el prompt, la voz, el
idioma, el límite de duración y su base de conocimiento.
```bash theme={null}
jelou voice agents list
jelou voice agents list --all # todos los agentes del proveedor
jelou voice agents get ag_1
jelou voice agents voices # voces disponibles para asignar
```
### Crear y ajustar un agente
```bash theme={null}
jelou voice agents create \
--name "Agente de cobranza" \
--language es \
--voice-id voice_abc123 \
--system-prompt "Eres un agente de cobranza amable y directo." \
--first-message "Hola, te llamo de parte de..."
jelou voice agents update ag_1 \
--system-prompt "Prompt actualizado" \
--max-duration-seconds 300
```
| Flag (`create`/`update`) | Descripción |
| ---------------------------- | ----------------------------------------------------- |
| `--name` | Nombre del agente (solo `create`) |
| `--language ` | Idioma, por ejemplo `es` |
| `--voice-id ` | Voz a usar (`jelou voice agents voices`) |
| `--system-prompt` | Prompt del sistema |
| `--first-message` | Frase de apertura |
| `--max-duration-seconds ` | Límite duro de duración de la llamada (solo `update`) |
```bash theme={null}
jelou voice agents assign ag_1 # asigna el agente a la organización activa
jelou voice agents delete ag_1 --yes
```
### Base de conocimiento del agente
```bash theme={null}
jelou voice agents knowledge list ag_1
jelou voice agents knowledge upload ag_1 ./manual-producto.pdf
jelou voice agents knowledge url ag_1 https://ejemplo.com/faq --name "FAQ pública"
jelou voice agents knowledge delete ag_1 doc_1 --yes
```
`jelou voice agents delete` es irreversible.
## Contactos y listas de contactos
Los contactos son las personas que una campaña puede llamar. Las listas
agrupan contactos y son el objetivo (`--contact-list-id`) de una campaña.
```bash theme={null}
jelou voice contacts list
jelou voice contacts get cnt_1
```
### Crear y actualizar contactos
```bash theme={null}
jelou voice contacts create \
--first-name Ana --last-name Torres \
--phone +593999999999 --email ana@ejemplo.com \
--company Acme --position Gerente --notes "Cliente VIP"
jelou voice contacts update cnt_1 --phone +593988888888
jelou voice contacts delete cnt_1 --yes
```
Todos los campos de `create` (`--first-name`, `--last-name`, `--phone`,
`--email`, `--company`, `--position`, `--notes`) también existen en `update`
como overrides parciales. El teléfono va en formato E.164.
### Listas de contactos
```bash theme={null}
jelou voice contacts lists list
jelou voice contacts lists get list_1
# Crear desde ids existentes
jelou voice contacts lists create --name "Cartera vencida" \
--contact-id cnt_1 --contact-id cnt_2
# Crear desde un archivo CSV/XLSX
jelou voice contacts lists upload ./contactos.csv --name "Campaña julio"
jelou voice contacts lists update list_1 --name "Nuevo nombre" --description "Actualizada"
jelou voice contacts lists add list_1 cnt_3
jelou voice contacts lists remove list_1 cnt_3
jelou voice contacts lists delete list_1 --yes
```
`--contact-id` en `lists create` es repetible. `lists upload` es la forma más
rápida de armar una lista grande: solo necesitas un CSV o XLSX con los
contactos.
## Campañas de llamadas (`jelou voice schedules`)
Una campaña ("schedule") llama a toda una lista de contactos con un agente y
un número de origen determinados.
```bash theme={null}
jelou voice schedules list
jelou voice schedules get sch_1
jelou voice schedules stats # estadísticas de campañas en toda la organización
jelou voice schedules progress sch_1 # avance de marcado de una campaña
jelou voice schedules calls sch_1 # llamadas hechas por una campaña
jelou voice schedules calls sch_1 --status completed
```
### Crear y activar una campaña
```bash theme={null}
jelou voice schedules create \
--name "Cobranza julio" \
--contact-list-id list_1 \
--agent-id ag_1 \
--from-number +593222222222 \
--scheduled-at "2026-07-20T09:00:00-05:00" \
--max-retries 2
jelou voice schedules activate sch_1 --agent-id ag_1 \
--retry-on-no-answer --max-retries 2 --retry-delay-minutes 30
```
| Flag | En `create` | En `activate` |
| --------------------------- | :---------: | :-----------: |
| `--name` | ✓ | — |
| `--contact-list-id` | ✓ | — |
| `--agent-id` | ✓ | ✓ (override) |
| `--from-number` (repetible) | ✓ | ✓ (override) |
| `--scheduled-at ` | ✓ | ✓ (override) |
| `--max-retries ` | ✓ | ✓ (override) |
| `--retry-delay-minutes ` | — | ✓ |
| `--retry-on-no-answer` | — | ✓ |
| `--yes` | — | ✓ |
`create` deja la campaña lista pero inactiva; `activate` es lo que empieza a
marcar. Los flags de `activate` sirven para pisar lo definido en `create` sin
tener que recrear la campaña.
`--name` es el único flag que exige el CLI en `create`; el resto lo valida la
API al enviar el payload. En la práctica, `--agent-id`, `--from-number` y
`--scheduled-at` son obligatorios — sin ellos la API responde 400 con
`fromNumbers/agentId/scheduledAt should not be empty`.
### Probar antes de activar
```bash theme={null}
jelou voice schedules test-call sch_1 --contact-id cnt_1 --agent-id ag_1
```
`jelou voice schedules activate` empieza a marcar llamadas reales de inmediato
y factura la cuenta — confirma el conteo de contactos antes de activar.
`test-call` también marca un número real, aunque sea uno solo. Ambos piden
confirmación salvo `--yes`.
### Controlar una campaña en curso
```bash theme={null}
jelou voice schedules pause sch_1
jelou voice schedules resume sch_1
jelou voice schedules cancel sch_1 # las llamadas en cola se descartan
jelou voice schedules delete sch_1 --yes
```
`cancel` descarta las llamadas que seguían en cola sin completarse — no es
reversible. `delete` solo debería usarse sobre campañas que ya terminaron o se
cancelaron.
## Facturación
```bash theme={null}
jelou voice billing summary --start-date 2026-07-01 --end-date 2026-07-31
jelou voice billing details --start-date 2026-07-01 --end-date 2026-07-31 \
--direction outbound --channel sip-trunk
```
| Flag | Descripción |
| ----------------------------- | ----------------------------------------------- |
| `--start-date` / `--end-date` | Rango de fechas |
| `--organization-id` | Limita a una organización puntual |
| `--direction` | `inbound` o `outbound` |
| `--channel` | Canal de facturación (`sip-trunk`, `twilio`, …) |
| `--page` / `--limit` | Paginación (solo `details`) |
`summary` da el total facturado del rango; `details` lo desglosa línea por
línea.
## Reportes de campaña
```bash theme={null}
jelou voice reports list
jelou voice reports get rep_1
jelou voice reports export rep_1 --out reporte-julio.xlsx
```
Sin `--out`, `export` guarda el archivo con un nombre por defecto en el
directorio actual.
## Webhooks
```bash theme={null}
jelou voice webhooks list
jelou voice webhooks get wh_1
jelou voice webhooks create --url https://mi-servidor.com/eventos-voz --type call.completed
jelou voice webhooks delete wh_1 --yes
```
## Estado del servicio
Verifica que el servicio de voz responda para el perfil activo:
```bash theme={null}
jelou voice status
```
Si un comando de voz falla, corre `jelou voice status` antes de investigar más:
distingue un problema del servicio de un problema de tus credenciales o filtros.
# WhatsApp: plantillas y campañas
Source: https://docs.jelou.ai/guides/cli/whatsapp
Administra plantillas HSM aprobadas por Meta y envía campañas masivas de WhatsApp a listas de destinatarios CSV desde el CLI de Jelou.
El CLI cubre la superficie saliente de WhatsApp: **plantillas** (mensajes HSM
reutilizables que Meta debe aprobar para enviar fuera de la ventana de 24 horas)
y **campañas** (envíos masivos a listas de destinatarios usando una plantilla
aprobada).
El `--bot-id` que piden estos comandos es el `referenceId` que aparece en
`jelou channels list --type Whatsapp`.
## `jelou channels testers`
Antes de conectar un número real de WhatsApp Business, puedes usar el sandbox
compartido de Jelou para probar el envío y la recepción de mensajes con tu
propio teléfono.
| Subcomando | Descripción |
| ------------------------------------------------------ | ------------------------------------------------------- |
| `testers list ` | Lista los números de teléfono blanqueados en el sandbox |
| `testers add --phone ` | Blanquea un número de teléfono en el sandbox |
| `testers remove --phone --yes` | Quita un número de teléfono del sandbox |
```bash theme={null}
jelou channels testers list channel-id-xyz
jelou channels testers add channel-id-xyz --phone +14155550100
jelou channels testers remove channel-id-xyz --phone +14155550100 --yes
```
`` es el id del canal, no el `--bot-id`/`referenceId` que usan
`template` y `campaign`. Solo tiene efecto en canales sandbox: una vez que
conectas un número real de WhatsApp Business, la whitelist de testers deja de
ser necesaria. El teléfono debe estar en formato E.164 (`+14155550100`).
## `jelou template`
| Subcomando | Descripción |
| --------------------------------- | --------------------------------------------------- |
| `list --bot-id ` | Lista plantillas (filtros: estado, tipo, categoría) |
| `get --bot-id ` | Muestra una plantilla con su cuerpo y parámetros |
| `validate ` | Valida un payload de plantilla localmente |
| `create --bot-id …` | Crea (y opcionalmente envía a Meta) una plantilla |
| `update --bot-id …` | Actualiza una plantilla APPROVED o REJECTED |
| `delete --bot-id --yes` | Elimina una plantilla |
| `upload-media ` | Sube una imagen/video al CDN y devuelve su URL |
```bash theme={null}
jelou template list --bot-id bot-abc123 --status APPROVED --category UTILITY --type text
jelou template create --bot-id bot-abc123 \
--display-name "Confirmación de orden" \
--element-name order_confirmation \
--template "Hola {{1}}, tu orden {{2}} está lista." \
--language es --category UTILITY --type text
jelou template create --bot-id bot-abc123 ... --draft # guardar sin enviar a Meta
jelou template create --bot-id bot-abc123 --from-file ./template.json # payload completo en JSON
jelou template upload-media ./header.png
```
Categorías: `UTILITY`, `MARKETING`, `AUTHENTICATION`. Estados: `APPROVED`,
`PENDING`, `REJECTED`. Tipos de contenido (`--type`): `text` (por defecto),
`IMAGE`, `VIDEO`, `DOCUMENT`, `CAROUSEL`. Usa `--header` para agregar un
encabezado de texto y `{{1}}`, `{{2}}`, … como marcadores en el cuerpo. Igual
que en `campaign create`, `--from-file` acepta la ruta a un JSON con el
payload completo de la plantilla en lugar de pasar cada flag por separado.
Solo se pueden editar plantillas `APPROVED` o `REJECTED`; las `PENDING` están
bloqueadas. Las plantillas sin `--draft` van a Meta de inmediato y la cuota de
aprobación es finita por empresa (los rechazos cuentan). Meta antepone un prefijo
aleatorio de 4 caracteres al `elementName` — usa el `elementName` devuelto
(con prefijo) en las campañas.
### Actualizar una plantilla
`jelou template update` solo acepta flags discretos: `--display-name`,
`--template`, `--header`, `--footer`, `--media-url` y `--draft`. El CLI toma
el estado actual de la plantilla, aplica únicamente los overrides indicados y
hace un PATCH del payload completo — no necesitas repetir los campos que no
cambian.
```bash theme={null}
jelou template update template-id-xyz --bot-id bot-abc123 \
--template "Hola {{1}}, tu orden {{2}} ya se envió." \
--footer "Equipo de soporte"
jelou template update template-id-xyz --bot-id bot-abc123 \
--footer "Nuevo footer" --draft # guarda sin reenviar a Meta
```
### Header con media
```bash theme={null}
URL=$(jelou template upload-media ./banner.png --json | jq -r '.url')
jelou template create --bot-id bot-abc123 \
--display-name "Oferta semanal" --element-name weekly_offer \
--template "Esta semana: {{1}} con {{2}}% de descuento" \
--media-url "$URL" --category MARKETING
```
## `jelou campaign`
| Subcomando | Descripción |
| ------------------------------ | ---------------------------------------------------------------------------------------------- |
| `list` | Lista campañas (filtros: bot, estado, tipo, nombre de plantilla, elementName, rango de fechas) |
| `get ` | Detalle de una campaña (destinatarios y agenda) |
| `create --from-file ` | Crea una campaña masiva desde un JSON + CSV local |
| `cancel ` | Detiene una campaña SCHEDULED o IN\_PROGRESS |
| `reschedule --at ` | Reprograma o revive una campaña cancelada |
```bash theme={null}
jelou campaign list --bot-id bot-abc123 --status SCHEDULED --json
jelou campaign list --type text --name "Confirmación de orden" --element-name xy12_order_confirmation
jelou campaign create --from-file ./campaign.json --json
jelou campaign cancel campaign-id-xyz
jelou campaign reschedule campaign-id-xyz --at "2026-06-20T15:30:00-05:00"
jelou campaign reschedule campaign-id-xyz --at "2026-07-01T09:00:00-05:00" --revive
```
`--type` filtra por tipo de campaña (`text`, `carousel`, …), `--name` por el
nombre visible de la plantilla y `--element-name` por su `elementName`.
Estados: `SCHEDULED` → `IN_PROGRESS` → `COMPLETED`; `CANCELLED` es terminal
salvo que uses `--revive`.
`--start-at` y `--end-at` filtran por la fecha de **creación** de la campaña
(`createdAt`), no por la fecha programada de envío (`scheduledAt`). Si buscas
"campañas agendadas para la próxima semana" estos filtros no sirven para eso:
te devolverán las campañas creadas en ese rango, sin importar cuándo estén
programadas para enviarse.
### Estructura del archivo de campaña
```json campaign.json theme={null}
{
"campaignName": "Blast de temporada",
"botId": "bot-abc123",
"elementName": "xy12_holiday_offer",
"language": "es",
"csvPath": "./destinatarios.csv",
"date": "2026-06-15T10:00:00-05:00",
"params": [
{ "param": 1, "column": "nombre" },
{ "param": 2, "column": "descuento" }
]
}
```
```csv destinatarios.csv theme={null}
phone_number,nombre,descuento
+14155550100,Alicia,20
+14155550101,Bruno,30
```
El CSV incluye una columna de teléfonos en formato E.164. El mapeo de parámetros
es explícito: `{ "param": , "column": "" }`. Usa el campo `date`
para agendar (omítelo para envío inmediato).
Una campaña consume créditos reales al despacharse. Revisa el conteo de
destinatarios y el costo antes de crearla. Un mapeo de parámetros equivocado
envía el texto literal del marcador (`Hola {{1}}`) a miles de personas —
verifica antes. Cancelar una campaña `IN_PROGRESS` produce un envío parcial.
# Workflows y pruebas
Source: https://docs.jelou.ai/guides/cli/workflows
Crea y valida workflows, pruébalos localmente con trazas y un dashboard, depura conversaciones de producción con jelou logs y reinicia el estado de un usuario.
Estos comandos cubren el ciclo de construir, probar y depurar los workflows de
un proyecto: autoría y validación (`jelou workflow`), pruebas locales con trazas
(`jelou test`), triaje de producción (`jelou logs`) y reinicio de estado de
usuario (`jelou users`).
La sincronización de workflows a archivos locales (`jelou link`, `pull`,
`status`, `push`, `incoming`) se documenta en
[Proyectos y canales](/guides/cli/project).
## `jelou workflow`
| Subcomando | Descripción |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list [--project-id ]` | Lista proyectos; con `--project-id`, lista los skills/canales del proyecto |
| `create --project-id --name ""` | Crea un workflow nuevo en un proyecto |
| `node-spec [] [--list]` | Imprime el schema de un tipo de nodo (con `--list` lista todos los tipos, o pásale un nombre de tipo para su JSON completo) sin cargar el catálogo entero |
| `validate []` | Valida los `workflows/*.json` contra el validador canónico |
`jelou workflow` tiene 16 subcomandos en total (además de los de arriba:
`update`, `set-default`, `delete`/`rm`, `hide`, `unhide`, `allow-user`,
`allow-country`, `evaluate`, `skill`, `build`, `branches`,
`canonicalize-branch`, `inject-ecommerce`, `adopt`, `authoring`). Explóralos
con `jelou workflow --describe`.
```bash theme={null}
jelou workflow list
jelou workflow list --project-id 01kf13bcth4ytadcx9h8pq8w9h --agent
jelou workflow create --project-id 01kf13bcth4ytadcx9h8pq8w9h --name "Generación de Orden V3"
jelou workflow node-spec --list
jelou workflow node-spec AI_TASK
jelou workflow validate
jelou workflow validate workflows/saludo.whatsapp.json
jelou workflow validate --allow-warnings
jelou workflow validate --fix workflows/borrador.json
jelou workflow validate --partial workflows/borrador.json
```
`validate` corre una tubería canónica de varias fases (schema → autofix →
normalize → ids → config → quality → whatsapp → edges → lint → finalize) y
reporta `{ status, errorCount, warnCount }` por fase. Con errores sale con
código distinto de 0; `--allow-warnings` sale 0 si solo quedan advertencias.
Flags adicionales: `--fix` (alias `--write`) canoniza el archivo en el mismo
lugar (acuña ids, normaliza tokens de branch, repara handles de edges);
`--quiet` solo imprime los archivos con errores o advertencias; `--out ` guarda el mismo payload JSON en disco además de en stdout.
Con `--partial ` corres el validador completo sobre un borrador en
progreso en modo *preview*: devuelve diagnósticos completos pero nunca
bloquea — siempre sale con código 0 sin importar los errores. Desde la v1.88,
un hook de "validar al escribir" corre este mismo preview automáticamente en
el instante en que un agente de IA escribe el JSON de un workflow, así que
los diagnósticos llegan de inmediato (son solo informativos: nunca bloquean
la escritura).
También desde la v1.88, `validate` valida además los `tools/*.json` (no solo
`workflows/*.json`) con las reglas reales de push de tools, y ya no marca por
error `$output.set()` dentro de un nodo CODE de una tool — esa es la forma
correcta en que una tool devuelve un valor.
## `jelou test`
Prueba workflows localmente: envía mensajes, persiste trazas y chats, e
inspecciónalos.
| Subcomando | Descripción |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `tool --input k=v` | Ejecuta una tool en el servidor y reporta qué output disparó (en el propio `--help` del CLI, `tool` ahora encabeza la lista, antes de `send`) |
| `send text --target --message ""` | Envía un mensaje a un workflow y persiste la traza |
| `turn status --target ` | Sigue un turno en curso con long-poll hasta que cierra |
| `chats list --target ` | Lista los chats (ejecuciones) registrados |
| `chats show --target ` | Detalle de un chat (turnos + salida renderizada) |
| `trace --target ` | Inspecciona la traza de una ejecución |
| `grep --target ` | Búsqueda de texto completo en los mensajes de usuario/asistente registrados |
| `stats --target ` | Conteos agregados, desglose por estado terminal y tamaño de la `qa.db` |
| `reset --target ` | Limpia la caché del gateway de una ejecución para volver a correrla desde el inicio |
| `cleanup --target ` | Elimina corridas antiguas de `qa.db` según la política de retención |
| `dashboard` | Inicia el dashboard local de pruebas |
| `finish --target ` | Marca una corrida registrada con veredicto PASS/FAIL/INCONCLUSIVE |
| `whatsapp ` | Pruebas con una cuenta real de WhatsApp (experimental) |
```bash theme={null}
jelou test tool generar-otp --input identificacion=0912345678
jelou test send text --target saludo-web --message "hola"
jelou test send text --target saludo-web --execution --message "siguiente"
jelou test chats list --target saludo-web --terminal TIMEOUT --since 7d --json
jelou test trace sKI7morplA4LKUAX51Mqx --target saludo-web
```
### Dashboard local de pruebas
```bash theme={null}
jelou test dashboard # abre el navegador en http://127.0.0.1:8766
jelou test dashboard --port 9090
jelou test dashboard --repo otro-repo--abc123 # lee la qa.db de otro repo
jelou test dashboard --no-open
```
Levanta una **interfaz web local de una sola URL** (servidor Hono + UI React)
para inspeccionar visualmente tus pruebas de workflows — es la versión gráfica
de lo que producen `jelou test send text`, `jelou test chats list` y
`jelou test trace`.
**Qué muestra:**
* **Targets / workflows** — los workflows que puedes probar (los que tienen
corridas registradas, más los del lockfile aunque aún no tengan ninguna).
* **Runs (chats de prueba)** — cada conversación de prueba registrada, con sus
turnos, el mensaje renderizado y su estado terminal (`STABLE`, `TIMEOUT`,
`HARNESS_STALL`, `HARNESS_ERROR`).
* **Traza por nodo** — para cada run, la ejecución paso a paso del workflow:
cada nodo con su estado inicial y final.
Internamente sirve estos endpoints de solo lectura
(`/api/targets`, `/api/workflows`, `/api/runs`, `/api/runs/:id`,
`/api/runs/:id/trace`) y, si hay un perfil que pueda enviar mensajes, permite
**iniciar chats nuevos** desde la UI (`POST /api/chats`,
`/api/runs/:id/messages`).
**Con qué datos trabaja:**
* Lee los archivos **`qa.db`** (SQLite) por target — los **mismos** que crean y
consultan los demás comandos `jelou test`. No hay una base aparte: el
dashboard solo los visualiza.
* Los datos se aíslan por **repo** (segmento `repo-id` = nombre del directorio
raíz de git + un hash corto) y por **perfil**. Por eso debes ejecutarlo dentro
del repo con tus corridas, o apuntar a otro con `--repo `.
| Flag | Descripción |
| ---------------------- | -------------------------------------------------------------------- |
| `--port ` | Puerto TCP (default 8766; si está ocupado, usa uno libre) |
| `--host ` | Host a bindear (debe ser loopback: `127.0.0.1`, `::1` o `localhost`) |
| `--repo ` | Lee la `qa.db` de otro repo en vez del actual |
| `--open` / `--no-open` | Abrir (o no) el navegador automáticamente |
Es **solo local** (escucha en loopback); para acceso remoto usa reenvío de
puertos por SSH: `ssh -L 8766:localhost:8766 `.
Los bundles BrainOps `jelou-build-workflow` y `jelou-test-workflow`
([skills](/guides/cli/skills)) orquestan este ciclo de construir y probar
workflows desde un agente de IA.
## `jelou logs` — triaje de conversaciones de producción
Acceso **solo lectura** al historial de conversaciones de un bot. Permite bajar
de conversación → línea de tiempo del chat → nodo que falló.
| Subcomando | Descripción |
| ----------------------------------------- | ------------------------------------------------------ |
| `conversations list --bot-id ` | Lista conversaciones (una fila por usuario × día) |
| `chat --bot-id --user-id ` | Línea de tiempo de un usuario (mensajes + ejecuciones) |
| `node --execution-id --node-id ` | Depura una sola ejecución de nodo |
```bash theme={null}
jelou logs conversations list --bot-id d42d688c-1c3f-4b99-9f32-70e14a10b365
jelou logs conversations list --bot-id d42d688c-… --message "devolución"
jelou logs chat --bot-id d42d688c-… --user-id 593959216623 --failed-only
jelou logs node --execution-id sKI7morplA4LKUAX51Mqx --node-id 69eb6c946b3d065a3bc6cc44
```
Todos aceptan `--from`/`--to` (ISO-8601), `--cursor` para paginar y `--out ` para guardar el envelope JSON. El `--bot-id` es el bot conectado al
canal — descúbrelo con `jelou channels list`. El flujo de drill-down imprime el
siguiente comando bajo cada ejecución FAILED.
## `jelou users reset`
Reinicia en duro el estado cacheado de un usuario con un bot (borra las claves
`state`, `skill` y `state_manual`). Útil antes de re-probar desde cero o para
desbloquear a un usuario atascado en un bucle.
```bash theme={null}
jelou users reset --bot-id d42d688c-… --user-id 593959216623 --yes
jelou users reset --channel-id 01KR… --user-id 593959216623 --yes
```
Es destructivo: las conversaciones en curso pierden su contexto. `--bot-id` y
`--channel-id` son mutuamente excluyentes; bajo `--agent`/`--no-input` se
requiere `--yes`.
# Tool para agentes IA
Source: https://docs.jelou.ai/guides/functions/ejemplo-ai-tool
Función expuesta como herramienta MCP para que agentes de IA la invoquen, con anotaciones .describe() y esquema estructurado.
Expones una función como herramienta MCP para que agentes de IA la invoquen. Con `.describe()` le dices al agente qué hace cada parámetro.
**Patrón:** `name` + `description` claros + `.describe()` en cada campo + MCP activo (default).
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "search-products",
description: "Busca productos en el catálogo por nombre, categoría o rango de precio",
input: z.object({
query: z.string().min(1).describe("Término de búsqueda: nombre del producto o palabras clave"),
category: z.string().optional().describe("Filtrar por categoría: electronics, clothing, home, food"),
minPrice: z.number().optional().describe("Precio mínimo en USD"),
maxPrice: z.number().optional().describe("Precio máximo en USD"),
limit: z.number().default(5).describe("Cantidad máxima de resultados (1-20)"),
}),
output: z.object({
results: z.array(z.object({
id: z.string(),
name: z.string(),
price: z.number(),
category: z.string(),
inStock: z.boolean(),
})),
total: z.number(),
}),
handler: async (input, ctx) => {
ctx.log("Búsqueda de productos", {
query: input.query,
category: input.category,
company: ctx.company.id,
});
const apiKey = ctx.env.get("CATALOG_API_KEY");
const params = new URLSearchParams({ q: input.query });
if (input.category) params.set("category", input.category);
if (input.minPrice) params.set("min_price", String(input.minPrice));
if (input.maxPrice) params.set("max_price", String(input.maxPrice));
params.set("limit", String(input.limit));
const res = await fetch(
`https://catalog.example.com/api/search?${params}`,
{ headers: { Authorization: `Bearer ${apiKey}` } },
);
const data = await res.json();
return {
results: data.items.map((item: any) => ({
id: item.id,
name: item.name,
price: item.price,
category: item.category,
inStock: item.stock > 0,
})),
total: data.total,
};
},
});
```
## Prueba local
```bash theme={null}
curl -X POST http://localhost:3000 \
-H "Content-Type: application/json" \
-d '{"query": "laptop", "category": "electronics", "maxPrice": 1500}'
```
## Por qué funciona así
* `name` y `description` son lo que el agente IA ve para decidir cuándo usar esta herramienta.
* `.describe()` en cada campo genera las descripciones de parámetros en el esquema MCP.
* El esquema `output` documenta qué estructura devuelve la función.
* MCP está activo por defecto (`config.mcp: true`), así que el endpoint `/mcp` expone automáticamente el tool.
* Un agente IA puede llamar esta función cuando un usuario pregunta "¿tienen laptops por menos de \$1500?".
# API proxy con autenticación
Source: https://docs.jelou.ai/guides/functions/ejemplo-api-proxy
Envuelve una API externa inyectando credenciales desde secrets, con ruta parametrizada y transformación de respuesta.
Envuelve una API externa inyectando credenciales desde secrets. El cliente llama a tu función, y tu función llama a la API real con las claves correctas.
**Patrón:** ruta con parámetros + inyección de autenticación + transformación de respuesta.
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "api-proxy",
description: "Proxy a una API externa con autenticación inyectada",
input: z.object({
fields: z.array(z.string()).optional().describe("Campos a incluir en la respuesta"),
}),
config: {
path: "/users/:id",
methods: ["GET"],
cors: {
origin: "https://app.example.com",
credentials: true,
},
},
handler: async (input, ctx) => {
const apiKey = ctx.env.get("INTERNAL_API_KEY");
const baseUrl = ctx.env.get("API_BASE_URL");
ctx.log("Proxy request", {
userId: ctx.params.id,
fields: input.fields,
});
const res = await fetch(
`${baseUrl}/api/users/${ctx.params.id}`,
{ headers: { Authorization: `Bearer ${apiKey}` } },
);
if (!res.ok) {
ctx.log("Upstream error", { status: res.status, userId: ctx.params.id });
return { error: "not_found", status: res.status };
}
const user = await res.json();
const result: Record = {
id: user.id,
name: user.name,
email: user.email,
};
if (input.fields?.includes("address")) {
result.address = user.address;
}
if (input.fields?.includes("orders")) {
result.recentOrders = user.orders?.slice(0, 5);
}
return result;
},
});
```
## Prueba local
```bash theme={null}
curl "http://localhost:3000/users/42?fields=address,orders"
```
## Por qué funciona así
* `config.path: "/users/:id"` — el `:id` se captura en `ctx.params.id`.
* Las credenciales nunca se exponen al cliente; viven en secrets.
* `cors.origin` restringe qué dominios pueden llamar al proxy desde el navegador.
* Puedes transformar o filtrar la respuesta antes de devolverla.
# Tarea cron programada
Source: https://docs.jelou.ai/guides/functions/ejemplo-cron
Ejecuta tareas periódicas como limpiar datos expirados, sincronizar registros o generar reportes con schedules declarativos.
Ejecuta tareas periódicas: limpiar datos expirados, sincronizar registros, generar reportes. Tu función se dispara automáticamente según el schedule que configures.
**Patrón:** `config.cron` + guard `isCron` + secrets para conexiones externas.
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "cleanup-job",
description: "Limpia sesiones expiradas y sincroniza registros",
input: z.object({}),
config: {
cron: [
{ expression: "0 3 * * *", timezone: "UTC" },
{ expression: "0 15 * * 1-5", timezone: "America/Guayaquil" },
],
mcp: false,
},
handler: async (_input, ctx) => {
if (!ctx.isCron) {
return { skipped: true, reason: "solo se ejecuta por cron" };
}
ctx.log("Ejecutando limpieza", { cron: ctx.trigger.cron });
// Evitar duplicados con ctx.memory
if (ctx.memory.available) {
const yaEjecutado = await ctx.memory.get("limpieza_hoy", false);
if (yaEjecutado) {
ctx.log("Ya se ejecutó hoy, omitiendo");
return { skipped: true, reason: "ya ejecutado" };
}
}
const dbUrl = ctx.env.get("DATABASE_URL");
const apiKey = ctx.env.get("EXTERNAL_API_KEY");
const staleRecords = await fetch(`${dbUrl}/api/sessions?expired=true`)
.then((r) => r.json());
let cleaned = 0;
for (const record of staleRecords) {
await fetch(`${dbUrl}/api/sessions/${record.id}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${apiKey}` },
});
cleaned++;
}
// Notificar al admin por WhatsApp
if (ctx.jelou.available && cleaned > 0) {
await ctx.jelou.send({
type: "text",
to: ctx.env.get("ADMIN_PHONE") || "",
text: `Limpieza completada: ${cleaned} registros eliminados.`,
});
}
// Marcar como ejecutado (expira en 20h)
if (ctx.memory.available) {
await ctx.memory.set("limpieza_hoy", true, 72000);
}
ctx.log("Limpieza completada", { cleaned, total: staleRecords.length });
return { cleaned, checked: staleRecords.length };
},
});
```
## Configurar secrets
```bash theme={null}
jelou secrets set cleanup-job DATABASE_URL=https://db.example.com EXTERNAL_API_KEY=YOUR_API_KEY
```
## Por qué funciona así
* `config.cron` define cuándo se ejecuta. Puedes tener múltiples schedules con distintas zonas horarias.
* El guard `if (!ctx.isCron)` previene que alguien ejecute la limpieza por HTTP accidentalmente.
* `config.mcp: false` — una tarea de mantenimiento no es un tool de IA.
* `ctx.trigger.cron` te dice cuál schedule disparó la ejecución.
# App multi-tool
Source: https://docs.jelou.ai/guides/functions/ejemplo-multi-tool
Agrupa múltiples herramientas en un solo despliegue con app(): gestión de contactos con crear, buscar y eliminar.
Con `app()` agrupas varias operaciones relacionadas en un solo despliegue. Tus agentes IA descubren cada tool individualmente vía MCP, y cada uno tiene su propia ruta HTTP.
**Patrón:** `app()` + múltiples `define()` + `.describe()` en cada campo + config compartida + `ctx.env.get()` para secrets.
```typescript index.ts theme={null}
import { app, define, z } from "@jelou/functions";
export default app({
config: { cors: { origin: "*" }, timeout: 15_000 },
tools: {
crearContacto: define({
description: "Crea un nuevo contacto con nombre, email y teléfono opcional",
input: z.object({
nombre: z.string().min(1).describe("Nombre completo del contacto"),
email: z.string().email().describe("Dirección de correo electrónico"),
telefono: z.string().optional().describe("Teléfono con código de país, ej: 593987654321"),
}),
output: z.object({
id: z.string(),
creado: z.boolean(),
}),
handler: async (input, ctx) => {
ctx.log("Creando contacto", { email: input.email });
const apiKey = ctx.env.get("CRM_API_KEY");
const res = await fetch("https://crm.example.com/api/contacts", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(input),
});
const data = await res.json();
return { id: data.id, creado: true };
},
}),
buscarContactos: define({
description: "Busca contactos por nombre o email. Retorna hasta 20 resultados.",
input: z.object({
q: z.string().min(1).describe("Término de búsqueda: nombre o email del contacto"),
limit: z.coerce.number().default(10).describe("Máximo de resultados (1-20)"),
}),
output: z.object({
contactos: z.array(z.object({
id: z.string(),
nombre: z.string(),
email: z.string(),
})),
total: z.number(),
}),
config: { methods: ["GET"] },
handler: async (input, ctx) => {
ctx.log("Buscando contactos", { q: input.q });
const apiKey = ctx.env.get("CRM_API_KEY");
const params = new URLSearchParams({
q: input.q,
limit: String(input.limit),
});
const res = await fetch(
`https://crm.example.com/api/contacts?${params}`,
{ headers: { Authorization: `Bearer ${apiKey}` } },
);
const data = await res.json();
return { contactos: data.items, total: data.total };
},
}),
eliminarContacto: define({
description: "Elimina un contacto por su ID. Retorna confirmación de eliminación.",
input: z.object({
contactoId: z.string().min(1).describe("ID único del contacto a eliminar"),
}),
output: z.object({
eliminado: z.boolean(),
}),
handler: async (input, ctx) => {
ctx.log("Eliminando contacto", { id: input.contactoId });
const apiKey = ctx.env.get("CRM_API_KEY");
const res = await fetch(
`https://crm.example.com/api/contacts/${input.contactoId}`,
{
method: "DELETE",
headers: { Authorization: `Bearer ${apiKey}` },
},
);
if (!res.ok) {
return { eliminado: false };
}
return { eliminado: true };
},
}),
},
});
```
## Prueba local
Inicia el servidor con `jelou functions dev` y prueba cada tool:
```bash curl /crear-contacto theme={null}
curl -X POST http://localhost:3000/crear-contacto \
-H "Content-Type: application/json" \
-d '{"nombre": "María García", "email": "maria@example.com", "telefono": "593987654321"}'
```
```json Respuesta 200 theme={null}
{
"id": "ct_a1b2c3",
"creado": true
}
```
```bash curl /buscar-contactos theme={null}
curl "http://localhost:3000/buscar-contactos?q=maria&limit=5"
```
```json Respuesta 200 theme={null}
{
"contactos": [
{ "id": "ct_a1b2c3", "nombre": "María García", "email": "maria@example.com" }
],
"total": 1
}
```
```bash curl /eliminar-contacto theme={null}
curl -X POST http://localhost:3000/eliminar-contacto \
-H "Content-Type: application/json" \
-d '{"contactoId": "ct_a1b2c3"}'
```
```json Respuesta 200 theme={null}
{
"eliminado": true
}
```
Si envías un input inválido, recibes un `400` con el detalle del error:
```bash curl con input inválido theme={null}
curl -X POST http://localhost:3000/crear-contacto \
-H "Content-Type: application/json" \
-d '{"nombre": "", "email": "no-es-email"}'
```
```json Respuesta 400 theme={null}
{
"error": "Validation failed",
"details": [
{ "path": ["nombre"], "message": "String must contain at least 1 character(s)", "code": "too_small" },
{ "path": ["email"], "message": "Invalid email", "code": "invalid_string" }
]
}
```
## Por qué funciona así
* **Rutas automáticas** — las keys `crearContacto`, `buscarContactos`, `eliminarContacto` generan `/crear-contacto`, `/buscar-contactos`, `/eliminar-contacto`.
* **GET vs POST** — `buscarContactos` usa `config: { methods: ["GET"] }` para recibir parámetros como query string. Los demás usan POST por defecto.
* **`description` importa** — cada tool tiene una descripción específica que le dice al agente IA exactamente qué hace y qué retorna. "Busca contactos por nombre o email" es mucho mejor que "Busca contactos".
* **Config compartida** — `cors` y `timeout` se definen una vez en `app()` y aplican a todos los tools. Cada tool puede sobreescribirlos si necesita.
* **Secrets centralizados** — los 3 tools usan `ctx.env.get("CRM_API_KEY")`. Configuras el secret una vez con `jelou secrets set`.
* **MCP unificado** — `curl http://localhost:3000/mcp` retorna los 3 tools como herramientas independientes que el agente puede invocar.
# Receptor de webhooks
Source: https://docs.jelou.ai/guides/functions/ejemplo-webhook
Recibe callbacks POST de servicios externos con validación de payload, ruta personalizada y procesamiento de eventos.
Recibes callbacks POST de cualquier servicio externo (pasarelas de pago, GitHub, CRMs, etc.), validas el payload y procesas el evento.
**Patrón:** ruta personalizada + solo POST + MCP desactivado + validación de payload.
```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";
export default define({
name: "webhook-receiver",
description: "Recibe y procesa webhooks de servicios externos",
input: z.object({
event: z.string(),
data: z.object({
id: z.string(),
status: z.string(),
metadata: z.record(z.unknown()).optional(),
}),
}),
config: {
public: true,
path: "/webhooks/events",
methods: ["POST"],
mcp: false,
},
handler: async (input, ctx) => {
ctx.log("Webhook recibido", {
event: input.event,
id: input.data.id,
requestId: ctx.requestId,
});
const secret = ctx.env.get("WEBHOOK_SECRET");
switch (input.event) {
case "payment.completed": {
ctx.log("Pago completado", { id: input.data.id });
return { acknowledged: true, action: "payment_processed" };
}
case "user.created": {
ctx.log("Usuario creado", { id: input.data.id });
return { acknowledged: true, action: "user_synced" };
}
default: {
ctx.log("Evento no manejado", { event: input.event });
return { acknowledged: true, action: "ignored" };
}
}
},
});
```
## Prueba local
```bash theme={null}
curl -X POST http://localhost:3000/webhooks/events \
-H "Content-Type: application/json" \
-d '{"event": "payment.completed", "data": {"id": "pay_123", "status": "success"}}'
```
## Por qué funciona así
* `config.methods: ["POST"]` — rechaza GET, PUT, etc. Los webhooks siempre son POST.
* `config.mcp: false` — no tiene sentido exponer un webhook como herramienta de IA.
* `config.path` — ruta fija que configuras en el servicio externo.
* El esquema `input` valida la estructura del payload antes de que llegue al handler.
## Validar firma del webhook
Una función pública puede ser llamada por cualquiera que conozca la URL. En producción, verifica la firma del servicio antes de procesar el evento — `ctx.verify*` lo hace en una línea:
```typescript theme={null}
handler: async (input, ctx, request) => {
await ctx.verifyHmac(request, {
secretEnv: "WEBHOOK_SECRET",
header: "x-webhook-signature",
});
// Procesar el evento...
return { acknowledged: true };
}
```
Si la firma no coincide, lanza un error y el evento no llega a tu lógica.
Para Stripe, Shopify y Meta usa el verificador de cada proveedor, que ya conoce su cabecera y su secret:
```typescript theme={null}
await ctx.verifyStripe(request);
await ctx.verifyShopify(request);
await ctx.verifyMeta(request);
```
Configura el secret antes de desplegar: `jelou functions secrets set mi-webhook WEBHOOK_SECRET=whsec_...`
Proveedores soportados, rotación de secrets, códigos de error y testing.
# Métricas
Source: https://docs.jelou.ai/guides/gestion/metricas
Explora indicadores clave, construye dashboards y comparte insights desde un solo lugar para entender qué está pasando en tus proyectos.
La nueva versión de **Métricas** concentra el análisis del proyecto en un solo lugar. Desde [apps.jelou.ai/metrics/v2](https://apps.jelou.ai/metrics/v2) puedes revisar los indicadores, armar dashboards con las métricas que más te importan, generar insights ejecutivos y compartir gráficos con tu equipo — todo con los mismos filtros globales.
El **Administrador de Métricas** puede crear, editar y eliminar dashboards, insights y métricas personalizadas. El rol de **solo visualización** puede abrir el panel y consultar los datos, pero no modificar nada. Los permisos se asignan desde **Configuración → Gestión de usuarios**.
## Vista general
El panel se divide en tres áreas:
* **Barra global de filtros** — rango de fechas y filtros compartidos por todos los gráficos de la sección activa.
* **Barra lateral izquierda** — cambia entre **Dashboards** e **Insights**. El dashboard **Resumen General** viene precargado; los demás dashboards e insights los creas tú.
* **Contenido** — gráficos de métricas que puedes reordenar y redimensionar.
## Resumen General
Es el dashboard por defecto. Consolida los indicadores más consultados del proyecto:
Cuatro indicadores fijos: **usuarios activos**, **workflows**, **conversaciones** y **HSM**. Cada uno muestra el valor del período y su variación. Al seleccionar uno, el gráfico de volumen inferior se actualiza para ese indicador.
Serie temporal del KPI seleccionado, sobre el rango de fechas activo. Cuando el rango supera los 90 días, la serie se agrupa por mes automáticamente.
Muestra cómo se mueven los usuarios entre los workflows del proyecto y qué proporción abandona o completa cada rama.
Tabla con las métricas de desempeño de cada workflow en el período. Un filtro permite incluir o excluir los **workflows compartidos** con otros proyectos.
Tabla con los términos más frecuentes, desglosados por workflow.
## Filtros globales
La barra superior aplica los filtros a todos los gráficos de la sección activa.
Selecciona rangos rápidos (hoy, últimos 7 días, últimos 30 días, este mes, etc.) o define un rango personalizado. La fecha es siempre global y no aparece en el selector de filtros adicionales — se aplica a todas las métricas.
Desde **Filtros** puedes añadir o quitar dimensiones a la barra global: **canal**, **equipo**, **proveedor**, **moneda**, **entorno**, **tipo de biometría**, **criterio**, **workflow**, **nodo** y **nombre del workflow**. Cada dashboard recuerda qué filtros están promovidos a la barra global y cuáles quedan disponibles en cada gráfico.
Cada gráfico expone sus propios filtros y puedes **promover** cualquiera de ellos a la barra global. Al hacerlo, el filtro pasa a la barra superior y se aplica únicamente a los gráficos que ya lo tienen entre sus dimensiones — el resto no se ve afectado. Debajo del filtro se muestra a cuántos gráficos del dashboard afecta.
Algunas métricas tienen un límite de días para proteger el rendimiento; cuando lo alcanzas, el panel muestra un aviso indicándote el máximo permitido.
La zona horaria del navegador se envía en cada consulta para que los cortes diarios reflejen tu horario local.
## Dashboards personalizados
Además del **Resumen General**, puedes crear tus propios dashboards para agrupar las métricas relevantes de un equipo, canal o iniciativa.
Desde el panel lateral, en la pestaña **Dashboards**, presiona **+ Crear dashboard**. Elige empezar en blanco o desde una [plantilla](#plantillas-disponibles), ingresa un nombre y confirma.
Presiona **Agregar métrica** para abrir el catálogo. Las métricas se organizan en categorías: **Inbox**, **E-commerce**, **Pagos**, **Voz**, **Biometría**, **Brain**, **IA** y **General**. Puedes buscar por nombre o filtrar por categoría.
Cada dashboard admite hasta **10 métricas**. Arrastra los gráficos para reordenarlos y usa las esquinas para redimensionarlos.
Puedes **renombrar** cada dashboard, **duplicarlo** para partir de una copia o **eliminarlo** de forma permanente.
### Plantillas disponibles
Al crear un dashboard puedes partir de una plantilla con las métricas esenciales de un área:
| **Dashboard** | Qué incluye |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Resumen General** | Dashboard de sistema: usuarios activos, workflows, conversaciones, HSM, volumen, flujo de usuarios, rendimiento por workflow y top de palabras. No se puede renombrar, duplicar ni eliminar. |
| **Operadores y Tickets** | Métricas esenciales de Bandeja de entrada. |
| **Biometría** | Métricas esenciales de verificación biométrica y KYC. |
| **Ecommerce** | Métricas esenciales de Ecommerce: ventas, productos y clientes. |
| **Brain Studio** | Métricas esenciales de Brain: evaluaciones del agente y palabras más usadas. |
| **Pagos** | Cobros, tasa de éxito, proveedores y monedas. |
| **Consumos de IA** | Costos y consumo de tokens de IA. |
| **Mensajería Outbound** | Rendimiento de envíos de plantillas de WhatsApp: KPIs de entrega, tendencia diaria, origen de los envíos y embudo. |
## Tipos de gráfico
Las métricas del catálogo se dibujan con uno de estos tipos. Al crear una [métrica personalizada](#métricas-personalizadas) eliges entre el subconjunto que admite esa fuente.
* **Número** — un solo valor con comparación contra el período anterior: actual, crecimiento y un desglose opcional al pie. Uso típico: totales principales — conversaciones, usuarios, mensajes enviados.
* **Grupo de números** — varios indicadores en una sola tarjeta, cada uno con su formato (entero, decimal, porcentaje, duración o moneda) y su variación. Uso típico: tasas de un mismo proceso.
* **Línea** — evolución de un valor en el rango de fechas, con un punto por fecha.
* **Área** — la misma serie, con relleno bajo la curva. Uso típico: volúmenes acumulables, como mensajes o sesiones por día.
* **Multilínea** — varias series en el mismo plano, con leyenda para mostrar u ocultar cada una. Uso típico: comparar categorías a la vez (por canal, por estado).
* **Barras** — barras verticales por categoría o por fecha. Uso típico: conteos con pocas etiquetas.
* **Barras horizontales** — ranking ordenado; funciona mejor cuando las etiquetas son largas. Uso típico: top de workflows, motivos o agentes.
* **Barras apiladas** — cada barra se divide en segmentos para ver la composición del total. Uso típico: sesiones por canal por día, estados de HSM por fecha.
* **Pastel** — distribución porcentual entre pocas categorías. Uso típico: porcentaje por canal o por tipo de sesión.
* **Histograma** — distribución de una variable en rangos (buckets). Uso típico: duración de sesiones o tiempos de respuesta.
* **Mapa de calor** — matriz de intensidad por color. Uso típico: conversaciones por hora y día de la semana.
* **Embudo** — conversión clásica: cada nivel muestra cuántos llegaron respecto de la etapa anterior.
* **Embudo por etapas** — columnas con porcentaje, denominador propio y alerta cuando una etapa cae en zona de riesgo. Uso típico: verificación de identidad o checkout.
* **Sankey** — flujo entre nodos proporcional al volumen, con puntos de abandono. Uso típico: recorridos entre workflows.
* **Sankey de recorridos** — variante a ancho completo para un journey paso a paso.
* **Tabla** — columnas de texto, número o porcentaje, con badges de estado, barras de progreso, filtros y paginación. Uso típico: detalle por workflow, agente o plantilla.
* **Nube de palabras** — términos dimensionados por frecuencia. Uso típico: palabras más usadas en conversaciones o búsquedas.
* **Mapa de burbujas** — burbujas cuyo tamaño representa el volumen en cada ubicación. Uso típico: resultados por ciudad o región.
## Métricas personalizadas
Si el catálogo no cubre tu caso, puedes registrar métricas propias sin salir del panel.
Convierte los eventos que emites desde tus workflows en gráficos. Cada métrica compara **hasta 5 eventos en el mismo gráfico**, etiquetados A–E. Cada evento se configura por separado con:
* **Evento** que quieres medir.
* **Métrica** a calcular: total de eventos, usuarios únicos, sesiones totales o suma de un valor numérico.
* **Filtros** por propiedad del evento.
* **Desgloces (breakdowns)** para segmentar la serie por propiedad — se recomienda no superar 2 desgloces.
Los tipos de gráfico disponibles son **Gráfico de líneas**, **Gráfico de barras**, **Indicador**, **Árbol de desglose** y **Gráfico circular**. El editor incluye un panel de previsualización en tiempo real con el rango que elijas.
Para generar los eventos, abre el nodo en Brain Studio, entra a la pestaña **Eventos** y registra el nombre en snake\_case más las propiedades que quieras filtrar o desglosar. Puedes configurar hasta 10 eventos por nodo, cada uno con hasta 5 propiedades.
Configura eventos de seguimiento en tus nodos para alimentar las métricas personalizadas.
Construye métricas sobre tus propias bases de datos sin escribir SQL. Elige una plantilla:
* **Conteo total** — cantidad de registros en la colección.
* **Conteo por grupo** — total agrupado por un campo (canal, estado, país…).
* **Conteo en el tiempo** — evolución diaria, semanal o mensual.
* **Agregado numérico** — suma, promedio, mínimo, máximo o cuenta distinta sobre un campo.
* **Funnel** — secuencia de etapas para medir conversión.
Cada plantilla define qué tipos de gráfico soporta entre **Indicador**, **Gráfico de barras**, **Barras horizontales**, **Gráfico de líneas**, **Gráfico circular**, **Tabla** y **Embudo**. Puedes aplicar filtros con operadores **igual a**, **distinto de**, **mayor que** y **menor que**, y elegir un rango propio para la métrica si no debe usar el global.
Eliminar una métrica personalizada la retira de todos los dashboards donde esté publicada. Si necesitas hacer ajustes, usa **Editar** en vez de recrearla.
## Insights
Los **Insights** son documentos ejecutivos generados a través del **Jelou Agent** que resumen la evolución del proyecto en un rango de fechas. A diferencia de los dashboards, un insight:
* Se guarda como HTML enriquecido con **texto**, **tablas** y **gráficos embebidos**.
* Se puede **imprimir** o **descargar** para compartirlo fuera de la plataforma.
* Mantiene el rango de fechas con el que se generó, así puedes comparar entregas mensuales.
Los insights son **altamente personalizables**: al pedírselo al Jelou Agent puedes indicarle cómo estructurar el reporte, qué **tipos de gráfico** incluir, qué **paleta de colores** usar o qué secciones destacar. Si esas preferencias las quieres aplicar siempre, puedes guardarlas en la **memoria del agente** para que se respeten automáticamente en cada insight que genere — incluidos los colores y estilos de todos los gráficos.
Desde la pestaña **Insights** del panel lateral puedes buscar, renombrar y eliminar insights existentes.
## Compartir gráficos
En cada gráfico están disponibles tres acciones útiles para el trabajo en equipo:
* **Compartir gráfico** — genera un enlace público de solo lectura para que cualquiera con el link vea el gráfico con sus filtros actuales.
* **Usar vía API** — abre un cajón lateral con el snippet listo para consumir la métrica desde tu backend o cuaderno, en el lenguaje que elijas: **cURL**, **JavaScript**, **Python** o **PHP**. Cada snippet incluye los filtros aplicados, la zona horaria y el `companyId`.
* **Configurar filtros** — permite fijar filtros específicos a ese gráfico, distintos de los globales, cuando la métrica lo admite.
## Buenas prácticas
Antes de crear dashboards, revisa el **Resumen General** para detectar qué indicadores movilizan decisiones y merecen su propio dashboard.
Los mejores dashboards responden a una sola pregunta operativa (por ejemplo, "¿cómo va la campaña de Black Friday?"). Divide en varios dashboards cuando empieces a mezclar audiencias muy distintas.
Cuando crees una métrica basada en eventos o en bases de datos, usa un nombre descriptivo — el mismo que aparecerá en dashboards e insights. Evita abreviaciones internas que otros usuarios no puedan interpretar.
El **enlace público** de un gráfico siempre refleja los datos actualizados, mientras que una captura queda obsoleta apenas cambia el filtro o el rango.
# Cómo usar integraciones en Brain
Source: https://docs.jelou.ai/guides/integraciones/como-usar-integraciones-en-brain
Cómo instalar una integración del Marketplace y usarla en un AI Agent o en el Canvas dentro de Brain Studio.
Las integraciones del Marketplace se instalan una vez y luego están disponibles en todo tu espacio de trabajo. Esta página explica las dos formas de instalarlas y las dos superficies donde puedes usarlas: **AI Agent** y **Canvas**.
## Cómo instalar una integración
### Con Jelou Agent
Describe lo que quieres construir. El [Jelou Agent](/guides/getting-started/jelou-agent) detecta qué integración necesitas, te pide confirmar la conexión y construye el flujo automáticamente.
Activa el modo **Jelou Agent** en el Canvas y escribe tu prompt.
Ejemplos:
* "Construye un agente que consulte los últimos leads de Bitrix24 y me permita agregar uno nuevo por voz"
* "Crea un flujo que cobre con Stripe y confirme el pago al usuario"
* "Arma un agente que agende citas en Google Calendar"
Si la integración no está instalada, el Jelou Agent la detecta y muestra una tarjeta de confirmación. Haz clic en **Confirmar** para conectarla. El Jelou Agent muestra la integración como **Conectado** y continúa construyendo el flujo.
El Jelou Agent genera el flujo completo con todos los nodos configurados. Haz clic en **Drag to canvas** para trasladarlo.
El Jelou Agent elige la superficie correcta según el caso. Un flujo de cobros genera nodos Canvas con la tool de pago y sus salidas específicas. Un flujo de CRM o agenda genera un AI Agent con la integración configurada como herramienta.
El flujo aparece en el Canvas con todos los nodos configurados.
### Desde el Marketplace
En Brain Studio, selecciona **Marketplace** en el menú lateral izquierdo.
Navega por categorías o usa el buscador. Haz clic en la integración y luego en **Conectar**. Ingresa tus credenciales (API key, token) o autoriza el acceso vía OAuth.
Haz clic en **Instalar**.
La integración aparece en la pestaña **Instaladas** del Marketplace y en el sidebar del Canvas bajo la sección **Marketplace**.
## Cómo usar una integración instalada
Lo habitual es usar la integración como **herramienta** en un nodo **AI Agent**: el modelo elige cuándo invocar cada tool según la conversación. Usa el **Canvas** cuando el proceso requiera **ramas y salidas explícitas** (por ejemplo cobros con varios resultados posibles). Si construyes con **Jelou Agent**, él incorpora la integración en la superficie que encaje con tu caso.
### En un AI Agent
**El caso más frecuente:** dentro de un [**nodo AI Agent**](/guides/nodos/ai-agent) das acceso a una o varias integraciones del Marketplace; el agente decide **cuándo** invocar cada herramienta según el contexto de la conversación.
Es la superficie natural cuando **las intenciones del usuario son variadas**: el mismo agente puede consultar un CRM, agendar en el calendario o enviar un correo, según lo que pida en cada momento.
En el nodo **AI Agent**, ve a la pestaña **Herramientas** y haz clic en **Agregar tool**.
Escribe el nombre de la integración en el buscador y selecciónala.
* **Integración completa:** el agente accede a todas las tools disponibles.
* **Selección individual:** activa solo las tools que necesitas.
Algunas integraciones muestran dos grupos de tools dentro del **AI Agent**. Shopify, por ejemplo, separa las tools nativas de **Jelou Shop** (activas por defecto) de las tools externas de la API de Shopify (desactivadas por defecto). Activa las que correspondan a tu caso de uso.
Para cada tool activa, configura qué hace el agente al ejecutarla:
* **Ninguna:** el agente continúa la conversación normalmente.
* **Ejecutar end\_function:** el agente finaliza la tarea al completar esa acción.
* **Pausar interacción:** el agente espera antes de continuar, útil para validaciones o confirmaciones intermedias.
### En el Canvas
**Usa el Canvas** cuando el proceso necesita **pasos ordenados** y cada resultado exige una **ruta distinta** en el flujo.
El caso más claro son los **cobros**: un pago puede terminar en éxito, fallo, error de código o error HTTP, y el flujo debe reaccionar de forma distinta ante cada uno. Las tools de pago exponen esas salidas; el **Canvas** te permite conectar cada una al paso siguiente que corresponde.
En el panel lateral izquierdo, despliega la sección **Marketplace**. Arrastra la integración que quieres usar al Canvas.
En el panel derecho, abre el selector **Tools** y elige la acción que quieres ejecutar.
Las tools están etiquetadas con su tipo:
* **Tool nativa** — capacidad construida por Jelou, con salidas específicas del proceso (Pago Exitoso, Pago Fallido, Error Code, Validación exitosa, etc.)
* **Tool externa** — acción de la API del proveedor, con dos salidas genéricas: Finalizó la tarea y Hubo un error
* **Tool nativa:** completa los campos del formulario (monto, motivo de pago, ambiente, etc.).
* **Tool externa:** completa el cuerpo de la petición en JSON. Usa `{{$memory.variable}}` para pasar variables del flujo.
Conecta cada salida al paso siguiente según el resultado esperado.
El nodo muestra el nombre de la tool seleccionada cuando está correctamente configurado. "Nodo sin configurar" indica que falta seleccionar una tool o completar campos requeridos.
## Canvas vs AI Agent
Comparación rápida entre **Canvas** y **AI Agent**:
| Criterio | Canvas | AI Agent |
| ---------------------- | -------------------------------------------------------- | ------------------------------------ |
| Control sobre el flujo | Total — tú defines cada paso | Delegado al agente |
| Tipo de proceso | Pasos fijos con resultados específicos | Intenciones variadas del usuario |
| Manejo de resultados | Explícito por nodo, con salidas específicas | Manejado por el agente |
| Ideal para | Cobros, verificaciones de identidad, firma de documentos | Asistentes con múltiples capacidades |
Canvas y AI Agent no son excluyentes. Un agente puede derivar a un nodo Canvas para ejecutar una transacción que requiere pasos controlados, y retomar la conversación una vez completada.
Explora todas las integraciones disponibles organizadas por categoría.
# Integraciones
Source: https://docs.jelou.ai/guides/integraciones/integraciones
Catálogo de todas las integraciones disponibles en el Marketplace de Jelou, organizadas por categoría.
## Explorar por categoría
* [Agendamiento & Servicios](#agendamiento-servicios)
* [CRM & Ventas](#crm-ventas)
* [Facturación & Contabilidad](#facturacion-contabilidad)
* [E-Commerce & Tiendas](#e-commerce-tiendas)
* [Pagos & Procesamiento](#pagos-procesamiento)
* [Identidad & Firma](#kyc-firma)
* [Marketing & Ads](#marketing-ads)
* [Productividad](#productividad)
Las categorías de **Pagos**, **E-Commerce**, **KYC** y **Firma** tienen secciones especializadas con documentación detallada. Las cards de abajo te llevan al punto de entrada de cada proveedor o a su sección correspondiente.
## Agendamiento & Servicios
Conecta herramientas de calendario, agendamiento y notificaciones.
}
/>
}
/>
}
/>
}
/>
}
/>
## CRM & Ventas
Sincroniza contactos, pipelines y datos de clientes con tu plataforma de CRM.
}
/>
}
/>
}
/>
}
/>
## Facturación & Contabilidad
Emite facturas electrónicas, conecta tu sistema contable y enlaza operaciones de **ERP y backoffice** (ventas, inventario y maestros).
}
/>
}
/>
}
/>
}
/>
}
/>
}
/>
## E-Commerce & Tiendas
Conecta tu tienda en línea para consultar inventario, pedidos y catálogos desde conversaciones. Ver documentación completa en [E-Commerce](/guides/integraciones/e-commerce/index).
}
/>
}
/>
}
/>
## Pagos & Procesamiento
Cobra directamente en tus flujos conversacionales. Ver documentación completa en [Pagos](/guides/integraciones/pagos/introduccion).
}
/>
}
/>
}
/>
}
/>
}
/>
}
/>
}
/>
}
/>
}
/>
}
/>