Single-page app React (Vite) servida por un backend Node.js ligero en el mismo contenedor, sin Docker. Backend lee Home Assistant en local (WebSocket), expone API REST + SSE al frontend por el mismo puerto, y NPM (Nginx Proxy Manager) pone SSL delante.
- Servidor HTTP: Hono +
@hono/node-server(≈ Express pero más ligero y con tipado) - WebSocket HAOS:
ws+ protocolo nativo de Home Assistant (subscribe_entities,recorder/statistics_during_period). Cliente propio enha.jscon reconexión automática y RPC por id - SQLite:
better-sqlite3(síncrono, rapidísimo, prebuilt por plataforma). Sin ORM: queries directas + WAL + migracionesALTER TABLEtolerantes. Retiene el histórico propio para no depender de la purga de HA - Auth: cookie
id.hmachttpOnly 30d, sesiones en SQLite,Securecondicional porx-forwarded-proto, rate-limit de login en memoria - SSE:
streamSSEde Hono para push de datos en vivo al frontend, con heartbeat y throttle - Despliegue: systemd (
helios.service) con usuario dedicado +AmbientCapabilities=CAP_NET_BIND_SERVICEpara :80 sin root
- Build:
vite buildcon sourcemaps y SPA fallback - Componentes: 40+ de shadcn/ui (dropdown, tabs, accordion, etc.) +
framer-motionpara animaciones +rechartspara gráficas +gsappara el arco solar - Estilo: Tailwind CSS v3 con tema claro/oscuro gestionado via CSS variables + clase
dark - Provider de datos: contrato síncrono (
EnergyDataApi) con cachés en refs y fetch perezoso. Los componentes consumen getters sin conocer HTTP. Eventos en vivo vía SSE.suncalcpara amanecer/atardecer local si el usuario elige ubicación en Ajustes - Auth:
AuthGate→Login(página propia). Eventohelios-unauthorizedpara volver al login automáticamente en cualquier 401
- En vivo:
subscribe_entitiesa los sensores de potencia (Solis, Fox pinza, 3 medidores Tongou, batería, scraper, sun, weather, temperatura exterior) - Curva del día (5 min):
recorder/statistics_during_period(types:["mean"]), retención ~10 días - Histórico diario:
recorder/statistics_during_period(types:["state","sum"],period:"day")
- Conectar a HAOS por WebSocket, no por REST polling
- Para UMs diarias: diff de
sum, nuncastate X-Accel-Buffering: noimprescindible en SSE detrás de NPM- Regla de sanidad: caps físicos por campo + offsets para glitches de contadores
- Verificar el frontend contra la API real con navegador headless (Playwright), no solo curl
- Systemd + CAP_NET_BIND_SERVICE en vez de Docker para LXCs dedicadas
Ver ARCHITECTURE.md para el detalle completo de cada capa y contrato.