Saltar al contenido principal

Mensajes rápidos por bot

Respuestas preconfiguradas por bot para el agente humano en la bandeja. El uso está en el manual de usuario, en Conversaciones → Mensajes rápidos (otro sitio: no se enlaza desde aquí).

Datos​

Tabla public.bot_quick_messages (migración 20261114000001_bot_quick_messages.sql):

ColumnaNota
tenant_id, bot_idFK con ON DELETE CASCADE. El bot tiene que ser del mismo tenant; lo exige la política de escritura
title1 a 80 caracteres
shortcutOpcional, ^[a-z0-9][a-z0-9_-]{0,29}$. Único por bot entre las filas vivas (índice parcial), así que el atajo de una fila borrada se puede reutilizar
body1 a 4096 caracteres, con variables {{grupo.campo}}
sort_order, is_activeOrden y visibilidad en la bandeja
deleted_atBorrado lógico. El DELETE físico no se concede

Por qué una tabla y no flow_definition.settings: los mensajes son del bot, no de una versión del flujo. Sirven igual para bots por prompt y por flujo, y publicar o restaurar una versión no debe borrarlos. Por eso los ajustes del editor de flujos los guardan al momento, sin pasar por «Guardar» ni por publicar.

Accesos​

OperaciónQuién
SELECTMiembro del tenant con bot.view o conversation.reply
INSERT / UPDATEMiembro con bot.update, y EXISTS (bots WHERE id = bot_id AND tenant_id = tenant_id)
DELETENadie desde el panel (no hay GRANT)

Se reutilizan permisos existentes. Uno nuevo obligaría a un backfill por rol en todos los tenants y dejaría sin mensajes a los agentes que hoy responden.

Todo va con la sesión (createServerSupabase) desde frontend/src/actions/quick-messages.ts, sin service-role. Un UPDATE bloqueado por RLS vuelve con 0 filas y error: null, así que las acciones piden .select() y tratan 0 filas como forbidden.

Pruebas por rol en supabase/tests/bot_quick_messages/: rojo sin la migración, verde con ella.

Sustitución de variables​

El catálogo de variables y la sustitución están en frontend/src/lib/quick-messages/, y es código puro con tests:

  • quick-message-variables.ts: el catálogo. Cada variable tiene su grupo, cómo se resuelve y la etiqueta i18n en bot.quickMessages.vars.*. Es la única fuente para el editor y la bandeja.
  • render-quick-message.ts: renderQuickMessage(body, ctx) → { text, missing }. Una variable sin dato queda literal y se reporta; las llaves que no son del catálogo no se tocan. hasUnresolvedTokens es lo que bloquea «Aprobar y enviar».
  • build-quick-message-context.ts: arma el contexto desde la cita elegida (por defecto la próxima) o desde un recurso suelto.

getQuickMessageConversationData(conversationId) lee el contacto de la conversación (con el número de channel_identities como respaldo del móvil) y hasta 10 citas próximas y 5 pasadas del contacto, sin cancelled ni no_show. La especialidad sale de scheduling_resources.metadata.specialty y la fecha se formatea en el timezone de la sede.

La sustitución ocurre en el panel, a propósito: el agente tiene que ver y aprobar el texto final. El envío es el sendMessage de siempre (frontend/src/actions/chat.ts). No hay un endpoint nuevo en el backend.

Límites conocidos​

  • sendMessage no marca source = 'agent' ni comprueba la ventana de 24 h de WhatsApp. Eso ya pasaba con cualquier respuesta escrita a mano; no es propio de esta función.
  • Solo texto: no hay adjuntos en un mensaje rápido.
  • El composer es de una línea: un mensaje con saltos de línea pasa siempre por el preview para no perderlos.