Ir al contenido principal

Nodo de código en diálogos

Actualizado hace más de 6 meses

El nodo de código permite a los desarrolladores ejecutar lógica personalizada en JavaScript dentro de un flujo de conversación. Es el nodo más potente y flexible de Convos, ya que permite consultar APIs, realizar transformaciones complejas, manejar lógica condicional y construir mensajes dinámicos.


¿Qué hace este nodo?

Cada vez que se ejecuta, el nodo recibe información del contexto del diálogo, permitiendo al desarrollador acceder a:

  • Datos del contacto.

  • Variables previamente capturadas o definidas.

  • Información de campañas transaccionales.

  • Mensajes entrantes.

Al finalizar el bloque de código, el nodo debe invocar la función output(), que indica qué hacer a continuación: guardar estado, capturar nuevas variables, enviar mensajes, etc.


Variables disponibles

Estas variables están disponibles globalmente en el script y representan el contexto del flujo:

Variable

Descripción

variables

Objeto con todas las variables capturadas previamente en el diálogo, tanto desde nodos de captura como generadas por código.

persisted

Información persistida desde campañas transaccionales. Por ejemplo: persisted.input.orderId. Útil para continuar flujos basados en campañas.

contact

Información del perfil del contacto. Se accede como contact.phone, contact.firstname, contact.field_1, hasta contact.field_100.

state

Objeto que guarda información de estado entre ejecuciones del mismo nodo. Permite dividir la lógica en etapas (stage).

inboundMessage

Objeto completo del mensaje recibido por el sistema. Contiene metadata, tipo, etc.

inboundMessageBody

Solo el texto plano del mensaje entrante. Útil para capturar respuestas rápidas.

Métodos disponibles

output({ state, variables, messages, ticket })

Es obligatorio. Cierra el nodo de código y permite:

  • Persistir un nuevo estado (state) para futuras ejecuciones del mismo nodo.

  • Crear o modificar variables (variables) accesibles por otros nodos.

  • Enviar mensajes al contacto (messages).

Ejemplo:

output({
state: {
stage: 1,
wait: true
},
variables: {
age: inboundMessageBody
},
messages: [
{
type: 'text',
text: {
body: '¿Cuál es tu edad?'
}
}
],
ticket: {}
});

✅ wait: true indica que el nodo debe volver a ejecutarse la próxima vez que el contacto responda.

fetch(url, options)

Permite hacer llamadas HTTP síncronas (tipo await fetch). Ideal para consumir APIs externas y personalizar el flujo.

Ejemplo:

const params = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email: contact.email })
};

const response = await fetch('https://api.miapp.com/user', params);
const data = JSON.parse(response);

mail(to, subject, html)

Permite hacer un envío de mail. Ideal para realizar notificaciones o integraciones vía mail.

Ejemplo

const html = '<h1>Nuevo registro</h1>';
mail(‘[email protected]’, 'Nuevo registro', html);

Estructura de mensajes

Solo se pueden enviar mensajes interactivos (no templates pre aprobados de Meta).

Envío de mensajes opcional

El envío de mensajes dentro del nodo de código no es obligatorio.

Podés usar este nodo únicamente para ejecutar lógica de programación, como:

• Calcular variables complejas.

• Realizar validaciones silenciosas.

• Consultar APIs externas.

• Enriquecer el diálogo con datos antes de continuar.

Esto es especialmente útil cuando no necesitás interactuar directamente con el usuario en ese momento del flujo, sino preparar información para pasos posteriores.

Ejemplo sin mensajes:

const resultado = 42;

output({
state: { wait: false },
variables: {
resultado: resultado
}
});

⚠️ Recordá que, aunque no envíes mensajes, siempre es necesario ejecutar output() para finalizar el nodo correctamente.

Listado de mensajes interactivos disponibles

Tipo

Estructura

text

{
"type": "text",
"text": {
"body": "Gracias por comunicarte."
}
}

image

{
"type": "image",
"media": {
"link": "https://domain.com/example/image.png",
"caption": "Imagen de ejemplo",
}
}

contacts

{
"type": "contacts",
"contacts": [
{
"org": {
"title": "Manager",
"company": "Growlat",
"department": "Convos"
},
"name": {
"prefix": null,
"suffix": null,
"lastName": null,
"firstName": "contacto",
"middleName": null,
"formattedName": "contacto"
},
"urls": [
{
"url": "https://help.convos.la/",
"type": "HOME|WORK"
}
],
"emails": [
{
"email": "[email protected]",
"type": "PERSONAL|WORK"
}
],
"phones": [
{
"type": "CELL|MAIN|IPHONE|HOME|WORK",
"waId": null,
"phone": "+541155138711"
}
],
"birthday": "1995-08-18",
"addresses": [
{
"street": "Av. Santa fé 1234",
"city": "Ciudad Autónoma de Buenos Aires",
"state": "Buenos Aires",
"zip": "1234",
"country": "Argentina",
"countryCode": "ar",
"type": "HOME|WORK"
}
]
}
]
}

document

{
"type": "document",
"media": {
"link": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
}
}

location

{
"type": "location",
"location": {
"name": "Casa",
"address": "Maipú 2944",
"latitude": "-32.9720076",
"longitude": "-60.6440044"
}
}

buttons

{
"type": "interactive",
"interactive": {
"body": {
"text": "Cuerpo del botón"
},
"type": "button",
"action": {
"buttons": [
{
"id": "si",
"type": "reply",
"title": "Sí"
},
{
"id": "no",
"type": "reply",
"title": "No"
},
{
"id": "quizas",
"type": "reply",
"title": "Quizás"
}
]
},
"footer": {
"text": "Pié del botón"
},
"header": {
"text": "Encabezado del botón",
"type": "text"
}
}
}

list

{
"type": "interactive",
"interactive": {
"body": {
"text": "Seleccioná una opción"
},
"type": "list",
"action": {
"button": "Opciones",
"sections": [
{
"rows": [
{
"id": "representante",
"title": "Representante",
"description": "Quiero hablar con un humano."
},
{
"id": "mapa",
"title": "Mapa",
"description": "Quiero saber donde están ubicados."
},
{
"id": "adjunto",
"title": "Adjunto",
"description": "Quiero que me manden un adjunto."
},
{
"id": "custom",
"title": "Custom",
"description": "Quiero que corran código Javascript."
},
{
"id": "captura",
"title": "Captura",
"description": "Quiero que capturen mis datos."
},
{
"id": "webhook",
"title": "Webhook",
"description": "Quiero que manden un webhook."
},
{
"id": "contacto",
"title": "Contacto",
"description": "Quiero que me manden un contacto."
},
{
"id": "boton",
"title": "Botón",
"description": "Quiero ver un mensaje de botón."
},
{
"id": "split",
"title": "Split",
"description": "Quiero un mensaje con porcentajes."
}
],
"title": "Opciones"
}
]
},
"footer": {
"text": "Gracias"
},
"header": {
"text": "Opciones de Ayuda",
"type": "text"
}
}
}

También puedes enviar múltiples mensajes

messages: [
{
type: 'image',
media: { link: 'https://...' }
},
{
type: 'text',
text: { body: 'Gracias por tu compra' }
}
];

Estructura de ticket

Si necesita crear un ticket dede el nodo codigo puede utilizar la key de ticket

output({
state: {
wait: true
},
variables: {},
messages: [
{
type: 'text',
text: {
body: 'Lo conectamos con un agente para que lo ayude'
}
}
],
ticket: {
inboxId: 1,
tags: ['vip']
}
});

Variables:

  • inboxId: La variable inboxId es requerida para saber a que bandeja de entrada se debe asignar el ticket.

  • tags: La variable tags es opcional, se puede utilizar para agregar etiquetas al ticket de maner automatica.

Casos de uso comunes

Recolección por etapas

Utilizando state.stage, podés dividir el flujo en múltiples pasos:

if (!state.stage || state.stage === 0) {
output({
state: { stage: 1, wait: true },
messages: [{ type: 'text', text: { body: '¿Cuál es tu edad?' } }]
});
} else if (state.stage === 1) {
output({
state: { stage: 2, wait: true },
variables: { age: inboundMessageBody },
messages: [{ type: 'text', text: { body: '¿Cuál es tu DNI?' } }]
});
} else {
output({
state: { wait: false },
variables: { document: inboundMessageBody },
messages: [{ type: 'text', text: { body: '¡Gracias por responder!' }}]
});
}

Envío de feedback con datos transaccionales

const params = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contact: persisted.input.phone,
rating: variables.rating,
feedback: variables.feedback ?? ''
})
};

await fetch('https://api.externo.com/feedback', params);

output({ state: { wait: false } });

Consulta de órdenes por email o ID

let search = variables.value_to_search;
let isEmail = search.includes('@');
let query = isEmail
? `{ orders(first: 1, query: "email:${search}") { edges { node { id, fulfillments { trackingInfo { url } } } } } }`
: `{ order(id: "gid://shopify/Order/${search}") { id, fulfillments { trackingInfo { url } } } }`;

const response = await fetch('https://shopify.com/graphql', {
method: 'POST',
headers: { Authorization: 'Basic XXXXXX' },
body: JSON.stringify({ query })
});

const data = JSON.parse(response);
const link = isEmail
? data.data.orders.edges[0]?.node.fulfillments[0]?.trackingInfo[0]?.url
: data.data.order?.fulfillments[0]?.trackingInfo[0]?.url;

output({
state: { wait: false },
messages: [{
type: 'text',
text: { body: link ? `Tu código de seguimiento es: ${link}` : 'No encontramos tu orden.' }
}]
});

Buenas prácticas

  • Usar state.wait = true cuando el flujo espera otra respuesta del usuario.

  • Evitar declarar una variable llamada state dentro del código (ya está reservada).

  • Usar try/catch si se realiza fetch para evitar errores de ejecución.

  • Si se usan datos sensibles, asegurarse de no exponerlos en mensajes al cliente.

  • Utilizar nombres de variables claros y semánticos para facilitar el mantenimiento del flujo.

  • Utilizar punto y comas al finalizar cada línea de programación para asegurar su correcto funcionamiento.

Estructura base recomendada

try {
// lógica principal
output({
state: { wait: false },
variables: { ... },
messages: [ ... ]
});
} catch (error) {
output({
state: { wait: false },
messages: [{
type: 'text',
text: { body: 'Ocurrió un error inesperado. Por favor, intenta nuevamente más tarde.' }
}]
});
}

¿Ha quedado contestada tu pregunta?