Saltar al contenido principal
Además de los triggers de texto, regex y predicado, @botmesh/core exporta constantes de trigger para el primer contacto de un usuario, para los tipos de mensaje (imágenes, notas de voz, ubicaciones…) y para las llamadas entrantes. Funcionan en cualquier sitio que acepte un trigger: bot.when(), flow() (incluido .scope()) y bot.conversation(name, fn, { trigger }).

WELCOME

WELCOME se dispara en la primera interacción de un usuario con el bot:
  • Ámbito: por chat en un DM y por participante en un grupo (cada miembro recibe su bienvenida), sin importar conversationScope. Un DM y un grupo cuentan como primeros contactos distintos. Para limitarlo a DMs: flow(WELCOME).scope('dm') o { ...WELCOME, scope: 'dm' }.
  • Qué cuenta como primer contacto: el primer mensaje que llega a la etapa de matchers. Un comando (/help), una conversación activa, la reanudación de un checkpoint, un hook before que aborta o el filtro groupMode no gastan la bienvenida.
  • Precedencia: WELCOME va antes que cualquier otro matcher, también antes que los triggers de texto. Si el primer mensaje es “menu”, lo atiende WELCOME; llama a ctx.pass() para que bot.when('menu') también lo reciba.
  • Persistencia: se guarda en el clave/valor de cursores del store (lo soportan todos los stores incluidos), así que sobrevive a reinicios con un store persistente (MemoryStore con file, SQLite, Redis, Postgres, MySQL o Mongo). Las claves son hashes (welcome:<sha256>), sin ids ni teléfonos en claro. Un IStore propio sin cursores usa una caché en memoria que no sobrevive a reinicios.
  • Concurrencia: se decide dentro de la cola FIFO por chat, así que dos primeros mensajes simultáneos nunca reciben dos bienvenidas. Entre varios procesos que comparten store es check-then-set: enruta cada chat a un solo proceso, como ya exigen las conversaciones.
  • Coste: sin handler WELCOME, cero lecturas extra al store. Con handler, como máximo una lectura (dos en grupos) y una escritura por usuario y proceso; después responde una caché en memoria.
  • Si falla la lectura del store, no se saluda (falla cerrado), el error va a bot.on('error') y el mensaje sigue su camino normal.

Rearmar la bienvenida

slashCommandsPlugin llama a resetWelcome en /reset (el DM, o el participante que lo envía). /cancel solo cancela la conversación; la bienvenida sigue gastada. bot.deleteSubject(userId) también rearma la bienvenida del usuario borrado. exportSubject no incluye este dato.

Triggers por tipo de mensaje

Precedencia

Los matchers corren por especificidad (mayor primero; con empate gana el registrado primero): WELCOME (100) > igualdad / string (40) > startsWith (30) > endsWith (25) > RegExp (20) > contains (10) > función predicado (5) > un tipo (IMAGE…, bot.onType) (2) > multi-tipo (MEDIA, typeTrigger(a, b)) (1) > catch-all (bot.onMessage). Un trigger de texto nunca queda tapado por uno de tipo: una imagen con el caption “hola” la atiende bot.when('hola'). bot.onType('image') y bot.when(IMAGE) están en el mismo nivel. Un trigger undefined o null (una variable de entorno sin definir, un import que falta), solo, dentro de un array o como { pattern }, lanza un TypeError al registrarse. Lo mismo pasa con un patrón de texto vacío o en blanco ('', ' ', ['hola', ''], { pattern: '' }): coincidiría con todo mensaje sin texto (una foto sin caption), o con todo mensaje con contains/startsWith/endsWith. Usa IMAGE/MEDIA para media y bot.onMessage() para un catch-all. [] se sigue aceptando: nunca coincide y es el trigger habitual de un flujo al que solo se entra con goto(). bot.lint() lo reporta (como info si el flujo solo es destino de un goto) y la regla de ESLint reporta cualquier trigger vacío antes de ejecutar el código.

CALL

CALL reacciona a una llamada entrante de voz o video:
  • Se dispara una vez por id de llamada (WhatsApp emite offer y luego ringing, reject, timeout o terminate). Para ver cada actualización, escucha bot.on('call', (ev) => ...).
  • El contexto tiene type === 'call', body === '' y ctx.call (el CallEvent: id, from, status y, opcionales, chatId, isGroup, isVideo y offline). ctx.reply() le responde a quien llama. react/quote/markRead/delete no tienen sentido aquí.
  • Una llamada 1:1 se trata como el DM de quien llama. Una llamada de grupo se proyecta en el grupo: las respuestas van al grupo, ctx.sender es quien llama, la conversación es por participante y se aplican scope y groupMode.
  • Corre después de los hooks before/after('call') y del evento 'call'. Un before('call', (ev) => ev.abort()) lo suprime (junto con los hooks after('call') y el evento 'call'). La blacklist, la whitelist y el gate de handoff se aplican antes de los hooks: una llamada bloqueada tampoco corre hooks 'call' ni emite el evento 'call'.
  • Las llamadas nunca llegan a comandos, triggers de texto o de función, onMessage ni al evento 'message', y no se guardan como mensajes. Con el bot detenido se ignoran.
  • Un trigger CALL que inicia una conversación nunca reemplaza una en curso: si quien llama ya tiene una conversación activa o un checkpoint dormido, corre el siguiente handler CALL. Los handlers simples bot.when(CALL, fn) sí corren a mitad de un flujo.
  • bot.onType('call', handler) equivale a bot.when(CALL, handler).
  • Testing: provider.call({ id: 'c1', from: 'user-1', status: 'offer' }) en MockProvider (@botmesh/core/testing).
Los flujos creados con flow(WELCOME) o con un trigger de tipo reciben un id anónimo, así que .persistent() necesita un .id('...') explícito.

Siguientes pasos