# Despliegue — sitio, backend de actas y usuarios Servidor: `git.helgen.online` · dominio público: `https://drogheda.helgen.online` ## Qué vive dónde | Parte | Ruta en el servidor | Cómo se actualiza | |---|---|---| | Sitio (docs, formularios, tableros) | `/srv/projects/drogheda/hbox-docs/site` | `./scripts/deploy.sh` desde la Mac | | Backend de actas (`app.py`, `tablero.py`) | `/srv/projects/drogheda/hbox-docs/backend` | `./scripts/deploy.sh` lo copia (rsync, sin git en el servidor) + `systemctl restart actas-hbox` si cambió `app.py` | | Actas recibidas (JSON + índice CSV) | `/srv/projects/drogheda/actas` | las escribe el backend | | **Fotos de campo** | `/srv/projects/drogheda/adjuntos` | las escribe el backend | | **Pendientes del tablero (kanban)** | `/srv/projects/drogheda/tareas` | las escribe el backend — **13-oct** | | **Inventario (ítems y movimientos)** | `/srv/projects/drogheda/inventario` | las escribe el backend — **13-oct** | | **Sitios** | `/srv/projects/drogheda/sitios` | las escribe el backend — **13-oct** | | **Plan de producción** | `/srv/projects/drogheda/plan-produccion.json` | lo escribe el backend (admin) — **13-oct** | | **Equipo (miembros, bitácora de accesos)** | `/srv/projects/drogheda/equipo` | las escribe el backend — **14-oct** | | **Enfoque de la semana (página de inicio)** | `/srv/projects/drogheda/enfoque.json` | lo escriben Leo y Adriana desde el inicio — **21-oct** | | **Lotes del tablero ya aplicados** | `/srv/projects/drogheda/lotes-aplicados.json` | lo escribe `backend/tablero.py` al aplicar un lote de Claude — **21-oct** | | Foto del tablero (para Claude) | `_estado/` en la Mac, fuera de git | `./scripts/tablero.sh foto` y cada `./scripts/deploy.sh` — **21-oct** | | Usuarios (HTTP Basic) | `/etc/nginx/hbox-docs.htpasswd` | `htpasswd` | **Lo importante:** `deploy.sh` publica el sitio y **copia** el backend (rsync; en el servidor el backend no es un repositorio de git), pero no reinicia el servicio. Si cambió `backend/app.py`, hay que hacer además el `restart`, o el sitio nuevo hablará con un backend viejo (las fotos darían 404 y el diagnóstico se bloquearía como si fuera un acta de banco). --- ## A · Despliegue de esta versión (diagnóstico en campo) Los pasos 3, 4 y 5 son **de una sola vez**; los pasos 1 y 2 son el ciclo normal de siempre. ### 1 · Desde la Mac — publicar el sitio ```bash cd ~/Documents/Claude/Projects/Drogheda/hbox-docs git add -A && git commit -m "diagnóstico en campo: acta, fotos, tablero y procedimiento" git push ./scripts/deploy.sh # mkdocs build --strict + rsync a /srv/.../site ``` ### 2 · En el servidor — actualizar el backend ```bash cd /srv/projects/drogheda/hbox-docs && git pull sudo systemctl restart actas-hbox curl -s http://127.0.0.1:8081/api/salud # {"ok":true,"servicio":"actas-hbox"} ``` ### 3 · Carpeta de fotos (una sola vez) ```bash sudo mkdir -p /srv/projects/drogheda/adjuntos sudo chown -R leo:leo /srv/projects/drogheda/adjuntos ``` El backend la deduce sola de `ACTAS_DIR`; si prefieres fijarla, agrega a `/etc/systemd/system/actas-hbox.service` (y luego `daemon-reload` + `restart`): ```ini Environment=ADJUNTOS_DIR=/srv/projects/drogheda/adjuntos Environment=ADJUNTO_MAX_MB=8 Environment=ARCHIVO_MAX_MB=20 Environment=ACTAS_REPETIBLES=diagnostico-campo,diagnostico-banco,instalacion-campo,reflasheo,dano,envio,recepcion,prueba-bateria-modem,prueba-ethernet-serie ``` ### 4 · nginx — subida de fotos e identidad del técnico (una sola vez) En el bloque `server { listen 443 … }` de `drogheda.helgen.online`, el `location /api/` queda así: ```nginx location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Remote-User $remote_user; # ← quién es el técnico client_max_body_size 25m; # ← sin esto, las fotos dan 413 proxy_read_timeout 120s; # ← subidas con señal mala } ``` Las **dos líneas marcadas son nuevas y obligatorias**: - Sin `client_max_body_size`, nginx corta la subida en 1 MB y el técnico ve *"la foto pesa demasiado"* aunque no lo pese. (El formulario ya reduce cada foto a ~1600 px / 300-400 kB antes de subirla; el margen es para ráfagas de varias fotos.) - Sin `X-Remote-User`, el backend no sabe quién envió cada acta: el nombre no se precarga y —más serio— **cualquier usuario autenticado podría mandar la cabecera a mano y hacerse pasar por `leo` para borrar actas**. Al fijarla en nginx, el valor del cliente se sobrescribe siempre. Revisa si ya la tienes: es fácil que no, porque no venía en el README original. ```bash sudo nginx -t && sudo systemctl reload nginx ``` ### 5 · Usuarios de los técnicos (una sola vez) ```bash sudo htpasswd /etc/nginx/hbox-docs.htpasswd alan sudo htpasswd /etc/nginx/hbox-docs.htpasswd daniel # (sin -c: -c CREA el archivo de cero y borraría a leo y a luz) 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. --- ## A2 · Despliegue 13-oct — tableros, inventario, sitios e instalación en campo Esta versión agrega al backend cuatro colecciones nuevas (pendientes del tablero, plan de producción, inventario con movimientos, sitios) y páginas que las usan (`tableros/`, `inventario/`, `sitios/`, `formularios/instalacion.html`). Igual que siempre: **el sitio se publica desde la Mac; el backend se actualiza en el servidor**. Si solo publicas el sitio, los tableros dirán «No se pudo cargar» porque hablan con endpoints que el backend viejo no tiene. ### 1 · Desde la Mac — publicar el sitio ```bash cd ~/Documents/Claude/Projects/Drogheda/hbox-docs git add -A && git commit -m "tableros kanban, inventario, sitios, acta de instalación (13-oct)" git push ./scripts/deploy.sh ``` ### 2 · En el servidor — backend y carpetas (las carpetas, una sola vez) ```bash ssh leo@git.helgen.online cd /srv/projects/drogheda/hbox-docs && git pull # carpetas de las colecciones nuevas (junto a /actas; el backend las deduce de ACTAS_DIR) sudo mkdir -p /srv/projects/drogheda/{tareas,inventario/items,inventario/movimientos,sitios} sudo chown -R leo:leo /srv/projects/drogheda/{tareas,inventario,sitios} sudo chown leo:leo /srv/projects/drogheda # (sin -R) el backend escribe plan-produccion.json en esta carpeta sudo systemctl restart actas-hbox curl -s http://127.0.0.1:8081/api/salud # {"ok":true,"servicio":"actas-hbox"} curl -s -H 'X-Remote-User: leo' http://127.0.0.1:8081/api/tareas/resumen # {"ok":true,"total":0,...} ``` Variables de entorno: **no hace falta tocar el `.service`**. Si quieres mover las carpetas, descomenta en `/etc/systemd/system/actas-hbox.service` las líneas `TAREAS_DIR`, `INVENTARIO_DIR`, `SITIOS_DIR`, `PLAN_FILE` (vienen comentadas en `backend/actas-hbox.service`) y luego `daemon-reload` + `restart`. ### 3 · Siembra inicial (una sola vez; repetirla no duplica nada) ```bash cd /srv/projects/drogheda/hbox-docs ./backend/venv/bin/python3 backend/seed_1013.py --usuario leo ``` Crea: el plan (570 → 113 V5.6 / 327 V5.7 / 130 v6), los 4 sitios (icave, lzc, eit, timsa — ubicación y contactos «por confirmar» donde no se sabe), 18 ítems de inventario con **stock 0** (Jesús hace el conteo físico con movimientos «ajuste») y los 12 pendientes del tablero del 13-oct. Lo que ya exista con el mismo `id` se deja intacto. Para importar los pendientes del Task Tracker de Notion, exporta una lista JSON con `id`, `titulo`, `pista`, `estado`, `responsable` y pásala con `--extra archivo.json`. ### 4 · Verificar ```bash curl -s -u leo:PASS https://drogheda.helgen.online/api/tareas | head -c 300 curl -s -u leo:PASS https://drogheda.helgen.online/api/plan | head -c 200 curl -s -u leo:PASS https://drogheda.helgen.online/api/inventario | head -c 200 curl -s -u leo:PASS https://drogheda.helgen.online/api/sitios | head -c 200 ``` Y desde el navegador: `/tableros/kanban.html` (arrastra un pendiente y revisa que el historial anote quién lo movió), `/inventario/inventario.html` (registra un «ajuste» con el conteo real), `/sitios/sitio.html?id=icave`, `/formularios/instalacion.html` con la unidad **PRUEBA**. ### 5 · Respaldo Se suman tres carpetas y un archivo a la rutina de siempre: ```bash rsync -az leo@git.helgen.online:/srv/projects/drogheda/tareas/ ~/respaldo-tareas/ rsync -az leo@git.helgen.online:/srv/projects/drogheda/inventario/ ~/respaldo-inventario/ rsync -az leo@git.helgen.online:/srv/projects/drogheda/sitios/ ~/respaldo-sitios/ rsync -az leo@git.helgen.online:/srv/projects/drogheda/plan-produccion.json ~/respaldo-plan/ ``` ## A3 · 14-oct: equipo y usuarios Agrega la colección `equipo` (quién es quién; los responsables de los pendientes), la administración de accesos desde el sitio (`/tableros/equipo.html`, solo admin) y hace obligatorio el responsable en cada pendiente. Compatible con lo ya desplegado: los pendientes viejos siguen funcionando; la siembra les calcula `responsable_ids` y pone `leo` solo donde el responsable estaba vacío. ```bash # Mac cd ~/Documents/Claude/Projects/Drogheda/hbox-docs && git add -A && git commit -m "equipo, usuarios y responsables (14-oct)" && git push && ./scripts/deploy.sh # servidor ssh leo@git.helgen.online cd /srv/projects/drogheda/hbox-docs && git pull sudo systemctl restart actas-hbox ./backend/venv/bin/python3 backend/seed_1013.py --usuario leo # crea equipo/ (9 miembros, sin accesos) + el pendiente de ferretería; no duplica curl -s -H 'X-Remote-User: leo' http://127.0.0.1:8081/api/equipo | head -c 200 ``` La carpeta `equipo/` la crea la siembra (o el backend al primer alta) junto a `/actas`; no hay variables nuevas que fijar (`EQUIPO_DIR`, `HTPASSWD_LOCK` vienen comentadas en el `.service`). **Para crear o quitar accesos desde la página, el servicio tiene que poder escribir el htpasswd** — es lo mismo que ya pide «Mi cuenta» (§7): `sudo chown leo:www-data /etc/nginx/hbox-docs.htpasswd && sudo chmod 640 /etc/nginx/hbox-docs.htpasswd`. Si no está hecho, la página de Equipo lo avisa y solo deja editar miembros. A partir de aquí los usuarios se crean en **Equipo → Nuevo usuario / miembro** (la contraseña inicial se muestra una sola vez), no con `htpasswd` en el servidor. Las cuentas que ya existen y no son miembro (p. ej. `luz`) aparecen como «cuentas con acceso que no están en el equipo»: dales de alta como miembro sin marcar «crear acceso». Verificar: `/tableros/equipo.html` como leo (botones) y como un técnico (solo lectura); crear un pendiente sin responsable debe rechazarse; `backend/tests-1014.sh` reproduce todo con un htpasswd temporal. | Síntoma | Causa | Arreglo | |---|---|---| | Los tableros dicen «No se pudo cargar (HTTP 404)» | backend viejo | paso 2 (`git pull` + restart) | | «El servicio no tiene permiso de escribir el archivo de usuarios» al crear un acceso | falta el `chown`/`chmod` del htpasswd | §7 | | «Falta el responsable del pendiente» al crear | regla del 14-oct | elige a alguien del equipo (o escríbelo en texto libre) | | «No puedo identificarte (falta X-Remote-User…)» al crear o mover | nginx no manda la cabecera | paso 4 del despliegue anterior | | El tablero sale vacío | falta la siembra | paso 3 | | «Solo un administrador…» al borrar o editar sitios/plan | ese usuario no está en `ACTAS_ADMINS` | así debe ser; pide a Leo | | `PermissionError` al guardar el plan | `/srv/projects/drogheda` no es de `leo` | `chown` del paso 2 | --- ## A4 · 21-oct: página de inicio, enfoque de la semana y actividad El inicio deja de ser una página escrita a mano: lee `docs/datos/proyecto.json` (fase, ruta a producción, fechas, sitios), el tablero y dos rutas nuevas del backend: - `GET /api/hoy` · `PUT /api/hoy`: el enfoque de la semana. Lo escriben `HOY_EDITORES` (por omisión `leo,adriana`) y los administradores; se guarda en `enfoque.json` junto a `/actas`, con sus 20 versiones anteriores. - `GET /api/actividad?dias=7&limite=30`: lo que pasó, del historial de los pendientes y de las actas recibidas (sin las series PRUEBA). La usan el inicio y la Bitácora. No hay carpetas ni variables nuevas que fijar (las dos vienen comentadas en el `.service`). Como cambió `backend/app.py`, después del deploy hay que reiniciar el servicio: ```bash # Mac cd ~/Documents/Claude/Projects/Drogheda/hbox-docs && ./scripts/deploy.sh ssh leo@git.helgen.online 'sudo systemctl restart actas-hbox' # verificar (en el servidor) curl -s -H 'X-Remote-User: leo' http://127.0.0.1:8081/api/hoy curl -s -H 'X-Remote-User: leo' 'http://127.0.0.1:8081/api/actividad?dias=7&limite=3' | head -c 400 ``` | Síntoma | Causa | Arreglo | |---|---|---| | En el inicio, el enfoque dice «El servidor no respondió» y la actividad, «HTTP 404» | backend viejo | el `restart` de arriba | | «El enfoque de la semana lo escriben adriana y leo» al guardar | ese usuario no está en `HOY_EDITORES` | así debe ser; para agregar a alguien, `Environment=HOY_EDITORES=leo,adriana,…` en el `.service` | | `PermissionError` al guardar el enfoque | `/srv/projects/drogheda` no es de `leo` | `chown` del paso 2 de A2 | ## A5 · 21-oct: el sitio como centro de mando — lotes y foto del tablero Regla de Leo (21-oct): **si no está en el sitio, no pasó**; cada tarea termina aquí. Claude no llega al servidor, así que deja sus cambios al tablero en **lotes** (`backend/lotes/AAAA-MM-DD_tema.json`, uno por tarea terminada) y lee el tablero en una **foto** (`_estado/tablero.md`, en la Mac). `backend/tablero.py` hace las dos cosas en el servidor a través del mismo backend (`127.0.0.1:8081`): cada cambio pasa por las mismas validaciones que el sitio y queda en el historial con su autor (`claude`) y su nota. No cambia `app.py` ni pide reiniciar nada. Formato y reglas: `backend/lotes/README.md`. ```bash # Mac — deploy.sh ya copia tablero.py y al final baja la foto cd ~/Documents/Claude/Projects/Drogheda/hbox-docs ./scripts/deploy.sh # termina con «Foto del tablero en _estado/tablero.md» y «Tablero: N cambio(s)…» ./scripts/tablero.sh revisar # qué haría el lote 2026-10-21_pasada-6 contra el tablero real ./scripts/tablero.sh aplicar # lo aplica y baja la foto nueva ``` **El primer lote** (`2026-10-21_pasada-6`, cierre de la pasada 6) cierra lo que la pasada terminó (pasadas 2 a 5 y la fuente de la V5.7), pone en curso la revisión y el Gerber del piloto, crea D-21-2 para Leo, el pedido del piloto (si no existe ya uno), la prueba de banco de la V5.7 (si no existe) y el pendiente de D34 de la v6 para Michelle, y comenta los de la v6. Lo que no cuadra con el tablero real (ya está en Hecho, no existe, hay uno parecido, el título coincide con varios) se salta, y `revisar` dice por qué antes de escribir nada. | Síntoma | Causa | Arreglo | |---|---|---| | `No contesta el backend en http://127.0.0.1:8081` | el servicio está abajo | `ssh leo@git.helgen.online 'sudo systemctl status actas-hbox'` | | `…/venv/bin/python3: No such file or directory` | el venv vive en otro lugar | `BACKEND_PATH=/ruta/al/backend ./scripts/tablero.sh …` | | `PermissionError` en `lotes-aplicados.json` | `/srv/projects/drogheda` no es de `leo` | `chown` del paso 2 de A2 | | «los cambios N ya no son los que se aplicaron» | alguien editó un lote ya aplicado | un lote aplicado no se edita: Claude escribe otro | | un cambio «se salta» y no debía | el título del pendiente real es distinto | pásale a Claude la línea de `revisar`; escribe un lote nuevo con el id correcto | ## A6 · 22-oct: modo crisis — semáforo, sitio de cada pendiente y juntas Qué cambia: la página de inicio muestra el **semáforo del proyecto** (rojo) y el color de cada sitio (`docs/datos/proyecto.json`); cada pendiente lleva su **sitio** (`sitios`: icave, timsa, eit, lzc, qro; sin sitio = Querétaro), con filtro y vista **Por sitio** en Pendientes; pista nueva **Gestión**; página nueva **Juntas**. `backend/tablero.py` acepta `sitios` en los lotes, salta un lote con errores sin frenar los demás y recorta notas de más de 300 caracteres (lo avisa). **Cambió `backend/app.py`: hay que reiniciar el servicio ANTES de aplicar lotes con `sitios`.** Con el backend viejo, `revisar` marca esos cambios como inválidos («el backend todavía no conoce «sitios»: reinicia actas-hbox») y no los manda. ```bash cd ~/Documents/Claude/Projects/Drogheda/hbox-docs mkdocs build --strict git add -A && git commit -m "Modo crisis: semáforo, sitio por pendiente, juntas" && git push cd .. && git add hbox-docs && git commit -m "hbox-docs: modo crisis" && git push && cd hbox-docs # sube el submódulo ./scripts/deploy.sh # sitio + backend (rsync); la foto todavía con el backend viejo ssh leo@git.helgen.online 'sudo systemctl restart actas-hbox' curl -s -u leo:PASS 'https://drogheda.helgen.online/api/tareas?sitio=eit' | head -c 300 # «sitios» en la respuesta ./scripts/tablero.sh revisar # ya con sitios ./scripts/tablero.sh aplicar ``` | Síntoma | Causa | Arreglo | |---|---|---| | En Pendientes no aparecen los botones de sitio | navegador con el `kanban.js` viejo | recarga (los `.js` llevan `?v=` desde el 21-oct) | | `Sitio inválido: …` al guardar | sitio que no existe | icave, timsa, eit, lzc o qro (también acepta Veracruz, Manzanillo, Ensenada, LCT, Querétaro) | | `revisar` dice «el backend todavía no conoce «sitios»» | no se reinició el servicio | el `restart` de arriba | ## A7 · 22-oct: tablero de Instalaciones por sitio y prueba en sitio Qué cambia: página nueva **Instalaciones** (`/tableros/instalaciones/`, pestañas ICAVE · TIMSA · EIT · LCT): una tarjeta por unidad con su vehículo, sus actas y su etapa (enviada → recibida → instalada → probada en sitio → en operación, más «con falla») contra la meta de cada sitio. Acta nueva **Prueba en sitio** (`formularios/prueba-sitio.html`, tipo `prueba-sitio`). El acta de instalación pide además 12/24 V, los tipos Reach Stacker y Empty Handler y el nombre en el monitor (H00xx). Los formularios aceptan `?serie=…&sitio=…` para abrir la siguiente acta ya llena (`acta.js`). Backend: `backend/instalaciones.py` (nuevo; lo engancha el final de `app.py`) da `GET /api/instalaciones[?sitio=eit]` y vuelve repetible `prueba-sitio` **sin tocar** `ACTAS_REPETIBLES` del servicio. `deploy.sh` ya copia el archivo nuevo. **Cambió `backend/app.py`: reinicia el servicio.** Sin reiniciar, la página dice «falta reiniciar el servicio actas-hbox» y la segunda prueba en sitio de una misma unidad daría «Ya existe un acta». ```bash cd ~/Documents/Claude/Projects/Drogheda/hbox-docs mkdocs build --strict git add -A && git commit -m "Tablero de Instalaciones por sitio y acta de prueba en sitio" && git push cd .. && git add hbox-docs && git commit -m "hbox-docs: tablero de Instalaciones" && git push && cd hbox-docs # sube el submódulo ./scripts/deploy.sh # sitio + backend (rsync, ya con instalaciones.py) ssh leo@git.helgen.online 'sudo systemctl restart actas-hbox' curl -s -u leo:PASS 'https://drogheda.helgen.online/api/instalaciones?sitio=icave' | head -c 300 # «"ok": true» ``` | Síntoma | Causa | Arreglo | |---|---|---| | «No se pudo leer el tablero de instalaciones (HTTP 404)» | servicio sin reiniciar, o falta `backend/instalaciones.py` en el servidor | `deploy.sh` y el `restart`; si falta el archivo, `journalctl -u actas-hbox` dice «instalaciones.py no se cargó» | | Una unidad instalada no aparece | no tiene acta, o su acta no trae el sitio ni el nombre del puerto | llenar el acta de instalación con el sitio y la **fecha real** | | La unidad sale en otro sitio | cuenta en el sitio de su acta más reciente | revisar el sitio de esa acta en el expediente | --- ## A8 · 22-oct: Adriana y Gibran pueden borrar pendientes Qué cambia: **borrar un pendiente** ya no es solo de los administradores (`ACTAS_ADMINS`): también pueden los usuarios de `TAREAS_BORRAN`, que por omisión son `adriana` y `gibranlp` (no hace falta tocar el servicio). No cambia ningún otro permiso: actas, usuarios, sitios y plan siguen siendo solo de administradores. Borrar ya no destruye: el pendiente pasa a `/srv/projects/drogheda/tareas/_borrados/__.json` con su historial, sigue en la Actividad («Adriana borró …», sin liga) y quien lo esperaba («Espera a que terminen») deja de esperarlo, con una nota en su historial. **Cambió `backend/app.py`: reinicia el servicio.** ```bash ./scripts/deploy.sh ssh leo@git.helgen.online 'sudo systemctl restart actas-hbox' ``` Para dar o quitar el permiso: en `/etc/systemd/system/actas-hbox.service`, la línea `Environment=TAREAS_BORRAN=adriana,gibranlp` con los usuarios que sí (los administradores siempre pueden); después `sudo systemctl daemon-reload && sudo systemctl restart actas-hbox`. Recuperar un pendiente borrado (en el servidor): ```bash cd /srv/projects/drogheda/tareas ls _borrados/ # __AAAAMMDDhhmmss.json mv _borrados/__.json .json # vuelve al tablero como estaba; las dependencias que se quitaron no regresan solas ``` | Síntoma | Causa | Arreglo | |---|---|---| | Adriana o Gibran no ven «Borrar» en el detalle | servicio sin reiniciar | el `restart` de arriba | | «Solo … pueden borrar pendientes» | ese usuario no es administrador ni está en `TAREAS_BORRAN` | así debe ser; si debe poder, agrégalo a `TAREAS_BORRAN` | --- ## A9 · 23-oct: inventario por sitio Qué cambia: **Inventario** (`/tableros/inventario/`) tiene una pestaña por sitio —ICAVE · TIMSA · EIT · LCT · Querétaro— y **Todo**. Cada movimiento dice en qué sitio pasó. Tipo nuevo **Envío**: saca material de un sitio (o de «Proveedor o fuera del inventario»: una compra en camino, o material que salió antes de que se contara) y lo deja **en camino** hasta que el sitio que lo espera le da **Recibir** (tipo `recepcion`; si llegó menos, la diferencia baja del total). Cada ítem puede decir cuánto lleva una instalación (**Por instalación**) y la pestaña de cada puerto calcula qué falta para instalar *N* unidades. Categoría nueva **Instalación** (soportes, tornillería, cable de potencia). Instalaciones tiene un botón **Material** que abre la pestaña del sitio. El CSV trae una columna por sitio y otra de lo que va en camino. Reglas para el equipo: [Política de inventario](docs/inventario/index.md). Datos: no hay migración. Los movimientos de antes no tienen sitio y cuentan como **Querétaro**; el total de cada ítem no cambia. El primer paso del equipo es el conteo físico de cada sitio (lote `2026-10-23_inventario-sitios`). **Cambió `backend/app.py`: reinicia el servicio.** Sin reiniciar, la página muestra solo **Todo** y avisa que falta reiniciar `actas-hbox`. ```bash cd ~/Documents/Claude/Projects/Drogheda/hbox-docs mkdocs build --strict git add -A && git commit -m "Inventario por sitio: pestañas, envíos en camino y kit por instalación" && git push cd .. && git add hbox-docs && git commit -m "hbox-docs: inventario por sitio" && git push && cd hbox-docs # sube el submódulo ./scripts/deploy.sh ssh leo@git.helgen.online 'sudo systemctl restart actas-hbox' curl -s -u leo:PASS https://drogheda.helgen.online/api/inventario | head -c 300 # debe traer «"sitios"» y «"envios"» ./scripts/tablero.sh revisar && ./scripts/tablero.sh aplicar ``` Pruebas en la Mac (opcional, necesita Flask): `bash backend/tests-1023.sh .` (35 revisiones: entrada por sitio, envío, recepción completa y con faltante, conteo físico, envío desde fuera, errores y CSV). | Síntoma | Causa | Arreglo | |---|---|---| | Solo se ve **Todo** y un aviso de reiniciar | servicio sin reiniciar | el `restart` de arriba | | «No hay stock suficiente en EIT: hay 0 pza registrados» | ese sitio no tiene su conteo físico ni entradas | **Conteo físico** en ese sitio primero; si es material que ya salió sin contarse, envío desde «Proveedor o fuera del inventario» | | Un envío no sale en la pestaña del sitio que lo espera | se registró con otro destino, o ya se recibió | tocar el código del ítem: sus movimientos dicen a dónde iba y quién lo recibió | | El total de un ítem subió sin que llegara nada | un envío desde «Proveedor o fuera del inventario» entra al total al registrarse (va en camino) | así debe ser; si al recibir llega menos, la diferencia baja | --- ## A10 · 23-oct: cuentas nuevas, contraseña inicial para todos y correo de cada quien Qué cambia: - `backend/cuentas.py` (nuevo; `deploy.sh` ya lo copia junto con `backend/cuentas/`): restablece contraseñas en bloque y da de alta a varios. Primera corrida (`cuentas/2026-10-23_alta-sitios.json`): altas de **jesush** (Jesús Honorato, TIMSA), **miguel** (Miguel Ángel Peña, EIT), **joel** (Joel Orantes, EIT), **antonio** (Antonio Ventura, LCT) y **danielg** (Daniel González Cruz, ensamble, Querétaro); el usuario `jesus` pasa a llamarse **Jesús Romero**; se llenan los correos que ya conocíamos; y **todos menos leo y claude** quedan con la misma contraseña inicial. - **Equipo**: columnas **Sitio** y **Correo**, filtro por sitio, campos «Sitio» y «Otro correo» (el del otro dominio) y, para el administrador, la marca **contraseña inicial**. - Quien sigue con la contraseña inicial ve una franja arriba en todas las páginas, con la liga a Mi cuenta; se quita sola cuando la cambia. - Los pendientes que tenían a alguien como texto libre («Miguel») ya cuentan como suyos en cuanto se da de alta. **Cambió `backend/app.py`: reinicia el servicio antes de correr `cuentas.py` y antes de aplicar el lote.** ```bash cd ~/Documents/Claude/Projects/Drogheda/hbox-docs mkdocs build --strict git add -A && git commit -m "Cuentas en bloque, contraseña inicial y sitio/correo en Equipo" && git push cd .. && git add hbox-docs CLAUDE.md && git commit -m "hbox-docs: cuentas del 23-oct" && git push && cd hbox-docs ./scripts/deploy.sh ssh leo@git.helgen.online 'sudo systemctl restart actas-hbox' ssh -t leo@git.helgen.online 'cd /srv/projects/drogheda/hbox-docs/backend && venv/bin/python3 cuentas.py todo cuentas/2026-10-23_alta-sitios.json' ./scripts/tablero.sh revisar && ./scripts/tablero.sh aplicar # lote 2026-10-23_usuarios-campo ``` `cuentas.py` muestra el plan (altas, a quién restablece, qué datos llena) y pide «si»; luego la contraseña, dos veces y sin eco. No la escribas en ningún archivo ni en un chat de grupo: pásala en persona o por mensaje directo. Para revisar después (sin contraseñas ni hashes): `ssh leo@git.helgen.online 'cd /srv/projects/drogheda/hbox-docs/backend && venv/bin/python3 cuentas.py lista'`. Pruebas en la Mac (opcional, necesita Flask): `bash backend/tests-cuentas.sh .` (47 revisiones). | Síntoma | Causa | Arreglo | |---|---|---| | `cuentas.py`: «No encuentro el archivo de usuarios» o `PermissionError` | no se corrió como `leo`, o el htpasswd no es de leo | correr como leo; `sudo chown leo:www-data /etc/nginx/hbox-docs.htpasswd && sudo chmod 640 /etc/nginx/hbox-docs.htpasswd` | | Alguien no puede entrar después | escribió otra contraseña, o su cuenta salió como «sin miembro activo» y no se tocó | `cuentas.py lista`; restablécesela en Mi cuenta (admin) | | Hay que deshacer el restablecimiento | — | el archivo anterior quedó en `/srv/projects/drogheda/equipo/_respaldos/htpasswd-AAAAMMDD-hhmmss`: cópialo encima de `/etc/nginx/hbox-docs.htpasswd` como leo | | No sale la franja de la contraseña ni la columna Sitio | servicio sin reiniciar | el `restart` de arriba | ## A11 · 27-oct: página de entrada, incidencias, standup, vigía y agente diario **Por qué.** Adriana reportó el 26-oct que, después de restablecer contraseñas, la gente no podía volver a entrar. Con la ventana gris de HTTP Basic el navegador y el teléfono guardan la contraseña anterior y la vuelven a mandar sola; Mi cuenta no tenía campo de usuario (el gestor de contraseñas nunca se actualizaba); «Adriana» con mayúscula no existe para nginx; no había cómo cerrar sesión ni registro de quién fallaba. Además, a las 9:15 del 27-oct se instaló a mano un `actas-hbox.service` viejo (sin `prueba-ethernet-serie`) que el repo no tenía. **Qué cambia.** - **Página de entrada propia** (`docs/entrar.html`, `backend/acceso.py`): nginx pregunta a `/api/_auth` en cada petición (`auth_request`); sesión = cookie firmada de 30 días; se entra con el usuario o con el correo de Equipo, sin importar mayúsculas ni espacios; cambiar la propia contraseña no saca a nadie; restablecer cierra las sesiones de esa persona; cada intento queda en `/srv/projects/drogheda/acceso/intentos-AAAA-MM.jsonl` (sin la contraseña) y se ve en Incidencias (administradores). Las contraseñas siguen en el mismo htpasswd: Mi cuenta, Equipo y `cuentas.py` no cambian. **`curl -u` ya no entra** (revisión de seguridad: por ahí se podían probar contraseñas sin límite). Los scripts del servidor (vigía, `tablero.py`, el agente) hablan con `127.0.0.1:8081` directo; para probar desde tu terminal: `read -s P; curl -c /tmp/j -H 'Content-Type: application/json' -d "{\"usuario\":\"leo\",\"password\":\"$P\"}" https://drogheda.helgen.online/api/entrar && curl -b /tmp/j https://drogheda.helgen.online/api/whoami`. Revisión de seguridad independiente antes de publicar (27-oct): además, POST/PATCH/DELETE solo desde páginas del sitio, bloqueo por usuario + IP (nadie deja fuera a otro), redirección después de entrar solo dentro del sitio, avisos públicos solo con su texto público, y lo tecleado como usuario se guarda enmascarado. - **nginx** (`backend/nginx/drogheda.helgen.online.conf`, lo instala `backend/nginx/cambiar-entrada.sh` con respaldo, `nginx -t`, 14 pruebas —incluida una sesión de prueba firmada con el secreto del servicio, sin contraseña— y regreso automático; revisa antes que todas las contraseñas sean `$apr1$`): sin sesión → `/entrar.html`; las API → 401 JSON; públicos solo la entrada, `/publico/`, las fuentes, `helgen.css`, `tema.js` y el ícono; `X-Remote-User` lo pone nginx desde la sesión. - **Servicio** (`backend/actas-hbox.service`): `ACTAS_ADMINS=leo,gibranlp` (como lo dejó Gibran), la lista completa de actas repetibles, gunicorn con hilos (`-k gthread -w 2 --threads 8`: cada página pasa por `/api/_auth`) y `Restart=always`. - **Incidencias** (`backend/incidencias.py`, `docs/js/incidencias.js`, `docs/tableros/incidencias.md`): «Problemas del sitio» al pie de cada página, reporte sin sesión desde la entrada, avisos públicos, errores del navegador y del servidor en `/srv/projects/drogheda/incidencias/_errores/`. - **Standup** (`backend/standup.py`, `docs/tableros/standup.md`, `docs/js/standup.js`) y **vigía** (`backend/vigia.py`, crontab de leo, minuto 7 de cada hora). Reglas: `docs/agentes.md`; instrucciones del agente: `agentes/agente-diario.md`. - **Correos de Equipo**: `backend/cuentas/2026-10-27_correos.json` (12 correos; solo llena vacíos). **Un comando** (en la Mac; pide sudo una vez y dos «si»): ```bash cd ~/Documents/Claude/Projects/Drogheda/hbox-docs && ./scripts/despliegue-1027.sh ``` Lo mismo a mano: `sync-docs.sh` → commit/push de hbox-docs y del submódulo → `./scripts/deploy.sh` → `ssh -t leo@git.helgen.online 'sudo bash /srv/projects/drogheda/hbox-docs/backend/nginx/cambiar-entrada.sh servicio && sudo bash /srv/projects/drogheda/hbox-docs/backend/nginx/cambiar-entrada.sh aplicar'` → `cuentas.py alta cuentas/2026-10-27_correos.json` → crontab del vigía + `semilla_1027.py` → `tablero.sh revisar` / `aplicar`. **Regresar a la ventana gris** (si algo no te gusta; el servicio nuevo puede quedarse): `ssh -t leo@git.helgen.online 'sudo bash /srv/projects/drogheda/hbox-docs/backend/nginx/cambiar-entrada.sh deshacer'`. **Confirmar la causa del 26-oct** (opcional, lee el log de nginx de antes del cambio): `ssh -t leo@git.helgen.online "sudo grep -hE 'password mismatch|was not found' /var/log/nginx/error.log /var/log/nginx/error.log.1 | tail -40"` — «password mismatch» = contraseña vieja o mal escrita; «was not found» = usuario mal escrito (p. ej. con mayúscula). | Síntoma | Causa | Arreglo | |---|---|---| | Sale «Un momento…» en todas las páginas | el backend no contesta (sin él no hay sesión) | `ssh -t leo@git.helgen.online 'sudo systemctl status actas-hbox'`; `journalctl -u actas-hbox -n 50` | | La entrada dice «No encuentro ese usuario» | escribió otro usuario; el registro de intentos lo dice | Incidencias → «Intentos de entrada»; su usuario está en Equipo | | Alguien entra pero las API dicen 401 | cookie de otro dominio o navegador sin cookies | que entre de nuevo en `/entrar.html`; si sigue, incidencia con datos técnicos | | `cambiar-entrada.sh aplicar` dice que el backend no tiene la entrada | falta el paso `servicio` (reiniciar con los módulos nuevos) | correr `servicio` primero | | Quiero cerrar la sesión de todos | — | `sudo rm /srv/projects/drogheda/.sesion-secreto && sudo systemctl restart actas-hbox` | ## A12 · 28-oct: fotos de la galería y documentos en las actas Lo pidió el equipo de campo (Leo, 28-oct 12:10): en los formularios solo se podía tomar la foto en vivo, no elegirla de la galería, y no había dónde subir un documento terminado. - `docs/formularios/acta.js` + `acta.css`: cada foto tiene dos botones, **Tomar foto** (cámara) y **Elegir de la galería** (sin `capture`); una foto HEIC que el navegador no puede convertir lo dice en vez de fallar en el servidor. Sección **Documentos** al final de toda acta (opcional; `cfg.sinDocumentos` la quita): PDF, Word, Excel, ODT/ODS, PowerPoint o la foto de una hoja. Los botones de subir se desactivan en un acta ya enviada. - `backend/app.py`: `/api/archivos` acepta además `EXT_DOC` (.pdf .docx .xlsx .pptx .odt .ods .doc .xls .ppt) y revisa los primeros bytes (un HTML renombrado a .pdf se rechaza). `/api/adjunto` manda `nosniff`, abre en el navegador fotos, PDF y texto, y descarga lo demás con su nombre original. - `docs/tableros/unidad.md`: el expediente dice «Archivos y documentos». - Pruebas: `bash backend/tests-actas-documentos.sh .` (15; se llamaba tests-1028.sh, nombre que pasó a Compras) además de 1027 (55), 1023 (35) y campeón (25); en teléfono, claro y oscuro. Ya está desplegado (28-oct, 12:17): páginas con rsync después de la comparación con el servidor, y el backend con `kill -HUP` al proceso maestro de gunicorn (corre como `leo`: recarga los workers sin sudo y sin cortar). Si algo falla: `git revert` del commit y `./scripts/deploy.sh`, luego `ssh -t leo@git.helgen.online 'sudo systemctl restart actas-hbox'`. ## A13 · 28-oct: comentarios en los pendientes y «¿por qué?» al moverlos Lo pidió Leo (28-oct, 12:57 y 16:12): que cada tarjeta tenga comentarios y que se pueda justificar por qué se movió, sobre todo cuando algo pasó a Hecho sin estar terminado. - `backend/app.py` (PATCH `/api/tarea`): `_nota` hasta 1000 caracteres (antes 300) y además del texto del historial queda aparte en `historial[-1].nota`. Comentar no necesita ser campeón; mover sí (sin cambio). - `docs/tableros/kanban.js` + `tablero.css`: sección **Comentarios** en el detalle (hilo + caja) y contador en la tarjeta; `reglaMover()` pide «¿por qué?» en el mismo PATCH: a Hecho (evidencia, D-27-6), al reabrir desde Hecho, a Bloqueado y de En curso a Por hacer; «Cancelar» o Esc lo deja donde estaba. Lo demás se mueve sin preguntar. Vale para arrastrar, la franja, las flechas, «Mover a…» y los botones del detalle. En el teléfono el detalle cabe en la pantalla. - `docs/tableros/pendientes.md` («Cómo se usa») y `juntas.md` (D-27-6) lo explican. - Pruebas: `bash backend/tests-comentarios.sh .` (16) además de 1027 (55), 1028 Compras (58), 1023 (35), campeón (25) y actas-documentos (15); en el navegador: escritorio y teléfono (iPhone 13 y SE), claro y oscuro. Desplegado el 28-oct: páginas con rsync después de la comparación con el servidor, backend con `kill -HUP` al maestro de gunicorn. Para deshacer: `git revert` del commit, `./scripts/deploy.sh` y `ssh -t leo@git.helgen.online 'sudo systemctl restart actas-hbox'`. ## A14 · 28-oct: elegir uno de tus comentarios como el porqué Lo pidió Leo (28-oct, 18:00): Michelle comenta antes de mover y luego el «¿por qué?» la hacía escribir lo mismo otra vez. - `backend/app.py` (PATCH `/api/tarea`): `_nota_de` = el `cuando` de un comentario **de la misma persona** en ese pendiente (si no existe o es de otra persona: 400). La entrada del movimiento guarda `ref` (sin `nota`, así no se repite) y su texto para la Actividad lleva «por su comentario del dd/mm hh:mm: «extracto»». Con `_nota` además, guarda las dos cosas. - `docs/tableros/kanban.js` + `tablero.css`: el «¿por qué?» lista tus últimos 5 comentarios del pendiente; si comentaste después del último cambio de columna, ese ya viene elegido (un clic en «Mover»). «Escribir otro motivo» sigue ahí. En el hilo, el comentario elegido lleva la etiqueta «→ Hecho». El porqué pegado igual que un comentario anterior de la misma persona también se muestra una sola vez. El orden se toma del historial, no de la hora (hay horas en UTC y en hora local). - Pruebas: `bash backend/tests-comentarios.sh .` (26) y las demás; en el navegador, el caso de Michelle de punta a punta. Desplegado igual que A13 (rsync de páginas tras la comparación; `kill -HUP` al maestro de gunicorn). ## B · Verificación después de desplegar ```bash # 1. el backend responde y sabe quién eres curl -s -H 'X-Remote-User: leo' http://127.0.0.1:8081/api/whoami # en el servidor (desde el 27-oct curl -u ya no entra por fuera) curl -s -H 'X-Remote-User: alan' http://127.0.0.1:8081/api/whoami # {"user":"alan","admin":false} ``` Si `user` sale vacío, falta la línea `X-Remote-User` del paso 4. ```bash # 2. la subida de fotos funciona de punta a punta curl -s -u alan:PASS -F tipo=diagnostico-campo -F serie=CAMPO-PRUEBA \ -F foto=@cualquier-foto.jpg \ https://drogheda.helgen.online/api/adjuntos # → {"ok":true,"fotos":[{"ruta":"diagnostico-campo/CAMPO-PRUEBA/2026…jpg",…}]} # 3. el acta de campo NO se bloquea al segundo envío curl -s -u alan:PASS https://drogheda.helgen.online/api/historial\?tipo=diagnostico-campo\&serie=CAMPO-PRUEBA ``` Y la prueba que de verdad cuenta, desde un teléfono: abrir `https://drogheda.helgen.online/formularios/diagnostico.html`, elegir la unidad **⚙ PRUEBA (no cuenta)**, tomar una foto y enviar. Las series con `PRUEBA` se guardan aparte, en `_pruebas/`, y no ensucian el registro. --- ## C · Ciclo normal a partir de aquí ```bash # contenido nuevo desde el repo Drogheda (lista curada dentro del script) ./scripts/sync-docs.sh git add -A && git commit -m "…" && git push ./scripts/deploy.sh # sitio + backend (rsync) + foto del tablero ./scripts/tablero.sh revisar # si Claude dejó un lote nuevo en backend/lotes/ ./scripts/tablero.sh aplicar # y SOLO si cambió backend/app.py (deploy.sh ya lo copió): ssh leo@git.helgen.online 'sudo systemctl restart actas-hbox' ``` ## D · Respaldo Todo lo que los técnicos generan son dos carpetas planas — cópialas y ya: ```bash rsync -az leo@git.helgen.online:/srv/projects/drogheda/actas/ ~/respaldo-actas/ rsync -az leo@git.helgen.online:/srv/projects/drogheda/adjuntos/ ~/respaldo-fotos/ ``` ## D4 · 28-oct: Compras (órdenes de compra del cotizador) y la hora local `backend/app.py` cambió (engancha `compras.py`) y el servicio también, así que **hay que reiniciar**. ```bash cd ~/Documents/Claude/Projects/Drogheda/hbox-docs ./scripts/deploy.sh # sube el sitio y backend/compras.py; revisa enlaces antes de publicar ssh leo@git.helgen.online sudo cp /srv/projects/drogheda/hbox-docs/backend/actas-hbox.service /etc/systemd/system/ sudo systemctl daemon-reload && sudo systemctl restart actas-hbox curl -s localhost:8081/api/compras -H 'X-Remote-User: leo' | head -c 200 # debe traer ok:true ``` Qué trae el servicio nuevo: - **`TZ=America/Mexico_City`** — el servidor corre en UTC y por eso el sitio enseñaba todo 6 horas adelantado (lo vio el equipo en la junta del 28-oct). Desde el reinicio, cada fecha que escribe el backend queda en hora de Querétaro. **Lo guardado antes del 28-oct sigue en UTC y no se corrige solo.** - **`COTIZADOR_SECRETO`** (comentado) — el secreto compartido con el cotizador. Mientras no esté, las rutas `/api/integracion/` contestan 503 con un mensaje claro, la página Compras lo avisa y **el resto del sitio funciona igual**. Para encenderlo: ```bash openssl rand -hex 32 # el MISMO valor va en el cotizador como PORTAL_SECRETO sudo systemctl edit actas-hbox # o descomenta la línea en el .service y vuelve a copiarlo ``` La base `/srv/projects/drogheda/drogheda.db` se crea sola al arrancar (SQLite en modo WAL) y se respalda con el resto de `/srv/projects/drogheda`. nginx **no** necesita cambios: `/api/integracion/` entra por el `location /api/` que ya existe y la firma la revisa el backend, no la sesión. Pruebas antes de publicar: `bash backend/tests-1028.sh ~/Documents/Claude/Projects/Drogheda/hbox-docs` (58). ## E · Problemas frecuentes | Síntoma | Causa | Arreglo | |---|---|---| | *"La foto pesa demasiado"* con fotos normales | falta `client_max_body_size` | paso 4 | | El nombre del técnico no se precarga | falta `X-Remote-User` | paso 4 | | *"Ya existe un acta para esta serie"* en diagnóstico | backend viejo | paso 2 (`git pull` + restart) | | 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 |