// public/docs-app/v1/doc/lru_plan_migracion_broker_s231.md
// 2026-07-16T14:10:00Z

# LRU · Plan de migración del broker MQTT a la máquina .102 · s231

**Ubicación canónica**: `public/docs-app/v1/doc/lru_plan_migracion_broker_s231.md`
**Sesión origen**: s231 · 2026-07-16 · Manuel & Claude
**Naturaleza**: destilado anillo 2 · plan operativo · materializa la rebanada **R2** del alfiler de resiliencia (`lru_alfiler_resiliencia_comunicaciones_s230.md`, abierta en s231)
**Estado**: **EJECUTADO en s232 (2026-07-16)** · cutover completado el mismo día siguiendo §3bis · dos ajustes sobre lo planeado, resueltos en sesión: D-s232-A (TLS del 15676 nativo en RabbitMQ, revierte D-s231-C) y D-s232-B (login anónimo `anonymous_login_user=lru_app` — la PWA conecta sin credenciales y el broker viejo lo permitía por config, hueco que el gemelo por usuarios no capturó) · verificación final: PWA Web MQTT 5.0 con TLS + placas de referencia aterrizadas + ciclo OTA completo en ambas clases · la `.200` queda encendida como respaldo real · detalle del día D en `LRU_ESTADO.md`/handoff del cierre s232
**Relación**: hallazgos H1-H6 del alfiler (§1bis) son la base empírica; D-abierta-6 (presencia PWA sin retained+wildcard) es el único hueco funcional conocido del cutover.

---

## §1 · Foto real (auditada s231)

**Máquinas** (identificación completada con sonda + management UI, s231):

| | `.200` (broker vivo, "máquina vieja") | `.102` (esta máquina, destino) |
|---|---|---|
| Broker | **RabbitMQ 3.13.3 / Erlang 26** (nodo `rabbit@HP-CUGP9G6NAG7K`) | RabbitMQ 4.2 docker |
| MQTT TCP | :21883 | :1883 |
| Web-MQTT | :15675 (http) · **:15676 (httpS/TLS)** | :15675 (http, path `/ws`) · TLS solo vía Caddy 443 `/mqtt` |
| Otros abiertos | AMQP :5672 · mgmt :15672 · RDP :3389 (80/443/1880/6379/1883 CERRADOS) | Caddy :80/:443 · Node-RED :1880 · Redis :6379 · Streams :5552 |

**Confirmaciones s231**: (a) en la `.200` **solo queda el broker** — batería de puertos limpia y Node-RED de la `.102` sin una sola referencia a la `.200` en flows (su único broker es `amqp://localhost:5672`, el nuevo); (b) **S2 de la sonda: el retained via `#` NO se entrega ya hoy** (RabbitMQ viejo, misma limitación #8824) → D-abierta-6 es teórica, el cutover no degrada la PWA; (c) matiz abierto: el 15676 de la `.200` es web-mqtt **TLS** mientras el build de la PWA lleva el fallback `ws://…:15676/ws` plano — identificar por qué puerto entra la PWA real (mgmt UI `.200` → Connections) antes de fijar la regla NAT equivalente; el RabbitMQ nuevo no tiene listener TLS y la vía natural para `wss` es Caddy 443 → `/mqtt` → :15675 (ya configurada en el Caddyfile).

**Reglas NAT del router** (Movistar Smart WiFi 6 Go · `192.168.1.1/cgi-bin/applications.cgi` · capturas s231):

| Regla | Proto | WAN | LAN | IP destino hoy |
|---|---|---|---|---|
| `mqtt_2` | TCP+UDP | 8080 | **21883** | `.200` |
| `mqtt_ws` | TCP | 15675 | 15675 | `.200` |
| `mqtt_wss` | TCP | 15676 | 15676 | `.200` |
| `debugBus` | TCP | 1880 | 1880 | `.102` (ya migrada) |
| `web_1` / `http_1` | TCP+UDP | 80/443 | 80/443 | `.102` (ya migradas) |

**Insight que hace la migración barata**: `boxdin.ddns.net:8080` (primario de ambas clases de firmware) y `192.168.1.200:21883` (respaldo LAN) son **el mismo listener** — el router traduce 8080→21883. La PWA (`ws://boxdin.ddns.net:15676/ws`) entra por la regla `mqtt_wss`. Por tanto: **el cutover del primario es editar reglas NAT; ni un byte de firmware ni de PWA cambia.**

## §2 · Preparación ejecutada en s231 (gates G0-G1 superados)

- **G0 · Config**: `C:\lru\docker\rabbitmq\definitions.json` — usuario `aula2/admin1` (hash sha256) + permisos (`configure` sobre colas mqtt-subscription/will · topic-perms amplios sobre `amq.topic`); hash real para `admin` (`admin_temp_change_me`); **BOM UTF-8 eliminado** del JSON. Pendiente sin bloquear: contraseñas de `lru_app`/`lru_hw` (hash vacío = no pueden loguear).
- **G0bis · Usuarios y espejo (s231, mismo día)**: 6 usuarios con hash en `definitions.json` (`admin`, `lru_app`, `lru_hw`, `manuel`, `manuel2` + `aula2` de flota) y mapeo espejo `"21883:1883"` en el compose → el cutover de `mqtt_2`/`mqtt_ws` queda en cambio de IP puro. **Espejo VERIFICADO**: T1-T6 verdes también contra :21883.
- **G1 · Ensayo (script `pwa6/scripts/test-broker-nuevo.mjs` · ejecutado por Manuel · TODO VERDE · repetido tras recrear el contenedor con usuarios+espejo)**:
  - T1 ✔ MQTT TCP 1883 con credenciales de flota.
  - T2 ✔ retained entregado a suscripción de topic exacto.
  - T3 ✔ **H6 confirmado empíricamente**: retained NO entregado a suscripción `#` (limitación RabbitMQ, issue #8824).
  - T4 ✔ tráfico vivo por `#`. · T5 ✔ web-mqtt `ws://:15675/ws`. · T6 ✔ LWT tras cierre sucio.

## §3 · Secuencia de cutover (sesión dedicada)

1. **Pre**: verde formal + commits al día · backup de la config del router (captura) · `docker compose up -d` sano en `.102` · ensayo T1-T6 re-ejecutado el mismo día.
2. ~~Resolver D-abierta-6 antes del cutover~~ **INNECESARIO (s231)**: la sonda S2 demostró que el broker viejo tampoco entrega retained via `#` — la PWA no pierde nada. ~~Cerrar el matiz del endpoint de la PWA~~ **CERRADO (s231)**: entra por `wss://boxdin.ddns.net:15676/ws` (DevTools) y su gemelo Caddy-TLS-15676 quedó configurado (§3.3).
3. **Cutover NAT** (con el espejo `21883:1883` añadido al compose en s231, `mqtt_2` es solo cambio de IP):
   - `mqtt_2`: IP `.200`→`.102` (puertos 8080→21883 intactos).
   - `mqtt_ws`: IP `.200`→`.102` (15675→15675 intactos).
   - `mqtt_wss`: IP `.200`→`.102` (15676→15676 intactos). **Cabo CERRADO en s231**: DevTools confirmó que la PWA de producción (servida desde `boxdin.com/pwa6`) entra por `wss://boxdin.ddns.net:15676/ws` → el gemelo en la `.102` es un site-block de **Caddy en 15676 TLS** (mismos certs de `C:\web_cert26`) proxeando a `lru-rabbitmq:15675` — añadido al Caddyfile + puerto publicado en compose + verificable con `scripts/test-wss-local.mjs`. La PWA no cambia ni un byte.
   - **Bonus del espejo**: tras el cutover, el respaldo LAN de la flota puede re-apuntarse a `.102:21883` sin cambiar puerto (clásico via `to/cfg`; S3 cuando salga de `#define`), o la `.200` sigue de respaldo tal cual.
4. **La `.200` NO se apaga**: queda como **respaldo real** (`mqtt1`/`MQTT_URI_BACKUP` ya apuntan a `.200:21883` en toda la flota). L1 y L2 dejan de estar colapsadas — bonus directo de resiliencia.
5. **Empujar la flota al primario nuevo**: por H1/H2 (sin failback), los devices conectados al respaldo `.200` en el momento del cambio se quedarían allí. Tras el cutover: reinicio suave de la flota (`to/reboot`, escalonado) o reinicio del broker `.200` para forzar reconexión → aterrizan en `boxdin:8080` = `.102`.
6. **Verificación**: PWA conectada al nuevo (DebugBus) · `from/status` de la flota llegando · tarjeta OTA operativa (probar un ciclo con una placa de referencia) · Node-RED: revisar a qué broker apuntan sus nodos MQTT y migrarlos si procede.
7. **Rollback** (si algo muerde): revertir las 2-3 reglas NAT a la `.200` — la flota vuelve sola por su propia reconexión. Coste minutos, sin tocar devices.

## §3bis · Checklist ejecutable del cutover (estado al cierre de s231: TODO PREPARADO)

**Lo que ya está hecho y verificado en la `.102`** (no repetir, solo re-verificar el día D):

- [x] RabbitMQ 4.2 docker con usuarios: `admin/admin_manuelvp` · `manuel/manuelvp12` · `manuel2/manuelvp12` (administrators) · `lru_app/lru_app_manuelvp` (management) · `lru_hw/lru_hw_manuelvp` · `aula2/admin1` (flota). Verdad en `C:\lru\docker\rabbitmq\definitions.json` (hashes; NO cambiar claves desde la UI: el import del arranque las pisa).
- [x] Espejo de puertos del broker viejo: `21883:1883` (compose) → T1-T6 verdes contra :21883.
- [x] Gemelo wss: Caddy `:15676` TLS (certs `C:\web_cert26`) → `lru-rabbitmq:15675` → `test-wss-local.mjs` OK.
- [x] Ensayos: `test-broker-nuevo.mjs` verde en :1883 y :21883 · `test-wss-local.mjs` verde · sonda S2 al viejo (retained+`#` tampoco funcionaba allí).

**Día D (sesión de cutover) — en este orden**:

1. Re-verificar la `.102` (5 min): `docker compose -f C:\lru\docker\docker-compose.yml ps` (healthy) · `node scripts\test-broker-nuevo.mjs --port 21883` · `node scripts\test-wss-local.mjs`.
2. Captura de las reglas NAT actuales del router (rollback documentado).
3. **Router `192.168.1.1` → Puertos** — cambiar SOLO la Dirección IP en 3 reglas, puertos intactos:
   - `mqtt_2` (8080→21883): `192.168.1.200` → `192.168.1.102`
   - `mqtt_ws` (15675→15675): `192.168.1.200` → `192.168.1.102`
   - `mqtt_wss` (15676→15676): `192.168.1.200` → `192.168.1.102`
4. **PWA**: recargar → debe conectar igual (`wss://boxdin.ddns.net:15676/ws`, ahora contra la `.102`). Verificar en mgmt `.102` (`localhost:15672`) → Connections: aparece la conexión Web MQTT.
5. **Flota**: los devices siguen en el broker viejo hasta que su TCP muera o reconecten. Empujón: reiniciar el RabbitMQ de la `.200` (o `to/reboot` escalonado) → reconectan contra `boxdin:8080` = `.102`. Vigilar en Connections del mgmt `.102` cómo van aterrizando (26 devices) y que los `from/status` retained se repueblan.
6. Los rezagados que aterricen en el respaldo `.200:21883` (no-failback H1/H2) se quedarán allí: segundo empujón puntual a esos.
7. **Prueba funcional**: un ciclo OTA completo con placa de referencia (`ble_A92418` o `s3_AC89D4`) a través del broker nuevo.
8. **La `.200` queda ENCENDIDA como respaldo** (`mqtt1`/`MQTT_URI_BACKUP` de toda la flota apuntan a ella). No desinstalar nada.
9. **Rollback si algo muerde**: revertir la IP en las 3 reglas → la flota vuelve sola.

## §4 · Riesgos y notas

- **H6/D-abierta-6** — TEÓRICA desde s231 (S2: el broker viejo tampoco lo entregaba). Los retained se repueblan solos: cada device publica `from/status` retained al conectar.
- **Endpoint web de la PWA** — cerrado en s231: `wss://…:15676/ws`, gemelo Caddy configurado y ensayable (§3.3). Nota de renovación: cuando roten los certs de `C:\web_cert26`, Caddy necesita reload — mismo ciclo de vida que el 443, nada nuevo.
- **No-failback (H1/H2)**: además del empujón post-cutover (§3.5), esperar devices "pegados" al respaldo si la `.200` estuvo sirviendo durante la ventana.
- **QoS2**: RabbitMQ-MQTT no lo soporta (degrada); el tráfico real observado es QoS0/1. Vigilar, no bloquea.
- **Retained store de RabbitMQ es node-local**: irrelevante con un solo nodo; anotar si algún día hay cluster.
- **Seguridad**: contraseñas actuales viajan en claro por 8080/15675 (igual que hoy); `admin_temp_change_me` debe cambiar antes de exponer el management; topic-perms de `aula2` son deliberadamente amplios — todo esto pertenece al hilo hermano seguridad (P11, sin abrir).
- **Los índices boxdin/docs no se ven afectados**: 80/443 ya sirven desde la `.102` (Caddy).

## §5 · Qué NO entra (P11)

Failover de endpoint en la PWA (R1 del alfiler — el cutover no lo necesita), formalización del `.200` más allá de "sigue encendida como respaldo" (R2 la absorbe después), telemetría de capa (R3), BLE (R4), TLS en MQTT.

---

*Anillo 2 · s231 · preparación ejecutada y ensayada en la misma sesión que la auditoría R0 que la hizo posible.*
