feat/energia-ui2
needs attentionf9b8901 · incrementalPR #316reviewed 2026-07-17 19:56 UTC3H · 5M · 4L · 2I- 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
- 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+3583−4apps/platform/src/api+413−0domains/utility/src/finding+2886−0apps/platform/src/lib+667−1docs/specs+122−0
- Blast
- 63 files, +24015/−4 across energia UI, findings domain, and API surface. All additions (no destructive changes to existing code).
Findings · 14
correctness3
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.
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.
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
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.
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
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.
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
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.
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.
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.
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
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.
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.
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.