API de Zertipower
Carpeta: backend/ · Puerto: 3000 · Swagger: http://localhost:3000/api
· Tecnología: NestJS 10 + Prisma 5 sobre MariaDB
El núcleo del sistema. La usan el Panel, la Calculadora y el Contador, y es la única pieza que escribe en la base de datos principal.
Qué hace
- Identidad y permisos. Emite los tokens de sesión y resuelve los roles.
- Reparto de la energía. Aplica los coeficientes de reparto de cada comunidad sobre la energía generada y registra las transacciones entre miembros, priorizando el autoconsumo.
- Datadis. Descarga los consumos horarios reales de cada punto de suministro.
- Predicciones. Consumo, producción solar y meteorología.
- Cartera de cada usuario. Deriva la dirección a partir de la identidad de Google, sin depender ya de un servicio externo.
- Facturación. Integración con Stripe.
Configuración
Es el módulo con más variables, todas en el bloque environment: del servicio
backend en docker-compose.yml. Aquí van agrupadas por tema; el catálogo
exhaustivo está en Variables de entorno.
Base de datos
| Variable | Qué es |
|---|---|
DATABASE_URL | Cadena de conexión que usa Prisma |
DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE | Los mismos datos, para las consultas SQL directas |
DB_PASSWORD sólo admite letras y dígitosSe interpola tal cual dentro de DATABASE_URL sin codificar, así que un #,
/, @, :, ? o % parte la URL y el backend no arranca, con un error que
despista mucho: invalid port number in database URL. Genérala con
openssl rand -hex 32; no con base64, cuyo alfabeto incluye / y +.
Acceso con Google
Es el botón «Iniciar sesión con Google» del Panel de Administración. El recorrido completo:
Panel → GET <api>/oauth/google → Google → GET <api>/oauth/google-callback → Panel
| Variable | Qué es | ¿Obligatoria? |
|---|---|---|
GOOGLE_CLIENT_ID | Identificador del cliente OAuth. Termina en .apps.googleusercontent.com. No es secreto: viaja en la URL a la que Google redirige. | No |
GOOGLE_CLIENT_SECRET | El secreto de ese mismo cliente. | No |
GOOGLE_CALLBACK_URL | A dónde devuelve Google al usuario. | Sí, pero trae valor por defecto |
Ninguna de las dos primeras detiene el arranque. Si faltan, el sistema funciona y
sólo queda inservible ese botón: se entra con admin / admin123. Van en
pareja, eso sí — con una sola no funciona.
El Contador no pasa por aquí: delega su login en Zertiauth, que tiene su propio cliente de Google, fuera de este repositorio. Cambiar estas tres variables no afecta al Contador. Ver Contador / Smart Meter.
Cada instalación necesita su propio cliente
El cliente que venía configurado fue borrado en Google, así que no sirve para
nadie: el botón devolvía Error 401: deleted_client — «Access blocked: The OAuth
client was deleted». Y como el identificador estaba escrito dentro del
docker-compose.yml, que está versionado, no había forma de arreglarlo sin
editar un archivo del repositorio.
Ahora las dos llegan del .env y el instalador las pregunta. Si se dejan en
blanco, el sistema arranca igual y sólo queda inservible ese botón: se entra con
usuario y contraseña.
Cómo crear el cliente
Hay una guía dedicada que lo explica pantalla por pantalla, sin dar nada por sabido —dónde pulsar, qué elegir y qué pegar—:
👉 Acceso con Google, paso a paso
En resumen, en Google Cloud Console:
crear un proyecto, configurar la pantalla de consentimiento, crear un cliente de
tipo Aplicación web, y registrar como URI de retorno el valor exacto de
GOOGLE_CALLBACK_URL —que es la dirección del backend, no la del Panel—.
Después, el ID y el secreto van al .env.
Los permisos que pide el backend son sólo dos, el nombre y el correo
(userinfo.profile y userinfo.email). No pide acceso a nada más.
Después basta con reiniciar el servicio; no hace falta reconstruir:
./dev.sh up -d backend
Cuando falla, qué significa
| Lo que dice Google | Qué pasa |
|---|---|
deleted_client | El cliente ya no existe en Google. Es lo que ocurre si se reutiliza el identificador antiguo: crea uno propio. |
redirect_uri_mismatch | La URI registrada no es idéntica a GOOGLE_CALLBACK_URL. Compara las dos carácter por carácter. |
invalid_client | El ID y el secreto no son del mismo cliente, o el secreto está mal copiado. Van en pareja: no se pueden mezclar. |
Falta client_id (error 400) | GOOGLE_CLIENT_ID está vacío en el .env. |
«Acceso bloqueado», access_denied | La aplicación es externa y sigue en pruebas, y esa cuenta no está en la lista de usuarios de prueba. |
Al entrar por primera vez con Google, el backend deriva la cartera del usuario a
partir de su identificador de Google. Ese identificador lo fija Google y no
cambia al cambiar de cliente OAuth, así que crear un cliente nuevo no altera
las carteras ya existentes. Lo que sí las cambia es tocar ZERTIAUTH_APP_SECRET
o ZERTIAUTH_APP_ID — ver Derivación de carteras.
Mapas y geocodificación
| Variable | Qué es |
|---|---|
GOOGLE_MAPS_API_KEY | Clave de Google Maps. Opcional. |
El backend la usa para convertir una dirección en coordenadas
(LocationUtils.getCoordinates). Sin ella el sistema arranca igual y sólo deja
de resolver coordenadas: Google responde REQUEST_DENIED y queda anotado en el
registro.
La misma clave la usan el Panel y la Calculadora, allí como argumento de construcción. Viene vacía en el repositorio a propósito, porque es público.
Derivación de carteras
| Variable | Qué es |
|---|---|
ZERTIAUTH_APP_ID | Identificador de la aplicación. No es secreto. |
ZERTIAUTH_APP_SECRET | Clave maestra. |
La cartera de cada usuario se deriva así:
privateKey = keccak256("<idGoogle>-<ZERTIAUTH_APP_ID>-<ZERTIAUTH_APP_SECRET>")
Si cambia cualquiera de las dos, todas las carteras cambian y los usuarios
pierden el acceso a sus cuentas y a lo que tengan on-chain. Y como el
identificador de Google no es secreto, quien tenga ZERTIAUTH_APP_SECRET puede
derivar la clave privada de cualquier usuario.
Hay un detalle que sorprende a quien migra: el identificador de Google es estable por proyecto de Google Cloud. Al crear un proyecto nuevo, el mismo usuario obtiene otro identificador y, por tanto, otra cartera, aunque se conserve el secreto.
Firma de sesiones
JWT_SECRET lo comparten esta API, la del Contador y la de tiempo real, y el
valor debe ser idéntico en las tres, porque unas firman lo que otras
verifican.
El backend la reutiliza para cinco cosas distintas:
- firmar y verificar los tokens de sesión;
- cifrar y descifrar las claves privadas guardadas en la base de datos;
- derivar la cartera de cada comunidad (
JWT_SECRET+ nombre); - derivar carteras por referencia (
keccak256(referencia + JWT_SECRET)); - cifrar y descifrar las credenciales de Datadis de cada punto de suministro.
Por eso rotarla no «cierra sesiones»: deja ilegibles las claves privadas ya cifradas y las credenciales de Datadis —con lo que el sistema deja de poder descargar consumos— y cambia las direcciones de las carteras derivadas. Sobre un sistema con datos, rotarla es una migración, no un cambio de configuración.
Predicción solar
| Variable | Qué es |
|---|---|
SOLAR_API_URL | Servicio de predicción de producción |
SOLAR_API_AUTH_CODE | Código de autorización. Opcional: sin él el stack arranca, pero esa predicción falla. |
SOLAR_API_ALLOW_INSECURE_TLS | Desactiva la validación del certificado del servicio |
SOLAR_INSTALLATIONS_JSON | Configuración por punto de suministro, p. ej. {"46":{"kwp":32.4}} |
Sustituye al proveedor anterior, Wattabit, que dejó de operar: sus
credenciales devuelven 401 y las variables RADIATION_API* se mantienen sólo
porque el backend las exige para arrancar.
Correo y Stripe
SMTP_* para el envío de correo —recuperación de contraseña y avisos— y
STRIPE_* para la facturación. Con valores de ejemplo el sistema arranca
igualmente; sólo falla el envío real y el cobro.
Cómo comprobar que está vivo
curl http://localhost:3000/api
La documentación Swagger, en http://localhost:3000/api, lista todos los endpoints disponibles.