← all branches

feat/energia-ui2

needs attention
f9b8901 · incrementalPR #316reviewed 2026-07-17 19:56 UTC3H · 5M · 4L · 2I
The branch
Purpose
Wire the energia UI to the real findings API instead of hardcoded/mock data, and add a complete findings detection engine + ledger domain entity
Goal
Replace all mock data in the energía UI screens with live findings from the API; ensure no client-side customer data leaks across org boundaries
Sub-goals
  • SG-1: Domain entity — findings ledger (utility domain, FCIS pattern)
  • SG-2: Detection engine — 8 corpus-verified detectors + runner
  • SG-3: REST API — GET /findings, ts-rest contract, energia-entitled guard
  • SG-4: UI wiring — Hallazgos, Resumen, Demanda screens consume real API data
  • SG-5: Client-data security — drop client-side customer data, fix tenant isolation
The changes (whole branch)
What
This commit (f9b8901) extracts FindingRow/FindingsGroup/FindingActionSheet/STATUS_* into a shared _components/FindingRow.tsx, and wires the Demanda page alert section to useFindings({ category: 'optimization' }) replacing the mock SeverityCard grid. hallazgos/page.tsx removes now-extracted local duplicates.
Why
Demanda was the last screen with hardcoded mock alerts; wiring it to the real findings API completes the UI migration for the energia module.
Areas
apps/platform/src/app/[locale]/(dashboard)/energia+35834apps/platform/src/api+4130domains/utility/src/finding+28860apps/platform/src/lib+6671docs/specs+1220
Blast
63 files, +24015/−4 across energia UI, findings domain, and API surface. All additions (no destructive changes to existing code).
ui-only preview — no production risk findings API is read-only GET all pages still behind energia module entitlement gate
CI· CI check data not accessible from this runner (403)CodeRabbit· No .coderabbit.yaml in repo

Findings · 14

correctness3

high

Client-side type filter after server limit silently drops demand findings

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

useFindings({ category: 'optimization', limit: 200 }) fetches at most 200 findings, then filters client-side by DEMAND_FINDING_TYPES. If the org has >200 optimization findings, valid demand alerts past position 200 are silently lost. Fix: add server-side status filter to exclude dismissed findings and shrink the budget.

medium

Dismissed findings needlessly consume the limit:200 window

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

No status filter passed to useFindings, so dismissed optimization findings fill slots in the 200-item window and are discarded client-side.

info

Two types named FindingStatus in scope — aliasing would prevent future confusion

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

FindingStatus from _lib/realData (view-model) and from @batu/api (wire type) exist under the same name. Currently handled correctly via toFindingView. Consider aliasing one import.

security2

low

CSS class fragment from server-typed union — no fallback for unknown key

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

SEVERITY_BORDER[f.severity] interpolated into className. Currently safe via discriminated union. No fallback for unknown key — unmapped value silently produces no border.

low

Regex dispatch on action string is fragile if field origin changes

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

Currently safe — action is set by client-side mapper from static constants. Would become a risk if action were ever wired to a raw API field.

conventions2

medium

Em dashes in two newly-added disclaimer paragraphs

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

ui-patterns.md prohibits em dashes in user-facing copy. Both newly added paragraphs ('Datos ilustrativos — la serie por sitio…') use em dashes.

medium

Hardcoded i18n strings in new DemandAlertsSection

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

All Spanish copy in the new section is hardcoded inline. layout.tsx already uses useTranslations for this route.

tests4

high

toFindingView mapper untested — pure function with branching logic

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

Contains period regex parsing, severityOf/statusOf defensive defaults, COTIZACION_TYPES action routing, and NaN guards. All finding screens depend on this mapper. Pure function — needs only a FindingResponse fixture.

high

summarizeFindings aggregation untested — customer-visible financial KPIs

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

Computes identified/recovered/inProcess/cfeErrors headline KPIs in Hallazgos and Resumen. Incorrect dismissal exclusion or category segregation would silently misreport savings to customers. Pure function.

medium

DEMAND_FINDING_TYPES filter logic untested

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

No test verifies that demand type findings pass through, unrelated optimization types are excluded, or dismissed demand findings are filtered. useMemo can be extracted as a pure function and tested in ~10 lines.

medium

Error branch (query.data?.status !== 200) untested

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

Compound error condition gates error card vs empty-state card. Not tested for non-200 API responses (403 from module entitlement, 401 on session lapse).

improvement3

low

SitePickerModal has an unused metrics prop

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

Component declares metrics: SiteMetric[] but never reads it. Drop from props type and call site.

low

FindingsGroup null guard is redundant on the Demanda code path

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

DemandAlertsSection renders its own empty-state when alerts.length === 0, then only calls FindingsGroup with a non-empty array. The guard inside FindingsGroup never triggers on this path — document the division of responsibility as more callers are added.

info

buildSiteMetrics() over static constants could be a module-level constant

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

useMemo(() => buildSiteMetrics(), []) is correct but unnecessary — the function closes over module-level constants and could simply be SITE_METRICS = buildSiteMetrics() at module scope.

History · 2 commits

  1. f9b8901needs attentionincremental3H · 5M · 4L2026-07-17 19:56current
  2. a6ab4b4needs attentionfull6H · 12M · 9L2026-07-16 14:02