Patrón Disparador → Condición → Acción
Trigger (event, scheduled, o webhook)
↓
Condition (lógica AND/OR, con reglas sobre user.*, event.*)
↓
Action (una o más, ejecutadas secuencialmente)
Triggers (3 tipos)
1. event
Disparado por eventos de la plataforma mediante emitAutomationEvent() en after().
{ "type": "event", "config": { "eventName": "lesson_completed", "filters": {} } }
Los 21 eventos soportados: lesson_completed, module_completed, course_completed, quiz_submitted, assignment_submitted, enrollment_created, enrollment_completed, forum_post_created, forum_reply_created, grade_posted, announcement_created, live_class_scheduled, live_class_started, student_inactive, tag_added_to_user, tag_removed_from_user, webhook_received, scheduled_trigger, y los eventos de gamificación badge_earned, level_up, contest_ended.
2. scheduled
Basado en cron.
{
"type": "scheduled",
"config": {
"mode": "interval|daily|weekly|cron",
"hours": 24,
"time": "08:00",
"timezone": "America/Sao_Paulo"
}
}
Procesado por GET /api/cron/automations (CRON_SECRET).
3. webhook
Endpoint público autenticado por token hexadecimal de 64 caracteres.
URL: /api/automations/webhook/[token]
GET → health check
POST → dispara la automatización (el body JSON se convierte en event.data)
Condiciones
{
"logic": "and",
"rules": [
{ "field": "user.role", "operator": "eq", "value": "student" },
{ "field": "event.score", "operator": "gte", "value": 80 }
]
}
Operadores: eq, neq, gt, gte, lt, lte, contains, not_contains, in, not_in.
Campos accesibles:
user.*— id, name, email, role, tagsevent.*— campos del evento (courseId, score, lessonTitle, etc)
Se soportan grupos anidados (AND/OR).
Acciones (9 tipos)
| Acción | Función |
|---|---|
| send_notification | Notificación in-app (trigger_user o users_with_tag) |
| add_tag | Agregar UserTag |
| remove_tag | Eliminar UserTag |
| create_announcement | Crea un Announcement y notifica a los alumnos (curso o tenant) |
| send_webhook | POST/PUT a URL externa (con prevención SSRF) |
| send_web_request | GET/POST/PUT/DELETE a API externa |
| enroll_user | Matricula al trigger_user en un curso |
| trigger_automation | Encadena otra automatización (profundidad máx. 5) |
| send_email | Email mediante servicio institucional (SMTP/Resend/SendGrid), por slug de plantilla o subject+HTML inline; destinatario trigger_user, users_with_tag, course_students o dirección personalizada |
Interpolación de plantillas
Las cadenas en las acciones soportan {{placeholders}}:
{{user.name}},{{user.email}},{{user.role}}{{event.courseId}},{{event.score}},{{event.lessonTitle}}
Seguridad
- Aislamiento por tenant: todas las consultas se filtran por tenantId. La automatización de un tenant NUNCA se ejecuta para un usuario de otro.
- Prevención SSRF:
url-validator.tsbloquea IPs privadas, localhost y endpoints de metadata (169.254.169.254). IPv4 + IPv6. - Timeout HTTP: 10s mediante AbortController.
- Token de webhook: hex de 64 caracteres, rotable.
- Profundidad de cadena: máx. 5 niveles de trigger_automation recursivo.
- Rate limit: maxRunsPerHour/maxRunsPerDay por automatización (Redis INCR, fallback a conteo en DB).
Ejemplo: notificar al tutor cuando un alumno menor lleva 5 días inactivo
{
"name": "Alerta de inactividad — menores",
"trigger": {
"type": "event",
"config": { "eventName": "student_inactive" }
},
"conditions": {
"logic": "and",
"rules": [
{ "field": "user.role", "operator": "eq", "value": "student" },
{ "field": "user.isMinor", "operator": "eq", "value": true },
{ "field": "event.inactiveDays", "operator": "gte", "value": 5 }
]
},
"actions": [
{
"type": "send_notification",
"config": {
"recipient": "users_with_tag",
"tag": "parent_of_{{user.id}}",
"title": "{{user.name}} lleva {{event.inactiveDays}} días inactivo",
"message": "Te animamos a que inicie sesión. Accede [aquí]."
}
}
]
}
Interfaz
/institution/automations:
- Lista con filtros (estado, tipo de trigger), toggle, búsqueda
- Wizard de 4 pasos (Trigger → Conditions → Actions → Review)
- Historial de ejecuciones con filas expandibles (estado, duración, actionResults JSON)