← all branches

feat/ui-impact

needs attentionviewing older commit
5b8a252 · incrementalpre-PRreviewed 2026-07-24 00:19 UTC3H · 6M · 9L · 6I
The branch
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
The changes (whole branch)
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+3130apps/platform/src/app/[locale]/(dashboard)/energia/layout.tsx+24apps/platform/src/messages/en.json+10apps/platform/src/messages/es.json+10
Blast
4 files, +317/-4. UI-only, no API or domain changes. Blast radius is limited to the energia demo module.
demo-only page (synthetic data) no i18n wiring nav items removed (medicion, sitios still exist as routes)
typecheck· node_modules not installed on runnerci· no GitHub Actions runs found for this branchcoderabbit· no .coderabbit.yaml in repo

Findings · 11

correctness2

medium

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.

low

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

low

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

high

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.

high

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.

high

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.

medium

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.

medium

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.

medium

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

medium

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

medium

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

  1. 96ca7d7needs attentionincremental0H · 9M · 12L2026-07-25 16:40
  2. 25516f7needs attentionincremental5H · 7M · 8L2026-07-25 03:29
  3. a7f8d64needs attentionincremental0H · 4M · 5L2026-07-25 01:56
  4. 7fc4ef0needs attentionincremental2H · 6M · 8L2026-07-24 21:04
  5. 027e5eaneeds attentionincremental4H · 9M · 8L2026-07-24 20:04
  6. 95a101eneeds attentionincremental3H · 6M · 5L2026-07-24 16:09
  7. 5d0d186needs attentionincremental1H · 3M · 5L2026-07-24 15:36
  8. c3c5121needs attentionincremental2H · 1M · 5L2026-07-24 15:17
  9. ded4e61needs attentionincremental2H · 3M · 9L2026-07-24 14:22
  10. 312a1f4needs attentionincremental1H · 4M · 4L2026-07-24 04:01
  11. b48af56needs attentionincremental1H · 6M · 6L2026-07-24 03:14
  12. 6d07cc8needs attentionincremental2H · 4M · 5L2026-07-24 00:50
  13. 261b55eneeds attentionincremental5H · 11M · 6L2026-07-24 00:38
  14. 5b8a252needs attentionincremental3H · 6M · 9L2026-07-24 00:19current
  15. f29cc5bneeds attentionfull9H · 17M · 11L2026-07-23 23:07