← all branches

feat/energia-ui

needs attentionviewing older commit
d563614 · incrementalPR #303reviewed 2026-07-13 04:06 UTC3H · 5M · 7L · 6I
The branch
Purpose
Build and preview the Energia enterprise energy management module — a new product tier for multi-site corporate energy consumers in Mexico.
Goal
Demo-ready energy dashboard screens for enterprise prospects, showing real Grupo Axo and Tiendas Neto customer data with a live org switcher.
Sub-goals
  • SG-1: Module scaffold — per-org toggleable, greyed pre-launch (commit 3716a44a)
  • SG-2: Port axo-rfp demo pages into energia route for preview (commit 30ee8dc9)
  • SG-3: Reground money screens to minimal design + real Axo/Neto data + org switcher (commit d5636141)
The changes (whole branch)
What
Latest commit replaces ~2,700 lines of complex analytics built on axoMockData with ~800 lines of clean minimal UI. Introduces EnergiaOrgContext and OrgSwitcher for switching between two real customer orgs; adds realData.ts with hardcoded production figures; simplifies hallazgos, pagos, and resumen pages to consume real data.
Why
The axoMockData-based analytics showed synthetic data. Client demos need real impact numbers to close deals. This commit trades analytical depth for clarity and authenticity in the money screens.
Areas
apps/platform+42451packages/api+11domains/core+11
Blast
23 files, +4247/-3 across 3 areas; entirely within apps/platform/src/app/(dashboard)/energia/ (21 files) plus minor touches to soft-launch.ts, DashboardSidebar.tsx, messages/*.json, organization.type.ts, and organization.schemas.ts.
TEMP soft-launch gate bypass — must revert before merge Real customer financial data in client JS bundle No server-side entitlement enforcement on /energia routes
typecheck· not run in this reviewtests· no CI checks on this PRcoderabbit· no .coderabbit.yaml

Findings · 22

correctness6

medium

Lifecycle stepper: en_proceso marks wrong step (i<=1 instead of i<=2)

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

The `reached` expression for `en_proceso` is `i <= 1`, marking steps 0 and 1 ('Aclaracion generada') as complete. Step 2 is 'En seguimiento con CFE' — the exact phase en_proceso represents — yet it renders as an empty circle. Should be `i <= 2`. For Neto's featured finding (status en_proceso) this shows checkmark checkmark empty empty instead of correct checkmark checkmark checkmark empty — a visible error in the demo's primary CTA card.

low

Hardcoded 'Un solo sitio vencido' copy not data-driven

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

CTA subtitle is hardcoded and only accurate for Axo (p.overdue === 1). Would be factually wrong if a new org has 0 or 2 overdues with !isUrgent.

low

Neto value bar fills ~98.7% — 507k MXN gap in data design

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

For Tiendas Neto: recovered(53k) + inProcess(110k) + operational(14,850k) = 15,013k vs identified(15,520k). Three segments fill only 98.7%, leaving a visible muted gap. Cosmetic; legend values are correct.

low

pct() floor can cause segments to sum > 100% with extreme data

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

Math.max(1.5, ...) applies a 1.5% floor per segment. If any two values are very small, three segments can sum > 100%. overflow-hidden prevents layout break but legend would misrepresent proportions. Latent with current data.

info

SVG gradient id='spendFill' is global — fragile if chart ever multi-instanced

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

Static SVG gradient id is document-global. Not a bug in current single-chart layout, but would cause incorrect fills if rendered more than once simultaneously.

info

{featured && ...} guard correctly handles undefined cfeFindings[0]

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

When cfeFindings is empty, cfeFindings[0] returns undefined. React JSX {undefined && ...} renders nothing. All featured.* refs are inside the guard. Correct.

security5

high

Real customer financials hardcoded in client-side JS bundle

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

The file's own comment says 'INTERNAL: real customer data — do not expose outside the app,' but every consumer carries 'use client', so the entire ENERGIA_DATA object is bundled and readable in DevTools Sources. This includes Grupo Axo annual spend (45M MXN), month-by-month series, payment amounts, CFE finding values, carbon emissions, and fee structure; and Tiendas Neto: 309M MXN annual spend, 20.8M pending, 6.4M unknown-status payments, per-entity revenue splits, RENE obligation flag. Anyone with the preview URL can exfiltrate both clients' complete financial profiles. Fix: move real figures server-side (server component or API call gated on org membership), or replace with anonymized stand-ins in the client bundle.

high

Cross-tenant disclosure: org switcher has no auth or membership check

apps/platform/src/app/[locale]/(dashboard)/energia/_lib/orgContext.tsx

OrgSwitcher lets any authenticated user freely toggle between Grupo Axo and Tiendas Neto data with no org-membership check. In a multi-tenant SaaS this is a cross-tenant disclosure: a Neto employee could view Axo's energy spend, payment posture, and CFE dispute details, and vice versa. Even for an internal demo, if both clients log in to the preview they see each other's data by clicking the switcher.

high

Soft-launch gate bypassed — all authenticated users can reach /energia

apps/platform/src/lib/soft-launch.ts

The gate has a 'TEMP: energia live for preview demo — revert before merge' comment but is already commented out, making the route reachable for every authenticated user. If this branch merges without reverting, all production users get unrestricted access to both customers' real financial data. There is no CI check, lint rule, or test assertion that would catch a missed revert.

medium

Confidential per-client pricing data exposed in bundle

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

Bundle exposes each customer's negotiated per-site monthly fee (Axo: 130 MXN; Neto: 25 MXN), annual contract values (414,960 MXN and 554,100 MXN), and value-multiple figures (3.7x and 28x). Competitors or clients who inspect the bundle can learn Batu's pricing strategy and discount depth.

low

'revert before merge' comment is the only guard against prod data leak

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

No CI check, lint rule, or test assertion catches a merge with real data still present. Consider a lint rule that fails if customer names appear in the client bundle, or replace real data with anonymized stand-ins.

conventions6

medium

Em-dash in user-facing JSX copy (against ui-patterns rule)

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

ui-patterns.md says no em dashes in user-facing copy. Found in resumen/page.tsx and pagos/page.tsx. Use a comma, colon, or en-dash instead.

low

Native <button> without type="button" in pagos

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

Raw <button> without type="button" defaults to submit in form contexts and bypasses the shared <Button> component's focus ring and active scale. Use <Button variant="ghost" size="sm"> instead.

low

JSDoc comment on EnergiaHeader describes what not why

apps/platform/src/app/[locale]/(dashboard)/energia/layout.tsx:21

Per CLAUDE.md: only comment when WHY is non-obvious. '/** Demo header: active org + the customer switcher (Axo / Neto). */' is readable from the code. Remove it.

info

Button size=xs and Badge variant=warning/success are valid design-system variants

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

size=xs is defined in packages/ui/src/components/button.tsx. Badge warning/success variants are documented and used throughout. No issue.

info

TailwindCSS v4 classes (border-l-red-500, from-emerald-500/[0.06]) are safe

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

Severity border classes are full string literals in SEVERITY_BORDER record — Tailwind v4 content scanner detects them. Arbitrary opacity /[0.06] is valid v4 syntax. No purging risk.

info

No ADR-016 (FCIS) concerns — pure UI/data change

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

No imports from domain shells, queries, or decisions. All data consumed from _lib/realData.ts (hardcoded const). FCIS boundaries fully respected.

tests1

low

fmtMxn() branching logic worth a unit test before production promotion

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

fmtMxn() has two code paths with numeric thresholds and edge cases. A short Vitest spec would lock the contract before reuse in production screens. Not a blocker for the demo branch.

improvement4

medium

Goal-gradient bar JSX and pct() duplicated in resumen and hallazgos

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

Identical pct() helper and the 20-line goal-gradient bar block appear verbatim in both pages. Extract pct as valueBarPct() in _lib/realData.ts and the bar into _components/ValueGradientBar.tsx. Removes ~45 lines of duplication.

medium

Two parallel MXN formatters: fmtMxn and formatMxn from axoMockData

apps/platform/src/lib/demo/axoMockData.ts

axoMockData.ts exports formatMxn() (no compact option); _lib/realData.ts exports fmtMxn() (superset). demanda/ and sitios/ pages still use the axoMockData version. Unify on fmtMxn and remove the duplicate.

low

Lifecycle stepper reached logic has noisy '&& true'

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

(featured.status === 'verificado' && true) is redundant. Cleaner: const reachedUpTo = featured.status === 'verificado' ? Infinity : featured.status === 'en_proceso' ? 2 : 0; const reached = i <= reachedUpTo;

info

All user-facing strings hardcoded Spanish — pre-launch i18n extraction needed

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

Acceptable for demo branch. The layout nav already uses useTranslations('energia.nav'). All page copy will need extraction into messages/es.json before productionizing.

History · 5 commits

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