Dashboard administrativo - plantas
Dashboard plantas
- Los roles que tienen permiso para ver el dashboaard son: TREEA_ADMIN, ADMIN_COMPANY y SUPERVISOR
Gráficas 4.1 y 4.3 - Documentación Técnica
Resumen General
Las gráficas 4.1 (Velocímetros/Gauges) y 4.3 (Termómetros) visualizan parámetros de calidad de agua con el mismo sistema de colores. La única diferencia es la representación visual: los velocímetros son semicirculares (gauges) y los termómetros son barras verticales.
Gráfica 4.1 (Velocímetros)
La gráfica 4.1 se utiliza para tipos de agua específicos con parámetros limitados:
Agua Potable: Solo se miden 3 parámetros:
- Cloro libre
- pH
- Turbiedad
Agua Residual: Solo se miden 3 parámetros:
- DQO (Demanda Química de Oxígeno)
- SST (Sólidos Suspendidos Totales)
- pH
Gráfica 4.3 (Termómetros)
- Para el resto de tipos de agua se miden todos los parámetros disponibles y se muestran visualmente en forma de termómetro.
Nota: El backend automáticamente valida el tipo de agua de la planta seleccionada y determina qué gráfica usar según los parámetros disponibles.
Flujo de Implementación desde el Frontend
Paso 1: Selección de Planta
Se crea un input de tipo select con el nombre "Planta", que lista todas las plantas de tratamiento para el tenant actual / cliente logueado.
Paso 2: Validación del Tipo de Agua
El backend automáticamente valida el tipo de agua de la planta seleccionada:
- Si el agua es potable o residual: genera datos para la gráfica del velocímetro (3 parámetros)
- En caso contrario: genera datos para la gráfica del termómetro (todos los parámetros disponibles)
Paso 3: Obtener Histórico Más Reciente
Se llama al endpoint:
GET /history-treatment-plants-summary/:idPlantParámetros:
idPlant: El_idde la planta seleccionada (del Paso 1)
Respuesta:
- Retorna el listado de mediciones/históricos para la planta seleccionada
- Orden: del más reciente al más antiguo
- Necesitamos: La propiedad
_idque representa el id del histórico más reciente
Paso 4: Obtener Datos de Cumplimiento
Se consume el endpoint:
GET /history-treatment/:idPlant/:idHistoryParámetros:
idPlant: Extraído del Paso 1idHistory: Extraído del Paso 3 (histórico más reciente)
Paso 5: Generación de Franjas de Color
El backend automáticamente genera los rangos o las franjas para los diferentes colores (rojo, naranja y verde) dependiendo de la especificación:
- "Valor máximo": Genera franjas según reglas de valor máximo
- "Rango": Genera franjas según reglas de rango
- "Análisis y reporte": No se generan gráficas (se omiten completamente)
Data que se expone al frontend y como debería leerse para generar la gráfica 4.1 y 4.3
- nameParam: Título de gauge, ejemplo: DQO, pH
- currentValue: Valor actual (número grande debajo de gauge), ejemplo 4.7, también define la posición de indicador (barra o aguja)
- Axis: Rango total del gauge
- axis.min: Inicio de la escala (normalmente 0)
- axis.max: Fin de la escala - 1.5 * max (aplica para rango como para valorMaximo)
- Bands: Franjas de color
- green: [start, end] - zona verde óptima. Para Rango - [Min + 0.1 * (Max - Min ), Max - 0.1 * (Max - Min ) ]. Para valor maximo - [0, 0.9 * Max ]. No hay verde superior separados, es un intervalo único en ambos casos.
- orangeLow: Zona amarilla inferior (solo para rango). No hay amarillo inferior el minimo es 0. Para rango. Inferior --- [Min, Min + 0.1 * (Max - Min ))]
- orangeHigh: Zona amarilla superior. Para valorMaximo [0.9 * Max , Max ]. Para rango. [Max - 0.1 * (Max - Min ), Max]
- redLow: Zona roja inferior (solo para rango) [0, limite Inferior] donde limite inferior = 1.5 * Min - 0.5 * Max. Para otros parametros [0, Min]
- redHigh: Zona roja superior (para valor maximo no hay rojo inferior - el minimo es 0 y no hay valores negativos), el rojo superior es igual a [Max, 1.5 * Max ] (VALOR MAXIMO) para rango el limite superior es [Limite superior, 1.5 * Max] donde Limite superior = 0.5 * Min + 0.5 * Max
- stateParam: Estado de cumplimiento
Nota: Se maneja una sola franja verde, tanto para rango como para valorMaximo, es una sola franja continua (no hay verde inferior y verde superior)
Especificaciones de Parámetros
Los parámetros pueden tener dos tipos de especificación:
- "Rango": Tiene un valor mínimo (Mín) y máximo (Máx) permitidos
- "Valor máximo": Solo tiene un valor máximo permitido (Mín = 0)
Nota: Los parámetros con especificación "Análisis y reporte" se omiten completamente de estas gráficas.
Rango del Eje (Axis)
Regla General
El eje siempre va desde 0 hasta 1.5 veces el Máximo configurado en el control correspondiente.
Fórmula:
axis = [0, 1.5 × Máx]Ejemplo:
- Si Máx = 0.1 → axis = [0, 0.15]
- Si Máx = 10 → axis = [0, 15]
Franjas de Color - Especificación "Rango"
Caso Especial: pH
Para parámetros de tipo pH con especificación "Rango", se calculan límites especiales:
Fórmulas:
Límite Inferior = 1.5 × Mín - 0.5 × Máx
Límite Superior = 0.5 × Mín + 0.5 × MáxEjemplo (pH: Mín=6, Máx=9):
- Límite Inferior = 1.5 × 6 - 0.5 × 9 = 9 - 4.5 = 4.5
- Límite Superior = 0.5 × 6 + 0.5 × 9 = 3 + 4.5 = 7.5
Franjas de Color para "Rango"
1. Rojo (Zona Crítica)
Rojo Inferior:
- Condición:
Valor < Límite Inferior(para pH) oValor < Mín(para otros) - Rango:
[0, Límite Inferior]o[0, Mín) - Significado: Valor crítico por debajo del mínimo permitido
Rojo Superior:
- Condición:
Valor >= Límite Superior(para pH) oValor >= Máx(para otros) - Rango:
[Límite Superior, 1.5 × Máx]o[Máx, 1.5 × Máx] - Significado: Valor crítico por encima del máximo permitido
2. Amarillo/Naranja (Zona de Precaución)
Amarillo Inferior:
- Fórmula:
[Mín, Mín + 0.1 × (Máx - Mín)) - Significado: Valor dentro del rango pero cerca del límite inferior (primer 10% por encima del mínimo)
Amarillo Superior:
- Fórmula:
[Máx - 0.1 × (Máx - Mín), Máx) - Significado: Valor dentro del rango pero cerca del límite superior (último 10% antes del máximo)
Ejemplo (pH: Mín=6, Máx=9):
- Amarillo Inferior:
[6, 6.3)(6 + 0.1 × 3) - Amarillo Superior:
[8.7, 9)(9 - 0.1 × 3)
3. Verde (Zona Óptima)
Fórmula: [Mín + 0.1 × (Máx - Mín), Máx - 0.1 × (Máx - Mín))
Significado: Zona central del rango (80% del rango), excluyendo los 10% de precaución en cada extremo.
Ejemplo (pH: Mín=6, Máx=9):
- Verde:
[6.3, 8.7)(zona central del 80%)
Franjas de Color - Especificación "Valor Máximo"
Límites del Eje
Límite Inferior: 0
Límite Superior: 1.5 × Máx
Franjas de Color
1. Verde (Zona Óptima)
Fórmula: [0, 0.9 × Máx)
Significado: Valores desde 0 hasta el 90% del máximo permitido (cumple con margen suficiente).
Ejemplo (Máx=0.1):
- Verde:
[0, 0.09)
2. Amarillo/Naranja (Zona de Precaución)
Fórmula: [0.9 × Máx, Máx)
Significado: Valores desde el 90% del máximo hasta el máximo (zona de alerta antes de exceder).
Ejemplo (Máx=0.1):
- Amarillo:
[0.09, 0.1)
Nota: Para "Valor máximo" solo hay amarillo superior (no hay amarillo inferior porque el mínimo es 0).
3. Rojo (Zona Crítica)
Fórmula: [Máx, 1.5 × Máx]
Significado: Valores que exceden el máximo permitido.
Ejemplo (Máx=0.1):
- Rojo:
[0.1, 0.15]
Nota: Para "Valor máximo" solo hay rojo superior (no hay rojo inferior porque no hay mínimo que proteger).
Ejemplos Prácticos
Ejemplo 1: Arsénico (Valor Máximo)
Datos:
- Especificación: "Valor máximo"
- Mín: 0
- Máx: 0.1
- Valor actual: 7
Cálculos:
- Eje:
[0, 0.15](1.5 × 0.1) - Verde:
[0, 0.09)(0.9 × 0.1) - Amarillo:
[0.09, 0.1)(0.9 × 0.1 hasta 0.1) - Rojo:
[0.1, 0.15](desde 0.1 hasta 0.15)
Resultado:
- Valor actual 7 está en ROJO (≥ 0.1) y fuera del eje visual (se saturaría en 0.15)
JSON de respuesta:
{
"nameParam": "Arsenico",
"specification": "Valor máximo",
"currentValue": 7,
"min": 0,
"max": 0.1,
"axis": {
"min": 0,
"max": 0.15
},
"bands": {
"green": [0, 0.09],
"orangeHigh": [0.09, 0.1],
"redHigh": [0.1, 0.15]
}
}Ejemplo 2: pH (Rango)
Datos:
- Especificación: "Rango"
- Mín: 6
- Máx: 9
- Valor actual: 5
Cálculos:
- Límite Inferior (pH especial): 1.5 × 6 - 0.5 × 9 = 4.5
- Límite Superior (pH especial): 0.5 × 6 + 0.5 × 9 = 7.5
- Eje:
[0, 13.5](1.5 × 9) - Delta: 9 - 6 = 3
- Verde:
[6.3, 8.7)(6 + 0.3 a 9 - 0.3) - Amarillo Inferior:
[6, 6.3) - Amarillo Superior:
[8.7, 9) - Rojo Inferior:
[0, 4.5] - Rojo Superior:
[7.5, 13.5]
Resultado:
- Valor actual 5 está en ROJO INFERIOR (< 4.5)
Estructura de Datos JSON
Para Especificación "Rango"
{
"nameParam": "string",
"specification": "Rango",
"currentValue": number,
"min": number,
"max": number,
"axis": {
"min": 0,
"max": number // 1.5 × Máx
},
"bands": {
"green": [number, number], // [Mín + 0.1×(Máx-Mín), Máx - 0.1×(Máx-Mín))
"orangeLow": [number, number], // [Mín, Mín + 0.1×(Máx-Mín))
"orangeHigh": [number, number], // [Máx - 0.1×(Máx-Mín), Máx)
"redLow": [number, number], // [0, Límite Inferior] o [0, Mín)
"redHigh": [number, number] // [Límite Superior, 1.5×Máx] o [Máx, 1.5×Máx]
}
}Para Especificación "Valor máximo"
{
"nameParam": "string",
"specification": "Valor máximo",
"currentValue": number,
"min": 0,
"max": number,
"axis": {
"min": 0,
"max": number // 1.5 × Máx
},
"bands": {
"green": [0, number], // [0, 0.9 × Máx)
"orangeHigh": [number, number], // [0.9 × Máx, Máx)
"redHigh": [number, number] // [Máx, 1.5 × Máx]
}
}Implementación en Código
Uso de Decimal.js
Todas las operaciones matemáticas deben usar Decimal.js para evitar errores de precisión en punto flotante.
Ejemplo para "Rango" - Verde:
let min: Decimal = new Decimal(param.ppmDesiredMin);
let max: Decimal = new Decimal(param.ppmDesiredMax);
let greenStart: number = min.plus(new Decimal(0.1).times(max.minus(min))).toNumber();
let greenEnd: number = max.minus(new Decimal(0.1).times(max.minus(min))).toNumber();
let green: [number, number] = [greenStart, greenEnd];Ejemplo para "Valor máximo" - Verde:
const greenMax: number = new Decimal(param.ppmDesiredMax).mul(0.9).toNumber();
const green: [number, number] = [0, greenMax];Creación del velocímetro
1. Dibujar el arco desde axis.min hasta axis.max 2. Las franjas de color las generamos según bands 3. Posicionamos el indicador en currentValue (La barra o sea la aguja) 4. Mostramos currentValue como número grande 5. En el título de la gráfica referenciamos a nameParam
Creación del termómetro
- nameParam: Título del termómetro
- currentValue: Valor actual
- axis.min y axis.max: Rango total
- bands: Todas las franjas de color
1. Dibujar la barra vertical desde axis.min (abajo), hasta axis.max (arriba) 2. Generamos las franjas de color horizontalmente según bands 3. Llenamos la barra hasta currentValue4. Mostramos currentValue como número grande 5. Mostramos nameParam como título
Notas Importantes
Parámetros omitidos: Los parámetros con especificación "Análisis y reporte" se excluyen completamente.
pH especial: Los parámetros de tipo "pH" con especificación "Rango" usan límites especiales para las franjas rojas (no para amarillo/verde).
Precisión: Siempre usar
Decimal.jspara cálculos matemáticos, especialmente con decimales.Eje siempre desde 0: El eje siempre comienza en 0, independientemente del valor mínimo del parámetro.
Valores fuera del eje: Si el valor actual excede 1.5 × Máx, se satura visualmente en el tope del eje, pero el valor numérico se mantiene.
Referencias
- Endpoint:
GET /v1/dashboard/parameter-compliance/:treatmentPlantId/:historyId - Controller:
src/controllers/dashboardManager/parameterCompliance.ts - Documento de requerimientos: Dashboard Interactivo de Planta
Última actualización: Noviembre 2025
Grafica 4.4 Tendencia Últimos 30 Días
DATA QUE SE ENVIA AL FRONTEND:
Estructura de Respuesta
{
"msg": "Tendencia de parametros - Últimos 30 días",
"trend": [ ... ]
}Dentro de trend[0]:
_id: ID del histórico más recientetreatmentPlantName: Nombre de la planta de tratamientotypeOfWater: Tipo de agua (aguaPotable, aguaResidual, etc.)parameters: Array con todos los parámetros
Cada parámetro en parameters[]:
nameParam: Nombre del parámetro (ej: "Cloro_Libre", "pH")specification: Tipo de especificación ("Valor máximo" o "Rango")currentValue: Último valor registrado del parámetrocompliancePercentage: Porcentaje de cumplimiento (ej: 98.2)range: Objeto conminymaxsegún la normaseries: Array con todos los puntos de la serie temporal:date: Fecha/hora en formato ISO (ej: "2025-09-11T14:00:00")value: Valor del parámetro en ese momentoinRange:truesi cumplió,falsesi no cumplió
Ejemplo completo:
{
"msg": "Tendencia de parametros - Últimos 30 días",
"trend": [{
"_id": "690e0c88a841b7e6b1388147",
"treatmentPlantName": "PLANTA 7 NOV 2025",
"typeOfWater": "aguaResidual",
"parameters": [
{
"nameParam": "Cloro_Libre",
"specification": "Valor máximo",
"currentValue": 4.27,
"compliancePercentage": 98.2,
"range": { "min": 0, "max": 5 },
"series": [
{ "date": "2025-09-11T14:00:00", "value": 2.5, "inRange": true },
{ "date": "2025-09-12T15:00:00", "value": 3.1, "inRange": true }
]
}
]
}]
}Para el frontend:
- Usa
seriespara dibujar el gráfico de línea (eje X:date, eje Y:value) - Usa
compliancePercentagepara el título (ej: "Cloro_Libre - Últimos 30 días | Cumplimiento: 98.2%") - Usa
rangepara dibujar líneas de referencia (min/max) en el gráfico - Usa
inRangepara colorear puntos (verde sitrue, rojo sifalse)
Ejemplo Real: Cálculo de compliancePercentage para Arsénico
Datos del Parámetro:
- Nombre: Arsénico
- Especificación: "Valor máximo"
- Rango: min = 0, max = 0.1 (según norma)
- Período: Últimos 30 días (720 horas)
- Fecha más reciente (latestDate):
2025-11-11T20:11:30.746Z(11 nov, 3:11 PM) - Fecha inicio (startDate):
2025-10-12T20:11:30.746Z(12 oct, 3:11 PM) - 30 días antes
Cálculo de isInRange:
Para "Valor máximo":
isInRange = currentValue <= range.max
// Solo se verifica el máximo, el mínimo no aplicaEjemplo con Arsénico:
- Medición 1:
10 <= 0.1→false - Medición 2:
10 <= 0.1→false - Medición 3:
5 <= 0.1→false
Serie Temporal (Mediciones Reales):
Nota: Las mediciones están ordenadas del más reciente al más antiguo (como se envía al frontend).
Medición 1 (más reciente):
- Fecha:
2025-11-11T20:11:30.746Z(11 nov, 3:11 PM) - Valor:
5ppm - Estado:
5 <= 0.1→false→ NO CUMPLE inRange: false
Medición 2:
- Fecha:
2025-11-11T17:23:54.779Z(11 nov, 12:23 PM) - Valor:
10ppm - Estado:
10 <= 0.1→false→ NO CUMPLE inRange: false
Medición 3 (más antigua):
- Fecha:
2025-11-09T21:24:36.391Z(9 nov, 4:24 PM) - Valor:
10ppm - Estado:
10 <= 0.1→false→ NO CUMPLE inRange: false
Cálculo de Horas Continuas:
IMPORTANTE:
- Para el cálculo, las series se ordenan del más antiguo al más reciente (orden cronológico).
- El cálculo incluye las horas desde
startDatehasta la primera medición (más antigua). - Las series se reordenan del más reciente al más antiguo antes de enviarse al frontend.
Orden cronológico para el cálculo (del más antiguo al más reciente):
Desde startDate hasta Medición 3 (más antigua - 9 nov):
- Diferencia: Del 12 oct 3:11 PM al 9 nov 4:24 PM
- Horas: ~672 horas (28 días)
- Estado: Se asume el mismo estado de la medición más antigua → NO CUMPLE
- → +672 horas de incumplimiento
Medición 3 → Medición 2 (9 nov → 11 nov 12:23 PM):
- Diferencia: Del 9 nov 4:24 PM al 11 nov 12:23 PM
- Horas: ~43.98 horas (casi 2 días)
- Estado: NO CUMPLE → +43.98 horas de incumplimiento
Medición 2 → Medición 1 (11 nov 12:23 PM → 11 nov 3:11 PM):
- Diferencia: Del 11 nov 12:23 PM al 11 nov 3:11 PM
- Horas: ~2.8 horas
- Estado: NO CUMPLE → +2.8 horas de incumplimiento
Medición 1 → latestDate (11 nov 3:11 PM → latestDate):
- Diferencia: Del 11 nov 3:11 PM hasta latestDate (11 nov 3:11 PM)
- Horas: ~0 horas (es la misma fecha)
- Estado: NO CUMPLE → +0 horas de incumplimiento
Totales:
Total horas cumplimiento: 0 horas
Total horas incumplimiento: 672 + 43.98 + 2.8 + 0 = 718.78 horas
Total horas del período: 720 horasCálculo del Porcentaje:
compliancePercentage = 100 - (718.78 / 720) × 100
= 100 - (0.9983) × 100
= 100 - 99.83
= 0.17%Resultado Final:
{
"nameParam": "Arsenico",
"specification": "Valor máximo",
"currentValue": 5,
"compliancePercentage": 0.17,
"range": { "min": 0, "max": 0.1 },
"series": [
{ "date": "2025-11-11T20:11:30", "value": 5, "inRange": false },
{ "date": "2025-11-11T17:23:54", "value": 10, "inRange": false },
{ "date": "2025-11-09T21:24:36", "value": 10, "inRange": false }
]
}Nota: Las series están ordenadas del más reciente al más antiguo.
Interpretación: El Arsénico tiene 0.17% de cumplimiento porque:
- Todas las mediciones (3 puntos) superaron el máximo permitido (0.1 ppm)
- Se asume que el estado de incumplimiento se mantuvo desde
startDate(12 oct) hasta la primera medición (9 nov) - 672 horas - El estado de incumplimiento se mantuvo entre todas las mediciones
- El pequeño porcentaje (0.17%) se debe a que el período completo es de 720 horas, pero solo se contaron 718.78 horas (diferencia por redondeo de fechas)