Buscar en la documentación

Módulo Dashboard

Widgets, métricas con la visibilidad del listado, caché invalidada por cambios y diseño guardado por persona.

6 min de lecturaVer en GitHub

El dashboard resume los incidentes que cada persona puede ver: totales, pendientes, vencidos, distribución por estado y por prioridad, tendencia, listas cortas y carga por agente. Cada persona organiza sus widgets y el diseño se guarda en el servidor.

Tres reglas guían el módulo:

  • Las cifras usan la misma visibilidad que el listado (VisibilityScope, traducida a SQL por VisibilitySql). Una cifra del dashboard nunca contradice lo que la persona encuentra al filtrar.
  • El back dice a qué filtro del listado lleva cada cifra (filter). El front no decide qué es “pendiente” ni “vencido”.
  • El diseño solo decide dónde va cada widget, nunca qué datos se ven. Los permisos se validan en cada lectura y en cada guardado.

Endpoints

Método y rutaQué hace
GET /api/v1/dashboardDiseño de la persona: alcance (general o personal), si lo personalizó y cada widget con sus topes, su lugar y si está visible
GET /api/v1/dashboard/metrics?widgets=a,b&trend_days=30Datos de los widgets pedidos. trend_days admite 7, 30 o 90. meta.generated_at dice cuándo se calculó el dato más antiguo
PUT /api/v1/dashboard/layoutGuarda el diseño completo: widgets: [{code, x, y, w, h, visible}]
DELETE /api/v1/dashboard/layoutVuelve al diseño original (borra lo guardado)

Entrar exige dashboard.view.own o dashboard.view.any. Con dashboard.view.any el título es “Resumen general”; con el otro, “Mi resumen”. Sin ninguno, la respuesta es 403 FORBIDDEN_ACTION.

Widgets

CódigoTipoPermiso propioDatos
operation_pulseGráfico—Total (todos los visibles), pendientes, vencidos y qué parte de los pendientes son, pendientes de la prioridad más alta, creados y resueltos hoy (día del negocio) y el cumplimiento del plazo: de lo resuelto en 30 días, qué porcentaje antes del vencimiento (null si no hay resueltos)
risk_mapGráfico—Pendientes por prioridad (de la más alta a la más baja) y grupo de vencimiento (overdue, today, week, later, con los límites de DueWindow), cada celda con su filtro del listado; y por prioridad, el total en cualquier estado
status_flowGráfico—Cantidad por estado: el camino (stages) y las salidas (exits): los estados finales a los que se llega directo desde el inicial
backlog_balanceGráfico—Pendientes de cada día del periodo (trend_days), reconstruidos desde los de hoy hacia atrás con lo que entró y salió cada día (resuelto, o terminado sin resolverse según su última actualización), y el cambio en el periodo
total_incidentsNúmero—Todos los visibles, en cualquier estado
pending_incidentsNúmero—Abiertos, en progreso o en espera
overdue_incidentsNúmero—Pendientes con el vencimiento pasado, y qué porcentaje de los pendientes son
unassigned_incidentsNúmeroincidents.view.unassignedPendientes sin asignar
incidents_by_statusGráfico—Cantidad por estado, incluidos los que están en cero
incidents_by_priorityGráfico—Cantidad y porcentaje por prioridad (los porcentajes siempre suman 100)
created_vs_resolvedGráfico—Creados y resueltos por día, contados en la zona horaria del negocio
due_soonLista—Los 5 pendientes que vencen antes, en las próximas 48 horas, y el total
assigned_to_meListaincidents.view.assignedLos 5 pendientes propios que vencen antes, y el total
workload_by_agentGráficoincidents.assignPendientes y vencidos de las 8 personas más cargadas

El catálogo vive en la tabla dashboard_widgets (ver diccionario). Un widget solo se ofrece si además tiene implementación en el código (WidgetCode). Agregar uno nuevo es sumar el caso en WidgetCode, su consulta y su forma en WidgetMetrics (o RiskMetrics), y su fila en el seeder. in_default_layout dice si forma parte del diseño original (ADR-0081).

Grilla y diseño

  • Grilla de 12 columnas. Cada widget tiene topes de ancho y alto en el catálogo. El servidor rechaza un diseño con widgets fuera de la grilla (WIDGET_OUT_OF_GRID), de tamaño inválido (WIDGET_SIZE_OUT_OF_RANGE, con los topes en params), repetidos (WIDGET_DUPLICATED), no disponibles para la persona (WIDGET_NOT_AVAILABLE) o visibles que chocan (WIDGETS_OVERLAP).
  • Diseño original (DashboardGrid::defaults): los widgets permitidos marcados con in_default_layout, en el orden del catálogo, de izquierda a derecha y fila por fila. Si a una fila le sobra espacio, el último widget crece dentro de su tope. Los demás permitidos quedan ocultos, debajo de todo, listos para “Agregar widget”.
  • Guardar escribe una fila por widget permitido, con su lugar y si está visible. Un widget oculto conserva su lugar, para volver a donde estaba si se agrega de nuevo. Los que no se enviaron quedan ocultos, debajo de todo.
  • Widgets nuevos en el catálogo: para quien ya personalizó, aparecen ocultos, listos para agregarse. Si el catálogo cambia los topes de un widget, el tamaño guardado se ajusta solo (DashboardGrid::fit).
  • Sin filas guardadas la persona ve el diseño original. “Restaurar” borra sus filas.

Métricas y caché

  • Fuentes compartidas (MetricSources): total, pendientes, por estado y por prioridad salen de un solo conteo agrupado por estado y prioridad. Cada fuente se guarda en caché por separado.
  • Clave por alcance: quienes ven todo comparten la caché (all). Para el resto, la clave lleva su id y sus permisos de visibilidad. “Asignados a mí” siempre lleva el id de quien pregunta.
  • Invalidación por generación: cada cambio en un incidente (crear, editar, cambiar de estado, asignar, eliminar o restaurar) emite el evento IncidentChanged después de confirmar la transacción. El dashboard sube un número de generación que forma parte de todas las claves, así que la siguiente lectura calcula de nuevo. Lo anterior expira solo, sin buscar ni borrar claves.
  • Tiempo de vida de 60 segundos (DASHBOARD_CACHE_SECONDS): cubre lo que cambia solo con la hora, como un pendiente que vence sin que nadie lo toque.
  • Tendencia en la hora del negocio (BUSINESS_TIMEZONE, por defecto America/Bogota): MySQL agrupa por hora UTC y la aplicación arma los días locales. El día de hoy forma parte de la clave, así que la ventana avanza sola a medianoche. Para zonas con desfase de media hora, el reparto por hora es aproximado.
  • Sin N+1: la cantidad de consultas no depende del volumen de datos (lo comprueba una prueba).

El rendimiento con 100.000 incidentes está medido en Rendimiento.

Capas

CapaPiezaQué hace
DomainWidgetCode, WidgetKind, WidgetDefinitionQué widgets existen, de qué tipo y con qué topes y permiso
DomainPlacement, WidgetLayout, DashboardGridLugar en la grilla, diseño original y validación
DomainDashboardAccess, PercentagesQuién entra y con qué alcance; porcentajes que suman 100
ApplicationManageDashboardLayoutVer, guardar y restaurar el diseño
ApplicationReadDashboardMetrics, Metrics/MetricSources, Metrics/WidgetMetricsDatos de cada widget, en caché por alcance
InfrastructureEloquentDashboardQueriesAgregados en SQL con la visibilidad del listado
InfrastructureDatabaseWidgetCatalog, DatabaseLayoutStoreCatálogo y diseño guardado
InfrastructureCacheMetricsCache, InvalidateDashboardMetricsCaché con generación e invalidación al cambiar un incidente
HttpDashboardController, DashboardResource, Requests/*Endpoints, forma JSON y validación de entrada

Dónde está en el código