Cuándo
21-09-2026, 22-09-2026 y 23-09-2026 — cinco commits de la misma familia de bug, en SAI, WiFi y cartelería.
Síntomas visibles
- Las dos gráficas de flota del tablero de SAI (Battery Charge y Output Load agregados) llevaban vacías desde la v1.40.3 — una regresión de larga vida, sin que nadie lo notara hasta añadir un test de contrato.
- El informe por SAI solo mostraba 5 de las 8 gráficas prometidas, y una de ellas con la etiqueta equivocada (“temperature” en vez de “battery_temperature”).
- En cualquier SAI APC, dos gráficas del perfil de fabricante (frecuencia de entrada, corriente de salida) salían siempre sin datos.
- El anillo “Battery Health” del tablero de SAI marcaba Charge 100 %, Load 0,0 % y Runtime 60 min con nota 100 en cualquier equipo — mientras la gráfica de carga (ya arreglada arriba) sí mostraba el dato real, 46 %—, y la tarjeta de carga media decía ”—”. Latente desde la v1.40.3 hasta la v1.153.1 (23-09-2026, visto en PROD sobre la organización de demostración).
- El anillo de salud del tablero de cartelería marcaba CPU y memoria siempre al 0 % en las 40 pantallas de la organización de demostración.
Causa raíz
Cinco variantes del mismo problema de fondo — el tablero lee un campo que el servidor ya no manda con ese nombre o esa forma, y falla en silencio devolviendo un valor por defecto en vez de un error:
- El descriptor del tablero de SAI pedía
charge/load; el servidor mandabattery_charge/output_load. Sin ningún test que comparara ambos lados del contrato, la discrepancia pasó desapercibida desde la v1.40.3. UPS_REPORT_METRICSen el backend solo enumeraba 5 de las 8 claves posibles, y una con el nombre viejo (temperature) en vez del real (battery_temperature).- El perfil de fabricante APC (migración 0033, ampliada en la 0040 solo para Riello/Salicru/Tripp Lite/Delta) nunca tuvo las claves
input_frequency/output_current— no es un desajuste de nombre sino una ausencia total en el perfil. MonitoringDashboard.extractDeepMetric(compartido por los tableros de SAI, WiFi y cartelería) solo leíadevice.deep_snmp_data; los listados (/ups/status-list,/ups/deep-data) dejaron de mandar esa estructura anidada y entregan la métrica como campo plano (output_load: 44.3). No es un desajuste de NOMBRE como las variantes 1-3, sino de FORMA (anidado vs plano) — el mismo test de contrato tablero-servidor de la variante 1 no lo habría cazado, porque compara nombres de campo, no su forma.- En cartelería,
/signage/status-listsí mandacpu_usage/memory_usagepor pantalla, pero el código solo copiaba la CPU con un nombre interno (_statusCpu);extractDeepMetricnunca la encontraba con el nombre que de verdad busca.
Fix aplicado
3b7f5177— descriptor del tablero de SAI corregido abattery_charge/output_load; test de contrato tablero-servidor para los 3 dominios (Wireless/UPS/Signage), para que esta clase de bug no vuelva a pasar 20 versiones sin detectarse.48121802—UPS_REPORT_METRICScompleto (8 de 8, con la clave correcta); informe de equipo al doble de ancho, una gráfica por línea; octava gráfica de WiFi (Channel Utilization = máximo por radio); test de contrato informe-servidor, hermano del anterior.fdf291dc— migración 0066: añadeinput_frequency/output_currental perfil de fabricante APC (OIDs del PowerNet-MIB, sin verificar aún contra un equipo real — marcadoUNVERIFIEDen la propia migración).e24b68e8(v1.153.1, 23-09-2026) —extractDeepMetricmira primero el campo plano (device[metricName]) y solo si no está cae adevice.deep_snmp_data; arregla a la vez el anillo de salud, la carga media y el recuento de SAI críticos. En cartelería,SignageMonitorcopia tambiéncpu_usage/memory_usagecon su nombre real (antes solo_statusCpu). Test nuevo:frontend/src/__tests__/static/monitoring_extract_metric_flat.test.js(campo plano, caída adeep_snmp_data, sin dato, y el anillo de SAI con dos equipos).
Lecciones
Un descriptor de tablero escrito a mano y un serializador de servidor escrito a mano, sin un test que los compare, divergen silenciosamente — y la gráfica o el anillo vacío se confunde fácilmente con “este modelo no reporta el dato” en vez de “el código pide la clave equivocada, o la busca con la forma equivocada”. La primera regresión de SAI llevaba desde la v1.40.3 sin detectarse; la de extractDeepMetric también — dos días después de escribir esta misma página, con el mismo origen (la migración a la base común MonitoringDashboard, v1.40.3) pero un mecanismo distinto (anidado vs plano) que el test de contrato de las variantes 1-3 no cubría, porque compara nombres, no formas.
Preventivos futuros
Los dos tests de contrato (tablero-servidor, informe-servidor) siguen como gate: cualquier cambio futuro que renombre un campo en un lado sin el otro rompe CI. El caso APC (fdf291dc) sigue pendiente de verificación contra un equipo real. extractDeepMetric tiene ahora su propio test de regresión (campo plano primero, deep_snmp_data como fallback) — pero sigue sin existir un test de contrato que cubra la FORMA del dato (anidado vs plano) para los tres dominios a la vez, solo el nombre. Si una futura migración vuelve a cambiar la forma de un campo consumido por extractDeepMetric, este incidente puede repetirse una sexta vez.
Véase también
- [[feature—monitoring—rangos-tiempo-graficas-7-30-dias]]
- [[incident—20260916—graficas-bandwidth-caidas-falsas-eurofinance]]
- [[incident—20260915—vmalert-alertas-ciegas]]
Referenciado desde
- 40 caídas falsas en 4h en las gráficas de Bandwidth — ventana de consulta demasiado corta para el ciclo lento del Agente
- Botón Report en el panel de grupo (Wireless/UPS/Signage) acotado a sus miembros
- Dos alertas de salud de vmalert que nunca podían disparar (métrica inexistente)
- Modo Customize en los dashboards de Wireless, UPS, Signage y Observatory
- Rangos de 7 y 30 días en las gráficas de equipo, flota y grupo