# ESP Project (Modernizado)

Sistema para gestionar entidades, sectores, dispositivos y puertos con frontend en HTML/TailwindCSS + Bootstrap y backend PHP con router, respuestas JSON unificadas, variables de entorno y MQTT desde backend. Estados en tiempo real vía Server-Sent Events (SSE).

## Cambios recientes (2025-09-17)

- Login: agregado checkbox "Recordar" (Tailwind). Si está activo, se guardan cookies `remember_user` y `remember_pass` de larga duración y se prellenan en el próximo acceso.
- Íconos: se centralizó el uso de SVG en `assets/iconos.json` y se inyectan automáticamente vía `layouts/layout.php` (`window.loadIcons()` y `window.applyIcons()`).
- Dashboard: se preserva la IP entre refresh empleando `lastKnownIP`. Se añadió botón "Mqtt" que abre un modal con JSON del dispositivo (estado SSE, IP, pines y firmware si está disponible).
- Firmware: se añadió el campo `firmware` en `data/dispositivos.json`. Tras OTA exitosa (detectada por SSE `reboot`), se persiste la versión del manifest vía `POST /api/edit_dispositivo`.

## Requisitos
- PHP 8.0+
- Composer
- Broker MQTT accesible (TLS opcional)


## Variables de Entorno (.env)
- MQTT_HOST, MQTT_PORT, MQTT_TLS
- MQTT_USERNAME, MQTT_PASSWORD, MQTT_CLIENT_ID
- CORS_ALLOW_ORIGIN (por defecto `*`)
- API_BASIC_USER, API_BASIC_PASS (para endpoints que lo requieran)

Ejemplo en `/.env.example`.


## Arquitectura
 - Presentación: `index.php` (router raíz) que renderiza vistas PHP en `paginas/` con `layouts/layout.php`.
   - Páginas clave: `paginas/index.php` (lista y gestión), `paginas/dashboard.php` (detalle), `paginas/puertos.php`, `paginas/flash.php`, `paginas/login.php`.
   - Frontend: `assets/js/layout.js`, `assets/js/apiClient.js`, `assets/js/auth.js`, CSS en `assets/css/layout.css`.
   - Íconos: `assets/iconos.json` + utilidades globales en `layouts/layout.php`.
 - API/Router: `api/index.php` con rutas vía `?r=...`.
 - Bootstrap API: `api/bootstrap.php` (CORS, helpers JSON, Dotenv). Si se define `NON_JSON`, no fuerza Content-Type JSON (usado por SSE).
 - MQTT Backend:
   - Publicar: `api/mqtt_publish.php` usando `php-mqtt/client`.
   - Estados SSE: `api/sse_states.php` se suscribe a MQTT y emite eventos `state`, `reboot`, `pin` e `ip`.
 - Datos: Archivos JSON (`/data/*.json`).

## Endpoints (router `api/index.php`)
- Autenticación: salvo `login` y `sse_states`, todas las rutas requieren sesión activa (`api/login`).
- GET `api/index.php?r=get_dispositivos`
- POST `api/index.php?r=add_dispositivo`
- POST `api/index.php?r=edit_dispositivo`
- POST `api/index.php?r=update_dispositivo`
- POST `api/index.php?r=delete_dispositivo`
- GET `api/index.php?r=get_sectores`
- POST `api/index.php?r=add_sector`
- POST `api/index.php?r=update_sector`
- POST `api/index.php?r=delete_sector`
- GET `api/index.php?r=get_puertos`
- POST `api/index.php?r=update_puertos`
- POST `api/index.php?r=asignar_puertos`
- POST `api/index.php?r=mqtt_publish` (publicación MQTT backend)
- POST `api/index.php?r=add_entidad`
- GET `api/index.php?r=sse_states` (SSE: stream de estados desde backend)
- POST `api/index.php?r=login`
- POST `api/index.php?r=logout`
- GET `api/index.php?r=me`

### Detalles de endpoints

1) get_dispositivos
- Metodo: GET
- URL: `api/index.php?r=get_dispositivos`
- Body: ninguno
- Respuesta: objeto cuyas claves son `chipid` y valor el dispositivo
```json
{
  "A1B2C3": {"id_disp":1,"chipid":"A1B2C3","tag":"X","nombre":"Y","ciudad":"Z","sector":"S","firmware":"12.9.2024.2222"}
}
```

2) add_dispositivo
- Método: POST (JSON)
- URL: `api/index.php?r=add_dispositivo`
- Body:
```json
{"chipid":"A1B2C3","tag":"X","nombre":"Y","ciudad":"Z","sector":"S"}
```
- Respuesta: `{ "success": true }` o error 400/500 con mensaje.

3) edit_dispositivo
- Método: POST (JSON)
- URL: `api/index.php?r=edit_dispositivo`
- Body ejemplo:
```json
{"chipid":"A1B2C3","tag":"nuevo","nombre":"modelo","ciudad":"CABA","sector":"Oficina"}
```
- Respuesta:
```json
{"success":true}
```

4) update_dispositivo
- Método: POST (JSON)
- URL: `api/index.php?r=update_dispositivo`
- Body: objeto `{ chipid: { ...campos } }` por cada dispositivo a actualizar.
- Respuesta: `{ "success": true }` o error 500.

5) delete_dispositivo
- Método: POST (JSON)
- URL: `api/index.php?r=delete_dispositivo`
- Body:
```json
{"chipid":"A1B2C3"}
```
- Respuesta (éxito):
```json
{"success":true,"msg":"Dispositivo eliminado correctamente","backup":"backups/dispositivos_backup_YYYYmmdd_HHMMSS.json"}
```

6) get_sectores
- Método: GET
- URL: `api/index.php?r=get_sectores`
- Respuesta: arreglo de strings
```json
["Oficina","Deposito"]
```

7) add_sector
- Método: POST (JSON)
- URL: `api/index.php?r=add_sector`
- Body:
```json
{"sector":"Oficina"}
```
- Respuesta: `{ "success": true }` o `{ "success": false, "msg": "..." }`

8) update_sector
- Método: POST (JSON)
- URL: `api/index.php?r=update_sector`
- Body:
```json
{"old_sector":"Oficina","new_sector":"Oficina 2"}
```
- Respuesta: `{ "success": true }` o error con `msg`.

9) delete_sector
- Método: POST (JSON)
- URL: `api/index.php?r=delete_sector`
- Body:
```json
{"sector":"Oficina"}
```
- Respuesta: `{ "success": true }` o error con `msg`.

10) get_puertos
- Método: GET
- URL: `api/index.php?r=get_puertos&chipid=CHIPID`
- Respuesta (éxito): objeto de configuración por puerto o `{"error":"..."}` si falta `chipid` o no existe.

11) update_puertos
- Método: POST (JSON)
- URL: `api/index.php?r=update_puertos`
- Body ejemplo (solo actualiza campos existentes):
```json
{
  "A1B2C3": {
    "D1": {"disp":"Luz Hall","notas":"","modo":"OUTPUT"},
    "D2": {"disp":"Sensor","notas":"","modo":"INPUT_PULLUP"}
  }
}
```
- Respuesta: `{ "success": true, "message": "Puertos actualizados con éxito" }`

12) asignar_puertos
- Método: POST (JSON)
- URL: `api/index.php?r=asignar_puertos`
- Body ejemplo (crea/actualiza entradas de puertos; define `gpio`, `disp`, `modo`, `notas`):
```json
{
  "A1B2C3": {
    "D1": {"gpio":5, "disp":"Luz Hall", "modo":"OUTPUT", "notas":""},
    "D2": {"gpio":4, "disp":"Sensor", "modo":"INPUT_PULLUP", "notas":"ok"}
  }
}
```
- Respuesta: `{ "success": true, "message": "Dispositivo actualizado con éxito" }`

13) mqtt_publish
- Método: POST (JSON)
- URL: `api/index.php?r=mqtt_publish`
- Body:
```json
{"topic":"dispositivo/CHIPID/comando/reboot","payload":"1","qos":0,"retain":false}
```
- Respuesta: `{ "success": true, "data": {"topic":"...","payload":"..."} }` o error 500 con mensaje.

14) sse_states
- Método: GET (stream SSE)
- URL: `api/index.php?r=sse_states`
- Eventos emitidos:
  - `state`: `{ "chipid": "A1B2C3", "estado": "online|offline|..." }`
  - `reboot`: `{ "chipid": "A1B2C3", "payload": "0|1" }`
  - `pin`: `{ "chipid": "A1B2C3", "alias": "D1", "valor": "ON|OFF|..." }`
  - `ip`: `{ "chipid": "A1B2C3", "ip": "192.168.0.10" }`
 - Notas: el servidor envía pings cada ~2s. El cliente debe reconectar ante errores.

15) login
- Método: POST (JSON)
- URL: `api/index.php?r=login`
- Body:
```json
{"usuario":"api","clave":"API#2025"}
```
- Respuesta: `{ "success": true, "data": { "user": "...", "rol": "admin", "session_id": "..." } }`

16) logout
- Método: POST (JSON)
- URL: `api/index.php?r=logout`
- Respuesta: `{ "success": true }`

17) me
- Método: GET
- URL: `api/index.php?r=me`
- Respuesta: `{ "success": true, "data": { "user": "...", "rol": "..." } }`

18) add_entidad
- Método: POST (JSON)
- URL: `api/index.php?r=add_entidad`
- Body:
```json
{"entidad":"Lampara"}
```
- Respuesta (éxito):
```json
{"success":true}
```
- Respuesta (error de validación):
```json
{"success":false,"message":"No se proporcionó entidad."}
```
- Notas:
  - Persiste en `data/entidades.json` agregando al arreglo `entidades`.
  - Actualmente no valida duplicados ni tipo, pendiente endurecer validaciones.

## Formatos de Respuesta
La mayoría de las respuestas (no-SSE) usan:
```json
{
  "success": true,
  "message": "OK",
  "data": { /* payload */ }
}
```
En error:
```json
{
  "success": false,
  "message": "Error descriptivo",
  "data": null
}
```
- Nota: Algunos GET (p. ej. `get_dispositivos`, `get_sectores`, `get_puertos`) devuelven arreglos/objetos crudos y, en caso de error, pueden responder con formas simples (por ejemplo `{ "error": "..." }`).

## MQTT desde backend
- Publicar (ejemplo curl):
```bash
curl -X POST "http://127.0.0.1:8000/api/index.php?r=mqtt_publish" \
  -H "Content-Type: application/json" \
  -d '{"topic":"dispositivo/123/comando/reboot","payload":"1","qos":0,"retain":false}'
```
- Requisitos: variables de entorno MQTT configuradas en `.env`.

## SSE (Estados en tiempo real)
- Frontend (`index.html`) usa:
```js
const es = new EventSource('api/index.php?r=sse_states');
es.addEventListener('state', (ev) => { /* actualiza UI */ });
es.addEventListener('reboot', (ev) => { /* reactiva botón */ });
```
- Backend: `api/sse_states.php` se conecta al broker, reemite como SSE y envía pings cada ~2s.
- Reconexión: el cliente reintenta al caer; el servidor mantiene `ignore_user_abort(true)`.

## Seguridad
 - Sesiones PHP para la interfaz web (`index.php` enruta a `paginas/` según sesión).
 - Endpoints `login`, `logout` y `me` para autenticación; el resto de rutas requieren sesión activa (excepto `login` y `sse_states`).
 - No expongas credenciales MQTT en el frontend.
 - Ajusta `CORS_ALLOW_ORIGIN` en producción.
 - TLS: `sse_states.php`/`mqtt_publish.php` permiten certs self-signed (`setTlsSelfSignedAllowed(true)`); ajustar según tu CA.

## Páginas y funcionalidades del sitio

- Dashboard de dispositivos (`paginas/index.php`):
  - Listado con estado online/offline en tiempo real (SSE/MQTT).
  - CRUD básico de dispositivos (crear, editar, eliminar) y gestión de sectores.
  - Acción de reinicio remoto vía MQTT.
- Dashboard individual (`paginas/dashboard.php`):
  - Estado de conexión, última actividad y IP del dispositivo (IP se preserva entre refresh y se actualiza por SSE `ip`).
  - Visualización de estado de pines y controles rápidos ON/OFF (vía MQTT) para salidas configuradas.
  - Botón "Mqtt" que abre modal con JSON informativo (estado SSE, IP, pines y firmware si está disponible).
- Gestión de puertos (`paginas/puertos.php`):
  - Asignación de alias, modo y notas por pin/placa.
- Flasheo de firmware (`paginas/flash.php`):
  - Integración con ESP Web Tools y Web Serial para flasheo USB en el navegador.
  - Manifiesto: `assets/flash/esp8266-manifest.json` (usa `version` y `builds[].parts[].path`).
  - Tras OTA exitosa (SSE `reboot`), se persiste la versión de firmware en `data/dispositivos.json` vía `POST /api/edit_dispositivo`.

## Compilación de firmware (PlatformIO)

- Requisitos: Python 3.12+, PlatformIO Core (pio).
- Script: `tools/build_firmware.ps1` compila `firmware/` y copia artefactos a `assets/flash/firmware/`.
- Artefactos esperados:
  - `firmware/.pio/build/nodemcuv2/firmware.bin`
  - `assets/flash/firmware/esp8266-nodemcuv2-YYYYMMDD-HHMMSS.bin`

## Cómo ejecutar en local

1) Configura `.env` (ver Variables de Entorno).
2) Instala dependencias PHP con Composer:
   - `composer install`
3) Sirve el proyecto con un servidor PHP o Apache/Nginx que respete `.htaccess`:
   - PHP embebido: `php -S 127.0.0.1:8000 -t .`
4) Accede a `http://127.0.0.1:8000/` y loguéate.

Notas:
- Recomendado PHP 8.0+ (probado en 8.x). Compatible con >=7.4 según dependencias.
- Asegúrate de que el servidor permita conexiones largas para SSE.

## Troubleshooting
- "Fallo al publicar MQTT": revisa conectividad al broker y credenciales `.env`.
- SSE no conecta: verifica que el servidor PHP permita procesos largos y no cierre conexiones por proxy/reverse-proxy.
- CORS: ajustar `CORS_ALLOW_ORIGIN`.
