Saltar al contenido principal
El objeto ctx es el segundo parámetro de todo handler. Contiene información de la empresa, bot, usuario, tipo de trigger, y da acceso a secrets, mensajería, memoria y logging.
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

PropiedadTipoDescripción
ctx.functionSlugstringSlug de la función
ctx.company{ id, name }Empresa del request
ctx.bot{ id, name, channel }Bot 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.triggerTriggerInfoTipo de trigger (http, cron, event)
ctx.envEnvAccessorAcceso a secrets
ctx.paramsRecord<string, string>Parámetros de ruta
ctx.queryRecord<string, string>Query string params
ctx.jelouJelouSDKCliente de mensajería WhatsApp
ctx.memoryMemorySDKMemoria de sesión key-value
ctx.methodstringMétodo HTTP (GET, POST, etc.)
ctx.pathstringPath del request
ctx.requestIdstringUUID único del request
ctx.isCronbooleantrue si es trigger cron
ctx.isEventbooleantrue si es trigger event
ctx.isHttpbooleantrue si es request HTTP
ctx.skillIdstring | nullID del skill de Brain Studio
ctx.executionIdstring | nullID de ejecución de Brain Studio
ctx.log()(...args) => voidLogger estructurado

Identidad

handler: async (input, ctx) => {
  // Empresa
  ctx.company.id;    // 42
  ctx.company.name;  // "Tienda ABC"

  // Bot
  ctx.bot.id;        // "bot-123"
  ctx.bot.name;      // "Bot 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:
handler: async (input, ctx) => {
  if (ctx.isHttp) {
    // Petición HTTP normal
    ctx.trigger; // { type: "http" }
  }
}
Usa los guards ctx.isCron, ctx.isEvent y ctx.isHttp en lugar de comparar ctx.trigger.type manualmente.

Request

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)

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<string, string>
}
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:
handler: async (input, ctx) => {
  if (ctx.jelou.available) {
    await ctx.jelou.send({
      type: "text",
      to: "+593987654321",
      text: "Tu pedido está listo",
    });
  }
}

Guía de mensajería

14 tipos de mensaje, templates HSM y manejo de errores.

Memoria (ctx.memory)

Persiste datos por sesión (key-value con TTL):
handler: async (input, ctx) => {
  if (ctx.memory.available) {
    const paso = await ctx.memory.get("paso", "inicio");
    await ctx.memory.set("paso", "confirmacion", 3600);
  }
}

Guía de memoria

Primitivos, JSON, TTL, límites y patrones comunes.

Brain Studio (skillId, executionId)

Cuando tu función es invocada desde Brain Studio como herramienta MCP:
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:
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 logs mi-funcion.

Ejemplo completo

index.ts
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,
    };
  },
});

Mensajería

Enviar WhatsApp con ctx.jelou.

Memoria

Persistir datos con ctx.memory.

Secrets

Variables de entorno cifradas.

Autenticación

Runtime tokens y funciones públicas.