Notificaciones en tiempo real en GLPI con Smart Notify

En el GLPI estándar, una notificación es un correo asíncrono que nadie vigila. El módulo gratuito Smart Notify pone una campana en la navbar que hace polling a la base (modo delta ~93 bytes) y agrega dieciséis fuentes de eventos ITIL. Mira cómo funciona el poll, el SQL de diagnóstico, la matriz de qué eventos dejar activos y las trampas de campo, del autoplay de Chrome al error 1064 de MySQL 8.

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 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 eventoDisparador (config)Por defectoNota de campo
Seguimientos (ticket, problema, cambio)notify_*_followupActivoLo que más mueve el backlog; mantenlo
Validaciones y respuestas de validaciónnotify_*_validation(_response)ActivoUna aprobación parada es un cuello silencioso
Tareas asignadasnotify_*_taskActivoEs la cola de trabajo del técnico
Soluciones registradasnotify_solutionActivoCierra el ciclo de quien hace seguimiento
Documentos adjuntosnotify_documentInactivoRuidoso donde hay muchas subidas
Tickets nuevosnotify_new_ticketActivoSeñal de entrada de la cola
Problemas / cambios nuevosnotify_new_problem / _changeInactivoMenos comunes; actívalos por perfil
Avisos manuales (broadcast)notify_manualActivoMantenimiento, 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:

PrioridadSonidoTimbre (aprox.)
BajasubtleCorto y grave (440 Hz)
NormaldefaultEstándar (523 Hz)
AltaalertMedio (659 Hz)
UrgentealarmLargo 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.

Preguntas Frecuentes

No. El GLPI nativo depende de la cola queuednotification y de plantillas de correo, un modelo asíncrono y fuera de la herramienta. El módulo gratuito Smart Notify añade una campana en la navbar que hace polling a la base cada 30 segundos y agrega dieciséis fuentes de eventos ITIL (seguimientos, validaciones, tareas, soluciones, documentos, tickets nuevos y avisos manuales), con sonidos por prioridad.

No, son capas complementarias. La campana avisa a quien está conectado a GLPI, típicamente el técnico en sesión. El solicitante que abrió el ticket por el self-service sigue dependiendo del correo. El error común es desactivar las notificaciones por correo creyendo que la campana cubre a todos; entonces el cliente deja de recibir avisos.

Poco, gracias al modo delta. El cliente devuelve el hash del último estado (state=) y, si nada cambió, la respuesta baja de unos 18 KB a ~93 bytes: la mayoría de los polls no traen novedad. El read-check se consolidó en un único SELECT en lote (antes era uno por notificación) y el GET libera el lock de sesión con session_write_close(). El intervalo es configurable en refresh_interval.

Calibrando lo que recibe cada perfil. Tres disparadores ya vienen desactivados por defecto (documentos adjuntos, problemas nuevos y cambios nuevos) porque son ruidosos o poco frecuentes. Cada técnico afina en el modal de preferencias, y el SQL sobre la tabla _preferences muestra quién silenció qué. Encenderlo todo lleva a la fatiga de alertas y el técnico empieza a ignorar la campana.

Es la política de autoplay de Chrome, no un bug. El navegador solo permite crear el AudioContext tras el primer gesto del usuario, así que el primer sonido de cada sesión suena solo tras el primer clic en la página. A partir de ahí, los sonidos por prioridad suenan con normalidad.

Funciona en ambos a partir de la versión 2.2.1. Las versiones anteriores traían un ALTER TABLE ... ADD COLUMN IF NOT EXISTS en install.sql, sintaxis exclusiva de MariaDB que en MySQL 8 falla con error 1064 y rompe la instalación (HTTP 500). La corrección movió las migraciones a PHP con helpers portables. En MySQL 8, usa 2.2.1 o superior.

?Necesitas ayuda?