← all branches

feat/energia-ui

needs attentionviewing older commit
3da3216 · incrementalPR #303reviewed 2026-07-13 17:15 UTC5H · 14M · 14L · 4I
The branch
Purpose
Preview/demo branch for the 7-screen energía enterprise energy management module. Not mergeable as-is: soft-launch.ts is temp-forced live for everyone, and pages use illustrative mock data from lib/demo/axoMockData.ts rather than the live ledger/detectors.
Goal
Show the energía feature running in-app on a Vercel preview with fully interactive buttons — no dead-end CTAs.
Sub-goals
  • SG-1: Port 7 pages (resumen, hallazgos, demanda, pagos, conciliación, esg, medición, sitios) as Next.js client pages under /energia/*
  • SG-2: Reground to minimal v2 design + real Axo/Neto mock data + org switcher
  • SG-3: Make demo fully interactive — all buttons open sheets (AclaracionSheet, InfoSheet) or navigate to sub-screens; CSV export wired; stat cards clickable
The changes (whole branch)
What
Adds 4 new _components files (AclaracionSheet, InfoSheet, csv.ts, sheetContent) implementing a full sheet system for the energía demo, plus incremental wiring across hallazgos, medicion, pagos, and resumen pages to replace dead-end buttons with interactive sheets and navigation.
Why
Commit 3da32163 completes the interactivity pass: every CTA in the demo now does something — finding rows open CFE claim sheets or cotización/sites sheets, 'Exportar ERP' triggers a real CSV download, stat cards navigate to sub-screens, and 'Agregar medidor' opens a context sheet.
Areas
apps/platform/src/app/[locale]/(dashboard)/energia+48201apps/platform/src/lib+250apps/platform/src/messages+280apps/platform/src/components+100packages/api+11domains/core+11
Blast
29 files, +4875/-3 across full branch. Contained within apps/platform/energia/* and lib/demo/*. No backend, no migrations, no API surface changes. soft-launch.ts override is scoped to this branch (TEMP comment, revert before merge).
TEMP soft-launch override forces energia live for all orgs — must revert before merge All UI copy is hardcoded es-MX — next-intl wiring deferred intentionally for demo phase Mock data only (lib/demo/axoMockData.ts) — not connected to live ledger/detectors
ci· No CI checks returned for PR 303 — likely still queued or not configured for this branchcoderabbit· No .coderabbit.yaml in repo

Findings · 36

correctness5

medium

illustrativeRpu pads with '4' instead of '0' — all demo RPUs start with '44'

apps/platform/src/app/[locale]/(dashboard)/energia/_components/AclaracionSheet.tsx:28

String(h).padStart(12, '4').slice(0, 12): the 32-bit hash max is 10 digits, so pad always fires and inserts leading '4' chars. Every generated RPU starts with '44' (e.g. '443523329657'). Real CFE RPUs use zero-padded sequences — a CFE-knowledgeable demo audience will notice. Fix: padStart(12, '0').

medium

Progress bar under-fills: clamped minimums leave a visible gap at the right end

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

pct = (n) => Math.max(1.5, (n/v.identified)*100). For Neto, recovered=53k (0.34%) and inProcess=110k (0.71%) both clamp to 1.5%, making the total rendered width ~98.68% — a visible gap. The three segment values also don't sum to identified (15,013,000 vs 15,520,000). Fix: make operational the residual (identified − recovered − inProcess) or drop the floor.

low

Space key on clickable cards causes page scroll before navigation

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

onKeyDown={(e) => (e.key === ' ') && router.push(...)} — space bar default scroll isn't suppressed. router.push fires but the browser also scrolls. Fix: add e.preventDefault() when e.key === ' ' before calling router.push. Affects both stat cards (lines 72 and 89).

low

Non-urgent pagos CTA hardcodes 'Un solo sitio vencido' regardless of actual p.overdue value

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

When isUrgent is false, the sheet description renders 'Un solo sitio vencido — resuélvelo antes del próximo corte.' This is true only because Axo currently has p.overdue===1. When demonstrating with another org that has 0 or 3+ overdue, the text is factually wrong. The urgent branch above correctly uses p.overdue.toLocaleString('es-MX').

low

SitesListContent: conditional rand() call in jitter branch creates fragile call-count contract

apps/platform/src/app/[locale]/(dashboard)/energia/_components/sheetContent.tsx:74

rand() is skipped for jitter when finding.mxn is null, but still called for the city. Uniform within one invocation so no current collision, but the PRNG call contract is implicit. A future per-row condition varying rand() consumption mid-loop would silently break RPU uniqueness. Low severity for a demo, but flag for productionisation.

conventions14

high

Hardcoded es-MX strings bypass next-intl in AclaracionSheet

apps/platform/src/app/[locale]/(dashboard)/energia/_components/AclaracionSheet.tsx:18

ui-patterns.md: all user-facing strings must come from messages/{locale}.json via next-intl. STEPS array, 'Estado del folio', 'Carta de aclaración (borrador)', 'Descargar carta (.txt)', and all body copy are hardcoded Spanish. Known/acknowledged for this demo branch — must be wired before merge.

high

Hardcoded es-MX strings bypass next-intl in sheetContent

apps/platform/src/app/[locale]/(dashboard)/energia/_components/sheetContent.tsx:39

ui-patterns.md: all user-facing strings must come from messages/{locale}.json via next-intl. 'Solicitud enviada a la red de instaladores Batu', 'Te contactamos en 48 h', column headers, and all copy are hardcoded. Known/acknowledged for demo branch.

high

Hardcoded es-MX strings bypass next-intl in hallazgos/page

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

STATUS_LABEL map, LIFECYCLE_STEPS labels, category labels ('Error CFE', 'Optimización') hardcoded. The energia layout.tsx already uses useTranslations('energia.nav'), confirming the namespace exists — this page should follow the same pattern before merge.

high

Hardcoded es-MX strings bypass next-intl in resumen/page

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

All body copy, labels, and CTA text hardcoded. Must use next-intl before merge.

high

Hardcoded es-MX strings bypass next-intl in pagos/page

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

All heading, label, body, and status text hardcoded. Must use next-intl before merge.

medium

Em dash used as prose punctuation in resumen/page (ui-patterns.md violation)

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

ui-patterns.md prohibits em dashes (—) in user-facing copy as punctuation. 'sitios — entre errores de CFE' uses it as a prose separator. Replace with a comma or rephrase as two sentences.

medium

Em dashes used as prose punctuation in hallazgos/page

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

Lines 112 and 115 use '—' as sentence-level punctuation ('sitios —{' '}...' and 'con CFE — el resto'). ui-patterns.md prohibits this. Rephrase to avoid the em dash.

medium

Em dashes used as prose punctuation in pagos/page

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

Line 146 ('vencido — resuélvelo antes del próximo corte') and line 224 ('por recibo — fecha de vencimiento ... — vive en la lista') use '—' as prose punctuation. ui-patterns.md prohibits this. Rephrase.

medium

Native <button> elements in medicion/page missing type="button"

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

Site selector buttons at line 166 lack type="button". Browsers default to type="submit" inside a form, risking accidental submission. The pagos/page.tsx fix (adding type="button") in this very PR establishes the pattern — apply it consistently here.

medium

KpiCard and MiniStat defined inline in medicion/page.tsx instead of _components/

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

ui-patterns.md: route-scoped components go in _components/ with underscore prefix. KpiCard (line 375) and MiniStat (line 414) are reusable presentational components defined after the page export. Move to _components/kpi.tsx.

low

Hardcoded hex colors in Recharts config instead of CSS variables

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

Lines 201, 202, 221, 222, 226 use hardcoded #7C3AED, #F1F5F9. Brand palette is defined as CSS variables (hsl(var(--primary)), etc.) for dark mode compatibility. Other chart usages in the codebase use 'hsl(var(--primary))' — follow the same pattern.

low

What-not-why JSDoc on InfoSheet

apps/platform/src/app/[locale]/(dashboard)/energia/_components/InfoSheet.tsx:13

CLAUDE.md: only comment when the WHY is non-obvious. The JSDoc block describes what the component does and lists callers — information readable from the props and callers. Remove it.

low

What-not-why JSDoc on pagosCsvRows

apps/platform/src/app/[locale]/(dashboard)/energia/_components/sheetContent.tsx:132

JSDoc 'Illustrative ERP-export rows (columns: sitio/rpu/periodo/monto/estatus)' describes the return shape (what), which is evident from the Record<string, string|number> return type. Remove per CLAUDE.md convention.

low

What-not-why JSDoc on SitesListContent

apps/platform/src/app/[locale]/(dashboard)/energia/_components/sheetContent.tsx:67

JSDoc 'Illustrative site list for "Ver sitios" / "Ver detalle"' describes what the component renders. Obvious from the function name and usage. Remove per CLAUDE.md convention.

tests8

medium

escapeCell has no unit tests — CSV quoting bugs would silently corrupt exports

apps/platform/src/app/[locale]/(dashboard)/energia/_components/csv.ts:16

escapeCell is load-bearing for exportCsv. Needed cases: comma → quoted, double-quote → doubled, newline → quoted, number → stringified, undefined → empty string. Test via exportCsv rows since escapeCell is private.

medium

exportCsv: no tests for CSV building or empty-rows edge case

apps/platform/src/app/[locale]/(dashboard)/energia/_components/csv.ts:26

Testable pure logic before the downloadFile side-effect: column order from first-row keys, BOM prefix, empty-array → empty file. Mock downloadFile and assert on the CSV string content.

medium

illustrativeRpu: no unit tests for deterministic hash output

apps/platform/src/app/[locale]/(dashboard)/energia/_components/AclaracionSheet.tsx:22

Pure function used in both letter text and SheetDescription. Tests: same input → same 12-char string, output is exactly 12 digits, empty string doesn't crash, two titles produce different RPUs. Export or test via component render.

medium

seeded PRNG: no unit tests for determinism and range

apps/platform/src/app/[locale]/(dashboard)/energia/_components/sheetContent.tsx:10

seeded(seed) drives all illustrative data generation. Tests: same seed → same sequence, different seeds diverge, output always in [0, 1), rpuFrom always returns 12 digits. Locks the stable contract and catches future PRNG drift.

low

pagosCsvRows: no tests for row-count caps or monto derivation

apps/platform/src/app/[locale]/(dashboard)/energia/_components/sheetContent.tsx:133

Test: pending > 40 → exactly 40 rows; unknown > 20 → exactly 20 rows; monto is a positive integer; all rows have exactly {sitio, rpu, periodo, monto, estatus} keys. PRNG is deterministic so snapshot tests are feasible.

low

No __tests__ directory for energia _components/ utilities

apps/platform/src/app/[locale]/(dashboard)/energia/_components/csv.ts

canonical-form.md targets a co-located __tests__/ directory for every module. The energia _components/ has four files with testable pure logic and zero tests. Add before the utilities are promoted to production.

info

buildLetter uses new Date() — not deterministic, cannot snapshot-test as-is

apps/platform/src/app/[locale]/(dashboard)/energia/_components/AclaracionSheet.tsx:31

buildLetter calls new Date() for the letter date. Inject date as a parameter before productionising so the full letter body can be asserted. No action needed on the demo branch.

info

No existing tests deleted in this diff

apps/platform/src/app/[locale]/(dashboard)/energia

Confirmed: the energia directory had no pre-existing test files before this commit. No regressions introduced.

improvement9

medium

Dynamic import of csv.ts in AclaracionSheet creates a redundant bundle chunk

apps/platform/src/app/[locale]/(dashboard)/energia/_components/AclaracionSheet.tsx:84

AclaracionSheet uses void import('./csv').then(...) for downloadFile, but sheetContent.tsx (co-bundled on the same route) statically imports './csv'. Next.js bundles csv.ts twice — once in the main chunk and once as a dynamic chunk. The async split adds a microtask delay with no benefit. Use a static import.

medium

useMemo(buildLetter, [finding]) uses object identity — memo busts on every org refetch

apps/platform/src/app/[locale]/(dashboard)/energia/_components/AclaracionSheet.tsx:78

The memo depends on the finding object reference. If the parent re-renders (org switch, query refetch), the object identity changes and the full letter is recomputed. The actual inputs are finding.title, finding.desc, finding.mxn, finding.sites — use those as deps.

medium

BillsTableContent useMemo deps include values that don't affect the PRNG seed

apps/platform/src/app/[locale]/(dashboard)/energia/_components/sheetContent.tsx:180

The seed is `${data.id}-bills` — only data.id determines the row sequence. The other deps (p.overdue, p.overdueMxn, p.pending, p.pendingMxn) affect monto amounts correctly, but a future maintainer may 'simplify' to [data.id] and silently freeze the amounts. Add a brief inline comment explaining the dep split: seed from id, amounts from p.*.

low

Two separate illustrative RPU generators will produce different values for the same finding

apps/platform/src/app/[locale]/(dashboard)/energia/_components/AclaracionSheet.tsx:22

illustrativeRpu(title) in AclaracionSheet (polynomial hash) and rpuFrom(rand) in sheetContent (PRNG draw) both produce 12-digit illustrative RPUs. They will disagree for the same finding. Extract a shared illustrativeRpu to _lib/demoUtils.ts or _lib/realData.ts.

low

FindingActionSheet dispatches on regex of a copy string — brittle routing

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

/cotiza/i.test(finding.action) couples UI routing to a Spanish copy label. A copy edit ('Pedir cotización') silently switches the component. Add an explicit sheetType discriminant ('cotizacion' | 'sites' | 'aclaracion') to the Finding type instead.

low

FindingActionSheet defined inside hallazgos/page.tsx — move to _components/ before it's reused

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

FindingActionSheet is a non-trivial composite component (routing on category + regex). Per ui-patterns.md colocation rules, move to _components/FindingActionSheet.tsx before resumen or medicion needs it.

low

pagosCsvRows is a pure data function exported from a UI component file

apps/platform/src/app/[locale]/(dashboard)/energia/_components/sheetContent.tsx:133

pagosCsvRows is only used in pagos/page.tsx for CSV export — no JSX. The file name implies UI content. Move to _lib/demoData.ts or co-locate with csv.ts to clarify what is UI and what is logic.

low

PERIODO is hardcoded to 'mayo–junio 2026' in three separate literals that already differ

apps/platform/src/app/[locale]/(dashboard)/energia/_components/AclaracionSheet.tsx:29

The period string appears as 'mayo–junio 2026' (AclaracionSheet.tsx:29), 'may-jun 2026' (sheetContent.tsx:145), and 'may–jun 2026' (sheetContent.tsx:175) — already inconsistent (full months vs abbreviated, em-dash vs en-dash). Export a single DEMO_PERIOD constant from _lib/realData.ts.

info

exportCsv silently produces a 0-byte file when rows is empty

apps/platform/src/app/[locale]/(dashboard)/energia/_components/csv.ts:28

When rows is empty, downloadFile is called with an empty string, producing a 0-byte file with no header row. When real data flows through, consider: header-only row, early no-op return, or a toast warning the user the export was empty.

History · 5 commits

  1. adcbf45needs attentionincremental1H · 1M · 4L2026-07-13 19:48
  2. 3da3216needs attentionincremental5H · 14M · 14L2026-07-13 17:15current
  3. 6859d29safeincremental0H · 1M · 1L2026-07-13 13:18
  4. d563614needs attentionincremental3H · 5M · 7L2026-07-13 04:06
  5. 30ee8dcneeds attentionfull5H · 9M · 10L2026-07-13 03:43