> ## Documentation Index
> Fetch the complete documentation index at: https://woku.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Reglas de alerta

> Motor de reglas configurable que reacciona a respuestas y al paso del tiempo: asigna, notifica y escala automáticamente

<Info>
  **Disponible en el plan Corporate.** Esta funcionalidad forma parte de las capacidades empresariales de woku. [Conversa con nuestro equipo comercial](https://woku.app/pricing).
</Info>

Una **regla de alerta** es una pieza de configuración que escucha un
evento (una respuesta recién creada) o se ejecuta periódicamente, y
cuando sus condiciones se cumplen dispara una lista ordenada de
**acciones**. Cada regla puede definir además **escalamientos**: una
o más sub-listas de acciones que se ejecutan después de un retraso si
el ciclo no se cerró antes.

Usos típicos:

* **NPS bajo asigna a supervisor**: cuando una respuesta de NPS
  marca menor a 7, asigna al supervisor de la sucursal y le manda un
  WhatsApp.
* **Texto con queja crítica**: si la respuesta menciona la palabra
  "denuncia" o "abogado", manda un email al equipo legal y abre un
  webhook a Salesforce.
* **SLA vulnerado**: si una respuesta lleva más de 60 minutos sin
  ser atendida, escala a la jefatura y cambia el estado a `urgent`.

## Anatomía de una regla

| Campo         | Tipo                                                    | Descripción                                                        |
| ------------- | ------------------------------------------------------- | ------------------------------------------------------------------ |
| `name`        | string                                                  | Nombre visible.                                                    |
| `description` | string opcional                                         | Para qué sirve.                                                    |
| `trigger`     | `{ type: 'event' \| 'cron', events?, cronExpression? }` | Cómo se activa.                                                    |
| `conditions`  | `{ all: [...] }` o `{ any: [...] }`                     | Grupo top-level de condiciones (compatible con json-rules-engine). |
| `actions`     | array                                                   | Acciones inmediatas al matchear (orden importa).                   |
| `escalations` | array                                                   | Sub-listas con `afterMin` para acciones diferidas.                 |
| `enabled`     | booleano                                                | Si está activa.                                                    |
| `priority`    | número                                                  | Prioridad de evaluación (menor = primero).                         |
| `metrics`     | `{ totalFired, totalEscalated, lastFiredAt }`           | Contadores en vivo.                                                |

### Triggers

* **`event`**, la regla se evalúa en cada `form-response.created`.
  La evaluación corre en un listener, no en el path del HTTP, así que
  no agrega latencia al endpoint que crea la respuesta.
* **`cron`**, la regla se evalúa cada 5 minutos (V1) sobre las
  respuestas no cerradas en las últimas 72 horas. Aquí caen las
  reglas tipo SLA o detección de volumen bajo.

### Condiciones

`conditions` es exactamente lo que consume `json-rules-engine`. La
forma mínima es:

```json theme={null}
{
  "all": [
    { "fact": "score", "operator": "lessThan", "value": 7 }
  ]
}
```

Operadores soportados out-of-the-box:

`equal`, `notEqual`, `lessThan`, `lessThanInclusive`, `greaterThan`,
`greaterThanInclusive`, `in`, `notIn`, `contains`, `doesNotContain`.

Operadores custom Woku:

* **`stringContains`**, `factValue.toLowerCase().includes(jsonValue.toLowerCase())`.

`all` representa AND, `any` representa OR. Para V1 el builder visual
soporta árboles de un solo nivel; la API acepta árboles anidados a
cualquier profundidad, un JSON guardado a mano funciona.

### Facts disponibles

El evaluador construye un fact bag por respuesta:

| Fact                                    | Origen                                                                                  |
| --------------------------------------- | --------------------------------------------------------------------------------------- |
| `score`                                 | Primer valor numérico en `answers`. Apunta al NPS por convención.                       |
| `textConcat`                            | Concatenación lowercased de todos los strings en `answers`. Útil para `stringContains`. |
| `formResponseId`, `formId`, `companyId` | Ids del contexto.                                                                       |
| `createdAt`                             | ISO timestamp.                                                                          |
| `sla_elapsed`                           | Solo en cron: minutos transcurridos desde `createdAt`.                                  |
| *cualquier key de `answers`*            | Cada campo del formulario es un fact independiente.                                     |

### Acciones

| Tipo            | Parámetros                   | Efecto                                                                                                                                                   |
| --------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `assign_user`   | `userId`                     | `FormResponse.assignedTo = userId`.                                                                                                                      |
| `change_status` | `status`                     | `FormResponse.status = status`. Si el status nuevo cae en `{closed, resolved, dismissed}`, dispara cancelación de escalamientos pendientes.              |
| `send_webhook`  | `url`, `headers?`            | `POST` con `{ alertRuleId, alertRuleExecutionId, companyId, formResponseId, formId, facts, firedAt, isEscalation }`. Timeout 10 s, retry 3x exponencial. |
| `send_email`    | `to`, `subject?`             | V1 logged-only; el módulo de mail engancha esta rama en un follow-up.                                                                                    |
| `send_whatsapp` | `to`, `template?`, `params?` | V1 logged-only; mismo plan que `send_email`.                                                                                                             |

Las acciones se encolan en BullMQ (`alertActionsQueue`, concurrencia
10\) y se ejecutan independientemente. El orden de la lista se
preserva en la cola.

### Escalamientos

Cada entrada de `escalations` define un retraso y una sub-lista de
acciones:

```json theme={null}
{
  "afterMin": 60,
  "actions": [
    { "type": "send_email", "to": "ops@example.com" },
    { "type": "send_whatsapp", "to": "+56912345678", "template": "alert_nps" }
  ],
  "cancelIfClosed": true
}
```

Cuando la regla matchea, cada acción de cada escalamiento se encola
con `delay = afterMin * 60_000`. Si después la respuesta entra en un
status terminal (`closed`/`resolved`/`dismissed`), los jobs pendientes
se cancelan y la ejecución se marca como cerrada.

Además, los jobs de escalamiento chequean `execution.closedAt` antes
de ejecutarse: si el cierre fue muy reciente, el job se vuelve no-op
incluso si el `removeJob` no llegó a tiempo. Esto cubre la ventana
de carrera entre el cambio de estado y la cancelación.

## Builder

El menú **Reglas de alerta** lleva al listado por empresa. Desde ahí
se puede crear, editar, eliminar o **probar** una regla.

El builder tiene cinco pestañas:

1. **General**: nombre, descripción, tipo de trigger, prioridad,
   habilitada.
2. **Condiciones**: lista de condiciones leaf con operador raíz
   AND/OR. Cada fila pide `fact`, `operator`, `value`. La serialización
   convierte `"7"` a `7`, `"true"` a `true`, valores con coma a
   arrays para `in`/`notIn`.
3. **Acciones**: lista drag-to-reorder con `@dnd-kit`. El tipo de
   acción determina los campos que pide.
4. **Escalamientos**: lista de `{ afterMin, actions, cancelIfClosed }`,
   con la misma UI de acciones anidada.
5. **Vista previa**: render del JSON exacto que se envía al servidor.

## Simulador

El botón **Probar** en cada fila del listado abre un modal que
ejecuta una simulación (dry-run). Pide un JSON con `answers` y
opcionalmente `extraFacts`, y devuelve:

* `matched`: si la regla habría coincidido.
* `actionsWouldFire`: la lista de acciones que se dispararían.
* `facts`: el fact bag exacto que vio el motor.

**No dispara nada**: no encola jobs, no muta `FormResponse`, no envía
emails ni webhooks. Es la herramienta para validar una regla antes
de exponerla a producción.

## Garantías

* **Aislamiento entre reglas**: cada regla se evalúa en su propio
  try/catch. Una regla con un operador inexistente o condiciones
  malformadas no impide que las demás se evalúen.
* **Backpressure**: las acciones corren en una cola dedicada con
  concurrencia 10. Una racha de respuestas no bloquea el path
  síncrono.
* **Idempotencia**: cada fire crea un `AlertRuleExecution` con
  `actionJobIds` + `escalationJobIds`. Los jobs de escalamiento
  vuelven a chequear `closedAt` antes de ejecutarse.
* **Dedup en cron**: el evaluador cron-based no dispara dos veces la
  misma regla contra la misma respuesta dentro de una ventana de
  24 horas (la ejecución previa es el token).
* **Auditable**: cada CRUD de regla queda registrado en el [audit
  log](/seguridad/audit-log) como `alert-rule.create/update/delete`.

## Limitaciones conocidas (V1)

* **`send_email` y `send_whatsapp` son stubs** que loguean la
  intención. El módulo de mail y el de WhatsApp engancharán esas
  ramas en una iteración siguiente sin cambiar el contrato del
  dispatcher.
* **Sin builder de árboles anidados**: el editor visual cubre un
  nivel `all` o `any`. Para reglas con anidamiento de `all` dentro de
  `any` (o viceversa) hay que editar el JSON directamente vía API.
* **Sin agente IA como acción**: el plan original menciona acciones
  de LangChain, quedan para una iteración futura.
* **Statuses terminales hard-coded**: V1 considera
  `{closed, resolved, dismissed}` como terminales. Una iteración
  siguiente permitirá overrides por empresa.
* **El cron corre cada 5 minutos**. Para reglas de SLA con SLAs muy
  cortos (\< 5 min) la latencia perceptible es esa frecuencia.
