feat/ui-impact
needs attentionviewing older commit5b8a252 · incrementalpre-PRreviewed 2026-07-24 00:19 UTC3H · 6M · 9L · 6I- Purpose
- Energia module UI demo for enterprise customers (Axo RFP). Interactive dashboard using synthetic data to show Batu for Enterprise capabilities — multi-site consumption analysis, findings detection, demand alerts, dollar-layer savings.
- Goal
- Add like-for-like consumption comparison (Comparativo) page with three switchable benchmarks (vs. prior year, vs. cohort expectation, vs. budget) and trim nav to the active demo screens.
- Sub-goals
- SG-1: Comparativo page with benchmark selector, KPI row, overlay chart, outlier list, brand table
- SG-2: Nav trim — remove Medicion and Sitios, add Comparativo between Hallazgos and Demanda
- SG-3: i18n keys for comparativo in en.json and es.json
- What
- Added comparativo/page.tsx (313 lines) — a 'use client' React page with six useMemo calculations driving a benchmark selector, three KPI cards, a Recharts line chart (actual vs expected overlay), an outlier store list, and a per-brand table. Modified energia/layout.tsx to add Comparativo nav item and remove Medicion/Sitios. Added comparativo key to both message files.
- Why
- Axo RFP demo requires a like-for-like consumption comparison view — the ESG module for kWh instead of tCO₂e — answerable against three benchmarks their energy team uses.
- Areas
- apps/platform/src/app/[locale]/(dashboard)/energia/comparativo/page.tsx+313−0apps/platform/src/app/[locale]/(dashboard)/energia/layout.tsx+2−4apps/platform/src/messages/en.json+1−0apps/platform/src/messages/es.json+1−0
- Blast
- 4 files, +317/-4. UI-only, no API or domain changes. Blast radius is limited to the energia demo module.
Findings · 11
correctness2
Overlay chart shows spurious zero for benchmark line when prior periods are outside demo date range
apps/platform/src/app/[locale]/(dashboard)/energia/comparativo/page.tsx:109
compareWindow (timeFrame.ts:76) filters out prior-year periods absent from AXO_PERIODS. AXO_PERIODS starts at 2024-04, so for the '24m' preset, PRIOR_WINDOW has 12 entries while CURRENT_WINDOW has 24. The overlay memo at line 109 zips by index: `PRIOR_WINDOW[idx]` is undefined for idx >= 12, and the guard `priorPeriod ? ... : 0` forces esperado to 0. The chart renders the benchmark line crashing to zero for the first half of a 2-year window. Fix: filter out entries where priorPeriod is undefined (omit those points), or pass null for esperado so Recharts breaks the line instead of drawing zero.
Median uses upper-middle element for even-length arrays
apps/platform/src/app/[locale]/(dashboard)/energia/comparativo/page.tsx:81
g[Math.floor(g.length / 2)] on a sorted array returns the upper-middle element for even-length arrays. True median averages the two middle values. This biases the 'pares' benchmark anchor for the enterprise demo — a small but systematic error visible in a side-by-side with actual statistics.
security1
Demo bundle (axoMockData) ships to all authenticated users — needs a teardown ticket
apps/platform/src/lib/demo/axoMockData.ts:1
The axoMockData.ts module already has a deletion note ('Delete this directory entirely once the RFP demo concludes'). The comparativo page adds another consumer. Track teardown explicitly so the demo bundle is removed post-RFP and doesn't inflate client payload permanently.
conventions6
Multi-paragraph JSDoc block violates CLAUDE.md comment rule
apps/platform/src/app/[locale]/(dashboard)/energia/comparativo/page.tsx:26
Lines 26–39 contain a 13-line JSDoc block explaining WHAT the component does and how its three benchmarks work. CLAUDE.md states explicitly: 'Never write multi-paragraph docstrings or multi-line comment blocks — one short line max.' The non-obvious 'why' is the peer-benchmark detector concept; everything else is derivable from the BENCHMARKS constant and BUDGET_FACTOR below. Replace with a single-line comment at most, or none.
Kpi component defined in page.tsx instead of _components/
apps/platform/src/app/[locale]/(dashboard)/energia/comparativo/page.tsx:294
The Kpi function (line 294) is inlined at the bottom of page.tsx. Both ui-patterns.md and canonical-form.md specify route-scoped components belong in _components/ (e.g. comparativo/_components/Kpi.tsx). Other energia routes follow this: energia/_components/KpiTile.tsx already exists as a shared component this one may duplicate. Move to _components/ or reuse the existing KpiTile.
All user-facing strings hardcoded — no i18n
apps/platform/src/app/[locale]/(dashboard)/energia/comparativo/page.tsx:1
The page has no useTranslations call and no next-intl import. Every label is a hardcoded Spanish string: benchmark labels ('vs. Año pasado', 'vs. Expectativa (pares)', 'vs. Presupuesto'), card titles, KPI labels, table headers, section subtitles. Every other energia page uses useTranslations('energia'). Hardcoded strings break the EN locale and make this page non-localizable. Add translations to en.json/es.json and wire useTranslations like sibling pages.
Em dashes in rendered JSX — violates copy-style rule
apps/platform/src/app/[locale]/(dashboard)/energia/comparativo/page.tsx:205
Lines 205, 230, 260 render em dash characters (—) directly in user-facing copy: 'Consumo mes a mes — actual vs ...', 'consumen más que sus comparables' (with em dash prefix), 'Por marca — actual vs ...'. ui-patterns.md §Copy Style: 'No em dashes (—) in user-facing copy.' Replace with en dashes (–) or colons per style guide.
Orphaned i18n keys for removed nav items (medicion, sitios) still in both message files
apps/platform/src/messages/es.json:335
layout.tsx removes medicion and sitios from nav, but both keys remain in en.json (lines 343–344) and es.json (lines 335–336) under energia.nav. Dead i18n keys accumulate translation debt. Remove them from both message files to keep the namespace clean.
Three eslint-disable react-hooks/exhaustive-deps suppressions masking expectedFor pattern
apps/platform/src/app/[locale]/(dashboard)/energia/comparativo/page.tsx:85
expectedFor is a plain arrow function in the render scope, called inside totals, byBrand, and outliers useMemos with the lint warning suppressed rather than fixing the root cause. The correct fix: wrap in useCallback([benchmark, medianGrowth]) and list it as a dep, or inline the ternary directly in each memo. The suppressions are not wrong today but are a maintenance trap.
tests1
Six useMemo bodies encode testable calculation logic but are embedded in the component
apps/platform/src/app/[locale]/(dashboard)/energia/comparativo/page.tsx:67
perStore, medianGrowth, totals, overlay, byBrand, outliers all have non-trivial logic (outlier threshold 1.08×, budget factor 0.97, period-index alignment). Extracting them as pure functions would enable unit tests. For a demo page this is optional, but the overlay zero-bug (above) is exactly the class of error a test over partial prior windows would catch.
improvement1
expectedFor should be useCallback to eliminate eslint suppressions across four useMemos
apps/platform/src/app/[locale]/(dashboard)/energia/comparativo/page.tsx:85
Wrap expectedFor in useCallback([benchmark, medianGrowth]) and list it as a dep in totals, overlay, byBrand, and outliers. This removes all three eslint-disable suppressions and makes the dependency graph explicit. Alternatively, inline the ternary directly — it's short enough.
History · 15 commits
- 96ca7d7needs attentionincremental0H · 9M · 12L2026-07-25 16:40
- 25516f7needs attentionincremental5H · 7M · 8L2026-07-25 03:29
- a7f8d64needs attentionincremental0H · 4M · 5L2026-07-25 01:56
- 7fc4ef0needs attentionincremental2H · 6M · 8L2026-07-24 21:04
- 027e5eaneeds attentionincremental4H · 9M · 8L2026-07-24 20:04
- 95a101eneeds attentionincremental3H · 6M · 5L2026-07-24 16:09
- 5d0d186needs attentionincremental1H · 3M · 5L2026-07-24 15:36
- c3c5121needs attentionincremental2H · 1M · 5L2026-07-24 15:17
- ded4e61needs attentionincremental2H · 3M · 9L2026-07-24 14:22
- 312a1f4needs attentionincremental1H · 4M · 4L2026-07-24 04:01
- b48af56needs attentionincremental1H · 6M · 6L2026-07-24 03:14
- 6d07cc8needs attentionincremental2H · 4M · 5L2026-07-24 00:50
- 261b55eneeds attentionincremental5H · 11M · 6L2026-07-24 00:38
- 5b8a252needs attentionincremental3H · 6M · 9L2026-07-24 00:19current
- f29cc5bneeds attentionfull9H · 17M · 11L2026-07-23 23:07