Panel Compartido con el Cliente en GLPI

Cómo crear y compartir paneles de atención con tus clientes usando Metabase o Grafana sobre GLPI, con filtrado por entidad (incluyendo subentidades), cálculo correcto del SLA en la base de datos y acceso a prueba de manipulación.

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étodoDónde se aplica el filtroRiesgo de fugaEsfuerzoCuándo usar
Metabase - Enlace públicoParámetro editable en la URLAltoBajoSolo demostración interna, nunca con datos de cliente
Metabase - Embed firmado (JWT)Parámetro bloqueado en el servidor (token)BajoMedioOpción recomendada para clientes
Grafana - Acceso anónimoExpone toda la organizaciónAltoBajoEvitar
Grafana - Public Dashboards (9.1+)Un panel por enlace, sin variable editableMedioBajoCliente único, sin subentidades sensibles
Grafana - organización por clienteAislado por org + proxyBajoAltoMuchos 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

  1. Usuario de base de datos de solo lectura, preferiblemente apuntando a una réplica.
  2. Filtro por entidad incluyendo el subárbol (CTE recursiva o lista de IDs).
  3. is_deleted = 0 en toda consulta sobre glpi_tickets.
  4. Periodo coherente: elige una fecha (creación, solución o cierre) y usa la misma en todas las tarjetas.
  5. Filtro bloqueado en el servidor (embed firmado), nunca solo en la URL.
  6. 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.

Preguntas Frecuentes

Filtrar solo por entities_id = X captura únicamente la entidad raíz. Para incluir sucursales y departamentos, construye el subárbol con una CTE recursiva sobre glpi_entities (MariaDB 10.2.2+) y usa entities_id IN (SELECT id FROM arbol_cliente). Como alternativa, genera la lista de IDs en la aplicación y pásala como parámetro.

Porque time_to_resolve es NULL cuando el ticket no tiene un SLA asociado. Al comparar solvedate <= time_to_resolve sin filtrar esos casos, los tickets sin SLA salen del denominador e inflan el porcentaje. Restringe la población con time_to_resolve IS NOT NULL.

No para datos sensibles. Con el enlace público el filtro por entidad queda en la URL y se puede editar, exponiendo datos de otros clientes. Usa un embed firmado (JWT) con el parámetro marcado como locked y aplicado en el servidor. El enlace público solo sirve para demostraciones internas.

Prefiere una réplica de solo lectura. Si no hay réplica, crea al menos un usuario con GRANT SELECT y define max_statement_time para que una consulta pesada de panel no retenga transacciones de GLPI en producción.

Depende del indicador. Para volumen de entrada usa date (creación). Para productividad y SLA usa solvedate (solución) o closedate (cierre). El error común es mezclarlas: contar los resueltos por la fecha de creación subestima el resultado del mes.

Compara solvedate con time_to_resolve en el ticket: SUM(solvedate <= time_to_resolve) sobre el total con SLA. Filtra is_deleted = 0, time_to_resolve IS NOT NULL y solvedate IS NOT NULL para que el denominador sea correcto.

?Necesitas ayuda?