← all branches

feat/ui-impact

needs attentionviewing older commit
312a1f4 · incrementalpre-PRreviewed 2026-07-24 04:01 UTC1H · 4M · 4L · 3I
The branch
Purpose
UI impact branch for the energía module — shipping a polished, demo-safe Demanda page with attributed color coding and real RPU wiring for the CFE aclaración letter generator.
Goal
Replace illustrative/hash-derived RPUs with real API RPUs in the aclaración letter, fix severity attribution so demand and FP are colored by their own driver (not OR'd into one opaque badge), and add a DriverLegend that explains every color and its action.
Sub-goals
  • SG-1: Real RPU from API flows to Finding view model and AclaracionSheet
  • SG-2: Attributed color helpers + DriverLegend explain colors by driver
  • SG-3: Severity ranking tracks worst driver separately per site
  • SG-4: Sub-chart annotations and 'Ilustrativo' badges frame demo data clearly
  • SG-5: Nav label updated to 'Demanda y FP' in both locales
The changes (whole branch)
What
Removed illustrativeRpu() hash function; added rpu?: string to Finding interface and rpu passthrough in toFindingView(); updated AclaracionSheet to use finding.rpu ?? 'por confirmar'; added demandTextColor/fpTextColor helpers, DriverLegend component, per-driver severity ranking, sub-chart annotations, Ilustrativo badges, and updated page title/description.
Why
Demo safety: the previous letter used a hash-derived fake RPU with a 'datos ilustrativos' disclaimer; the real RPU is now in the API so the letter can use it. UX clarity: a red badge on a site must always resolve to a single cause (demand rebase vs FP penalty), and the legend must name that cause and its corrective action.
Areas
apps/platform/src/app/[locale]/(dashboard)/energia+9642apps/platform/src/messages+22
Blast
6 files, +98/-44 lines; scope is fully contained to the energía Demanda UI and the Finding view model type. No backend, domain, or API contract changes.
demo-data ui-only pre-PR
typecheck· not run in this reviewtests· no CI signal available for pre-PR branchcoderabbit· no .coderabbit.yaml present

Findings · 13

correctness2

low

avgFp3m NaN when site has no recent bills

apps/platform/src/app/[locale]/(dashboard)/energia/demanda/page.tsx:137

`avgFp3m = recentBills.reduce(...) / recentBills.length` produces NaN when recentBills is empty. Not triggered today on static mock data (all AXO sites have 24 billed periods), but if ever fed real API data a site with < 3 billed periods will silently produce NaN for avgFp3m, making every downstream comparison (`avgFp3m < 0.9`, fpTextColor, fpSeverity) return false. Fix: `recentBills.length > 0 ? sum / recentBills.length : 1.0`.

info

f.rpu ?? undefined is a no-op (rpu is non-nullable in FindingResponse)

apps/platform/src/app/[locale]/(dashboard)/energia/_lib/findingView.ts:150

FindingResponse.rpu is typed as `string` (never null), so `f.rpu ?? undefined` always evaluates to `f.rpu`. The nullish coalescing is harmless but misleading — implies the field could be undefined at runtime when the schema guarantees it cannot.

conventions4

high

demanda/page.tsx has zero i18n coverage — all display strings hardcoded Spanish

apps/platform/src/app/[locale]/(dashboard)/energia/demanda/page.tsx

Page title, subtitle, DriverLegend labels, sub-chart annotations, badge copy ('En vivo · ledger de hallazgos', 'Ilustrativo') are all hardcoded Spanish literals. The project convention requires all user-facing strings in messages/{locale}.json via next-intl. This commit adds several new hardcoded strings on an already-uncovered page. The nav label ('Demanda y FP') IS correctly in both locale files — the gap inside the page is more visible.

medium

rpu comment in realData.ts ('from the API') is misleading against the SECURITY annotation

apps/platform/src/app/[locale]/(dashboard)/energia/_lib/realData.ts:44

The file's SECURITY header (lines 3-4) says it must contain NO real RPUs. The rpu?: string field is type-level only and PLACEHOLDER_FINDINGS never sets it — no real data ships in this file. But the comment '// the finding's real RPU (from the API)' implies the opposite to a future contributor, suggesting it's acceptable to populate rpu here. The comment should clarify that this field is only populated by findingView.ts (the API projection path), not by synthetic data.

low

Arbitrary Tailwind font-size values (text-[10px], text-[11px]) bypass the design-system scale

apps/platform/src/app/[locale]/(dashboard)/energia/demanda/page.tsx

TailwindCSS v4 with a design-system scale — arbitrary values circumvent consistent typography. Closest equivalent is text-xs (12px); if these micro-label sizes are intentional, add them to the theme config.

info

WHAT-style comments on storeId/storeName fields in realData.ts

apps/platform/src/app/[locale]/(dashboard)/energia/_lib/realData.ts:45

Comments '// details.storeId — links a consumption finding to its real series' and '// details.storeName — display label when present' describe what the fields are, not why a non-obvious decision was made. Project convention restricts comments to WHY reasoning.

tests4

medium

toFindingView rpu passthrough not asserted in findingView.test.ts

apps/platform/src/app/[locale]/(dashboard)/energia/_lib/__tests__/findingView.test.ts:66

The existing test calls toFindingView() but only checks v.impactKind and v.title. The new rpu field is never asserted — a regression that drops or misnames it would be invisible. Add: expect(v.rpu).toBe('123456789012') plus a case with rpu: null to verify the ?? undefined coercion.

medium

demandTextColor and fpTextColor branch logic untested

apps/platform/src/app/[locale]/(dashboard)/energia/demanda/page.tsx:38

Both are pure functions with distinct threshold boundaries (pct: 100, 85; fp: 0.9) but are unexported module-level consts inside a 'use client' page. Cheapest fix: extract to demanda/_lib/metrics.ts, export, and unit-test boundary values. The boundary at pct === 100 (not > 100) and pct === 85 are silent correctness questions that tests would pin.

medium

buildSiteMetrics severity tie-break logic untested

apps/platform/src/app/[locale]/(dashboard)/energia/demanda/page.tsx:131

The rank comparison has three meaningful paths: demand wins outright, FP wins outright, and tie (both critico — rank equal, demand wins by >=). None is covered. Function is unexported; extraction to demanda/_lib/metrics.ts would allow unit testing.

info

illustrativeRpu deletion leaves no orphaned tests; buildLetter fallback untested

illustrativeRpu() had no tests before deletion — nothing to clean up. The new 'por confirmar' fallback path in buildLetter() (when finding.rpu is undefined) is also untested, but low urgency given the demo context.

improvement3

low

rank lookup object recreated inside the per-site loop — hoist to module level

apps/platform/src/app/[locale]/(dashboard)/energia/demanda/page.tsx:150

const rank: Record<Sev, number> = { critico: 2, atencion: 1, saludable: 0 } is allocated fresh inside the per-site map body of buildSiteMetrics(). It contains no site-specific data. Hoist to module level alongside demandTextColor/fpTextColor — consistent with the file's existing pattern for static helpers.

low

rpu: f.rpu ?? undefined — simplify to rpu: f.rpu

apps/platform/src/app/[locale]/(dashboard)/energia/_lib/findingView.ts:150

FindingResponse.rpu is string (non-optional). The ?? undefined is a no-op that misleads the reader. Write rpu: f.rpu to match the actual type contract.

info

Extract metrics helpers from page.tsx to testable module

apps/platform/src/app/[locale]/(dashboard)/energia/demanda/page.tsx

demandTextColor, fpTextColor, and the rank logic in buildSiteMetrics are pure functions co-located in a 'use client' page component. Moving them to demanda/_lib/metrics.ts would make them exportable and unit-testable without mounting the component. This also resolves the tests lens findings on the same functions.

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:01current
  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:19
  15. f29cc5bneeds attentionfull9H · 17M · 11L2026-07-23 23:07