Skip to content

Workflows — Triggers

Documentacion interna completa del sistema de triggers. Para las reglas e invariantes, ver Workflows — Reglas oficiales.


El codigo de Boost no decide logica de negocio. Expone bloques reutilizables. El usuario arma su logica con workflows.

Si crearamos un trigger por cada caso de uso, terminariamos con message.inbound.unassigned.whatsapp.first_message y explotaria combinatorialmente. En su lugar:

  • Pocos eventos reales (capa 1)
  • Un motor de filtros universal (capa 2)
  • Muchos presets bonitos (capa 3)

Son los eventos puros que ocurren en el sistema. El engine solo conoce estos.

Evento Donde se emite Que significa
message.inbound InteractionsService (webhook) Llego un mensaje de cualquier canal
scheduled Cron job interno (Bull queue) Es la hora configurada
manual POST /api/workflows/:id/execute Usuario hizo click en ejecutar

Agregar = 1 linea de emit + definir contexto. El FilterEngine no cambia.

Evento Donde se emite
message.outbound InteractionsService.sendMessage()
contact.created ContactsService.create()
lead.created LeadsService.create()
lead.assigned RecordAssignmentsService.createLeadAssignment()
lead.unassigned RecordAssignmentsService (delete assignment)
opportunity.state_changed OpportunitiesService.moveState()
quote.approved QuotesService.approve()
quote.rejected QuotesService.reject()
order.created OrdersService

Capa 2 — Motor universal de filtros (FilterEngine)

Section titled “Capa 2 — Motor universal de filtros (FilterEngine)”

Un solo FilterEngine construido una vez. Recibe contexto + filtros, devuelve true o false.

{ field: "trigger.channel_type_code", operator: "eq", value: "whatsapp_api" }
FilterEngine.evaluate(context, filters) -> boolean
Operador Descripcion Ejemplo
eq Igual a trigger.assignment_status eq "unassigned"
neq No igual a trigger.channel_type_code neq "instagram_dm"
contains Texto contiene trigger.message_text contains "precio"
not_contains Texto no contiene trigger.message_text not_contains "spam"
in Valor en lista trigger.channel_type_code in ["whatsapp_api", "whatsapp_business"]
not_in Valor no en lista trigger.channel_type_code not_in ["instagram_dm"]
is_empty Es null o vacio trigger.sender_name is_empty
is_not_empty Tiene valor trigger.message_text is_not_empty
gt Mayor que zauru.recent_invoices.count gt 0
gte Mayor o igual zauru.recent_invoices.total gte 1000
lt Menor que zauru.recent_invoices.count lt 10
lte Menor o igual workflow.step_visits.step_2 lte 5

Logica compuesta: OR y 1 AND (2 OR 3). Operadores de fecha: before, after.

  • MVP: AND — todos los filtros deben cumplirse.
  • Post-MVP: OR y logica compuesta (1 AND (2 OR 3)).
Field Tipo Operadores Descripcion
trigger.channel_type_code string eq, neq, in, not_in whatsapp_api, whatsapp_business, facebook_messenger, instagram_dm
trigger.assignment_status string eq "assigned" o "unassigned"
trigger.message_text string contains, not_contains, is_empty, is_not_empty Cuerpo del mensaje
trigger.is_first_message boolean eq Primer mensaje de este contacto
trigger.sender_name string contains, eq, is_empty, is_not_empty Nombre del remitente
Field Tipo Operadores Descripcion
run_at_time string eq Hora "HH:mm". Requerido
timezone string eq IANA tz. Default: entity tz
active_days number[] in 1=Lun, 7=Dom. Default: todos

Sin filtros.


Cada preset = evento + filtros pre-llenados + label.

El usuario ve una tarjeta. La selecciona. Tiene su trigger configurado. No necesita saber que es un message.inbound con filtros.

Tarjeta (label UI) Preset code Evento Filtros pre-llenados Categoria
Mensaje de persona no asignada msg_unassigned message.inbound trigger.assignment_status eq "unassigned" Mensajeria
Mensaje de persona asignada msg_assigned message.inbound trigger.assignment_status eq "assigned" Mensajeria
Primer mensaje de contacto msg_first message.inbound trigger.is_first_message eq true Mensajeria
Cualquier mensaje entrante msg_any message.inbound sin filtros Mensajeria
Mensaje con palabra clave msg_keyword message.inbound trigger.message_text contains "" (usuario llena) Mensajeria
Todos los dias a una hora sched_daily scheduled active_days in [1,2,3,4,5,6,7] Tiempo
Solo entre semana sched_weekdays scheduled active_days in [1,2,3,4,5] Tiempo
Ejecutar manualmente manual manual sin filtros Manual
Tarjeta Evento Categoria
Lead creado lead.created CRM
Lead asignado lead.assigned CRM
Lead sin asignar lead.unassigned CRM
Contacto creado contact.created CRM
Cotizacion aprobada quote.approved CRM
Oportunidad cambio de estado opportunity.state_changed CRM

Todos los datos del trigger se guardan bajo el namespace trigger.* en el context_json de la ejecucion. Ver workflows-actions.md para el schema completo de contexto.

{
"trigger": {
"type": "message.inbound",
"entity_id": 1,
"contact_id": 42,
"lead_id": 15,
"group_id": 7,
"channel_type_code": "whatsapp_api",
"assignment_status": "unassigned",
"assigned_employee_ids": [],
"sender_identifier": "50242358270",
"sender_name": "Rolando Osorio",
"message_text": "Hola, quiero informacion",
"message_id": "msg_...",
"external_chat_id": "chat_...",
"entity_channel_account_id": 3,
"is_first_message": true,
"occurred_at": "2026-06-19T14:10:00.000Z"
}
}
{
"trigger": {
"type": "scheduled",
"entity_id": 1,
"execution_date": "2026-06-19",
"execution_time": "08:00",
"timezone": "America/Guatemala",
"day_of_week": 4
}
}
{
"trigger": {
"type": "manual",
"entity_id": 1,
"triggered_by_employee_id": 5,
"triggered_by_user_id": 3,
"contact_id": 42,
"lead_id": 15,
"group_id": 7,
"triggered_at": "2026-06-19T16:30:00.000Z"
}
}

Lo que se guarda en workflow_step_triggers:

{
"trigger_type": "message.inbound",
"preset_code": "msg_unassigned",
"filters": [
{ "field": "trigger.assignment_status", "operator": "eq", "value": "unassigned" },
{ "field": "trigger.channel_type_code", "operator": "in", "value": ["whatsapp_api"] }
]
}

El engine nunca mira preset_code. Solo evalua trigger_type + filters.

  1. GET /api/workflows/trigger-presets — lista de tarjetas disponibles.
  2. Al crear workflow, enviar preset_code.
  3. El backend resuelve evento + filtros.
  4. Post-MVP: panel de “filtros avanzados” consultando el FieldRegistry.
  • Nuevo evento = 1 emit + fields + presets. 0 cambios en FilterEngine.
  • Nuevo filtro = 1 field en el registry. 0 cambios en FilterEngine.
  • Nuevo preset = evento + filtros pre-llenados. 0 codigo en el engine.