En el GLPI estándar, una notificación es un correo asíncrono: cae en spam, el técnico no vive en el webmail y el seguimiento se queda horas sin respuesta. Smart Notify sustituye ese modelo por una campana en la propia barra de GLPI que hace polling a la base de datos y agrega más de una docena de eventos ITIL casi en tiempo real. Sustentar parques GLPI de clientes en producción nos enseñó que encender la campana es lo fácil; lo difícil es calibrar lo que muestra para que no se vuelva ruido. Esta guía cubre cómo funciona el poll por dentro, un SQL de diagnóstico, la matriz de qué eventos dejar activos y las trampas de campo que más rompen la instalación.
Por qué el correo nativo no resuelve la alerta operativa
Las notificaciones nativas de GLPI dependen de la cola queuednotification y de plantillas de correo. Es un modelo asíncrono y fuera de la herramienta: el técnico debe alternar entre GLPI y la bandeja de entrada, el correo transaccional cae en el spam corporativo y nadie vigila el webmail en tiempo real. El resultado clásico de la operación es una aprobación parada durante horas y una tarea asignada que nadie vio.
La campana resuelve esto para quien está conectado a GLPI. Pero atención a la primera trampa: la campana no sustituye al correo para el solicitante. Notifica al técnico en sesión, no al cliente que abrió el ticket por el self-service. El error común es desactivar las notificaciones por correo creyendo que la campana cubre a todos, y el solicitante deja de recibir avisos. Campana y correo son capas complementarias, no excluyentes.
Cómo funciona la campana por dentro (polling + delta)
El JavaScript de la campana hace un poll cada 30 segundos (refresh_interval por defecto) contra un endpoint AJAX. En cada ciclo el backend recorre hasta dieciséis fuentes de eventos, arma la lista, aplica la retención (retention_days, 90 días por defecto), corta en max_notifications (50 por defecto) y ordena por fecha descendente. Cada notificación recibe un hash estable (sha1(tipo_id_timestamp)): es lo que marca el elemento como leído sin añadir un campo nuevo al ticket.
Lo que solo nota quien opera es el coste de ese poll cada 30 s por técnico conectado. Por eso el endpoint usa modo delta: el cliente devuelve el hash del último estado (state=); si nada cambió, la respuesta baja de unos 18 KB a ~93 bytes. Y el read-check pasó a ser un único SELECT en lote: antes era un SELECT por notificación, llegando a ~700 por poll en usuarios con mucho backlog. El GET también llama a session_write_close() de inmediato para no retener el lock de sesión de PHP mientras recorre la base.
# La campana hace polling cada 30s en este endpoint (enrutado por module_ajax):
GET /plugins/nextool/ajax/module_ajax.php?module=smartnotify&file=notifications.php&action=get&state=${HASH}
# Sin novedad desde el ultimo poll, la respuesta es un delta de ~93 bytes:
{ "success": true, "unchanged": true, "state": "...md5...", "unread_count": 4 }
# Solo cuando algo cambia llega el payload completo (~18KB) con el array notifications[].
Las fuentes de eventos y qué dejar activo
La configuración global expone dieciséis disparadores y cada técnico afina los suyos en el modal de preferencias (accesible directamente desde el desplegable de la campana). Importan dos reglas de targeting: el seguimiento que tú escribiste no te notifica (la consulta filtra users_id != usuario), y la notificación llega por implicación directa o por grupo técnico (notify_group_involvement), respetando siempre las entidades activas de la sesión.
En sustentación, tres disparadores vienen desactivados por defecto a propósito, y la experiencia confirma la elección: los documentos adjuntos son ruido en parques con muchas subidas; los nuevos problemas y cambios son eventos menos frecuentes que ensucian la campana de quien solo gestiona tickets. Encenderlo todo es el camino más rápido a la fatiga de alertas: el técnico empieza a ignorar la campana y entonces no sirve para nada.
| Familia de evento | Disparador (config) | Por defecto | Nota de campo |
|---|---|---|---|
| Seguimientos (ticket, problema, cambio) | notify_*_followup | Activo | Lo que más mueve el backlog; mantenlo |
| Validaciones y respuestas de validación | notify_*_validation(_response) | Activo | Una aprobación parada es un cuello silencioso |
| Tareas asignadas | notify_*_task | Activo | Es la cola de trabajo del técnico |
| Soluciones registradas | notify_solution | Activo | Cierra el ciclo de quien hace seguimiento |
| Documentos adjuntos | notify_document | Inactivo | Ruidoso donde hay muchas subidas |
| Tickets nuevos | notify_new_ticket | Activo | Señal de entrada de la cola |
| Problemas / cambios nuevos | notify_new_problem / _change | Inactivo | Menos comunes; actívalos por perfil |
| Avisos manuales (broadcast) | notify_manual | Activo | Mantenimiento, cambio de proceso |
Diagnóstico por SQL: quién silenció qué
Cuando un cliente se queja de que "la campana no avisa", el problema casi siempre es una preferencia individual o la entidad equivocada, no un bug. Estas tres consultas resuelven el triaje directo en la base (tablas reales del módulo):
-- 1. Cuantos tecnicos silenciaron cada tipo de evento (halla ruido/fatiga):
SELECT
SUM(ticket_followups = 0) AS mudo_followup_ticket,
SUM(solutions = 0) AS mudo_solucion,
SUM(documents = 0) AS mudo_documento,
SUM(sound_enabled = 0) AS sonido_off
FROM glpi_plugin_nextool_smartnotify_preferences;
-- 2. Registros de lectura por usuario (backlog y ultima actividad):
SELECT users_id, COUNT(*) AS lecturas_registradas, MAX(date_read) AS ultima_lectura
FROM glpi_plugin_nextool_smartnotify_read
GROUP BY users_id
ORDER BY ultima_lectura DESC;
-- 3. Broadcasts manuales activos y su destino (all/user/group/entity):
SELECT id, title, priority, target_type, target_id, entities_id, date_creation
FROM glpi_plugin_nextool_smartnotify_manual
WHERE is_active = 1
ORDER BY date_creation DESC;
Sonidos por prioridad y la trampa del autoplay
Las notificaciones emiten sonido mediante la Web Audio API, con cuatro timbres mapeados por prioridad. Antes de mirar la pantalla, el técnico oye si lo que llegó es rutina o urgencia:
| Prioridad | Sonido | Timbre (aprox.) |
|---|---|---|
| Baja | subtle | Corto y grave (440 Hz) |
| Normal | default | Estándar (523 Hz) |
| Alta | alert | Medio (659 Hz) |
| Urgente | alarm | Largo y agudo (880 Hz) |
La trampa aquí no es del módulo, es del navegador: la política de autoplay de Chrome solo permite crear el AudioContext tras el primer gesto del usuario. En la práctica, el primer sonido de una sesión solo suena tras el primer clic en la página. Cada mes alguien abre un ticket diciendo "el sonido no funciona": la respuesta es hacer un clic en la página. No es un bug, es la Web Audio API respetando al navegador.
Broadcasts manuales y limpieza automática
Los administradores envían avisos manuales con targeting preciso: todos, un usuario, un grupo o una entidad (la columna target_type en la tabla _manual). Es el canal correcto para una ventana de mantenimiento o un cambio de proceso sin enviar correo a toda la base. La retención la gestiona una CronTask diaria (smartnotify_cleanup), registrada en la instalación como MODE_EXTERNAL. Esto importa: en MODE_INTERNAL la tarea corre por web-hit y puede quedar trabada en state=2. Dispara el cron de GLPI por fuera (en GLPI 11, front/cron.php) y la limpieza ocurre sola según retention_days y logs_retention_days.
# CronTask registrada en la instalacion (idempotente), 1 vez al dia, MODE_EXTERNAL:
# Nombre: smartnotify_cleanup Frecuencia: DAY_TIMESTAMP (86400s)
# Dispara el cron de GLPI por fuera (nunca dependas del MODE_INTERNAL via web-hit):
* * * * * www-data /usr/bin/php /var/www/html/front/cron.php >/dev/null 2>&1
La trampa que rompe la instalación: MySQL 8 vs MariaDB
Un caso real de sustentación (junio de 2026, cliente en MySQL 8.0.46): la instalación del módulo se convertía en un HTTP 500 y el parque se quedaba sin campana. La causa raíz no estaba en GLPI, estaba en el SQL: un bloque ALTER TABLE ... ADD COLUMN IF NOT EXISTS, sintaxis exclusiva de MariaDB. En MySQL 8 ese IF NOT EXISTS a nivel de columna no existe y el statement falla con error 1064, tumbando toda la instalación. La lección vale para cualquier plugin de GLPI: el DDL condicional no es portable entre MariaDB y MySQL. La corrección (versión 2.2.1+) quitó el DDL condicional del .sql y movió las migraciones a PHP con helpers portables del plugin base. Si usas MySQL 8, asegúrate de tener el módulo en 2.2.1 o superior.
En NexTool implantamos y sustentamos entornos GLPI de punta a punta. Si tu equipo convive con tickets olvidados y notificaciones por correo que nadie abre, Smart Notify es gratuito y se activa en minutos desde el catálogo de módulos NexTool, y nuestro equipo de sustentación ayuda a calibrar lo que recibe cada perfil para que la campana sea señal, no ruido.
Este contenido se produjo con ayuda de inteligencia artificial y fue revisado por el equipo de Nextool Solutions.