@pierre-gilles,
Primero, gracias por esta respuesta, que tiene el mérito de ser completa y honesta sobre tus limitaciones.
Para poder responderte plenamente, y no solo con un « estoy seguro de mi código », tomé el tiempo de realizar una auditoría rigurosa de la PR esta tarde. Establecí el requisito explícito de verificar que cada cambio es necesario, sólido, bien pensado, sin regresiones y, sobre todo, que las pruebas no están distorsionadas — incluso rompiendo intencionalmente el código para verificar que las pruebas detectan las regresiones (pruebas de mutación reales). Es precisamente la duda legítima que planteas sobre la IA y las pruebas, y quería responder con algo verificable, no con una afirmación.
A continuación, te presento el resultado de esta auditoría, factual y reproducible en tu entorno.
1. Sobre tu objeción central: « la PR modifica en profundidad la lógica de cálculo »
Mantengo mi respuesta anterior, pero esta vez con la verificación línea por línea. Los dos archivos donde reside la lógica de negocio son:
server/services/energy-monitoring/lib/energy-monitoring.calculateConsumptionFromIndex.js
server/services/energy-monitoring/lib/energy-monitoring.calculateCostFrom.js
He revisado cada línea modificada. Lo que está estrictamente sin cambios:
convertEnergyUnit(...) — conversiones de unidades
contracts[contract](...) — todas las fórmulas de costo (base, HP/HC, Tempo)
- Cálculo del delta de índice y gestión del reinicio del contador
- Filtro de precios por fecha + EDF Tempo +
subtract(30, 'minutes')
saveMultipleHistoricalStates / saveHistoricalState
Los únicos añadidos en calculateConsumptionFromIndex.js están encapsulados en un if (selectorSet.size > 0) (filtro por lista blanca): desactivados en ausencia de lista blanca, por lo que el comportamiento legado se conserva estrictamente para el cálculo incremental en vivo que se ejecuta cada 30 minutos.
En calculateCostFrom.js, los añadidos son:
- Análisis de las fechas de inicio/fin (nueva entrada para el modo rango)
- Filtro por selectors (encapsulado en
if (selectorSet.size > 0))
destroyStatesBetween si se proporciona endAt, de lo contrario destroyStatesFrom (comportamiento legado)
- Un
try/catch por característica de costo (robustez: una característica que falla no bloquea las otras)
La fórmula de costo en sí — aquella que te tomó meses estabilizar en base/HP/HC/Tempo — no se ha movido ni un carácter. Es verificable en unos minutos con git diff master HEAD -- server/services/energy-monitoring/lib/energy-monitoring.calculateCostFrom.js y un Ctrl+F en contracts[contract], convertEnergyUnit, energyPricesForDate.
2. Sobre tu objeción sobre las pruebas: « la IA puede introducir regresiones y adaptar las pruebas para validarlas »
Es un temor perfectamente legítimo, y es precisamente por eso que hice pruebas de mutación reales: rompí temporalmente 3 puntos críticos del código y volví a ejecutar toda la suite. El objetivo es verificar que las pruebas detectan la regresión, no que pasen con la regresión.
Aquí están las 3 mutaciones probadas y el resultado:
| Mutación aplicada |
Pruebas que fallan |
| Validación de fechas retirada del controlador |
reject invalid start date format + reject invalid end date format (2 pruebas) |
Filtro de lista blanca del motor en vivo calculateConsumptionFromIndex retirado |
skip consumption features not in whitelist selectors (1 prueba) |
Bloque finally de restauración del cursor ENERGY_INDEX_LAST_PROCESSED retirado |
restore last processed value on selector-based recalculation + restore last processed value on window error (2 pruebas) |
Total: 5 pruebas fallan precisamente en las afirmaciones correctas. Las pruebas no son un lavado de cara de la IA, realmente detectan las regresiones en las protecciones críticas. Reproducible en tu entorno: solo necesitas comentar estos 3 bloques y volver a ejecutar npm run test-service --service=energy-monitoring. Archivos restaurados después de la verificación, confirmé con git status que no hay modificaciones residuales.
También verifiqué la no regresión de las pruebas existentes:
- Las pruebas legacy modificadas (en
calculateConsumptionFromIndex.test.js) tienen únicamente un añadido de undefined como segundo parámetro (para alinearse con la nueva firma). Ninguna afirmación ha sido debilitada o eliminada.
calculateCostFromYesterday.test.js está en realidad refuerza: antes solo verificaba calledOnce, ahora verifica la firma exacta de los argumentos pasados.
- Pruebas del Controlador: el patrón
try/catch + expect.fail es reemplazado por next(error) — es el patrón correcto de Express.
3. Sobre tu propuesta « PR solo front-end con presets 1/3/6/12 meses »
Entiendo la lógica: evitar la fase de validación pesada en el negocio sin tocar el back-end. Pero esta propuesta no cubre la necesidad real de los usuarios, y es importante que lo explique:
Caso de uso n.º 1 — El recálculo histórico en dispositivos existentes añadidos al seguimiento a posteriori
Esta es la promesa del seguimiento de energía en Gladys. Muchos usuarios (incluido yo) tienen tomas Tasmota o Zigbee2mqtt en sus instancias Gladys durante varios años, a veces con 4 años de datos de índice almacenados. Cuando se añade la integración de seguimiento de energía a estos dispositivos, se debe poder recalcular el consumo y el costo en todo el historial disponible, y no solo en los últimos 12 meses.
Hoy, el único medio es el from-beginning global, que:
- recalcula para todos los dispositivos a la vez (y no solo el que acabamos de añadir)
- no llega al final en las instalaciones considerables (caso que vivo y que otros reportan)
- bloquea la solicitud HTTP del lado front durante toda la duración del cálculo (probablemente la regresión silenciosa que hace que digamos « no funciona » para las instalaciones grandes)
Un preset « últimos 12 meses » no resuelve ninguno de estos tres problemas. Una selección por característica + rango de fechas los resuelve a los tres.
Caso de uso n.º 2 — La tarifa retroactiva
Un usuario que se da cuenta de que cambió de tarifa hace 18 meses y que quiere recalcular solo ese rango está bloqueado con un preset de 12 meses.
Caso de uso n.º 3 — La granularidad
Cuando tienes 30 dispositivos seguidos y quieres corregir solo un dato en un dispositivo durante una semana, lanzar un recálculo global en 1/3/6/12 meses es desproporcionado y arriesgado para los otros dispositivos.
4. Sobre las mejoras de robustez aportadas por la PR
También quiero mencionar dos puntos en los que la PR hace el código más seguro que master:
wrapperDetached (nueva primitiva de trabajo) desacopla la solicitud HTTP del cálculo largo. Antes, el from-beginning bloqueaba la solicitud HTTP hasta el final del cálculo, lo que provocaba timeouts en las instalaciones grandes. Probablemente es la causa silenciosa de los « no funciona » que reportamos regularmente.
- El
try/finally alrededor de la restauración del cursor ENERGY_INDEX_LAST_PROCESSED protege la instancia de un estado corrupto si una ventana falla durante el recálculo. En master, si una ventana falla, el cursor queda destruido y el cálculo incremental en vivo comienza desde cero en la próxima iteración.
5. Puntos honestos que voy a corregir antes del push
Para no decirte que todo es perfecto, la auditoría también identificó 3 limpiezas menores (no bloqueantes, sin riesgo de regresión):
- Un fragmento de compatibilidad hacia atrás en
calculateCostFrom que ya no tiene utilidad (todos los llamadores internos ya pasan la nueva firma).
- Una duplicación del ~70% entre
FromBeginning y Range que puedo factorizar.
- 2-3 pruebas « pasando por casualidad » en
calculateCostFrom.test.js que no prueban realmente lo que pretenden (para endurecer o retirar — las pruebas de negocio reales permanecen en las pruebas originales no modificadas).
Me comprometo a corregir estos 3 puntos antes del corte.
6. Mi propuesta concreta
Dado lo anterior, te propongo seguir con el corte en sub-PRs, pero adaptando tu preocupación por « fase de validación incompresible »:
- PR1 :
destroyStatesBetween + wrapperDetached + sus pruebas. ~150-200 líneas. Sin lógica de negocio, solo utilidades. Fusionable rápidamente, bajo riesgo.
- PR2 : recálculo por selección de características desde el principio (sin rango de fechas). El filtro de lista blanca está encapsulado en
if (selectorSet.size > 0), por lo que el comportamiento heredado se preserva estrictamente.
- PR3 : recálculo por rango de fechas +
shouldRestoreLastProcessed. Aquí es donde se centra la « verdadera » revisión de negocio.
- PR4 : ajustes de la interfaz de usuario.
En PR3 — la que más validación te requiere — estoy listo para:
- Proporcionarte conjuntos de datos de prueba reproducibles a partir de mi producción (salida SQL anonimizada) que puedas volver a ejecutar en tu entorno
- Añadir documentación en el código sobre
shouldRestoreLastProcessed
- Hacerte una demostración en vídeo del comportamiento antes/después si ayuda
Si validas este desglose, empiezo por PR1 esta semana. Si prefieres que espere, dime con un plazo — aunque sea amplio — y cerraré la PR de nuevo con tranquilidad.
Gracias por tu tiempo, y disculpa si la respuesta es densa — he preferido darte hechos en lugar de afirmaciones.