Patrón Disparador → Condición → Acción
Trigger (event, scheduled, o webhook)
↓
Condition (lógica AND/OR, con rules 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 hex de 64 caracteres.
URL: /api/automations/webhook/[token]
GET → health check
POST → dispara la automatización (el cuerpo JSON se convierte en event.data)
Conditions
{
"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).
Actions (9 tipos)
| Action | 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 + notifica a los alumnos (curso o tenant) |
| send_webhook | POST/PUT a URL externa (con prevención de 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 el 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 filtran por tenantId. La automatización de un tenant NUNCA se ejecuta para un usuario de otro.
- Prevención de SSRF:
url-validator.tsbloquea IPs privadas, localhost y endpoints de metadatos (169.254.169.254). IPv4 + IPv6. - Timeout HTTP: 10s mediante AbortController.
- Token de webhook: hex de 64 caracteres, rotable.
- Profundidad de cadena: máximo 5 niveles de trigger_automation recursivo.
- Límite de tasa: maxRunsPerHour/maxRunsPerDay por automatización (Redis INCR, fallback a conteo en DB).
Ejemplo: notificar al padre 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 hacer un inicio de sesión. Accede [aquí]."
}
}
]
}
UI
/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)