Dashboard (features/dashboard)
Panel de métricas del tenant: cuatro tarjetas de totales, una serie temporal de mensajes y cuatro gráficos (tickets por tipo, contactos, agentes y etiquetas con más actividad), todo filtrable por rango de fechas. Es la ruta a la que AdminGuard redirige a quien no es administrador (la raíz de la aplicación redirige a whaticket/chat, no al dashboard). Los datos salen del módulo de backend dashboard, que no tiene página propia en esta documentación; su fuente son los modelos de Contact, User, Ticket, Message y Label.
Ruta
DashboardModule se carga en shared/routes/content.routes.ts bajo dashboard con canActivate: [AuthGuard] y sin permissionGuard. DashboardRoutingModule tampoco declara guards ni data. La entrada de menú (dashboard.titleSidebar, icono charts, type: 'link') no tiene permission: es visible para cualquier usuario autenticado.
Archivos
| Archivo | Rol |
|---|---|
dashboard.module.ts / dashboard-routing.module.ts | Declaran la página; importan FormsModule, TranslateModule, HighchartsChartComponent (de highcharts-angular) y SharedModule |
pages/dashboard-page/dashboard-page.component.ts | Lógica: presets de fecha, carga paralela y construcción de las opciones de los gráficos |
pages/dashboard-page/dashboard-page.component.html | Cabecera con breadcrumb, filtro de fechas, esqueletos de carga, tarjetas y gráficos |
pages/dashboard-page/dashboard-page.component.scss | Vacío (solo un comentario: usa Bootstrap y estilos globales) |
services/dashboard.service.ts | DashboardDataService, acceso HTTP a /dashboard/* |
Componentes compartidos usados: shared-metric-card (inputs title, value, subtitle, icon, color, trend), shared-apex-chart (recibe options y height) y shared-feather-icons.
Servicios y endpoints
DashboardDataService (providedIn: 'root') usa ${environment.apiUrl}/dashboard. Todos los métodos aceptan un DateFilter (from?, to?, cadenas YYYY-MM-DD) y lo envían como query. El controlador del backend (dashboard.controller.ts) solo aplica @Auth(): no declara RequirePermission.
| Método | Endpoint | Query | Respuesta (data.results) |
|---|---|---|---|
getOverview(filter) | GET /dashboard/overview | from, to | DashboardOverview |
getTopContacts(filter, limit = 10) | GET /dashboard/top-contacts | from, to, limit | TopContact[] |
getTopUsers(filter, limit = 10) | GET /dashboard/top-users | from, to, limit | TopUser[] |
getTopLabels(filter, limit = 10) | GET /dashboard/top-labels | from, to, limit | TopLabel[] |
getMessagesTimeline(filter) | GET /dashboard/messages-timeline | from, to | MessageDay[] |
getTicketsByType(filter) | GET /dashboard/tickets-by-type | from, to | TicketByType[] |
Las respuestas llegan como { data: { results } } (sin message). El servicio devuelve [] si results falta, salvo getOverview, que devuelve undefined. El backend ignora fechas inválidas y, para to, extiende el límite a las 23:59:59.999 del día.
Modelos
core/interfaces/api/dashboard.interface.ts (el archivo también conserva interfaces heredadas de otra aplicación, como DashboardStatistics, FinancialSummary o topPlayersAgents, que esta pantalla no usa):
| Interfaz | Campos |
|---|---|
DateFilter | from?, to? |
DashboardOverview | totalContacts, activeContacts, totalUsers, activeUsers, totalTickets, totalMessages |
TopContact | _id, name, phone?, ticketCount, isActive |
TopUser | _id, name, username, email, ticketCount |
TopLabel | _id, name, color, contactCount |
MessageDay | date, count |
TicketByType | type, count |
Pantalla y flujo
DashboardPageComponent arranca con isLoading = true y en ngOnInit llama setPreset('30d').
- Presets:
today,7d,30d,3m(90 días) yall(sin límites).setPresetcalculafilterFromyfilterTo(YYYY-MM-DDcontoISOString) y llama_loadAll. - Rango manual: dos
input type="date"ligados conngModelafilterFromyfilterTo(conminymaxcruzados).onCustomDateChangemarca el presetallcomo activo y recarga. - Carga:
_loadAllponeisLoading = true,stockChartReady = falsey ejecuta unforkJoinde las seis consultas. En éxito guarda los datos, construye los gráficos y apagaisLoading; en error solo apagaisLoading(sin mensaje propio). - Tarjetas: contactos (con activos como subtítulo), usuarios (con activos), tickets y mensajes.
- Serie temporal de mensajes: gráfico Highcharts Stock (
highcharts-chartconconstructorTypestockChart), con selector de rango (7d, 15d, 1m, todo), navegador y barra de desplazamiento, línea con degradado y una serieflagsque marca el pico (▲) y el mínimo positivo (▽) cuando hay más de dos puntos. Los textos del gráfico (tooltip, botones, "Mensajes", "Pico del período") están escritos en español en el código, no pasan por i18n, y el tooltip formatea la fecha cones-EC. - Gráficos ApexCharts vía
shared-apex-chart: donut de tickets por tipo (la etiqueta vacía se rotulaUnknown), barras horizontales de contactos por tickets, columnas de agentes por tickets atendidos (usanameousernamesi el nombre está vacío) y barras horizontales de etiquetas por contactos, coloreadas con el color de cada etiqueta. Cada panel muestradashboard.noDatasi no hay datos.
Estado, sockets y utilidades
No usa NgRx ni sockets: el estado vive en el componente y no se refresca solo. Dependencias: highcharts (módulo highcharts/highstock, registrado con provideHighcharts en app.module.ts), highcharts-angular, apexcharts y ng-apexcharts.
Pruebas
No hay specs dentro de features/dashboard (ni de la página ni de DashboardDataService). Solo shared/routes/content.routes.spec.ts incluye un caso que fija que la ruta dashboard queda sin data.permission ni permissionGuard.
Gotchas e inconsistencias
- Módulos de negocio menciona
DashboardService; la clase real se llamaDashboardDataService(existe además unDashboardServiceen el backend). - Sin control de permisos: cualquier usuario autenticado ve los totales del tenant, incluidos los de usuarios, y el backend tampoco restringe por permiso.
toISOStringcalcula la fecha en UTC: cerca de medianoche, el "hoy" del navegador puede caer en el día anterior o siguiente.onCustomDateChangeactiva el botónallaunque el rango sea personalizado (no hay un estado "personalizado").totalUsersyactiveUsersno dependen del rango de fechas (el backend los cuenta sin filtrocreatedAt); contactos, tickets y mensajes sí.getOverviewno tiene el valor por defecto[]de los demás: una respuesta sinresultsdejaoverviewenundefinedy las tarjetas fallarían al leer sus campos.forkJoines todo o nada: si una de las seis consultas falla, no se muestra nada y la pantalla queda sin datos ni aviso propio.- Los colores de los gráficos están fijados en el código (por ejemplo
#7366ff), no salen del tema. - Las constantes
DASHBOARD_*deglobal.constant.ts(por ejemploDASHBOARD_BETTING_OVERVIEW) yDASHBOARD_ROUTESson restos de otro producto y no se usan aquí.