La transparencia genera confianza, pero un panel mal aislado se convierte en un incidente de protección de datos. Compartir indicadores de atención con el cliente reduce los correos de seguimiento y transmite profesionalidad. El reto técnico no es dibujar el gráfico: es garantizar que cada cliente vea solo sus propios tickets. Así se hace con GLPI + Metabase o Grafana, con el filtro por entidad bloqueado en el lugar correcto.
Antes de publicar: tres decisiones que lo definen todo
En la operación de entornos GLPI multicliente, tres elecciones deciden si el panel será un diferencial o un problema de seguridad:
- Dónde bloquear el filtro por entidad: en el enlace, en la consulta o en el embed firmado. Solo el último resiste la manipulación de la URL.
- Qué fecha usar para el periodo: creación (
date), solución (solvedate) o cierre (closedate). Mezclar las tres distorsiona todos los indicadores. - De dónde leer los datos: la base primaria o una réplica de solo lectura. Metabase y Grafana lanzan consultas pesadas y compiten con GLPI en producción.
Paso 1 - usuario de base de datos de solo lectura
Nunca apuntes la herramienta de BI al usuario de GLPI. Crea un usuario dedicado con SELECT y nada más. En MariaDB:
-- Usuario de solo lectura para BI, restringido a la red interna
CREATE USER 'bi_ro'@'10.20.%' IDENTIFIED BY '...password_fuerte...';
GRANT SELECT ON glpi.* TO 'bi_ro'@'10.20.%';
-- El límite de tiempo por consulta evita que un panel bloquee la base (MariaDB)
SET GLOBAL max_statement_time = 15; -- segundos
FLUSH PRIVILEGES;
Si es posible, apunta el BI a una réplica de lectura en lugar de la base primaria. Así, un filtro olvidado en un gráfico no retiene transacciones de GLPI en producción; hemos visto un solo panel con un JOIN mal escrito degradar el tiempo de respuesta de toda la mesa de ayuda.
Paso 2 - aislar por entidad sin filtrar subentidades
Cada cliente es una entidad en GLPI, y el instinto es filtrar por entities_id = X. Aquí está la trampa más común: si el cliente tiene sucursales o departamentos como subentidades, ese filtro captura solo la entidad raíz y los números salen menores que la realidad. Hay que incluir todo el subárbol. En MariaDB 10.2.2+ se resuelve con una CTE recursiva:
-- Todas las entidades del cliente (raíz + descendientes)
WITH RECURSIVE arbol_cliente AS (
SELECT id FROM glpi_entities WHERE id = 4 -- entidad raíz del cliente
UNION ALL
SELECT e.id
FROM glpi_entities e
JOIN arbol_cliente a ON e.entities_id = a.id
)
SELECT
COUNT(*) AS total,
SUM(t.status IN (1,2,3,4)) AS abiertos,
SUM(t.status IN (5,6)) AS cerrados
FROM glpi_tickets t
WHERE t.entities_id IN (SELECT id FROM arbol_cliente)
AND t.is_deleted = 0;
Fíjate en is_deleted = 0: GLPI usa borrado lógico y, sin ese filtro, los tickets en la papelera entran en la cuenta. Es el segundo error que más corregimos en paneles heredados de otro equipo.
Paso 3 - SLA sin inflar el porcentaje
La tasa de cumplimiento de SLA parece trivial: se solucionó antes del plazo, cuenta. El detalle que engaña está en el campo time_to_resolve (el plazo de SLA guardado en el ticket): es NULL cuando ningún SLA está asociado al ticket. Si comparas solvedate <= time_to_resolve sin tratar el NULL, esos tickets desaparecen del denominador y el porcentaje sube artificialmente. Deja la población explícita:
WITH RECURSIVE arbol_cliente AS (
SELECT id FROM glpi_entities WHERE id = 4
UNION ALL
SELECT e.id FROM glpi_entities e
JOIN arbol_cliente a ON e.entities_id = a.id
)
SELECT
COUNT(*) AS con_sla,
SUM(t.solvedate <= t.time_to_resolve) AS en_plazo,
ROUND(100 * SUM(t.solvedate <= t.time_to_resolve) / COUNT(*), 1) AS pct_sla
FROM glpi_tickets t
WHERE t.entities_id IN (SELECT id FROM arbol_cliente)
AND t.is_deleted = 0
AND t.time_to_resolve IS NOT NULL -- solo tickets con SLA
AND t.solvedate IS NOT NULL -- ya solucionados
AND t.date >= '2026-06-01'
AND t.date < '2026-07-01';
Qué indicadores tienen sentido para el cliente
No todo lo que sigue el equipo interno interesa - o debe exponerse - al cliente. Un conjunto reducido funciona mejor:
- Operativo: tickets abiertos por estado y prioridad, abiertos vs. cerrados en el periodo, tiempo medio de respuesta y de resolución.
- SLA: porcentaje dentro de plazo y tickets en riesgo (cercanos al vencimiento de
time_to_resolve). - Satisfacción: nota media de la encuesta (
glpi_ticketsatisfactions) y tendencia mensual. - Contrato: horas consumidas vs. contratadas y saldo, cuando hay bolsa de horas.
Dónde bloquear el filtro: matriz de decisión
El punto más delicado es impedir que el cliente cambie el parámetro en la URL y vea datos de otro. Cada método aplica el filtro en un lugar distinto:
| Método | Dónde se aplica el filtro | Riesgo de fuga | Esfuerzo | Cuándo usar |
|---|---|---|---|---|
| Metabase - Enlace público | Parámetro editable en la URL | Alto | Bajo | Solo demostración interna, nunca con datos de cliente |
| Metabase - Embed firmado (JWT) | Parámetro bloqueado en el servidor (token) | Bajo | Medio | Opción recomendada para clientes |
| Grafana - Acceso anónimo | Expone toda la organización | Alto | Bajo | Evitar |
| Grafana - Public Dashboards (9.1+) | Un panel por enlace, sin variable editable | Medio | Bajo | Cliente único, sin subentidades sensibles |
| Grafana - organización por cliente | Aislado por org + proxy | Bajo | Alto | Muchos clientes, aislamiento fuerte |
La trampa que más nos costó (en la operación diaria)
En un entorno con decenas de entidades, el panel de un cliente mostraba cerca de la mitad de los tickets que indicaba el informe manual. La causa no era la consulta de conteo: era el enlace público de Metabase. El filtro por entidad estaba como parámetro de panel, y cualquiera con el enlace podía editar el valor en la URL y navegar por otras entidades. Cambiamos a un embed firmado (JWT), con entities_id inyectado en el token y el parámetro marcado como locked. El cliente perdió la capacidad de tocar el filtro y ganó un número que por fin coincidía con el contrato. La lección que llevamos a cada proyecto: la seguridad del panel no vive en la consulta, vive en dónde se aplica el filtro.
Lista de verificación de publicación
- Usuario de base de datos de solo lectura, preferiblemente apuntando a una réplica.
- Filtro por entidad incluyendo el subárbol (CTE recursiva o lista de IDs).
is_deleted = 0en toda consulta sobreglpi_tickets.- Periodo coherente: elige una fecha (creación, solución o cierre) y usa la misma en todas las tarjetas.
- Filtro bloqueado en el servidor (embed firmado), nunca solo en la URL.
- Prueba del enlace con un usuario externo antes de enviarlo al cliente.
¿Necesitas paneles de cliente con aislamiento por entidad a prueba de manipulación? NexTool implanta y mantiene entornos GLPI con BI integrado (Metabase y Grafana) y gobernanza de datos alineada con la normativa de protección de datos. Conoce el servicio de soporte y mantenimiento, o consulta la comparativa Metabase vs Grafana y los KPIs esenciales para Service Desk.
Este contenido fue producido con la ayuda de inteligencia artificial y revisado por el equipo de Nextool Solutions.