diff --git a/DESPLIEGUE.md b/DESPLIEGUE.md
index d839fce..6ba6fe5 100644
--- a/DESPLIEGUE.md
+++ b/DESPLIEGUE.md
@@ -98,6 +98,42 @@ sudo cut -d: -f1 /etc/nginx/hbox-docs.htpasswd # lista de usuarios
Administradores (quién puede borrar o reabrir actas) = variable `ACTAS_ADMINS` del servicio,
hoy `leo`. Alan y Daniel quedan como técnicos: llenan y envían, no borran.
+### 7 · Permisos para que los técnicos cambien su contraseña (una sola vez)
+
+La página **Mi cuenta** (`/cuenta.html`) deja que cada quien cambie su propia contraseña, y que el
+administrador restablezca la de otro. Para eso el servicio tiene que poder **escribir** el archivo de
+usuarios que nginx solo **lee**:
+
+```bash
+# quién es el usuario de nginx (normalmente www-data)
+grep -E "^\s*user" /etc/nginx/nginx.conf
+
+sudo chown leo:www-data /etc/nginx/hbox-docs.htpasswd
+sudo chmod 640 /etc/nginx/hbox-docs.htpasswd
+sudo systemctl restart actas-hbox
+```
+
+Así: **leo** (el servicio) escribe, **www-data** (nginx) lee, y nadie más puede ni leerlo.
+
+Comprueba que las contraseñas estén en el formato que el servicio sabe verificar:
+
+```bash
+sudo cut -d: -f2 /etc/nginx/hbox-docs.htpasswd | cut -c1-6
+```
+
+Todas deben decir `$apr1$`. Si alguna sale distinta (`$2y$` es bcrypt), esa persona no va a poder
+cambiar la suya sola: restablécesela desde la propia página, que la vuelve a guardar en `$apr1$`.
+
+!!! note "Por qué no se usa el comando `htpasswd` desde el servicio"
+ Pasar una contraseña como argumento (`htpasswd -b … clave`) la deja visible en `ps` para
+ cualquiera con sesión en el servidor. El hash `$apr1$` se calcula dentro del proceso; está
+ verificado carácter por carácter contra la salida de `htpasswd`, incluyendo acentos y espacios.
+
+!!! tip "Al cambiar la contraseña, la sesión no se cae"
+ HTTP Basic se guarda en el navegador: quien cambie la suya sigue navegando con la anterior hasta
+ que cierre sesión o cierre el navegador. La página lo explica y ofrece la liga de **Salir** para
+ probarla de inmediato. No es un error.
+
---
## B · Verificación después de desplegar
@@ -158,3 +194,5 @@ rsync -az leo@git.helgen.online:/srv/projects/drogheda/adjuntos/ ~/respaldo-foto
| Las fotos se ven rotas en el tablero | falta la carpeta `adjuntos` o su permiso | paso 3 |
| `mkdocs build --strict` falla | un enlace del `nav` apunta a un archivo que no existe | revisa `mkdocs.yml` |
| El técnico no puede entrar | usuario no creado, o se usó `htpasswd -c` y borró a los demás | paso 5 |
+| «El servicio no tiene permiso de escribir el archivo de usuarios» | falta el `chown`/`chmod` del htpasswd | paso 7 |
+| «Tu contraseña está guardada en un formato que no puedo verificar» | esa entrada es bcrypt, no `$apr1$` | restablecerla desde `/cuenta.html` como administrador |
diff --git a/backend/actas-hbox.service b/backend/actas-hbox.service
index d826880..c55df89 100644
--- a/backend/actas-hbox.service
+++ b/backend/actas-hbox.service
@@ -12,6 +12,8 @@ Environment=ADJUNTO_MAX_MB=8
Environment=ARCHIVO_MAX_MB=20
Environment=ACTAS_REPETIBLES=diagnostico-campo,instalacion-campo
Environment=ACTAS_ADMINS=leo
+Environment=HTPASSWD_FILE=/etc/nginx/hbox-docs.htpasswd
+Environment=PASS_MIN=8
Environment=PORT=8081
ExecStart=/srv/projects/drogheda/hbox-docs/backend/venv/bin/gunicorn -w 2 -b 127.0.0.1:8081 app:app
Restart=on-failure
diff --git a/backend/app.py b/backend/app.py
index 91733f8..44a1f53 100644
--- a/backend/app.py
+++ b/backend/app.py
@@ -26,7 +26,7 @@ Rutas:
Actas compuestas (integracion, ensamble): traen d["componentes"] = {clave: serie_modulo}. En los tipos
de TIPOS_UNICOS un módulo no puede aparecer en dos actas reales (409).
"""
-import os, re, json, csv, datetime, uuid
+import os, re, json, csv, datetime, uuid, hashlib
from flask import Flask, request, jsonify, abort, send_file
app = Flask(__name__)
@@ -435,5 +435,131 @@ def _muy_grande(e):
f"logs: máximo {LOG_MAX_MB} MB)."), 413
+# ------------------------------------------------- contraseñas (HTTP Basic de nginx)
+# El archivo de usuarios de nginx se escribe aquí mismo, sin invocar `htpasswd`:
+# pasar una contraseña como argumento de un proceso la deja visible en `ps`.
+HTPASSWD = os.environ.get("HTPASSWD_FILE", "/etc/nginx/hbox-docs.htpasswd")
+PASS_MIN = int(os.environ.get("PASS_MIN", "8"))
+ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+
+
+def _to64(v, n):
+ return "".join(ITOA64[(v >> (6 * i)) & 0x3F] for i in range(n))
+
+
+def apr1(password, salt):
+ """Hash Apache MD5 ($apr1$): lo que escribe htpasswd por defecto y lo que lee nginx."""
+ pw = password.encode("utf-8")
+ sl = salt.encode("ascii")
+ ctx = hashlib.md5(pw + b"$apr1$" + sl)
+ final = hashlib.md5(pw + sl + pw).digest()
+ i = len(pw)
+ while i > 0:
+ ctx.update(final[:16] if i > 16 else final[:i]); i -= 16
+ i = len(pw)
+ while i:
+ ctx.update(b"\0" if (i & 1) else pw[:1]); i >>= 1
+ final = ctx.digest()
+ for i in range(1000):
+ c = hashlib.md5()
+ c.update(pw if (i & 1) else final)
+ if i % 3: c.update(sl)
+ if i % 7: c.update(pw)
+ c.update(final if (i & 1) else pw)
+ final = c.digest()
+ out = "".join(_to64((final[a] << 16) | (final[b] << 8) | final[c], 4)
+ for a, b, c in ((0, 6, 12), (1, 7, 13), (2, 8, 14), (3, 9, 15), (4, 10, 5)))
+ return "$apr1$" + salt + "$" + out + _to64(final[11], 2)
+
+
+def _leer_htpasswd():
+ if not os.path.isfile(HTPASSWD):
+ return None
+ with open(HTPASSWD, encoding="utf-8") as f:
+ return f.read().splitlines()
+
+
+def _escribir_htpasswd(lineas):
+ """Reescribe el archivo de forma atómica, conservando permisos y dueño."""
+ carpeta = os.path.dirname(HTPASSWD) or "."
+ st = os.stat(HTPASSWD)
+ tmp = os.path.join(carpeta, ".htpasswd.tmp-" + uuid.uuid4().hex[:8])
+ with open(tmp, "w", encoding="utf-8") as f:
+ f.write("\n".join(lineas) + "\n")
+ os.chmod(tmp, st.st_mode & 0o7777)
+ try:
+ os.chown(tmp, st.st_uid, st.st_gid)
+ except PermissionError:
+ pass
+ os.replace(tmp, HTPASSWD)
+
+
+@app.get("/api/usuarios")
+def usuarios():
+ """Lista de usuarios (solo administrador), para el restablecimiento."""
+ if not is_admin():
+ return jsonify(ok=False, error="Solo un administrador puede ver la lista."), 403
+ lineas = _leer_htpasswd()
+ if lineas is None:
+ return jsonify(ok=False, error="No encuentro el archivo de usuarios."), 500
+ return jsonify(ok=True, usuarios=sorted(l.split(":", 1)[0] for l in lineas if ":" in l))
+
+
+@app.post("/api/password")
+def cambiar_password():
+ yo = current_user()
+ if not yo:
+ return jsonify(ok=False, error="No puedo identificarte. Vuelve a iniciar sesión."), 403
+ try:
+ d = request.get_json(force=True) or {}
+ except Exception:
+ return jsonify(ok=False, error="JSON inválido"), 400
+
+ destino = str(d.get("usuario") or yo).strip()
+ actual = str(d.get("actual") or "")
+ nueva = str(d.get("nueva") or "")
+ confirma = str(d.get("confirma") or "")
+ es_reset = (destino.lower() != yo.lower())
+
+ if es_reset and not is_admin():
+ return jsonify(ok=False, error="Solo puedes cambiar tu propia contraseña."), 403
+ if len(nueva) < PASS_MIN:
+ return jsonify(ok=False, error=f"La nueva contraseña debe tener al menos {PASS_MIN} caracteres."), 400
+ if nueva != confirma:
+ return jsonify(ok=False, error="Las dos contraseñas nuevas no coinciden."), 400
+ if not es_reset and nueva == actual:
+ return jsonify(ok=False, error="La nueva contraseña es igual a la actual."), 400
+
+ lineas = _leer_htpasswd()
+ if lineas is None:
+ return jsonify(ok=False, error="No encuentro el archivo de usuarios en el servidor."), 500
+
+ idx = next((i for i, l in enumerate(lineas)
+ if ":" in l and l.split(":", 1)[0] == destino), None)
+ if idx is None:
+ return jsonify(ok=False, error=f"El usuario {destino} no existe."), 404
+
+ guardado = lineas[idx].split(":", 1)[1]
+ if not es_reset:
+ partes = guardado.split("$")
+ if not guardado.startswith("$apr1$") or len(partes) < 4:
+ return jsonify(ok=False, error="Tu contraseña está guardada en un formato que este "
+ "servicio no puede verificar. Pide a Leo que la restablezca."), 400
+ if apr1(actual, partes[2]) != guardado:
+ return jsonify(ok=False, error="La contraseña actual no es correcta."), 403
+
+ salt = "".join(ITOA64[b % 64] for b in os.urandom(8))
+ lineas[idx] = destino + ":" + apr1(nueva, salt)
+ try:
+ _escribir_htpasswd(lineas)
+ except PermissionError:
+ return jsonify(ok=False, error="El servicio no tiene permiso de escribir el archivo de "
+ "usuarios. Avisa a Leo (permisos de hbox-docs.htpasswd)."), 500
+ except OSError as e:
+ return jsonify(ok=False, error="No se pudo guardar: " + str(e)), 500
+
+ return jsonify(ok=True, usuario=destino, reset=es_reset)
+
+
if __name__ == "__main__":
app.run(host="127.0.0.1", port=int(os.environ.get("PORT", "8081")))
diff --git a/docs/cuenta.html b/docs/cuenta.html
new file mode 100644
index 0000000..7f78b1c
--- /dev/null
+++ b/docs/cuenta.html
@@ -0,0 +1,183 @@
+
+
+
Elige algo que recuerdes sin apuntarlo. Mínimo 8 caracteres.
+
+
+
+
+
+
+
+
+
Mientras más larga, mejor. Una frase con espacios funciona y se recuerda.
+
+
+
+
+ 👁 Ver lo que escribí
+
+
+
+
+
+
+
Restablecer la contraseña de alguien más
+
Solo administración. Úsalo cuando un técnico olvidó la suya: no necesitas la anterior.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Si olvidaste tu contraseña
+
+ No hay recuperación por correo: pídele a Leo que te la restablezca desde esta misma página.
+ Es cosa de un minuto.
+
+
+
+
+
+
+
diff --git a/docs/descargas/SHA256SUMS.txt b/docs/descargas/SHA256SUMS.txt
index eef56a1..8c0bf9b 100644
--- a/docs/descargas/SHA256SUMS.txt
+++ b/docs/descargas/SHA256SUMS.txt
@@ -1 +1 @@
-a8de74b3d7768e9d5656ae0bff05475445c0cd2599ecfdc4ab081e517ed3d6ef hbox-v5-bringup_0.4.0.bin
+98656ddcc4e209e291d554341318c74d3f05713ce7198c7f6dc2d816eb3c6c3f hbox-v5-bringup_0.5.0.bin
diff --git a/docs/descargas/hbox-v5-bringup-README.md b/docs/descargas/hbox-v5-bringup-README.md
index 9eb1cbe..6954428 100644
--- a/docs/descargas/hbox-v5-bringup-README.md
+++ b/docs/descargas/hbox-v5-bringup-README.md
@@ -1,7 +1,7 @@
# hbox_v5_bringup — EinarBoard v5 first-flash bring-up firmware
Bench-proves a newly assembled v5 board over USB-C **J11** only. Never powers the RTK stack.
-0.4.0 field-minimum: AUTO by default (stack self-powers with 5 V on P2, kept on through 5 V loss), watchdog, fan, link recovery, LEDs; charge OFF. Bench mode: `P`+`Y` stack ON/OFF, `n` NMEA monitor (fix 4 = RTK FIX). Console (0.2.1): CR+LF lines, `EVT` on every state change, named `STAT` every 5 s, `$PHLG` every 10 s in the fleet v3.x layout (server-parseable).
+0.5.0: Modo B in-band $PHLG (server Salud column) on top of the 0.4.0 field-minimum: AUTO by default (stack self-powers with 5 V on P2, kept on through 5 V loss), watchdog, fan, link recovery, LEDs; charge OFF. Bench mode: `P`+`Y` stack ON/OFF, `n` NMEA monitor (fix 4 = RTK FIX). Console (0.2.1): CR+LF lines, `EVT` on every state change, named `STAT` every 5 s, `$PHLG` every 10 s in the fleet v3.x layout (server-parseable).
Checks: HSE 25 MHz + USB CDC · LSE Y4 · I2C: PCF8574 0x20 / SHT40 0x44 / TMP102 0x48 /
SC16IS752 0x4D · gauge + RGB LED walk · ADC · digital inputs. Prints PASS/FAIL, then a
`STAT` line every 2 s. Console commands: `r` rerun · `g` gauge walk · `i` I2C scan · `h` help.
@@ -23,14 +23,15 @@ generic variant defaults to HSI, which is not accurate enough for USB.
## Flash (DFU, J11)
Hold **SW2** (BOOT0) → tap **SW1** (RESET) → release SW2 → device `0483:DF11` appears.
```bash
-dfu-util -a 0 -s 0x08000000:leave -D release/hbox-v5-bringup_0.4.0.bin
+dfu-util -a 0 -s 0x08000000:leave -D release/hbox-v5-bringup_0.5.0.bin
```
-or STM32CubeProgrammer → USB → Connect → Download `release/hbox-v5-bringup_0.4.0.bin` @ `0x08000000`.
+or STM32CubeProgrammer → USB → Connect → Download `release/hbox-v5-bringup_0.5.0.bin` @ `0x08000000`.
## Release
| File | Version | SHA-256 (first 16) | Size |
|---|---|---|---|
-| **`release/hbox-v5-bringup_0.4.0.bin`** | **0.4.0 (2026-09-17) — USE THIS (field-minimum, AUTO by default)** | `a8de74b3d7768e9d` | 75 524 B |
+| **`release/hbox-v5-bringup_0.5.0.bin`** | **0.5.0 (2026-09-18) — USE THIS (field-minimum + Modo B telemetry)** | `98656ddcc4e209e2` | 77 312 B |
+| `release/superseded/hbox-v5-bringup_0.4.0.bin` | 0.4.0 — superseded (no in-band telemetry) | `a8de74b3d7768e9d` | 75 524 B |
| `release/superseded/hbox-v5-bringup_0.3.0.bin` | 0.3.0 — superseded (AUTO opt-in, no watchdog/fan/recovery) | `d4608c739cef2661` | 72 860 B |
| `release/superseded/hbox-v5-bringup_0.2.1.bin` | 0.2.1 — superseded (no AUTO mode; stack OFF on 5 V loss) | `78b34f4603550c05` | 70 084 B |
| `release/superseded/hbox-v5-bringup_0.2.0.bin` | 0.2.0 — superseded (LF-only console, no EVT/$PHLG) | `8491625d494f9ee1` | 64 780 B |
@@ -38,6 +39,14 @@ or STM32CubeProgrammer → USB → Connect → Download `release/hbox-v5-bringup
| `release/hbox-v5-bringup_0.1.0.bin` | 0.1.0 — SUPERSEDED (no float printf, ETH_CS driven) | `1a9a77ae479c8f86` | 41 040 B |
## Changelog
+- **0.5.0** (2026-09-18) — **Modo B: `$PHLG` in band.** Once the stack is on and the F9P has been talking for 10 s, USART1
+ (PA10 tap / PA9) is started **before** K1 (PB5) is energised; the F9P TX2 stream then flows F9P → PA10 → MCU → PA9 → K1 NO → modem DIN
+ byte for byte, and a `$PHLG` (same 24 fields) is injected every 30 s only after > 8 ms of line idle (a GGA is never split).
+ Corrections modem → F9P stay on their direct wire. Fail-safe is hardware: PB5 low (reset, IWDG, any stack-off) = K1 NC = direct path.
+ Prerequisites shipped with it: **256-byte UART rings** (`build_opt.h`, `static_assert`ed at compile), **non-blocking SHT40**
+ (two-phase kick/poll), console STAT/`$PHLG` deferred to line-idle gaps, K1 released before `EEPROM.write` (flash erase stalls the core)
+ and before the `r` self-tests. `B` toggles Modo B (default allowed). `STAT` adds `modoB= relay= phlg_aire=`.
+ Server: no change needed — `retranslator._parse_phlg` already accepts the sentence; units get the **Salud** column.
- **0.4.0** (2026-09-17) — **field-minimum ("-fm")**: what a unit needs to be left on a machine for days **without the production
charge governor**. **AUTO is now the default** (only the flash byte `0x5A` means MANUAL; a virgin board powers the stack by itself).
IWDG 16 s watchdog · fan policy = v3.0.3 (vehicle 5 V+ign → 100 %; battery → thermostat 48/45 C on the hottest sensor) ·
diff --git a/docs/descargas/hbox-v5-bringup_0.4.0.bin b/docs/descargas/hbox-v5-bringup_0.5.0.bin
similarity index 57%
rename from docs/descargas/hbox-v5-bringup_0.4.0.bin
rename to docs/descargas/hbox-v5-bringup_0.5.0.bin
index 7939648..3288d08 100644
Binary files a/docs/descargas/hbox-v5-bringup_0.4.0.bin and b/docs/descargas/hbox-v5-bringup_0.5.0.bin differ
diff --git a/docs/descargas/index.md b/docs/descargas/index.md
index 22df8b6..cd875e3 100644
--- a/docs/descargas/index.md
+++ b/docs/descargas/index.md
@@ -7,30 +7,34 @@ Archivos que el técnico necesita en el banco. Verificar el SHA-256 antes de fla
| Archivo | Versión | SHA-256 | Uso |
|---|---|---|---|
-| [**hbox-v5-bringup_0.4.0.bin**](hbox-v5-bringup_0.4.0.bin) | **0.4.0** (2026-09-17) | `a8de74b3d7768e9d…` ([SHA256SUMS](SHA256SUMS.txt)) | Sección 5 del [procedimiento de prueba y flasheo v5](../procedimientos/v5-prueba-y-flasheo.md) y sección 5 del [ensamble](../procedimientos/v5-ensamble.md) (modo de banco `P` / `n`) |
+| [**hbox-v5-bringup_0.5.0.bin**](hbox-v5-bringup_0.5.0.bin) | **0.5.0** (2026-09-18) | `98656ddcc4e209e2…` ([SHA256SUMS](SHA256SUMS.txt)) | Sección 5 del [procedimiento de prueba y flasheo v5](../procedimientos/v5-prueba-y-flasheo.md) y sección 5 del [ensamble](../procedimientos/v5-ensamble.md) (modo de banco `P` / `n`) |
Flasheo por DFU en **J11** (mantener **SW2**, pulsar y soltar **SW1**, soltar SW2):
```bash
-dfu-util -a 0 -s 0x08000000:leave -D hbox-v5-bringup_0.4.0.bin
+dfu-util -a 0 -s 0x08000000:leave -D hbox-v5-bringup_0.5.0.bin
```
o con **STM32CubeProgrammer** → USB → Connect → Download del `.bin` en `0x08000000`.
-!!! note "Tarjetas ya flasheadas con 0.1.1 / 0.2.0 / 0.2.1 / 0.3.0"
- Re-flashear con 0.4.0 por el mismo procedimiento DFU. **La primera línea de la consola tras reset dice la versión**
- (`hbox-v5-bringup 0.4.0 built …`): si la línea `STAT` no termina en `stack=…`, la tarjeta sigue con 0.1.1.
+!!! note "Tarjetas ya flasheadas con cualquier versión anterior"
+ Re-flashear con 0.5.0 por el mismo procedimiento DFU. **La primera línea de la consola tras reset dice la versión**
+ (`hbox-v5-bringup 0.5.0 built …`): si la línea `STAT` no termina en `stack=…`, la tarjeta sigue con 0.1.1.
-!!! tip "Consola 0.4.0"
+!!! tip "Consola 0.5.0"
Líneas `EVT` en cada cambio (stack, ignición, 5 V, cargador, NMEA, fix), `STAT` con nombres cada 5 s y `$PHLG` cada 10 s
(misma trama de salud que lee el servidor). Teclear `l` para la leyenda y `v` para los puntos de medición de la etapa RTK.
Para encender la etapa RTK: `P` y luego `Y` (requiere 5 V en P2).
-!!! warning "0.4.0 = firmware de campo mínimo — AUTO de fábrica"
+!!! warning "0.5.0 = firmware de campo mínimo + telemetría en banda — AUTO de fábrica"
Desde 0.4.0 el modo AUTO es el **de fábrica**: la etapa RTK se enciende sola 3 s después de ver 5 V en P2 — no hace falta consola
ni teclear `A`. Incluye watchdog 16 s, ventilador (política v3.0.3), recuperación de enlace (ciclo de etapa si el F9P deja de
hablar 60 s; LIMP tras 3 ciclos), LEDs de carga y color de estado, corte térmico 60 °C y corte de batería 3.40 V.
- **El cargador sigue APAGADO: el paquete es de un solo uso hasta el firmware de producción.** Anotarlo en el acta de la unidad. Si se pierde el 5 V, la etapa **sigue encendida desde la batería**
+ **El cargador sigue APAGADO: el paquete es de un solo uso hasta el firmware de producción.** Anotarlo en el acta de la unidad.
+
+ **0.5.0 — Modo B:** 10 s después de que el F9P empieza a hablar, el MCU conmuta K1 y la trama `$PHLG` viaja al servidor
+ cada 30 s junto con el NMEA (columna **Salud** del monitor). En consola: `STAT … modoB=ON relay=… phlg_aire=…` (debe subir de
+ 1 en 1 cada 30 s). Si el MCU falla, K1 regresa solo al paso directo F9P→módem. `B` lo bloquea/permite para pruebas. Si se pierde el 5 V, la etapa **sigue encendida desde la batería**
y la consola registra `EVT BACKUP inicio` / `BACKUP fin` con duración y tramas NMEA: esa es la prueba de respaldo.
Se apaga sola si vbat < 3.40 V o temperatura > 50 °C. `A` de nuevo regresa a MANUAL. El cargador sigue apagado en todos los modos.
diff --git a/docs/index.md b/docs/index.md
index 81875e7..1f0691b 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -109,6 +109,14 @@ Sitio interno de Helgen Technologies. **Busca abajo lo que vas a hacer hoy.**
[Índice de actas](actas/index.md) · [Tablero de producción](tablero.html)
+- ### 👤 Mi cuenta
+
+ ---
+
+ Cambiar tu contraseña del sitio. Si la olvidaste, Leo te la restablece desde ahí mismo.
+
+ [Cambiar mi contraseña](cuenta.html)
+
- ### 🌿 Git y archivos de diseño
---
diff --git a/docs/procedimientos/v5-ensamble.md b/docs/procedimientos/v5-ensamble.md
index c4bc6f0..6002551 100644
--- a/docs/procedimientos/v5-ensamble.md
+++ b/docs/procedimientos/v5-ensamble.md
@@ -53,7 +53,7 @@ si el módulo ya está asignado.
| Ítem | Notas |
|---|---|
| Gabinete HBOX con panel perforado (GX16-5, 3 × SMA, RJ45, prensaestopas) | según el diseño mecánico vigente |
-| EinarBoard **V5-NN** aprobada, con firmware de bring-up **0.4.0 o superior** | ver acta de tarjeta |
+| EinarBoard **V5-NN** aprobada, con firmware de bring-up **0.5.0 o superior** | ver acta de tarjeta |
| simpleRTK2B Pro **ARD-NNN** y módem 4G NTRIP Master **RTK-NNNN** aprobados e integrados | con sus bolsas |
| Fuente **Mean Well SD-15B-05** (24 V → 5 V) | va dentro del HBOX |
| Pack de batería **1S4P** con NTC (3 hilos → P4) | conectar al final |
diff --git a/docs/procedimientos/v5-prueba-final-unidad.md b/docs/procedimientos/v5-prueba-final-unidad.md
index f06fd9b..ed10ded 100644
--- a/docs/procedimientos/v5-prueba-final-unidad.md
+++ b/docs/procedimientos/v5-prueba-final-unidad.md
@@ -1,13 +1,13 @@
-# HBOX v5 — Prueba final de unidad ensamblada con firmware 0.4.0 (field-minimum)
+# HBOX v5 — Prueba final de unidad ensamblada con firmware 0.5.0 (field-minimum + telemetría)
**Alcance:** unidad completa (EinarBoard V5 + ArduSimple + módem 4G + batería + antenas) dentro del HBOX, antes de embarcar.
-**Firmware:** `hbox-v5-bringup_0.4.0.bin` (SHA-256 `a8de74b3…`) — descarga en .
+**Firmware:** `hbox-v5-bringup_0.5.0.bin` (SHA-256 `98656ddc…`) — descarga en .
**Duración:** ~45 min por unidad (P1–P6). Registrar todo en el acta de ensamble (`HBOX5-Exx-Axxx-Rxxxx`).
-!!! warning "Lo que 0.4.0 NO hace"
+!!! warning "Lo que 0.5.0 NO hace"
**No carga la batería** (cargador apagado). El paquete es de un solo uso: sostiene la unidad cuando se va el 5 V del vehículo
hasta 3.40 V y después la unidad solo funciona con alimentación del vehículo. Las unidades regresan a laptop (DFU en J11, 2 min)
- cuando salga el firmware de producción. Anotar en el acta: *«fw 0.4.0-fm, cargador OFF»*.
+ cuando salga el firmware de producción. Anotar en el acta: *«fw 0.5.0-fm, cargador OFF»*.
## 0. Preparación
- Consola: cable USB-C en **J11**, PuTTY/CoolTerm **115200**. Cada línea es `STAT`, `EVT` o `$PHLG`; teclear `l` para la leyenda.
@@ -20,7 +20,7 @@ Energizar 24 V (con ignición) y **no tocar la consola**.
| Esperado | Si falla |
|---|---|
-| Banner `hbox-v5-bringup 0.4.0` · `modo=AUTO` · `inputs at boot: v5=5.2–5.4V(ok) ign=ON` | otra versión → re-flashear; `ign=OFF` → arnés pin 3 / divisor 22k-10k |
+| Banner `hbox-v5-bringup 0.5.0` · `modo=AUTO` · `inputs at boot: v5=5.2–5.4V(ok) ign=ON` | otra versión → re-flashear; `ign=OFF` → arnés pin 3 / divisor 22k-10k |
| `---- RESULT: 12 PASS / 0 FAIL ----` · `>>> BOARD OK` | cualquier `[FAIL]` → tarjeta a revisión, no embarcar |
| `EVT … STACK ON reason=auto` **sin teclear nada** (≈10 s tras el banner) | no enciende → `STAT` muestra `v5=` bajo 4.5 o `modo=MAN` (teclear `A`) |
| `EVT … NMEA primera trama` < 3 s después · `EVT FAN ON (vehiculo: 5V+ign)` | sin NMEA → ArduSimple no arranca: medir SW_5V (MOD1 pin 5) ≈ 5 V |
@@ -33,7 +33,9 @@ Dejar la unidad 10 min encendida con el módem registrándose (LEDs del módem /
|---|---|
| `STAT … v5=5.2x–5.3x(ok)` **todo el tiempo**; nunca `(BAJO)` ni `EVT V5 BAJO` | `BAJO` → el riel cae con la ráfaga LTE: SD-15B ajustado a ~5.3 V en P2 (memo 24-Jul, PFI LTC4040) |
| `tbat`/`tair`/`tmcu` estables (< 45 °C en banco) · `i2cerr=0` | `i2cerr` subiendo → bus I²C / soldadura U3/U4/U13 |
-| `$PHLG,0.4.0-fm,RUN,…` cada 10 s, `rec=0`, sin `LIMP` | `rec` > 0 sin causa → NMEA intermitente: revisar MOD1 / UART1 F9P |
+| `$PHLG,0.5.0-fm,RUN,…` cada 10 s, `rec=0`, sin `LIMP` | `rec` > 0 sin causa → NMEA intermitente: revisar MOD1 / UART1 F9P |
+| **Modo B:** ≈10 s tras la primera NMEA, `EVT MODOB ON`; luego `STAT … modoB=ON relay=` (sube cientos/s) `phlg_aire=` (+1 cada 30 s) | `modoB=off` permanente → sin NMEA en PA3 o `B` bloqueado; `relay=0` con `modoB=ON` → K1 / PA10 |
+| **Servidor:** en ≤ 2 min la unidad `H00xx` muestra la columna **Salud** (fw `0.5.0-fm`, vbat, temperaturas) y sigue reportando posición 1 Hz | posición sí pero sin Salud → el módem no reenvía la `$PHLG`: revisar `$$$AS+` (modo transparente) |
## P3 — Respaldo de batería (6 min) — **la prueba que nunca se había hecho en v5**
Con la etapa ON: **desconectar el 24 V** (se va el 5 V del SD-15B). Esperar 5 min. Reconectar.
@@ -63,7 +65,7 @@ Con la etapa ON y > 90 s de encendida: **retirar el ArduSimple del socket** (o s
## P6 — Apagado y acta (2 min)
- Quitar ignición: `EVT IGN OFF`, `EVT FAN OFF (bateria: termostato)`; la etapa sigue ON mientras haya 5 V (el SD-15B sigue con 24 V).
- Quitar 24 V: si la batería está, entra `BACKUP` (P3); dejarla hasta `SLEEP` **no** es necesario — reconectar y apagar con `P`.
-- Guardar el log. En el acta: versión (`0.4.0-fm`), `uid=` del banner, resultado P1–P5, `vbat` final, y la nota *cargador OFF*.
+- Guardar el log. En el acta: versión (`0.5.0-fm`), `uid=` del banner, resultado P1–P5, `vbat` final, y la nota *cargador OFF*.
## Lectura rápida del LED RGB (para el técnico de campo)
| Color | Significado |
diff --git a/docs/procedimientos/v5-prueba-y-flasheo.md b/docs/procedimientos/v5-prueba-y-flasheo.md
index e3f1c64..4c4b8e1 100644
--- a/docs/procedimientos/v5-prueba-y-flasheo.md
+++ b/docs/procedimientos/v5-prueba-y-flasheo.md
@@ -37,7 +37,7 @@ y dejarla flasheada con el firmware de arranque entregado por ingeniería.
| 1 | Fuente de banco 0–30 V con límite de corriente ajustable |
| 1 | Cable USB-C ↔ USB-C (o USB-A ↔ USB-C) **con datos** (no solo carga) |
| 1 | Laptop con **STM32CubeProgrammer** instalado (o `dfu-util` en terminal) |
-| 1 | Archivo de firmware: **`hbox-v5-bringup_0.4.0.bin`** — descarga: **https://drogheda.helgen.online/descargas/** (repo: `04-Source-Code/firmware/hbox_v5_bringup/release/`, SHA-256 inicia `a8de74b3`) |
+| 1 | Archivo de firmware: **`hbox-v5-bringup_0.5.0.bin`** — descarga: **https://drogheda.helgen.online/descargas/** (repo: `04-Source-Code/firmware/hbox_v5_bringup/release/`, SHA-256 inicia `98656ddc`) |
| 1 | Jumper 2.54 mm para P11 (interruptor de corte) |
| 1 | Lupa o lámpara con aumento |
| — | Hoja de registro (sección 7) |
@@ -183,7 +183,7 @@ Solo para tarjetas que pasaron las secciones 1 a 4.
2. Arriba a la derecha, en el selector de conexión elegir **USB**. Pulsar el botón de refrescar;
debe aparecer `USB1`. Pulsar **Connect**.
3. Ir a la pestaña **Erasing & Programming** (icono de flecha hacia abajo, lado izquierdo).
-4. **File path** → seleccionar `hbox-v5-bringup_0.4.0.bin`.
+4. **File path** → seleccionar `hbox-v5-bringup_0.5.0.bin`.
5. **Start address** = `0x08000000`. Marcar **Verify programming** y **Run after programming**.
6. Pulsar **Start Programming**. Esperar "File download complete" y "Verify… successful".
7. Pulsar **Disconnect**.
@@ -192,7 +192,7 @@ Solo para tarjetas que pasaron las secciones 1 a 4.
```bash
dfu-util -l # debe listar [0483:df11]
-dfu-util -a 0 -s 0x08000000:leave -D hbox-v5-bringup_0.4.0.bin
+dfu-util -a 0 -s 0x08000000:leave -D hbox-v5-bringup_0.5.0.bin
```
### 5d. Verificación después del flasheo
@@ -206,7 +206,7 @@ dfu-util -a 0 -s 0x08000000:leave -D hbox-v5-bringup_0.4.0.bin
| **D17** (LED azul) + LED de panel en P8 (si hay) | **Parpadean** a 1 Hz (MCU ejecutando el firmware) |
| **DB1 → DB4** (LEDs del indicador de batería) | Se encienden **en secuencia** una vez, luego se apagan; después R, G, B en P14 si hay LED RGB conectado |
| Laptop | Aparece un **puerto serie** nuevo ("STMicroelectronics Virtual COM Port" / `usbmodem…`) |
-| Terminal serie a **115200 baud** | Encabezado `hbox-v5-bringup 0.4.0` (cualquier otra version: re-flashear), luego líneas `[PASS]` / `[FAIL]`, y al final **`>>> BOARD OK`** |
+| Terminal serie a **115200 baud** | Encabezado `hbox-v5-bringup 0.5.0` (cualquier otra version: re-flashear), luego líneas `[PASS]` / `[FAIL]`, y al final **`>>> BOARD OK`** |
3. Si el terminal se abrió tarde y no se vio el reporte: teclear **`r`** en el terminal → repite
todas las pruebas (y vuelve a encender los LEDs en secuencia).
diff --git a/mkdocs.yml b/mkdocs.yml
index 0b05248..bd0407b 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -88,5 +88,6 @@ nav:
- "Histórico — Proto bench v3": procedimientos/historico-proto-bench-v3.md
- "Histórico — Protoboard": procedimientos/historico-protoboard.md
+ - "👤 Mi cuenta (cambiar contraseña)": cuenta.html
- "⚙ Administración (Leo)": admin.html
- "🚪 Salir": salir.html
diff --git a/scripts/sync-docs.sh b/scripts/sync-docs.sh
index b468443..e075e80 100644
--- a/scripts/sync-docs.sh
+++ b/scripts/sync-docs.sh
@@ -22,7 +22,7 @@ declare -a MAP=(
"03-Architecture-Hardware/kicad/docs/GIT-GUIDE_Colaboradores-Externos_ES.md|docs/git/guia-git-es.md"
"03-Architecture-Hardware/kicad/docs/GIT-GUIDE_External-Collaborators_EN.md|docs/git/git-guide-en.md"
# Descargas para técnicos (firmware de bring-up v5)
- "04-Source-Code/firmware/hbox_v5_bringup/release/hbox-v5-bringup_0.4.0.bin|docs/descargas/hbox-v5-bringup_0.4.0.bin"
+ "04-Source-Code/firmware/hbox_v5_bringup/release/hbox-v5-bringup_0.5.0.bin|docs/descargas/hbox-v5-bringup_0.5.0.bin"
"04-Source-Code/firmware/hbox_v5_bringup/release/SHA256SUMS|docs/descargas/SHA256SUMS.txt"
"04-Source-Code/firmware/hbox_v5_bringup/README.md|docs/descargas/hbox-v5-bringup-README.md"
)