Lectura estimada: 15 minutos Actualizado: 14/7/2026 Creado por: Equipo Botmaker

Acciones de código: desarrolla, integra y depura tu lógica en la plataforma

En este artículo aprenderás a crear y disparar Acciones de código, responder al usuario con mensajes y botones, integrar servicios externos, persistir el estado del contacto y depurar tus desarrollos antes de pasarlos a producción.



Las Acciones de código son scripts de JavaScript que el bot ejecuta dentro de una conversación. Son la alternativa recomendada para agregar lógica arbitraria o para conectar servicios externos y obtener información relevante en tiempo real. Cada Acción de código recibe el contexto del usuario (identidad, mensaje recibido, variables) y determina la respuesta que se va a generar.

Puedes:

  • Validar datos del usuario (CPF, DNI, etc.) antes de avanzar en el flujo.
  • Integrar servicios externos por REST o SOAP y guardar la información en variables.
  • Generar mensajes interactivos: botones, quick replies y carruseles de productos.
  • Persistir el estado del contacto entre ejecuciones con user y db.
  • Diagnosticar el comportamiento en producción mediante logs etiquetados.


Requisitos previos

Acceso a tu cuenta de Botmaker con permisos para desarrollar en la sección Código.

Conocimientos básicos de JavaScript: variables, funciones, async/await y llamadas HTTP. No se requiere experiencia previa con Botmaker.

La plataforma soporta Node.js v22. Si necesitas una biblioteca que no esté disponible, escríbenos a architecture@botmaker.com y nuestro equipo evaluará la solicitud.



Conceptos clave

Acción de código: script de JavaScript que el bot ejecuta al alcanzar una regla del flujo.

context: objeto de solo lectura con la información de la conversación (datos del contacto, mensaje recibido y parámetros de un botón).

result: objeto con el que defines la respuesta al usuario (texto, imagen, botones, etc.). Toda ejecución debe finalizar con result.done().

user: permite leer y escribir variables del contacto, que persisten entre ejecuciones.

db: base de datos clave-valor del business, compartida entre todos los chats.

rp (request-promise): cliente HTTP recomendado para llamar a servicios externos.

bmconsole: herramienta de logging que etiqueta los logs en el panel de Eventos del editor.

Nota: cada ejecución tiene un límite de 90 segundos. Además, cada ejecución es independiente y no comparte memoria con las siguientes: la información que quieras conservar debe guardarse con user o db.


Cómo crear y disparar una Acción de código

Paso 1: Ingresa a la sección Código

En el menú lateral izquierdo busca la palabra Código y haz clic. Serás redirigido a la pantalla de Acciones de código.

ES 01


ES 02


Paso 2: Crea una nueva acción

Haz clic en Crear nueva acción de código. Asigna un nombre a la acción (no lo olvides), elige la versión de Node.js y el tipo de acción. Puedes partir de una plantilla de código para apalancar tu desarrollo.


ES 03


Paso 3: Publica la acción

Cuando termines de escribir el código, haz clic en Publicar para que la acción quede disponible para usarse.


ES 04


Paso 4: Usa la acción dentro de un bot

Dirígete al Botdesigner. En la intersección de un flujo, haz clic en el botón Más y selecciona Acción. Luego haz clic en Acciones de Código: podrás elegir entre Acciones de código y Acciones de código con parámetros, y seleccionar cualquiera de las que tengas publicadas.


ES 14


Cómo escribir tu primera Acción de código

Paso 1: Prueba un "Hola, mundo"

Crea una Acción de código en el editor, inserta el siguiente código y pruébalo desde el simulador:

result.text('¡Hola, mundo!');

result.done();

result.text(...) define un mensaje de texto y result.done() finaliza la ejecución y dispara el envío al usuario.

Para leer el nombre del contacto:

const nombre = context.userData.FIRST_NAME || 'amigo';

result.text(`¡Hola, ${nombre}!`);

result.done();

Paso 2: Adopta la plantilla recomendada

A partir de aquí, conviene usar esta plantilla en todas tus Acciones de código. Aísla la lógica en una función main, captura los errores y garantiza el cierre de la ejecución:

// ====== CONSTANTS ======

const CA_NAME = 'mi-accion-de-codigo';

 

// ====== HELPERS ======

// (funciones auxiliares, si aplica)

 

// ====== MAIN ======

const main = async () => {

  // lógica principal

};

 

main()

  .catch(err => {

    const errorMessage = `[${CA_NAME}] Error - ${err.message}`;

    user.set('ca_error', errorMessage);

    bmconsole.log(errorMessage);

  })

  .finally(() => result.done());

  • async main() permite usar await en cualquier llamada y aísla la lógica principal.
  • .catch(err => ...) captura excepciones y evita que un error interrumpa la respuesta del bot. El último error queda guardado en ca_error para verlo desde el chat.
  • .finally(() => result.done()) garantiza el cierre de la ejecución en cualquier escenario. Sin esta llamada, el bot no envía respuesta al usuario.

Cómo responder al usuario

Estos son los métodos de result disponibles para responder:

  • result.text(texto) — mensaje de texto.
  • result.image(url, caption) — imagen con caption opcional.
  • result.video(url) — video.
  • result.audio(url) — audio.
  • result.file(url, caption) — archivo (PDF, Excel, etc.).
  • result.fileFromBuffer(buffer, mimeType, fileName) — archivo generado dinámicamente.
  • result.gotoRule('nombre de la regla') — redirige el flujo a otra regla.

Puedes encadenar varios mensajes en una misma ejecución:

// ====== CONSTANTS ======

const CA_NAME = 'enviar-factura';

const URL_FACTURA = 'https://midominio.com/facturas/123.pdf';

 

// ====== MAIN ======

const main = async () => {

  result.text('A continuación, tu factura:');

  result.file(URL_FACTURA, 'factura.pdf');

  result.text('Ante cualquier duda, escríbenos.');

};

 

main()

  .catch(err => {

    user.set('ca_error', `[${CA_NAME}] ${err.message}`);

    bmconsole.log(err.message);

  })

  .finally(() => result.done());

Cómo leer los datos del usuario y del mensaje

El objeto context contiene la información de la conversación y se compone de tres ramas.

context.userData — datos del contacto:

  • FIRST_NAME, LAST_NAME, EMAIL, PICTURE_URL.
  • PLATFORM_CONTACT_ID — identificador del contacto en el canal (número de WhatsApp, ID de Messenger, etc.).
  • CHAT_PLATFORM_ID — el canal: whatsapp, webchat, googlechat, etc.
  • tags — array de etiquetas asignadas al contacto.
  • variables — objeto con todas las variables del contacto.
  • constants — string JSON con las constants del business (URLs base y otros valores cargados por el administrador).

context.message — el mensaje recibido:

  • MESSAGE — el texto enviado por el usuario.
  • FROM — identificador del remitente.
  • IMAGES_URLS, FILES_URLS, AUDIOS_URLS, VIDEOS_URLS — adjuntos.
  • LANGUAGE_CODE — idioma detectado.

context.params — parámetros recibidos cuando la acción se invoca desde un botón (más detalle en la sección de botones).

Para leer y escribir variables del contacto usa user.get y user.set. Estas variables persisten entre ejecuciones y sus valores deben ser de tipo string:

const cep = user.get('cep');                          // lectura

user.set('ultimaConsulta', new Date().toISOString()); // escritura

Nota: puedes asignar hasta 200 variables por ejecución y guardar hasta 100 KB por valor.

Si cargaste un archivo CSV en el menú Registros, puedes leerlo y filtrarlo con entityLoader:

/* Entidad cargada:

   id | name

   1  | Gabriel

   2  | Dario

*/

 

entityLoader('nombre de la entidad', json => {

  if (!json) {

    user.set('error', 'No hay datos');

    result.done();

    return;

  }

  const data = json.find(row => row.id === 2);

  result.text('Muestro el nombre -> ' + data.name);

});

Cómo integrar servicios externos (HTTP)

El cliente HTTP recomendado es rp (request-promise). Incluye un timeout por defecto y publica sus logs automáticamente en el panel de Eventos.

GET con query string y respuesta JSON:

const buscarUsuario = (id) => rp({

  method: 'GET',

  uri: 'https://api.ejemplo.com/usuarios',

  qs: { id, key: API_KEY },

  json: true,

  headers: { Accept: 'application/json' },

});

POST con JSON body y bearer token:

const crearTicket = (titulo, descripcion) => rp({

  method: 'POST',

  uri: 'https://api.ejemplo.com/tickets',

  headers: {

    'Content-Type': 'application/json',

    'Authorization': `Bearer ${TOKEN}`,

  },

  body: { titulo, descripcion, contactoId: user.get('userId') },

  json: true,

});

POST form-urlencoded (típico para obtener un token OAuth):

const obtenerToken = () => rp({

  method: 'POST',

  uri: 'https://oauth.ejemplo.com/token',

  form: {

    grant_type: 'client_credentials',

    client_id: CLIENT_ID,

    client_secret: CLIENT_SECRET,

  },

  json: true,

});

Siempre valida la respuesta antes de acceder a campos anidados:

const main = async () => {

  let data;

  try {

    data = await buscarUsuario(user.get('userId'));

  } catch (err) {

    result.text('No fue posible consultar los datos en este momento. Inténtalo más tarde.');

    return;

  }

  if (!data || !data.results || data.results.length === 0) {

    result.text('No se encontró información asociada a la cuenta.');

    return;

  }

  result.text(`El saldo es ${data.results[0].balance}`);

};

También puedes llamar a servicios SOAP/XML (parseando la respuesta con xml2js), leer una Google Sheet pública en CSV (con csv), descargar un archivo binario para reenviarlo con result.fileFromBuffer, o enviar un template de WhatsApp mediante la API de notificaciones de Botmaker (https://api.botmaker.com/v2.0/notifications).

Cómo crear botones, carruseles y navegación

Botones simples:

const main = async () => {

  const b = result.buttonsBuilder();

  b.text('¿Qué deseas hacer?');

  b.addButton('Ver pedido', 'rule-ver-pedido');           // dispara una regla del flujo

  b.addURLButton('Ir al sitio', 'https://miempresa.com'); // abre una URL externa

  b.addPhoneButton('Llamar', '+5511912345678');           // inicia una llamada

  b.send();

};

Quick replies (botones tipo "pill" en WhatsApp y otros canales):

result.buttonsBuilder()

  .text('¿Confirmas la compra?')

  .quickReplies()

  .addButton('Sí', 'confirmar')

  .addButton('No', 'cancelar')

  .send();

Botones que invocan otra Acción de código con parámetros:

result.buttonsBuilder()

  .text('Selecciona una opción:')

  .addClientActionButton('Opción A', 'mi-otra-ca', { opcion: 'A' })

  .addClientActionButton('Opción B', 'mi-otra-ca', { opcion: 'B' })

  .send();

 

// En la Acción de código de destino:

// const opt = context.params.opcion;

Navegación condicional con gotoRule:

if (datosOK) result.gotoRule('proximo-paso');

else result.gotoRule('volver-a-pedir-datos');

Carrusel a partir de una lista:

const main = async () => {

  const productos = await listarProductos();

  const car = result.carouselBuilder();

  productos.forEach(p => {

    const btns = result.buttonsBuilder()

      .addClientActionButton('Comprar', 'agregar-al-carrito', { sku: p.sku })

      .buildButtons();

    car.addItem(p.titulo, p.descripcion, p.imagen, btns, p.url);

  });

  car.send();

};

Cómo persistir información (user y db)

Elige el mecanismo según el alcance del dato:

  • user.set / user.get — datos del contacto: preferencias, último estado, IDs externos del contacto.
  • db.set / db.get — datos del business, compartidos entre chats: cache de tokens, listas globales, contadores.

El componente db no necesita importarse y ofrece estas operaciones:

  • db.get(k) — devuelve el string almacenado, o null si no existe.
  • db.set(k, v, expSeconds) — almacena con expiración opcional en segundos.
  • db.exists(k) — devuelve un boolean.
  • db.del(k) — elimina la clave.
  • db.zadd(k, v, score, expSeconds) — agrega un elemento a un conjunto ordenado por score.
  • db.zrangebyscore(k, min, max) — lee un intervalo por score (incluye los límites).
  • db.zremrangebyscore(k, min, max) — elimina un intervalo por score.

Nota: en Acciones de código de tipo Endpoint o Cron, estos métodos se usan anteponiendo request. (por ejemplo, request.db.get(...)).

Un patrón muy común es cachear un token con expiración para no pedirlo en cada ejecución:

const obtenerTokenCacheado = async () => {

  const cached = await db.get('api-token');

  if (cached) return cached;

 

  const fresh = await obtenerTokenFresco();

  await db.set('api-token', fresh.access_token, 3600); // expira en 1 hora

  return fresh.access_token;

};

Para guardar varios campos, serializa un objeto a JSON y guárdalo en una sola variable:

const datos = { nombre: 'Juan', plan: 'Premium', score: 850 };

user.set('miObjeto', JSON.stringify(datos));

 

// lectura posterior:

const obj = JSON.parse(user.get('miObjeto') || '{}');

Bibliotecas incluidas

Estas funciones y bibliotecas están disponibles directamente en el editor; se invocan por su nombre, sin necesidad de importación:

  • _ / lodash — utilidades de arrays y objetos.
  • moment y momentTimezone — manejo de fechas y husos horarios.
  • uuidv4 — generación de UUIDs.
  • md5 y sha256 — funciones de hash.
  • xml2js — parsing de XML / SOAP.
  • csv — parsing y escritura de CSV.
  • secureRandom — generación de valores aleatorios criptográficos.
  • turf y turfHelpers — cálculos geoespaciales.
  • jwt — firma y verificación de JWTs.
  • google — googleapis (Sheets, Drive, Calendar).

Cómo depurar tus Acciones de código

Usa bmconsole para registrar logs etiquetados en el panel de Eventos, lo que facilita el filtrado en producción:

bmconsole.log('Iniciando consulta API');

bmconsole.warn('Respuesta inesperada, se usó fallback');

bmconsole.error(`Falla en la API: ${err.message}`);

Para ver el último error desde el editor sin abrir el panel de Eventos, guárdalo en una variable del contacto:

} catch (err) {

  user.set('ca_error', `[mi-CA] ${err.message}`);

  bmconsole.error(err.stack);

}

El modo tester te permite mostrar información de depuración solo cuando el contacto está marcado como desarrollo (la marca se configura desde el editor sobre el contacto):

const isTester = user.isTester;

const debug = (msg) => isTester ? result.text(`[DEBUG] ${msg}`) : bmconsole.log(msg);

 

debug(`La API respondió: ${JSON.stringify(data)}`);

Buenas prácticas antes de pasar a producción

  • Usa siempre la plantilla estándar: async main() + .catch + .finally(() => result.done()).
  • Invoca result.done() exactamente una vez.
  • Agrega json: true en rp cuando el endpoint responde JSON: la respuesta se parsea automáticamente.
  • Valida las respuestas antes de acceder a campos anidados, por ejemplo if (res?.results?.[0]) { ... }.
  • Limita los timeouts en rp (por ejemplo, timeout: 10000) cuando el endpoint puede demorar. La ejecución total no debe superar los 90 segundos.
  • No almacenes volúmenes grandes en variables del contacto: usa db.set con expiración en esos casos.
  • Configura las constants y los secrets desde el panel de administración y léelos desde context.userData.constants.
  • Prueba con un contacto en modo desarrollo antes de habilitar la acción para producción.
  • Usa bmconsole para que los logs queden correctamente etiquetados.

Errores frecuentes

  • El bot no envía respuesta: falta result.done(). Verifica la presencia de .finally(() => result.done()).
  • Execution timed out after 90000ms: una llamada HTTP quedó colgada o falta un await. Reduce los timeouts en rp y verifica el uso correcto de await.
  • Too many user variables: más de 200 user.set() en una ejecución. Consolida los datos en un objeto JSON serializado.
  • Trying to set a very long value: más de 100 KB en una variable del contacto. Migra a db.set con expiración o reduce el dato.
  • TypeError: Cannot read property X of undefined: la respuesta de la API no fue validada. Usa if (res?.results?.[0]) { ... }.
  • Los logs no aparecen en Eventos: estás usando console.log en vez de bmconsole.log.


Para trabajar con Acciones de código generativas, te compartimos el artículo "¿Qué son las Acciones de Código Generativas y cómo usarlas?".

Para crear una acción de código tipo MCP, te compartimos el artículo "Cómo crear una acción de código tipo MCP".



Recuerda visitar nuestro Centro de Ayuda para mayor información