Sitio: v6 rev. 1.1 (T38 y propuesta α), plan v6 rev. 1.3, avisos de biblioteca en V5.6/V5.7, lote 2026-10-21_t38-v6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QCy5VHMVLkAmftYM6swWmx
This commit is contained in:
parent
d204b930c7
commit
a91580e1af
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ site/
|
||||
.cache/
|
||||
__pycache__/
|
||||
_to_delete/
|
||||
_estado/
|
||||
|
||||
64
CLAUDE.md
Normal file
64
CLAUDE.md
Normal file
@ -0,0 +1,64 @@
|
||||
# hbox-docs — the command centre (drogheda.helgen.online)
|
||||
|
||||
> **Mantra: «Si no está en el sitio, no pasó.»** If it isn't on drogheda.helgen.online, it didn't happen.
|
||||
> The rule and the closing loop live in the master index (`../CLAUDE.md`, section ★ *Command centre*).
|
||||
> This file is the mechanics: where each piece of the site lives and how to change it.
|
||||
|
||||
This repo (submodule of Drogheda) is the internal site: MkDocs Material in Mexican Spanish + a Flask
|
||||
backend (`backend/app.py`, gunicorn `actas-hbox` on `127.0.0.1:8081`, nginx basic auth in front). Leo,
|
||||
Adriana, Michelle, Jesús and the field technicians work from it; Claude closes every task on it too.
|
||||
|
||||
## Read first (start of every task)
|
||||
|
||||
1. `_estado/tablero.md` — the board snapshot (pendientes by person, overdue, last 14 days of activity,
|
||||
lotes applied). Taken by `./scripts/tablero.sh foto` and by every `./scripts/deploy.sh`. If it is
|
||||
missing or older than a day, ask Leo for a fresh `./scripts/tablero.sh foto` before writing a lote.
|
||||
2. `docs/datos/proyecto.json` — Ruta a producción, per-version cards, hitos, fase.
|
||||
3. `docs/hardware/index.md` — state of every board with its documents.
|
||||
4. `../CLAUDE.md` §0 — engineering status.
|
||||
|
||||
## Where each thing lives
|
||||
|
||||
| On the site | Source | Who edits | How |
|
||||
|---|---|---|---|
|
||||
| **Pendientes** (kanban, «Por persona», Ingeniería/Producción views) | server `/srv/projects/drogheda/tareas` | everyone in the UI; **Claude only through lotes** | `backend/lotes/AAAA-MM-DD_tema.json` → Leo runs `./scripts/tablero.sh revisar` then `aplicar`. Format and rules: `backend/lotes/README.md` |
|
||||
| **Actividad** (home, Bitácora) | automatic: task history + actas | — | nothing to write; lotes show up as «Claude terminó / creó / comentó en …» |
|
||||
| **Enfoque de la semana** (home) | server `/api/hoy` (`enfoque.json`) | Leo and Adriana only | Claude proposes the text in the hand-off; `enfoque_inicial` in `proyecto.json` shows only while nobody has written one |
|
||||
| **Ruta a producción**, version cards, fase, pedido, próximas fechas, map pins | `docs/datos/proyecto.json` | Claude or Leo | edit by hand (keep the one-array-per-line format); step states `hecho · ahora · bloqueado · falta`, card tone `hecho · ahora · aviso · bloqueado`; bump `actualizado` |
|
||||
| **Últimas decisiones** (Ingeniería) | `docs/tableros/ingenieria.md` | Claude or Leo | new `<li><b>DD-mmm (hora)</b> — …</li>` at the top, with the decision number (D-xx-n) and links |
|
||||
| **Bitácora → Documentos por fecha** | `docs/bitacora.md` | Claude or Leo | newest first under the month: `**DD oct**` + `: [Title](path.md#anchor): what it decides or changes` |
|
||||
| **Documentos recientes** (home) | automatic from `date:` front matter (`hooks/documentos.py`) | — | give every new page a `date:`; pages under «Historia» are left out |
|
||||
| Engineering docs (audits, ECOs, passes, procedures) | `../NN-…/AAAA-MM-DD_Drogheda_…md` | Claude or Leo | write the source in the Drogheda folder, add it to the `MAP` in `scripts/sync-docs.sh`, run the sync. **Never edit the copy in `docs/`** |
|
||||
| Navigation | `mkdocs.yml` | Claude or Leo | current audits/passes under *Tarjetas y hardware → Auditorías y pasadas*; superseded ones to *Historia → Tarjetas superadas* with a banner saying what replaces them |
|
||||
| Section index pages | `docs/index.md`, `docs/hardware/index.md`, `docs/produccion/`, `docs/taller/`, `docs/campo/` | Claude or Leo | site-only (not in the `MAP`); `hardware/index.md` carries the per-board state and the 21-oct style warning box |
|
||||
| Figures | `docs/<section>/img/AAAA-MM-DD_Drogheda_…png` | Claude | always on the site, embedded in the doc they explain |
|
||||
| Card images (white copper) | `docs/img/tarjetas/*.webp` | Claude | `scripts/tarjetas/` (see its README) |
|
||||
| Engineer's rules | `docs/guia-ingeniero.md` | Leo / Claude | «El sitio es el centro de mando» is the team-facing version of the closing loop |
|
||||
| Deploy notes | `DESPLIEGUE.md` | Claude | one dated section per change that needs server steps |
|
||||
|
||||
## Rules when editing the site
|
||||
|
||||
- **Mexican Spanish** for every word the team reads (pages, notes in lotes, task titles).
|
||||
- `mkdocs build --strict` must pass (warnings fail). Build a copy first if another session may be editing.
|
||||
- **Another session may be editing hbox-docs at the same time.** Check the file's mtime/hash right before
|
||||
writing; never overwrite a file that changed since you read it — merge instead.
|
||||
- No secrets: the 4G/NTRIP parameter file (caster, APN) never goes on the site, only its template.
|
||||
- Never run git in the mounted folder (stale `.git/index.lock`); give Leo the commands. The mount can't
|
||||
delete: move leftovers to `_to_delete/` (gitignored).
|
||||
- `.css`/`.js` links get `?v=<hash>` automatically (`hooks/versiones.py`); no manual cache busting.
|
||||
- If `backend/app.py` changes, the hand-off must say `sudo systemctl restart actas-hbox`.
|
||||
- UI changes: check with the local proxy (site + real backend) in light, dark and phone width.
|
||||
|
||||
## Hand-off block (end of every task that touched the site)
|
||||
|
||||
```bash
|
||||
cd ~/Documents/Claude/Projects/Drogheda/hbox-docs
|
||||
./scripts/sync-docs.sh # only if a synced doc changed
|
||||
mkdocs build --strict
|
||||
git add -A && git commit -m "…" && git push
|
||||
cd .. && git add hbox-docs && git commit -m "hbox-docs: …" && git push # bump the submodule
|
||||
cd hbox-docs && ./scripts/deploy.sh # site + backend; also refreshes _estado/
|
||||
./scripts/tablero.sh revisar # if there is a new lote: read what it will do
|
||||
./scripts/tablero.sh aplicar
|
||||
# ssh leo@git.helgen.online 'sudo systemctl restart actas-hbox' ← only if app.py changed
|
||||
```
|
||||
@ -7,7 +7,7 @@ Servidor: `git.helgen.online` · dominio público: `https://drogheda.helgen.onli
|
||||
| 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`) | `/srv/projects/drogheda/hbox-docs/backend` | `git pull` en el servidor + `systemctl restart actas-hbox` |
|
||||
| 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** |
|
||||
@ -16,11 +16,13 @@ Servidor: `git.helgen.online` · dominio público: `https://drogheda.helgen.onli
|
||||
| **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 **solo el sitio**. Si cambió `backend/app.py` —como en esta
|
||||
versión— hay que hacer además el `git pull` + `restart` en el servidor, 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
|
||||
**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).
|
||||
|
||||
---
|
||||
@ -291,6 +293,38 @@ curl -s -H 'X-Remote-User: leo' 'http://127.0.0.1:8081/api/actividad?dias=7&limi
|
||||
| «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 |
|
||||
|
||||
---
|
||||
|
||||
## B · Verificación después de desplegar
|
||||
@ -327,9 +361,11 @@ en `_pruebas/`, y no ensucian el registro.
|
||||
# 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
|
||||
# y SOLO si tocaste backend/app.py:
|
||||
ssh leo@git.helgen.online 'cd /srv/projects/drogheda/hbox-docs && git pull && sudo systemctl restart actas-hbox'
|
||||
./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
|
||||
|
||||
32
README.md
32
README.md
@ -1,9 +1,12 @@
|
||||
# hbox-docs — sitio de documentación técnica del HBOX
|
||||
|
||||
Sitio **MkDocs Material** (español) con el subconjunto de documentación del proyecto
|
||||
Drogheda que los técnicos necesitan: procedimientos de banco, montaje/arneses,
|
||||
diagnóstico remoto y la guía Git. Vive como **submódulo de Drogheda**, igual que el
|
||||
repo de KiCad.
|
||||
Sitio **MkDocs Material** (español de México) + backend Flask: el **centro de mando** del proyecto
|
||||
Drogheda en `https://drogheda.helgen.online`. Pendientes (kanban), ruta a producción, actas por unidad,
|
||||
inventario, sitios, bitácora y los documentos que se publican (procedimientos, auditorías, ECOs,
|
||||
manuales). Vive como **submódulo de Drogheda**, igual que el repo de KiCad.
|
||||
|
||||
**Regla: si no está en el sitio, no pasó.** Cada tarea termina aquí: su pendiente, su documento y, si
|
||||
algo estorbó, una mejora. Para Claude: `CLAUDE.md` (dónde vive cada cosa y cómo se cambia).
|
||||
|
||||
## Flujo de trabajo
|
||||
|
||||
@ -19,6 +22,11 @@ mkdocs serve # http://127.0.0.1:8000
|
||||
|
||||
# 4. publicar al servidor
|
||||
./scripts/deploy.sh # leo@git.helgen.online:/srv/projects/drogheda/hbox-docs/site (puerto 22)
|
||||
# también copia el backend y baja la foto del tablero a _estado/
|
||||
|
||||
# 5. lotes de Claude para el tablero (si hay uno nuevo en backend/lotes/)
|
||||
./scripts/tablero.sh revisar # qué cambiaría cada uno; no escribe nada
|
||||
./scripts/tablero.sh aplicar # los aplica y baja la foto nueva
|
||||
```
|
||||
|
||||
**Política de publicación:** solo se publica lo que está en la lista de
|
||||
@ -77,8 +85,16 @@ existente, con el mismo `auth_basic`.
|
||||
## Estructura
|
||||
|
||||
```
|
||||
mkdocs.yml configuración del sitio (nav, tema, español)
|
||||
docs/ contenido publicado (generado por sync-docs.sh + index.md)
|
||||
scripts/sync-docs.sh lista curada Drogheda → docs/
|
||||
scripts/deploy.sh mkdocs build --strict + rsync al servidor
|
||||
CLAUDE.md reglas para Claude: dónde vive cada cosa del sitio y cómo se cambia
|
||||
mkdocs.yml configuración del sitio (nav, tema, español, hooks)
|
||||
docs/ contenido publicado (lo sincronizado por sync-docs.sh + páginas propias del sitio)
|
||||
docs/datos/proyecto.json ruta a producción, tarjeta por versión, fechas y sitios de la página de inicio
|
||||
overrides/, hooks/ plantillas del tema Helgen y ganchos de MkDocs (documentos recientes, versión de CSS/JS)
|
||||
backend/ app.py (actas, pendientes, inventario, sitios, equipo, enfoque, actividad) + servicio
|
||||
backend/tablero.py foto del tablero y lotes de Claude (lo llama scripts/tablero.sh)
|
||||
backend/lotes/ lotes de cambios al tablero, uno por tarea terminada (formato en su README)
|
||||
scripts/sync-docs.sh lista curada Drogheda → docs/ (la política de publicación)
|
||||
scripts/deploy.sh mkdocs build --strict + rsync del sitio y del backend + foto del tablero
|
||||
scripts/tablero.sh foto / revisar / aplicar desde la Mac
|
||||
_estado/ foto del tablero (tablero.json, tablero.md); fuera de git
|
||||
```
|
||||
|
||||
235
backend/lotes/2026-10-21_pasada-6.json
Normal file
235
backend/lotes/2026-10-21_pasada-6.json
Normal file
@ -0,0 +1,235 @@
|
||||
{
|
||||
"lote": "2026-10-21_pasada-6",
|
||||
"autor": "claude",
|
||||
"fecha": "2026-10-21",
|
||||
"motivo": "Pasada 6 de V5.6 y V5.7 hecha por Claude (T34–T37): cobre listo para Gerber; T25 y T28 aplicados en la v6",
|
||||
"fuente": "/hardware/v56-v57-auditoria-21oct/#6-pasada-6-hecha-por-claude-t34-a-t37",
|
||||
"cambios": [
|
||||
{
|
||||
"id": "t-v56-pasada-2",
|
||||
"poner": {
|
||||
"estado": "hecho"
|
||||
},
|
||||
"nota": "Cerrado: a la pasada 2 le siguieron las pasadas 3 a 5 de Michelle; la 6 la hizo Claude el 21-oct (T34 + T35). El cobre de la V5.6 quedó listo para Gerber."
|
||||
},
|
||||
{
|
||||
"id": "t-v57-fuente",
|
||||
"poner": {
|
||||
"estado": "hecho"
|
||||
},
|
||||
"nota": "Cerrado: la fuente U25 quedó colocada y ruteada (pasada 3 de Michelle, 17-oct) y se revisó en las pasadas 4 a 6."
|
||||
},
|
||||
{
|
||||
"id": "t-v57-cierre",
|
||||
"poner": {
|
||||
"estado": "hecho"
|
||||
},
|
||||
"nota": "Cerrado: la V5.7 ya replica las correcciones de la V5.6 y la pasada 6 (Claude, T36 + T37) dejó su cobre listo para Gerber. C149/C150 quedan como están si Leo aprueba D-21-2."
|
||||
},
|
||||
{
|
||||
"buscar": "pasada 3",
|
||||
"todos": true,
|
||||
"poner": {
|
||||
"estado": "hecho"
|
||||
},
|
||||
"nota": "Cerrado: la pasada 6 (Claude, 21-oct, T34–T37) dejó el cobre de la V5.6 y la V5.7 listo para Gerber; lo que pedía esta pasada quedó hecho o se pasó a la siguiente."
|
||||
},
|
||||
{
|
||||
"buscar": "pasada 4",
|
||||
"todos": true,
|
||||
"poner": {
|
||||
"estado": "hecho"
|
||||
},
|
||||
"nota": "Cerrado: la pasada 6 (Claude, 21-oct, T34–T37) dejó el cobre de la V5.6 y la V5.7 listo para Gerber; lo que pedía esta pasada quedó hecho o se pasó a la siguiente."
|
||||
},
|
||||
{
|
||||
"buscar": "pasada 5",
|
||||
"todos": true,
|
||||
"poner": {
|
||||
"estado": "hecho"
|
||||
},
|
||||
"nota": "Cerrado: la pasada 6 (Claude, 21-oct, T34–T37) dejó el cobre de la V5.6 y la V5.7 listo para Gerber; lo que pedía esta pasada quedó hecho o se pasó a la siguiente."
|
||||
},
|
||||
{
|
||||
"id": "t-v56-revision-gerber",
|
||||
"poner": {
|
||||
"titulo": "V5.6 y V5.7 — revisión pad a pad + Gerber/BOM/CPL del piloto",
|
||||
"estado": "en-curso",
|
||||
"responsable": "leo, claude",
|
||||
"fecha_limite": "2026-10-22",
|
||||
"descripcion": "Revisión del cobre hecha por Claude en la pasada 6 (21-oct, T34–T37), en las dos tarjetas: DRC solo con los 3 errores de MOD1 (excluidos), 0 sin conectar, paridad solo PF*, RS1 Kelvin en VIN y CLN, todas las piezas montadas con LCSC.\nFalta: (1) Leo, commit y push de kicad/; (2) quien abra cada tarjeta, git pull --ff-only, B y DRC con «Rellenar zonas» y «Comprobar paridad» (tiene que salir igual); (3) Gerber, BOM y CPL con Fabrication Toolkit (JLCPCB-Order-Guide.md) y, en el visor de JLC, la rotación de U6, U7, U25, Q7 y RS1.",
|
||||
"enlaces": [
|
||||
{
|
||||
"titulo": "Pasada 6: qué falta antes del Gerber",
|
||||
"url": "/hardware/v56-v57-auditoria-21oct/#61-que-falta-antes-del-gerber"
|
||||
},
|
||||
{
|
||||
"titulo": "Auditoría del 21-oct (pasadas 4 a 6)",
|
||||
"url": "/hardware/v56-v57-auditoria-21oct/"
|
||||
},
|
||||
{
|
||||
"titulo": "Tarjetas y hardware",
|
||||
"url": "/hardware/"
|
||||
}
|
||||
],
|
||||
"etiquetas": [
|
||||
"fabricación",
|
||||
"piloto"
|
||||
]
|
||||
},
|
||||
"nota": "Revisión del cobre hecha (pasada 6, T34–T37). Ahora cubre la V5.6 y la V5.7; falta el paquete: push de Leo, B + DRC en KiCad y Gerber/BOM/CPL."
|
||||
},
|
||||
{
|
||||
"crear": {
|
||||
"id": "d-21-2-c149-c150",
|
||||
"titulo": "D-21-2: ¿C149/C150 de la V5.7 se quedan como están?",
|
||||
"pista": "ingenieria",
|
||||
"estado": "por-hacer",
|
||||
"responsable": "leo",
|
||||
"version_tarjeta": "V5.7",
|
||||
"fecha_limite": "2026-10-22",
|
||||
"descripcion": "Propuesta de Claude (pasada 6): no girarlos. Del lazo de ≈ 56 mm², ≈ 40 son U25 → D34 (VIN y SW en lados opuestos del chip); girándolos queda en ≈ 46 en el mejor caso y para bajar de 30 hay que mover D34, es decir, rehacer la fuente. Debajo de toda la fuente, In1 es GND sin cortes, así que el retorno de alta frecuencia sigue al camino de ida.\nSí → se quedan y se verifica en el banco (timbre de SW y VIN de U25 a 9, 24 y 32 V con 2.5 A). No → otra pasada en la V5.7 antes del Gerber.",
|
||||
"enlaces": [
|
||||
{
|
||||
"titulo": "Por qué se quedan (§6.2)",
|
||||
"url": "/hardware/v56-v57-auditoria-21oct/#62-v57-por-que-c149c150-se-quedan-como-estan"
|
||||
},
|
||||
{
|
||||
"titulo": "Decisiones para Leo (§4)",
|
||||
"url": "/hardware/v56-v57-auditoria-21oct/#4-decisiones-para-leo"
|
||||
}
|
||||
],
|
||||
"etiquetas": [
|
||||
"decisión"
|
||||
]
|
||||
},
|
||||
"si_no_hay": [
|
||||
"D-21-2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"buscar": [
|
||||
"piloto",
|
||||
"v5.6",
|
||||
"-banco",
|
||||
"-gerber"
|
||||
],
|
||||
"poner": {
|
||||
"fecha_limite": "2026-10-22"
|
||||
},
|
||||
"nota": "El cobre de la V5.6 y la V5.7 quedó listo para Gerber (pasada 6, 21-oct). El pedido de 5 + 5 sale en cuanto estén el Gerber, BOM y CPL (t-v56-revision-gerber)."
|
||||
},
|
||||
{
|
||||
"crear": {
|
||||
"id": "t-piloto-v56-v57-pedido",
|
||||
"titulo": "Pedir el piloto: 5 V5.6 y 5 V5.7 a JLC",
|
||||
"pista": "compras",
|
||||
"estado": "por-hacer",
|
||||
"responsable": "leo",
|
||||
"version_tarjeta": "—",
|
||||
"fecha_limite": "2026-10-22",
|
||||
"descripcion": "Con el paquete de fabricación de t-v56-revision-gerber. Reglas DFM del LTC4040 (U6): pasta del pad expuesto ≤ 50 %, vías del EP rellenas y tapadas (Epoxy Filled & Capped) o fuera de la pasta, y X-ray/AOI en U6. Al llegar: entrada en el inventario y revisar U6 con lupa en cada tarjeta antes de energizar.",
|
||||
"bloqueada_por": [
|
||||
"t-v56-revision-gerber"
|
||||
],
|
||||
"enlaces": [
|
||||
{
|
||||
"titulo": "Reglas que aplican a toda revisión",
|
||||
"url": "/hardware/"
|
||||
},
|
||||
{
|
||||
"titulo": "Inventario",
|
||||
"url": "/tableros/inventario/"
|
||||
}
|
||||
],
|
||||
"etiquetas": [
|
||||
"piloto",
|
||||
"fabricación"
|
||||
]
|
||||
},
|
||||
"si_no_hay": [
|
||||
"piloto de 5",
|
||||
"piloto v5.6",
|
||||
"piloto de v5.6",
|
||||
"piloto: 5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"buscar": [
|
||||
"banco",
|
||||
"v5.7"
|
||||
],
|
||||
"nota": "Agregar a esta prueba lo que pide D-21-2: nodo SW y VIN de U25 con punta de tierra corta (sobretiro y oscilación al encender el FET) a 9, 24 y 32 V con 2.5 A de carga."
|
||||
},
|
||||
{
|
||||
"crear": {
|
||||
"id": "t-v57-banco-prototipos",
|
||||
"titulo": "V5.7 en banco: barrido 9/12/24/32 V, polaridad invertida, temperatura y timbre de U25",
|
||||
"pista": "ingenieria",
|
||||
"estado": "por-hacer",
|
||||
"responsable": "jesús, claude",
|
||||
"version_tarjeta": "V5.7",
|
||||
"descripcion": "Con los 5 prototipos del piloto. Claude escribe el procedimiento (ECO V5.7 §6) antes de que lleguen; Jesús lo corre y llena el acta. Incluye la medición que pide D-21-2: nodo SW y VIN de U25 con punta de tierra corta (sobretiro y oscilación al encender el FET) a 9, 24 y 32 V con 2.5 A de carga.",
|
||||
"enlaces": [
|
||||
{
|
||||
"titulo": "ECO V5.7 — fuente a bordo",
|
||||
"url": "/hardware/v57-eco-fuente/"
|
||||
},
|
||||
{
|
||||
"titulo": "D-21-2 (§6.2)",
|
||||
"url": "/hardware/v56-v57-auditoria-21oct/#62-v57-por-que-c149c150-se-quedan-como-estan"
|
||||
}
|
||||
],
|
||||
"etiquetas": [
|
||||
"banco",
|
||||
"piloto"
|
||||
]
|
||||
},
|
||||
"si_no_hay": [
|
||||
"V5.7 en banco",
|
||||
"prototipos V5.7",
|
||||
"prototipos de la V5.7"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "t-v6-cierre-pedido",
|
||||
"nota": "21-oct: Claude aplicó T25 (barrenos) y T28 (código de U25, stackup, pasta de U6 al 50 %) sobre 9255b84; falta el push. Quedan 6 bloqueantes; lo primero: D34 a 13 mm del pin SW y dentro de MH8. Ver /hardware/v6-estado-21oct/"
|
||||
},
|
||||
{
|
||||
"buscar": [
|
||||
"barrenos",
|
||||
"v6"
|
||||
],
|
||||
"nota": "21-oct: T25 --set v6 ya puso los barrenos en el archivo (falta el push de Leo). Quedan por liberar MH4, MH6 y MH8; lo primero es D34 (t-v6-d34-mh8). Ver /hardware/v6-estado-21oct/"
|
||||
},
|
||||
{
|
||||
"crear": {
|
||||
"id": "t-v6-d34-mh8",
|
||||
"titulo": "v6: D34 de regreso junto a U25.8 y fuera de MH8; R53/R54 a ≤ 3 mm de U6.18",
|
||||
"pista": "ingenieria",
|
||||
"estado": "por-hacer",
|
||||
"responsable": "michelle",
|
||||
"version_tarjeta": "v6",
|
||||
"fecha_limite": "2026-10-24",
|
||||
"descripcion": "Después de git pull --ff-only (T25 y T28 ya están en el archivo). Cátodo de D34 a 2–3 mm del pin 8 de U25; /SW_PWRIN ≤ 25 mm² sin pasar bajo L22; nada dentro del círculo de 4 mm de MH8 (195.2, 135.3); R53/R54 (divisor BSTFB) a ≤ 3 mm de U6.18, lo que libera el lugar de D34. Después MH4 y MH6 y el resto de los bloques A y B del plan. Cierre: B + DRC con «Rellenar zonas» y «Comprobar paridad», 0 errores; commit «v6 bloque A».",
|
||||
"enlaces": [
|
||||
{
|
||||
"titulo": "Estado de la v6 al 21-oct, §3",
|
||||
"url": "/hardware/v6-estado-21oct/#3-lo-que-tiene-que-corregir-michelle-antes-de-seguir"
|
||||
},
|
||||
{
|
||||
"titulo": "Plan de fines de semana",
|
||||
"url": "/hardware/v6-plan-fines-de-semana/"
|
||||
}
|
||||
],
|
||||
"etiquetas": [
|
||||
"layout",
|
||||
"bloqueante"
|
||||
]
|
||||
},
|
||||
"si_no_hay": [
|
||||
"D34"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
104
backend/lotes/2026-10-21_t38-v6.json
Normal file
104
backend/lotes/2026-10-21_t38-v6.json
Normal file
@ -0,0 +1,104 @@
|
||||
{
|
||||
"lote": "2026-10-21_t38-v6",
|
||||
"autor": "claude",
|
||||
"fecha": "2026-10-21",
|
||||
"motivo": "T38 aplicado en la v6 (courtyards de 11 huellas, MH6 libre, regla de barrenos) y propuesta α del bloque A con metas corregidas",
|
||||
"fuente": "/hardware/v6-estado-21oct/#5-t38-21-oct-noche-courtyards-mh6-y-regla-de-barrenos",
|
||||
"cambios": [
|
||||
{
|
||||
"id": "T261022-0b76",
|
||||
"poner": {
|
||||
"titulo": "v6 bloque A: propuesta α (D34 entre U25 y L22, L22 al este), MH8 libre; antes R53/R54 a ≤ 3 mm de U6.18",
|
||||
"responsable": "michelle",
|
||||
"fecha_limite": "2026-10-24",
|
||||
"descripcion": "Después de git pull --ff-only (T25, T28 y T38 ya están en el archivo).\n1. R53/R54 a ≤ 3 mm de U6.18 (bloque B, paso 6): hoy están donde va el nodo SW.\n2. D34 girado 90° en (181.0, 121.75), cátodo abajo.\n3. L22 en (192.1, 122.5), sin girar. Revisar la pared de la HBOX: el cuerpo queda a 1.35 mm de la orilla.\n4. Nodo SW en F.Cu: pin 8 → C152.2 → D34.K → L22.1, ≤ 10 mm² fuera de los pads.\n5. In1/In2 limpios debajo: sacar /I2C_SDA, /I2C_SCL, /MAIN5V y /VFB_BST.\n6. 4 vías de GND junto al ánodo de D34.\n7. /VEH5V de L22.2 por debajo del cuerpo de L22, 2 mm, hasta R51.1; recorrer la vía de GND de (185.8, 115.5).\n8. Borrar las 2 vías de GND de MH8.\nMetas: cátodo ≤ 3.5 mm del pin 8, L22.1 ≤ 9 mm, lazo ≤ 60 mm². Cierre: B + DRC, solo quedan los encimes de margen; commit «v6 bloque A».",
|
||||
"enlaces": [
|
||||
{
|
||||
"titulo": "Propuesta α (estado de la v6, §3.1)",
|
||||
"url": "/hardware/v6-estado-21oct/#31-bloque-a-propuesta-corrige-la-rev-10"
|
||||
},
|
||||
{
|
||||
"titulo": "Plan, §3.1 (metas corregidas)",
|
||||
"url": "/hardware/v6-plan-fines-de-semana/#31-fuente-u25-bloque-a"
|
||||
}
|
||||
]
|
||||
},
|
||||
"nota": "Corregido: la meta de antes (D34 abajo del pin 8 y L22 en su lugar) no cabe; entre U25 y P1 hay 4.1 mm y D34 mide 6.7. La nueva es la propuesta α, revisada con los courtyards de T38."
|
||||
},
|
||||
{
|
||||
"id": "t-v6-barrenos",
|
||||
"poner": {
|
||||
"titulo": "v6: liberar MH4 y MH8 (MH6 ya quedó con T38; T25 regresó los 5 barrenos el 21-oct)"
|
||||
},
|
||||
"nota": "T38: MH6 libre y regla nueva, solo GND a < 4 mm del centro en las caras; el plano /3V3 ya se aparta de los 5 barrenos. Falta MH8 (sale con el bloque A) y MH4 (ESD de la SIM, /SIM_*, /NTC): el DRC ya los marca."
|
||||
},
|
||||
{
|
||||
"id": "T261022-acf8",
|
||||
"poner": {
|
||||
"titulo": "v6: T25 + T28 + T38 aplicados (barrenos, U25 LCSC, pasta de U6, stackup, courtyards, MH6, regla de barrenos) — falta commit y push"
|
||||
},
|
||||
"nota": "21-oct noche: T38 aplicado (courtyards de 11 huellas y su biblioteca, MH6 libre, regla de barrenos). Va en el mismo commit de kicad que la v6; se cierra cuando Leo suba."
|
||||
},
|
||||
{
|
||||
"id": "T261022-2a06",
|
||||
"poner": {
|
||||
"estado": "hecho"
|
||||
},
|
||||
"nota": "Leo hizo commit y push el 21-oct a las 19:56."
|
||||
},
|
||||
{
|
||||
"id": "t-v6-cierre-pedido",
|
||||
"nota": "21-oct noche: T38 aplicado. Courtyards reales en 11 huellas (1 encime real: C153 bajo L22), MH6 libre y regla de barrenos (el plano /3V3 de B.Cu quedaba desnudo bajo los separadores). DRC: 52 errores conocidos, 0 sin conectar."
|
||||
},
|
||||
{
|
||||
"crear": {
|
||||
"id": "d-21-3-v6-bloque-a",
|
||||
"titulo": "D-21-3: ¿OK a la propuesta α del bloque A de la v6?",
|
||||
"pista": "ingenieria",
|
||||
"estado": "por-hacer",
|
||||
"responsable": "leo",
|
||||
"version_tarjeta": "v6",
|
||||
"fecha_limite": "2026-10-23",
|
||||
"descripcion": "D34 entre U25 y L22, y L22 recorrida al este: queda a 0.62 mm de cobre de la orilla y su cuerpo a 1.35 mm, así que hay que revisar la pared de la HBOX. Metas corregidas: cátodo ≤ 3.5 mm, L22.1 ≤ 9 mm, nodo SW ≤ 10 mm² fuera de los pads, lazo ≤ 60 mm². Michelle la hace el sábado 24.",
|
||||
"enlaces": [
|
||||
{
|
||||
"titulo": "Propuesta α (§3.1)",
|
||||
"url": "/hardware/v6-estado-21oct/#31-bloque-a-propuesta-corrige-la-rev-10"
|
||||
}
|
||||
],
|
||||
"etiquetas": [
|
||||
"decisión"
|
||||
]
|
||||
},
|
||||
"si_no_hay": [
|
||||
"D-21-3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"crear": {
|
||||
"id": "t-v6-excluir-margen",
|
||||
"titulo": "v6: al cerrar el layout, excluir en el DRC los encimes de courtyard que son solo de margen",
|
||||
"pista": "ingenieria",
|
||||
"estado": "por-hacer",
|
||||
"responsable": "michelle",
|
||||
"version_tarjeta": "v6",
|
||||
"fecha_limite": "2026-11-01",
|
||||
"descripcion": "T38 destapó 13 encimes de margen, con hueco físico de 0.29 a 0.76 mm. Se excluyen al final, cuando ya no se muevan las piezas: clic derecho → Excluir esta violación, con el comentario «T38: solo margen, hueco X mm». No se pueden poner por script, porque KiCad pide la posición exacta del marcador.",
|
||||
"enlaces": [
|
||||
{
|
||||
"titulo": "Lista de encimes (§5.1)",
|
||||
"url": "/hardware/v6-estado-21oct/#51-courtyards"
|
||||
}
|
||||
],
|
||||
"etiquetas": []
|
||||
},
|
||||
"si_no_hay": [
|
||||
"encimes de courtyard"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "t-v56-revision-gerber",
|
||||
"nota": "Desde T38, el DRC de la V5.6 y la V5.7 muestra 2 y 4 avisos de «no coincide con la biblioteca» (U7, U26; y U25, L22 en la V5.7). Es solo el courtyard: no actualicen esas huellas antes del Gerber."
|
||||
}
|
||||
]
|
||||
}
|
||||
83
backend/lotes/README.md
Normal file
83
backend/lotes/README.md
Normal file
@ -0,0 +1,83 @@
|
||||
# Lotes del tablero
|
||||
|
||||
Un **lote** es la lista de cambios al tablero de pendientes que deja una tarea terminada: qué se cerró,
|
||||
qué se abrió, qué se movió y por qué. Lo escribe Claude (que no llega al servidor) y lo aplica Leo:
|
||||
|
||||
```bash
|
||||
./scripts/tablero.sh revisar # qué haría cada lote nuevo, contra el tablero de ahora; no escribe nada
|
||||
./scripts/tablero.sh aplicar # lo aplica y baja la foto nueva a _estado/
|
||||
```
|
||||
|
||||
`backend/tablero.py` hace el trabajo en el servidor: llama al mismo backend que el sitio
|
||||
(`127.0.0.1:8081`) con el usuario del campo `autor`, así que cada cambio pasa por las mismas validaciones
|
||||
y queda en el historial del pendiente con su autor y su nota. En la página de inicio se ve en
|
||||
**Actividad** como cualquier otro cambio («Claude terminó …»).
|
||||
|
||||
## Reglas
|
||||
|
||||
1. **Un lote por tarea terminada**, con nombre `AAAA-MM-DD_tema.json` igual a su campo `lote`.
|
||||
2. **Un lote aplicado no se edita.** Si algo salió mal, se escribe otro lote. `tablero.py` se niega a
|
||||
seguir si un cambio ya aplicado no es igual al del archivo.
|
||||
3. **Nunca se reaplica.** Lo aplicado se anota en `/srv/projects/drogheda/lotes-aplicados.json`; si
|
||||
alguien deshace un cambio a mano, el lote no lo vuelve a hacer.
|
||||
4. **No se toca lo que ya está en Hecho**, salvo que el cambio lo diga con `si_estado`.
|
||||
5. **Cada cambio a un pendiente existente lleva `nota`**: el porqué, en una o dos frases (≤ 300
|
||||
caracteres). Es lo que la gente lee en el historial.
|
||||
6. **Se busca antes de crear.** `crear` usa un `id` fijo (si ya existe, se salta) y `si_no_hay` evita
|
||||
duplicar un pendiente que alguien ya abrió a mano con otro id.
|
||||
7. **Leo revisa antes de aplicar.** `revisar` muestra cada cambio contra el tablero real; lo que no
|
||||
cuadra se salta y se dice por qué (no existe, ya está en Hecho, el título coincide con varios…).
|
||||
|
||||
## Formato
|
||||
|
||||
```json
|
||||
{
|
||||
"lote": "2026-10-21_pasada-6",
|
||||
"autor": "claude",
|
||||
"motivo": "Pasada 6 de V5.6 y V5.7 hecha por Claude: cobre listo para Gerber",
|
||||
"fuente": "/hardware/v56-v57-auditoria-21oct/#6-pasada-6-hecha-por-claude-t34-a-t37",
|
||||
"cambios": [
|
||||
{ "id": "t-v56-pasada-2",
|
||||
"poner": { "estado": "hecho" },
|
||||
"nota": "Cerrado: le siguieron las pasadas 3 a 6." },
|
||||
|
||||
{ "buscar": "pasada 3", "todos": true,
|
||||
"poner": { "estado": "hecho" },
|
||||
"nota": "Cerrado: la pasada 6 dejó el cobre listo para Gerber." },
|
||||
|
||||
{ "buscar": ["revisión pad a pad", "-v6"],
|
||||
"si_estado": ["por-hacer", "bloqueado"],
|
||||
"poner": { "estado": "en-curso", "responsable": "leo, claude", "fecha_limite": "2026-10-22" },
|
||||
"nota": "Revisión hecha; falta el paquete de fabricación." },
|
||||
|
||||
{ "crear": { "id": "d-21-2-c149-c150", "titulo": "D-21-2: ¿C149/C150 se quedan?",
|
||||
"pista": "ingenieria", "estado": "por-hacer", "responsable": "leo",
|
||||
"version_tarjeta": "V5.7", "fecha_limite": "2026-10-22",
|
||||
"descripcion": "…", "enlaces": [{ "titulo": "Por qué", "url": "/hardware/…" }],
|
||||
"etiquetas": ["decisión"] },
|
||||
"si_no_hay": ["D-21-2"] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Llave | Dónde | Qué hace |
|
||||
|---|---|---|
|
||||
| `lote`, `autor`, `motivo` | lote | Obligatorias. `autor` es el usuario con el que se escribe (`claude`; Leo usa `leo`). `fuente`: liga opcional al documento que respalda el lote |
|
||||
| `id` / `buscar` | cambio | El pendiente: por su id, o por un texto de su título, sin importar acentos, mayúsculas ni signos. `buscar` también puede ser una lista: el título debe tener todos los textos y `"-texto"` quiere decir «que no lo tenga» (`["piloto", "v5.6", "-banco"]`). Tiene que coincidir con **uno solo**, primero entre los abiertos |
|
||||
| `todos` | cambio | Con `buscar`: aplica el cambio a **todos** los abiertos que coincidan (por ejemplo, cerrar todas las «pasada 3») |
|
||||
| `si_estado` | cambio | Solo aplica si el pendiente está en uno de estos estados. Por omisión: por hacer, en curso o bloqueado |
|
||||
| `poner` | cambio | Los campos que cambian: `titulo`, `descripcion`, `pista`, `estado`, `responsable`, `fecha_limite`, `enlaces`, `etiquetas`, `bloqueada_por`, `version_tarjeta`. Lo que ya tiene ese valor no se manda |
|
||||
| `nota` | cambio | Obligatoria. Comentario en el historial; sin `poner`, el cambio es solo el comentario |
|
||||
| `crear` | cambio | Pendiente nuevo, con los mismos campos que el formulario del sitio más un `id` fijo (`t-…`, `d-…`): `titulo`, `pista`, `responsable` obligatorios |
|
||||
| `si_no_hay` | cambio | Con `crear`: no lo crea si hay un pendiente abierto cuyo título contenga alguno de estos textos |
|
||||
|
||||
Valores: `pista` ∈ ingenieria · produccion · mecanica · compras · campo; `estado` ∈ por-hacer ·
|
||||
en-curso · bloqueado · hecho; `version_tarjeta` ∈ V5.5 · V5.6 · V5.7 · v6 · —; `responsable`, ids del
|
||||
[Equipo](https://drogheda.helgen.online/tableros/equipo/) separados por coma; `fecha_limite` AAAA-MM-DD.
|
||||
|
||||
## La foto
|
||||
|
||||
`./scripts/tablero.sh foto` (y cada `./scripts/deploy.sh`) baja el tablero a `_estado/` en la Mac:
|
||||
`tablero.json` (pendientes con su historial, enfoque de la semana, actividad de 14 días y lotes
|
||||
aplicados) y `tablero.md` (lo mismo para leer). `_estado/` no va a git. Claude la lee al empezar una
|
||||
tarea para saber qué hay, quién lo tiene y qué ids usar.
|
||||
582
backend/tablero.py
Normal file
582
backend/tablero.py
Normal file
@ -0,0 +1,582 @@
|
||||
#!/usr/bin/env python3
|
||||
"""El tablero de pendientes desde la terminal: foto para leerlo y lotes para cambiarlo (21-oct-2026).
|
||||
|
||||
drogheda.helgen.online es el centro de mando del proyecto: cada tarea termina ahí, también las que
|
||||
hace Claude. Claude no llega al servidor, así que deja sus cambios al tablero en un **lote**
|
||||
(backend/lotes/AAAA-MM-DD_tema.json); Leo lo revisa y lo aplica con ./scripts/tablero.sh, y Claude lee
|
||||
el tablero en la **foto** (_estado/tablero.json y _estado/tablero.md en la Mac de Leo, fuera de git).
|
||||
|
||||
En el servidor (lo llama ./scripts/tablero.sh por ssh):
|
||||
python3 tablero.py foto pendientes, enfoque y actividad de 14 días, en JSON (stdout)
|
||||
python3 tablero.py revisar lotes/ qué haría cada lote que falta; no escribe nada
|
||||
python3 tablero.py aplicar lotes/ aplica lo que falta; lo ya aplicado se salta
|
||||
python3 tablero.py pendientes lotes/ una línea: cuántos cambios faltan por aplicar
|
||||
En la Mac:
|
||||
python3 backend/tablero.py resumen _estado/tablero.json la foto en texto (tablero.md)
|
||||
|
||||
Habla con el backend por HTTP local (TABLERO_API, por omisión http://127.0.0.1:8081) y escribe con
|
||||
X-Remote-User = «autor» del lote: pasa por las mismas validaciones que el sitio y cada cambio queda en
|
||||
el historial del pendiente, con su autor y su nota. Lo aplicado se anota en
|
||||
$DATOS_BASE/lotes-aplicados.json (la misma carpeta de datos que usa app.py). Un lote aplicado no se
|
||||
vuelve a aplicar aunque alguien deshaga el cambio a mano, y no se edita después de aplicarse: se escribe
|
||||
otro. Un cambio nunca toca un pendiente que ya está en Hecho (salvo que el lote lo pida con si_estado).
|
||||
Formato de los lotes: backend/lotes/README.md. Solo usa la biblioteca estándar de Python.
|
||||
"""
|
||||
import argparse
|
||||
import datetime
|
||||
import glob
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
API = os.environ.get("TABLERO_API", "http://127.0.0.1:8081").rstrip("/")
|
||||
DATOS_BASE = os.environ.get("DATOS_BASE") or (
|
||||
os.path.dirname(os.environ.get("ACTAS_DIR", "/srv/projects/drogheda/actas").rstrip("/")) or "/srv/projects/drogheda")
|
||||
REGISTRO = os.environ.get("TABLERO_REGISTRO") or os.path.join(DATOS_BASE, "lotes-aplicados.json")
|
||||
LECTOR = "claude" # X-Remote-User para las lecturas (no cambia nada; queda claro quién consulta)
|
||||
|
||||
ESTADOS = ("por-hacer", "en-curso", "bloqueado", "hecho")
|
||||
ABIERTOS = ("por-hacer", "en-curso", "bloqueado")
|
||||
NOMBRE = {"por-hacer": "Por hacer", "en-curso": "En curso", "bloqueado": "Bloqueado", "hecho": "Hecho"}
|
||||
CAMPOS = ("titulo", "descripcion", "pista", "estado", "responsable", "fecha_limite", "enlaces", "etiquetas",
|
||||
"bloqueada_por", "version_tarjeta")
|
||||
LOTE_ID = re.compile(r"^\d{4}-\d{2}-\d{2}_[a-z0-9][a-z0-9-]{1,60}$")
|
||||
TAREA_ID = re.compile(r"^[a-z0-9][a-z0-9-]{2,59}$")
|
||||
MES = ("ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic")
|
||||
|
||||
|
||||
class SinBackend(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ utilidades
|
||||
|
||||
def ahora():
|
||||
return datetime.datetime.now().replace(microsecond=0).isoformat()
|
||||
|
||||
|
||||
def norm(s):
|
||||
"""'Pasada 6 — V5.6' → 'pasada 6 — v5.6': minúsculas, sin acentos, espacios simples."""
|
||||
s = unicodedata.normalize("NFKD", str(s or "")).encode("ascii", "ignore").decode().lower()
|
||||
return re.sub(r"\s+", " ", s).strip()
|
||||
|
||||
|
||||
def clave(s):
|
||||
"""Para buscar por título sin que estorben acentos, mayúsculas ni signos:
|
||||
'Pasada 3 — parte A (V5.6)' → 'pasada 3 parte a v5.6'; 'D-21-2' → 'd 21 2'."""
|
||||
return re.sub(r"\s+", " ", re.sub(r"[^a-z0-9.]+", " ", norm(s))).strip(" .")
|
||||
|
||||
|
||||
def corto(v, n=48):
|
||||
if isinstance(v, list):
|
||||
v = ", ".join(str(x.get("titulo", x)) if isinstance(x, dict) else str(x) for x in v) or "—"
|
||||
s = "—" if v in (None, "") else str(v)
|
||||
return s if len(s) <= n else s[:n - 1] + "…"
|
||||
|
||||
|
||||
def fecha_corta(iso):
|
||||
"""'2026-10-22…' → '22-oct' (con hora si la trae: '22-oct 19:40')."""
|
||||
s = str(iso or "")
|
||||
if not re.match(r"^\d{4}-\d{2}-\d{2}", s):
|
||||
return s or "—"
|
||||
f = f"{int(s[8:10])}-{MES[int(s[5:7]) - 1]}"
|
||||
return f + (" " + s[11:16] if len(s) >= 16 else "")
|
||||
|
||||
|
||||
def huella(cambio):
|
||||
return hashlib.sha256(json.dumps(cambio, sort_keys=True, ensure_ascii=False).encode("utf-8")).hexdigest()[:12]
|
||||
|
||||
|
||||
def llamar(metodo, ruta, usuario, cuerpo=None):
|
||||
datos = None if cuerpo is None else json.dumps(cuerpo, ensure_ascii=False).encode("utf-8")
|
||||
req = urllib.request.Request(API + ruta, data=datos, method=metodo, headers={
|
||||
"X-Remote-User": usuario, "Content-Type": "application/json", "Accept": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=20) as r:
|
||||
return r.status, json.loads(r.read().decode("utf-8") or "{}")
|
||||
except urllib.error.HTTPError as e:
|
||||
try:
|
||||
return e.code, json.loads(e.read().decode("utf-8") or "{}")
|
||||
except ValueError:
|
||||
return e.code, {"ok": False, "error": f"HTTP {e.code}"}
|
||||
except (urllib.error.URLError, OSError) as e:
|
||||
raise SinBackend(f"No contesta el backend en {API} ({getattr(e, 'reason', e)}). "
|
||||
"¿Está arriba el servicio actas-hbox?")
|
||||
|
||||
|
||||
VALORES = {} # pista y version_tarjeta válidas, tal como las publica el backend en GET /api/tareas
|
||||
|
||||
|
||||
def leer_tareas():
|
||||
st, j = llamar("GET", "/api/tareas", LECTOR)
|
||||
if st != 200 or not j.get("ok"):
|
||||
raise SinBackend(f"No pude leer los pendientes: {j.get('error') or st}")
|
||||
VALORES.update(pista=list(j.get("pistas") or []), version_tarjeta=list(j.get("versiones") or []))
|
||||
return j.get("tareas") or [], j.get("equipo") or {}
|
||||
|
||||
|
||||
def valores_malos(campos):
|
||||
"""Lo que el backend rechazaría: se ve en «revisar» en vez de fallar al aplicar."""
|
||||
malos = []
|
||||
for k, validos in VALORES.items():
|
||||
if k in campos and validos and campos[k] not in validos:
|
||||
malos.append(f"{k} «{campos[k]}» no existe (usa {', '.join(validos)})")
|
||||
return malos
|
||||
|
||||
|
||||
def leer_registro():
|
||||
try:
|
||||
with open(REGISTRO, encoding="utf-8") as f:
|
||||
r = json.load(f)
|
||||
return r if isinstance(r, dict) and isinstance(r.get("lotes"), dict) else {"lotes": {}}
|
||||
except FileNotFoundError:
|
||||
return {"lotes": {}}
|
||||
|
||||
|
||||
def escribir_registro(r):
|
||||
os.makedirs(os.path.dirname(REGISTRO) or ".", exist_ok=True)
|
||||
tmp = REGISTRO + ".tmp"
|
||||
with open(tmp, "w", encoding="utf-8") as f:
|
||||
json.dump(r, f, ensure_ascii=False, indent=1)
|
||||
os.replace(tmp, REGISTRO)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ lotes: formato
|
||||
|
||||
def validar_cambio(c):
|
||||
e = []
|
||||
if not isinstance(c, dict):
|
||||
return ["debe ser un objeto"]
|
||||
if "crear" in c:
|
||||
t = c["crear"]
|
||||
sobra = set(c) - {"crear", "si_no_hay"}
|
||||
if sobra:
|
||||
e.append("llaves que no van junto a «crear»: " + ", ".join(sorted(sobra)))
|
||||
if not isinstance(t, dict):
|
||||
return e + ["«crear» debe ser un objeto"]
|
||||
malas = set(t) - set(CAMPOS) - {"id"}
|
||||
if malas:
|
||||
e.append("campos que no existen: " + ", ".join(sorted(malas)))
|
||||
if not TAREA_ID.match(str(t.get("id") or "")):
|
||||
e.append("«crear.id» es obligatorio: minúsculas, números y guiones (3 a 60)")
|
||||
for k in ("titulo", "pista", "responsable"):
|
||||
if not str(t.get(k) or "").strip():
|
||||
e.append(f"falta «crear.{k}»")
|
||||
if t.get("estado") and t["estado"] not in ESTADOS:
|
||||
e.append(f"estado inválido: {t['estado']}")
|
||||
if t.get("fecha_limite") and not re.match(r"^\d{4}-\d{2}-\d{2}$", str(t["fecha_limite"])):
|
||||
e.append("«fecha_limite» va como AAAA-MM-DD")
|
||||
sn = c.get("si_no_hay")
|
||||
if sn is not None and not (isinstance(sn, str) or (isinstance(sn, list) and all(isinstance(x, str) for x in sn))):
|
||||
e.append("«si_no_hay» es un texto o una lista de textos")
|
||||
return e
|
||||
sobra = set(c) - {"id", "buscar", "todos", "si_estado", "poner", "nota"}
|
||||
if sobra:
|
||||
e.append("llaves que no existen: " + ", ".join(sorted(sobra)))
|
||||
if bool(c.get("id")) == bool(c.get("buscar")):
|
||||
e.append("lleva «id» o «buscar» (uno de los dos)")
|
||||
if c.get("todos") and not c.get("buscar"):
|
||||
e.append("«todos» solo va con «buscar»")
|
||||
b = c.get("buscar")
|
||||
if b is not None:
|
||||
textos = b if isinstance(b, list) else [b]
|
||||
if not textos or not all(isinstance(x, str) for x in textos):
|
||||
e.append("«buscar» es un texto o una lista de textos")
|
||||
elif sum(len(clave(x)) for x in textos if not x.startswith("-")) < 4:
|
||||
e.append("«buscar» necesita 4 letras o más (sin contar los «-texto»)")
|
||||
nota = str(c.get("nota") or "").strip()
|
||||
if not nota:
|
||||
e.append("falta «nota»: el porqué del cambio, queda en el historial del pendiente")
|
||||
elif len(nota) > 300:
|
||||
e.append(f"«nota» de {len(nota)} caracteres; el backend guarda 300")
|
||||
p = c.get("poner", {})
|
||||
if not isinstance(p, dict):
|
||||
e.append("«poner» debe ser un objeto")
|
||||
else:
|
||||
malas = set(p) - set(CAMPOS)
|
||||
if malas:
|
||||
e.append("campos que no existen en «poner»: " + ", ".join(sorted(malas)))
|
||||
if "estado" in p and p["estado"] not in ESTADOS:
|
||||
e.append(f"estado inválido en «poner»: {p['estado']}")
|
||||
if p.get("fecha_limite") and not re.match(r"^\d{4}-\d{2}-\d{2}$", str(p["fecha_limite"])):
|
||||
e.append("«fecha_limite» va como AAAA-MM-DD (o null para quitarla)")
|
||||
se = c.get("si_estado")
|
||||
if se is not None and (not isinstance(se, list) or not se or any(x not in ESTADOS for x in se)):
|
||||
e.append("«si_estado» es una lista de estados (por-hacer, en-curso, bloqueado, hecho)")
|
||||
return e
|
||||
|
||||
|
||||
def cargar_lote(ruta):
|
||||
try:
|
||||
with open(ruta, encoding="utf-8") as f:
|
||||
lote = json.load(f)
|
||||
except ValueError as ex:
|
||||
raise ValueError(f"{ruta}: no es JSON válido ({ex})")
|
||||
if not isinstance(lote, dict):
|
||||
raise ValueError(f"{ruta}: el lote debe ser un objeto JSON")
|
||||
err = []
|
||||
lid = str(lote.get("lote") or "")
|
||||
if not LOTE_ID.match(lid):
|
||||
err.append("«lote» debe ser AAAA-MM-DD_tema (minúsculas y guiones)")
|
||||
elif os.path.splitext(os.path.basename(ruta))[0] != lid:
|
||||
err.append(f"el archivo debe llamarse {lid}.json")
|
||||
if not TAREA_ID.match(norm(lote.get("autor")) or "-"):
|
||||
err.append("falta «autor»: el usuario con el que se escribe (p. ej. claude)")
|
||||
if not str(lote.get("motivo") or "").strip():
|
||||
err.append("falta «motivo»: qué tarea cerró este lote")
|
||||
cambios = lote.get("cambios")
|
||||
if not isinstance(cambios, list) or not cambios:
|
||||
err.append("«cambios» debe ser una lista con al menos un cambio")
|
||||
else:
|
||||
for i, c in enumerate(cambios, 1):
|
||||
err += [f"cambio {i}: {x}" for x in validar_cambio(c)]
|
||||
if err:
|
||||
raise ValueError(f"{ruta}:\n - " + "\n - ".join(err))
|
||||
lote["autor"] = norm(lote["autor"])
|
||||
return lote
|
||||
|
||||
|
||||
def archivos_de(rutas):
|
||||
out = []
|
||||
for r in rutas:
|
||||
if os.path.isdir(r):
|
||||
out += sorted(f for f in glob.glob(os.path.join(r, "*.json")))
|
||||
elif os.path.isfile(r):
|
||||
out.append(r)
|
||||
else:
|
||||
raise ValueError(f"No existe {r}")
|
||||
return out
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ lotes: plan y aplicación
|
||||
|
||||
def _ids(responsable):
|
||||
return sorted({norm(x).replace(" ", "") for x in re.split(r"[,;/]+", str(responsable or "")) if norm(x)})
|
||||
|
||||
|
||||
def igual(campo, actual, nuevo):
|
||||
if campo == "responsable":
|
||||
return _ids(actual) == _ids(nuevo)
|
||||
if campo in ("etiquetas", "bloqueada_por"):
|
||||
return sorted(norm(x) for x in (actual or [])) == sorted(norm(x) for x in (nuevo or []))
|
||||
if campo == "enlaces":
|
||||
par = lambda lista: [(str(x.get("titulo", "")).strip(), str(x.get("url", "")).strip())
|
||||
for x in (lista or []) if isinstance(x, dict)]
|
||||
return par(actual) == par(nuevo)
|
||||
if campo == "fecha_limite":
|
||||
return (actual or None) == (nuevo or None)
|
||||
return str("" if actual is None else actual).strip() == str("" if nuevo is None else nuevo).strip()
|
||||
|
||||
|
||||
def _quien_es(t):
|
||||
return f"{t['id']} «{corto(t.get('titulo'), 70)}»"
|
||||
|
||||
|
||||
def contiene(titulo, texto):
|
||||
q = clave(texto)
|
||||
return bool(q) and f" {q} " in f" {clave(titulo)} "
|
||||
|
||||
|
||||
def coincide(titulo, buscar):
|
||||
"""«buscar» es un texto o una lista de textos que el título debe tener todos; «-texto» = que no lo tenga
|
||||
(["piloto", "v5.6", "-banco"] encuentra «Piloto V5.6: 5 tarjetas» y no «Banco del piloto V5.6»)."""
|
||||
for x in (buscar if isinstance(buscar, list) else [buscar]):
|
||||
if x.startswith("-"):
|
||||
if contiene(titulo, x[1:]):
|
||||
return False
|
||||
elif not contiene(titulo, x):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _texto_busqueda(buscar):
|
||||
return " + ".join(f"«{x}»" for x in (buscar if isinstance(buscar, list) else [buscar]))
|
||||
|
||||
|
||||
def _cambio_a(t, c):
|
||||
"""Un pendiente existente → (detalle, petición) o (motivo, None) si no aplica."""
|
||||
si = c.get("si_estado") or list(ABIERTOS)
|
||||
if t.get("estado") not in si:
|
||||
return (f"{_quien_es(t)} está en {NOMBRE.get(t.get('estado'), t.get('estado'))}; "
|
||||
f"este cambio solo aplica en {', '.join(NOMBRE[x] for x in si)}"), None
|
||||
poner = {k: v for k, v in (c.get("poner") or {}).items() if not igual(k, t.get(k), v)}
|
||||
if valores_malos(poner):
|
||||
return "✗ " + f"{_quien_es(t)}: " + "; ".join(valores_malos(poner)), None
|
||||
partes = [f"{k}: {corto(t.get(k), 30)} → {corto(v, 30)}" for k, v in poner.items()]
|
||||
detalle = f"{_quien_es(t)}: " + ("; ".join(partes) + " · " if partes else "") + f"nota «{corto(c['nota'], 80)}»"
|
||||
return detalle, ("PATCH", "/api/tarea?id=" + urllib.parse.quote(t["id"]), dict(poner, _nota=c["nota"].strip()), t["id"])
|
||||
|
||||
|
||||
def planear(c, tareas):
|
||||
"""Qué haría un cambio contra el tablero de ahora → (acción, detalle, [peticiones]).
|
||||
acción: crear · cambiar · saltar · invalido. Cada petición: (método, ruta, cuerpo, id del pendiente)."""
|
||||
por_id = {t.get("id"): t for t in tareas}
|
||||
if "crear" in c:
|
||||
t = c["crear"]
|
||||
if t["id"] in por_id:
|
||||
return "saltar", f"ya existe {_quien_es(por_id[t['id']])}", []
|
||||
evitar = c.get("si_no_hay")
|
||||
for x in ([evitar] if isinstance(evitar, str) else (evitar or [])):
|
||||
for o in tareas:
|
||||
if o.get("estado") != "hecho" and contiene(o.get("titulo"), x):
|
||||
return "saltar", f"ya hay uno abierto parecido (dice «{x}»): {_quien_es(o)}", []
|
||||
cuerpo = dict(t)
|
||||
cuerpo.setdefault("estado", "por-hacer")
|
||||
if valores_malos(cuerpo):
|
||||
return "invalido", f"nuevo {cuerpo['id']}: " + "; ".join(valores_malos(cuerpo)), []
|
||||
datos = [cuerpo["pista"], NOMBRE.get(cuerpo["estado"], cuerpo["estado"]), cuerpo["responsable"]]
|
||||
if cuerpo.get("fecha_limite"):
|
||||
datos.append("vence " + fecha_corta(cuerpo["fecha_limite"]))
|
||||
return "crear", f"nuevo {cuerpo['id']} «{corto(cuerpo['titulo'], 70)}» ({', '.join(datos)})", \
|
||||
[("POST", "/api/tareas", cuerpo, cuerpo["id"])]
|
||||
if c.get("id"):
|
||||
t = por_id.get(c["id"])
|
||||
if not t:
|
||||
return "saltar", f"no existe el pendiente {c['id']} (¿se borró?)", []
|
||||
objetivo = [t]
|
||||
else:
|
||||
q = _texto_busqueda(c["buscar"])
|
||||
abiertos = [o for o in tareas if o.get("estado") != "hecho" and coincide(o.get("titulo"), c["buscar"])]
|
||||
if c.get("todos"):
|
||||
if not abiertos:
|
||||
return "saltar", f"ningún pendiente abierto dice {q} en el título", []
|
||||
objetivo = abiertos
|
||||
else:
|
||||
cand = abiertos or [o for o in tareas if o.get("estado") == "hecho" and coincide(o.get("titulo"), c["buscar"])]
|
||||
if len(cand) != 1:
|
||||
if not cand:
|
||||
return "saltar", f"ningún pendiente dice {q} en el título", []
|
||||
return "saltar", (f"{q} coincide con {len(cand)} pendientes (usa «todos» o un texto más preciso): "
|
||||
+ "; ".join(_quien_es(o) for o in cand[:4])), []
|
||||
objetivo = cand
|
||||
planes = [_cambio_a(t, c) for t in objetivo]
|
||||
peticiones = [p for _, p in planes if p]
|
||||
if len(objetivo) == 1:
|
||||
detalle, p = planes[0]
|
||||
if not p:
|
||||
malo = detalle.startswith("✗ ")
|
||||
return ("invalido" if malo else "saltar"), (detalle[2:] if malo else detalle), []
|
||||
return "cambiar", detalle, peticiones
|
||||
detalle = f"{_texto_busqueda(c['buscar'])}, {len(objetivo)} pendientes:\n" + "\n".join(
|
||||
f" – {d}" if p else f" – se salta: {d}" for d, p in planes)
|
||||
malos = [d for d, p in planes if not p and d.startswith("✗ ")]
|
||||
if malos:
|
||||
return "invalido", detalle, []
|
||||
return ("cambiar" if peticiones else "saltar"), detalle, peticiones
|
||||
|
||||
|
||||
def simular(tareas, peticion):
|
||||
"""En «revisar», que los cambios siguientes del mismo lote vean los anteriores."""
|
||||
metodo, ruta, cuerpo, rid = peticion
|
||||
if metodo == "POST":
|
||||
return tareas + [dict(cuerpo)]
|
||||
return [dict(t, **{k: v for k, v in cuerpo.items() if not k.startswith("_")}) if t.get("id") == rid else t
|
||||
for t in tareas]
|
||||
|
||||
|
||||
def correr(rutas, aplicar, silencioso=False):
|
||||
archivos = archivos_de(rutas)
|
||||
lotes = [(a, cargar_lote(a)) for a in archivos] # todos válidos o ninguno se aplica
|
||||
ids = [lote["lote"] for _, lote in lotes]
|
||||
if len(ids) != len(set(ids)):
|
||||
raise ValueError("Hay dos lotes con el mismo «lote».")
|
||||
reg = leer_registro()
|
||||
tareas, _ = leer_tareas()
|
||||
por_aplicar = errores = 0
|
||||
di = (lambda *a: None) if silencioso else print
|
||||
if not lotes:
|
||||
di("No hay lotes en " + ", ".join(rutas) + ".")
|
||||
for archivo, lote in lotes:
|
||||
lid = lote["lote"]
|
||||
r = reg["lotes"].get(lid)
|
||||
previos = (r or {}).get("cambios", {})
|
||||
cambiado = [i for i, c in enumerate(lote["cambios"], 1)
|
||||
if str(i) in previos and previos[str(i)].get("huella") != huella(c)]
|
||||
if r and r.get("completo"):
|
||||
di(f"✓ {lid}: aplicado el {fecha_corta(r.get('fin'))} ({r.get('motivo', '')[:70]})"
|
||||
+ (f"\n ⚠ el archivo cambió después de aplicarse (cambios {', '.join(map(str, cambiado))}); "
|
||||
"no se vuelve a aplicar: lo nuevo va en otro lote" if cambiado else ""))
|
||||
continue
|
||||
if cambiado:
|
||||
di(f"✗ {lid}: los cambios {', '.join(map(str, cambiado))} ya no son los que se aplicaron. "
|
||||
"Un lote aplicado no se edita: escribe uno nuevo.")
|
||||
errores += 1
|
||||
continue
|
||||
di(f"\n▶ {lid} — {lote['motivo']} (lo escribe: {lote['autor']})")
|
||||
if aplicar and not r:
|
||||
r = reg["lotes"][lid] = {"archivo": os.path.basename(archivo), "autor": lote["autor"],
|
||||
"motivo": lote["motivo"], "inicio": ahora(), "fin": None, "completo": False,
|
||||
"aplicado_por": os.environ.get("SUDO_USER") or os.environ.get("USER") or "?",
|
||||
"cambios": {}}
|
||||
for i, c in enumerate(lote["cambios"], 1):
|
||||
previo = previos.get(str(i))
|
||||
if previo and previo.get("resultado") in ("hecho", "saltado"):
|
||||
di(f" {i}. ya {previo['resultado']}: {previo['detalle']}")
|
||||
continue
|
||||
accion, detalle, peticiones = planear(c, tareas)
|
||||
signo = {"crear": "+", "cambiar": "~", "saltar": "·", "invalido": "✗"}[accion]
|
||||
if not aplicar:
|
||||
di(f" {i}. {signo} {'se salta: ' if accion == 'saltar' else ''}{detalle}")
|
||||
por_aplicar += len(peticiones)
|
||||
for p in peticiones:
|
||||
tareas = simular(tareas, p)
|
||||
errores += accion == "invalido"
|
||||
continue
|
||||
if accion == "invalido":
|
||||
r["cambios"][str(i)] = {"resultado": "error", "detalle": detalle, "huella": huella(c), "cuando": ahora()}
|
||||
di(f" {i}. ✗ no se manda: {detalle}")
|
||||
errores += 1
|
||||
elif accion == "saltar":
|
||||
r["cambios"][str(i)] = {"resultado": "saltado", "detalle": detalle, "huella": huella(c), "cuando": ahora()}
|
||||
di(f" {i}. · se salta: {detalle}")
|
||||
else:
|
||||
ya = set((previo or {}).get("hechos_en") or []) # en un reintento, lo que ya salió no se repite
|
||||
fallas = []
|
||||
for metodo, ruta, cuerpo, rid in peticiones:
|
||||
if rid in ya:
|
||||
continue
|
||||
st, resp = llamar(metodo, ruta, lote["autor"], cuerpo)
|
||||
if st in (200, 201) and resp.get("ok"):
|
||||
t = resp.get("tarea") or {}
|
||||
tareas = [x for x in tareas if x.get("id") != t.get("id")] + [t]
|
||||
ya.add(rid)
|
||||
if resp.get("aviso"):
|
||||
di(f" aviso del backend ({rid}): {resp['aviso']}")
|
||||
else:
|
||||
fallas.append(f"{rid}: {resp.get('error') or st}")
|
||||
r["cambios"][str(i)] = {"resultado": "error" if fallas else "hecho",
|
||||
"detalle": detalle + (" → " + "; ".join(fallas) if fallas else ""),
|
||||
"hechos_en": sorted(ya), "huella": huella(c), "cuando": ahora()}
|
||||
if fallas:
|
||||
di(f" {i}. ✗ {detalle}\n el backend dijo: " + "; ".join(fallas))
|
||||
errores += 1
|
||||
else:
|
||||
di(f" {i}. {signo} {detalle}")
|
||||
escribir_registro(reg) # después de cada cambio: si algo se cae, no se pierde lo aplicado
|
||||
if aplicar:
|
||||
r["completo"] = all(r["cambios"].get(str(i), {}).get("resultado") in ("hecho", "saltado")
|
||||
for i in range(1, len(lote["cambios"]) + 1))
|
||||
r["fin"] = ahora() if r["completo"] else None
|
||||
escribir_registro(reg)
|
||||
if not aplicar and not silencioso:
|
||||
print(f"\n{por_aplicar} cambio(s) por aplicar." + (" Para aplicarlos: ./scripts/tablero.sh aplicar" if por_aplicar else ""))
|
||||
return por_aplicar, errores
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ foto y resumen
|
||||
|
||||
def foto():
|
||||
tareas, equipo = leer_tareas()
|
||||
out = {"tomada": ahora(), "api": API, "tareas": tareas, "equipo": equipo}
|
||||
st, h = llamar("GET", "/api/hoy", LECTOR)
|
||||
out["enfoque"] = h.get("enfoque") if st == 200 else None
|
||||
st, a = llamar("GET", "/api/actividad?dias=14&limite=150", LECTOR)
|
||||
out["actividad"] = a.get("eventos", []) if st == 200 else []
|
||||
out["lotes"] = {k: {"completo": v.get("completo"), "fin": v.get("fin"), "motivo": v.get("motivo")}
|
||||
for k, v in leer_registro()["lotes"].items()}
|
||||
json.dump(out, sys.stdout, ensure_ascii=False, indent=1)
|
||||
sys.stdout.write("\n")
|
||||
|
||||
|
||||
def resumen(ruta):
|
||||
with open(ruta, encoding="utf-8") as f:
|
||||
F = json.load(f)
|
||||
tareas, eq = F.get("tareas") or [], F.get("equipo") or {}
|
||||
hoy = str(F.get("tomada") or ahora())[:10]
|
||||
apodo = {"Leopoldo": "Leo"} # como en el sitio (Kanban.nombreCorto)
|
||||
nombre = lambda i: (lambda n: apodo.get(n, n))(((eq.get(i) or {}).get("nombre") or i or "—").split(" ")[0])
|
||||
estado_de = {t.get("id"): t.get("estado") for t in tareas}
|
||||
abiertos = [t for t in tareas if t.get("estado") != "hecho"]
|
||||
vencido = lambda t: bool(t.get("fecha_limite")) and t.get("estado") != "hecho" and t["fecha_limite"] < hoy
|
||||
cuenta = {e: sum(1 for t in tareas if t.get("estado") == e) for e in ESTADOS}
|
||||
o = [f"# Tablero de pendientes — foto del {fecha_corta(F.get('tomada'))}", "",
|
||||
"Generado por `./scripts/tablero.sh foto` (no se edita: se vuelve a bajar). Fuente: " + str(F.get("api")), "",
|
||||
f"**Abiertos: {len(abiertos)}** — por hacer {cuenta['por-hacer']} · en curso {cuenta['en-curso']} · "
|
||||
f"bloqueados {cuenta['bloqueado']} · **vencidos {sum(map(vencido, abiertos))}** · hechos {cuenta['hecho']} en total", ""]
|
||||
en = F.get("enfoque")
|
||||
if en:
|
||||
o += [f"## Enfoque de la semana ({nombre(en.get('editado_por'))}, {fecha_corta(en.get('editado_en'))})", "",
|
||||
en.get("titulo") or "", ""] + [f"- {p}" for p in (en.get("puntos") or [])] + [""]
|
||||
else:
|
||||
o += ["## Enfoque de la semana", "", "Nadie lo ha escrito en el sitio (se ve el de docs/datos/proyecto.json).", ""]
|
||||
|
||||
def linea(t):
|
||||
bits = [NOMBRE.get(t.get("estado"), t.get("estado")), t.get("pista") or "—"]
|
||||
if t.get("version_tarjeta") not in (None, "", "—"):
|
||||
bits.append(t["version_tarjeta"])
|
||||
if t.get("fecha_limite"):
|
||||
bits.append(("**VENCIDO** " if vencido(t) else "vence ") + fecha_corta(t["fecha_limite"]))
|
||||
espera = [x for x in (t.get("bloqueada_por") or []) if estado_de.get(x) != "hecho"]
|
||||
if espera:
|
||||
bits.append("espera a " + ", ".join(espera))
|
||||
return f"- {t.get('titulo')} — `{t.get('id')}` · " + " · ".join(bits)
|
||||
|
||||
orden = lambda t: (ESTADOS.index(t.get("estado")) if t.get("estado") in ESTADOS else 9, t.get("fecha_limite") or "9999")
|
||||
personas = {}
|
||||
for t in abiertos:
|
||||
for p in (t.get("responsable_ids") or _ids(t.get("responsable")) or ["(sin responsable)"]):
|
||||
personas.setdefault(p, []).append(t)
|
||||
o += ["## Abiertos por persona", ""]
|
||||
for p in sorted(personas, key=lambda x: (-len(personas[x]), x)):
|
||||
o += [f"### {nombre(p)} ({len(personas[p])})", ""] + [linea(t) for t in sorted(personas[p], key=orden)] + [""]
|
||||
hace14 = (datetime.date.fromisoformat(hoy) - datetime.timedelta(days=14)).isoformat()
|
||||
hechos = sorted((t for t in tareas if t.get("estado") == "hecho" and str(t.get("modificado_en") or "") >= hace14),
|
||||
key=lambda t: str(t.get("modificado_en")), reverse=True)
|
||||
o += ["## Hechos en los últimos 14 días", ""] + \
|
||||
[f"- {fecha_corta(t.get('modificado_en'))} · {t.get('titulo')} — `{t.get('id')}`" for t in hechos] + \
|
||||
([] if hechos else ["- (ninguno)"]) + [""]
|
||||
o += ["## Actividad (14 días, lo más nuevo arriba)", ""]
|
||||
for e in (F.get("actividad") or [])[:60]:
|
||||
if e.get("tipo") == "pendiente":
|
||||
o.append(f"- {fecha_corta(e.get('cuando'))} · {nombre(e.get('quien'))}: {corto(e.get('cambio'), 140)} "
|
||||
f"— «{corto(e.get('titulo'), 60)}» `{e.get('id')}`")
|
||||
else:
|
||||
o.append(f"- {fecha_corta(e.get('cuando'))} · {e.get('tecnico') or nombre(e.get('quien'))}: acta "
|
||||
f"{e.get('acta')} de {e.get('serie') or 'sin serie'} {e.get('resultado') or ''}".rstrip())
|
||||
lotes = F.get("lotes") or {}
|
||||
o += ["", "## Lotes de Claude", ""] + [
|
||||
f"- `{k}` — {'aplicado el ' + fecha_corta(v.get('fin')) if v.get('completo') else 'a medias'}: {v.get('motivo') or ''}"
|
||||
for k, v in sorted(lotes.items(), reverse=True)] + ([] if lotes else ["- (ninguno aplicado todavía)"])
|
||||
print("\n".join(o))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ main
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0],
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__)
|
||||
ap.add_argument("accion", choices=("foto", "revisar", "aplicar", "pendientes", "resumen"))
|
||||
ap.add_argument("rutas", nargs="*", help="lotes (archivos o carpetas); para «resumen», la foto")
|
||||
a = ap.parse_args()
|
||||
aqui = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lotes")
|
||||
try:
|
||||
if a.accion == "foto":
|
||||
foto()
|
||||
return 0
|
||||
if a.accion == "resumen":
|
||||
resumen(a.rutas[0] if a.rutas else "_estado/tablero.json")
|
||||
return 0
|
||||
rutas = a.rutas or [aqui]
|
||||
if a.accion == "pendientes":
|
||||
n, err = correr(rutas, aplicar=False, silencioso=True)
|
||||
print(f"Tablero: {n} cambio(s) de lotes por aplicar → ./scripts/tablero.sh revisar" if n
|
||||
else "Tablero: no hay lotes por aplicar.")
|
||||
return 3 if err else 0
|
||||
n, err = correr(rutas, aplicar=(a.accion == "aplicar"))
|
||||
return 3 if err else 0
|
||||
except ValueError as e:
|
||||
print("✗ " + str(e), file=sys.stderr)
|
||||
return 1
|
||||
except SinBackend as e:
|
||||
print("✗ " + str(e), file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -23,6 +23,10 @@ Arriba, lo que pasó en el tablero de pendientes y en las actas en los últimos
|
||||
### Octubre de 2026
|
||||
|
||||
**21 oct (noche)**
|
||||
: [Estado de la v6, rev. 1.1: T38 y propuesta α](hardware/v6-estado-21oct.md#5-t38-21-oct-noche-courtyards-mh6-y-regla-de-barrenos): courtyards reales en 11 huellas, MH6 libre y el plano `/3V3` fuera de los barrenos. El bloque A cambia: D34 entre U25 y L22 (D-21-3, falta el OK de Leo).
|
||||
: [Plan de fines de semana de la v6, rev. 1.3](hardware/v6-plan-fines-de-semana.md#31-fuente-u25-bloque-a): metas del bloque A corregidas.
|
||||
: [El sitio es el centro de mando](guia-ingeniero.md#el-sitio-es-el-centro-de-mando): cada tarea termina aquí, con su pendiente, su documento y, si algo estorbó, una mejora. Los cambios de Claude al tablero llegan en lotes que Leo revisa y aplica.
|
||||
: [Pasada 6 hecha por Claude (T34–T37)](hardware/v56-v57-auditoria-21oct.md#6-pasada-6-hecha-por-claude-t34-a-t37): el cobre de la V5.6 y la V5.7 queda listo para Gerber. D-21-2 propone dejar C149/C150 como están; falta el OK de Leo.
|
||||
: [Estado de la v6 y T25/T28 aplicados](hardware/v6-estado-21oct.md). T28 cerró 2 de los 8 bloqueantes; D34 quedó lejos del pin SW de U25 (lazo de ≈ 115 mm²) y MH8 cae encima de D34. Falta el push de Leo.
|
||||
: [Pasada 5 revisada](hardware/v56-v57-auditoria-21oct.md#5-pasada-5-revisada-6458c5d-y-lo-que-falta): el sensado de RS1 dejó de ser Kelvin en las dos tarjetas; falta la pasada 6, ≈ 30 min por tarjeta.
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_como": "Datos fijos de la página de inicio y del tablero de Ingeniería (Ruta a producción y una tarjeta por versión). Los actualiza Claude o Leo cuando una tarjeta cambia de etapa o se mueve un hito; después, deploy. Estados de cada paso: hecho, ahora, bloqueado, falta. Cada paso es [estado, texto, debajo]. Tono de la tarjeta: hecho, ahora, aviso, bloqueado. Hitos: fecha AAAA-MM-DD (null si aún no hay fecha), fin opcional y «cuando», el texto que se ve. El enfoque de la semana NO va aquí: lo escriben Leo y Adriana en la página de inicio (/api/hoy); enfoque_inicial solo se muestra mientras nadie lo ha escrito.",
|
||||
"_como": "Datos fijos de la página de inicio y del tablero de Ingeniería (Ruta a producción y una tarjeta por versión). Los actualiza Claude o Leo cuando una tarjeta cambia de etapa o se mueve un hito; después, deploy. Estados de cada paso: hecho, ahora, bloqueado, falta. Cada paso es [estado, texto, debajo]. Tono de la tarjeta: hecho, ahora, aviso, bloqueado. Hitos: fecha AAAA-MM-DD (null si aún no hay fecha), fin opcional y «cuando», el texto que se ve. El enfoque de la semana NO va aquí: lo escriben Leo y Adriana en la página de inicio (/api/hoy); enfoque_inicial solo se muestra mientras nadie lo ha escrito. Este archivo es parte del cierre de cada tarea (hbox-docs/CLAUDE.md, «Cierre de una tarea»).",
|
||||
"actualizado": "2026-10-21",
|
||||
"fase": { "nombre": "Pre-producción", "texto": "V5.6 y V5.7 en la pasada 6; la v6 en layout" },
|
||||
"fase": { "nombre": "Pre-producción", "texto": "V5.6 y V5.7 con el cobre listo para Gerber; la v6 en layout" },
|
||||
"pedido_nota": "primero los pilotos: 5 V5.6 y 5 V5.7, luego 20 v6",
|
||||
"gestion": { "id": "adriana", "texto": "Adriana lleva la gestión del proyecto" },
|
||||
"etapas": ["Esquemático", "Layout", "Auditoría", "Gerber", "Piloto", "Banco", "Pedido"],
|
||||
@ -17,38 +17,38 @@
|
||||
"docs": [["Manual V5", "/manuales/manual-tarjeta-V5/"], ["Auditoría del 16-oct, §4", "/hardware/auditoria-produccion-16oct/"], ["Lecciones de la V5.5", "/hardware/v6-revision-lecciones-v55/"], ["Acta de tarjeta V5", "/formularios/v5.html"]]
|
||||
},
|
||||
{
|
||||
"version": "V5.6", "clave": "v56", "estado": "Casi lista", "tono": "ahora",
|
||||
"version": "V5.6", "clave": "v56", "estado": "Lista para Gerber", "tono": "ahora",
|
||||
"nuevo": "Lo nuevo: control del módem y acelerómetro. Pedido de 113",
|
||||
"imagen": "/img/tarjetas/v56.webp", "alt": "Cobre de la EinarBoard V5.6; lo nuevo, en blanco",
|
||||
"pasos": [["hecho", "28 sep"], ["ahora", "Pasada 6", "Michelle, ≈ 30 min"], ["falta", "Revisión del cobre", "Claude"], ["falta", "Gerber y BOM", "Leo y Claude"], ["falta", "5 piezas", "con las 5 de la V5.7"], ["falta", "Firmware 0.7.0", "Jesús; por escribir"], ["falta", "113"]],
|
||||
"detalle": "Casi lista (21-oct, noche): la pasada 5 (6458c5d) dejó bien C129 y C130, pero la vía de entrada de RS1 quedó encima del hilo de sensado de U6.6: el sensado ya no es Kelvin. Falta la pasada 6, ≈ 30 min.",
|
||||
"siguiente": "Pasada 6 (Michelle, ≈ 30 min): la vía de entrada de RS1 a 0.2 mm o más del hilo de sensado, con una 2.ª vía unida en F.Cu y en B.Cu; 2.ª vía de la entrada de U7; exclusiones de MH7, MH9 y MH10; el tramo suelto de /RS485_B → revisión del cobre (Claude, sobre todo el sensado de RS1) → Gerber, BOM y CPL → pedido del piloto de 5, junto con los 5 de la V5.7 → banco con el firmware 0.7.0 → las 108 restantes, con SD-15B.",
|
||||
"quien": "Michelle (layout) · Claude (revisión) · Leo (scripts, pedido, firmware) · Jesús (banco)",
|
||||
"docs": [["Lo que falta: pasada 6 (21-oct, noche)", "/hardware/v56-v57-auditoria-21oct/#5-pasada-5-revisada-6458c5d-y-lo-que-falta"], ["Auditoría de la pasada 4 (21-oct)", "/hardware/v56-v57-auditoria-21oct/"], ["Qué es crítico (19-oct)", "/hardware/v56-clasificacion-19oct/"], ["Pasada 4", "/hardware/v56-v57-layout-pasada-4/"], ["Re-auditoría del 19-oct", "/hardware/re-auditoria-19oct/"], ["ECO V5.6", "/hardware/v56-eco/"], ["Hoja de datos", "/hardware/v56-datasheet/"], ["Pinout", "/hardware/v56-pinout/"]]
|
||||
"pasos": [["hecho", "28 sep"], ["hecho", "Pasada 6", "21 oct, Claude"], ["hecho", "DRC y paridad", "21 oct, Claude"], ["ahora", "Gerber y BOM", "Leo: push, B + DRC"], ["falta", "5 piezas", "con las 5 de la V5.7"], ["falta", "Firmware 0.7.0", "Jesús; por escribir"], ["falta", "113"]],
|
||||
"detalle": "Cobre listo para Gerber (21-oct, noche). La pasada 6 la hizo Claude con T34 y T35: RS1 otra vez Kelvin, 2.ª vía de la entrada de U7 y exclusiones de MOD1. DRC: solo los 3 errores de MOD1 (excluidos), 0 sin conectar, paridad solo PF*.",
|
||||
"siguiente": "Leo: commit y push de kicad/ → quien abra la tarjeta, git pull --ff-only, B y DRC con «Rellenar zonas» y «Comprobar paridad» (tiene que salir igual) → Gerber, BOM y CPL con Fabrication Toolkit (en el visor de JLC, la rotación de U6, U7, U25, Q7 y RS1) → pedido del piloto de 5, junto con los 5 de la V5.7 → banco con el firmware 0.7.0 → las 108 restantes, con SD-15B.",
|
||||
"quien": "Leo (push, Gerber y pedido) · Claude (revisión, hecha) · Jesús (banco) · Michelle (nada obligatorio)",
|
||||
"docs": [["Pasada 6: qué falta antes del Gerber", "/hardware/v56-v57-auditoria-21oct/#61-que-falta-antes-del-gerber"], ["Pasada 6 hecha por Claude (T34 a T37)", "/hardware/v56-v57-auditoria-21oct/#6-pasada-6-hecha-por-claude-t34-a-t37"], ["Qué es crítico (19-oct)", "/hardware/v56-clasificacion-19oct/"], ["Pasada 4", "/hardware/v56-v57-layout-pasada-4/"], ["Re-auditoría del 19-oct", "/hardware/re-auditoria-19oct/"], ["ECO V5.6", "/hardware/v56-eco/"], ["Hoja de datos", "/hardware/v56-datasheet/"], ["Pinout", "/hardware/v56-pinout/"]]
|
||||
},
|
||||
{
|
||||
"version": "V5.7", "clave": "v57", "estado": "No lista", "tono": "aviso",
|
||||
"version": "V5.7", "clave": "v57", "estado": "Lista para Gerber", "tono": "ahora",
|
||||
"nuevo": "Lo nuevo: fuente de 9 a 32 V a bordo. Pedido de 327",
|
||||
"imagen": "/img/tarjetas/v57.webp", "alt": "Cobre de la EinarBoard V5.7; la fuente a bordo, en blanco",
|
||||
"pasos": [["hecho", "13 oct"], ["ahora", "Pasada 6", "Michelle, ≈ 30 min"], ["falta", "Revisión del cobre", "Claude"], ["falta", "Con la V5.6", "Leo y Claude"], ["falta", "5 prototipos", "con las 5 de la V5.6"], ["falta", "9 a 32 V", "Jesús: polaridad y calor"], ["falta", "327"]],
|
||||
"detalle": "No lista (21-oct, noche), pero ya sin el bloqueante de K1. La pasada 5 dejó bien C130 y las 2 vías de /VEH5V; quedan RS1 (igual que en la V5.6), C149/C150 sin girar (lazo de ≈ 56 mm², meta < 30) y el GND de C129.",
|
||||
"siguiente": "Pasada 6 (Michelle, ≈ 30 min): RS1 como en la V5.6, 2.ª vía de la entrada de U7, 1 o 2 vías al GND de C129, C149/C150 girados hacia D34 y el EP de U25 (lazo de menos de 30 mm²), exclusiones y el tramo suelto de /24V → revisión del cobre (Claude) → 5 prototipos con el piloto de la V5.6 (ECO §6: 9, 12, 24 y 32 V, polaridad y temperatura) → pedido de 327. Entrada de 9 a 32 V.",
|
||||
"quien": "Michelle (layout) · Claude (revisión) · Leo (pedido) · Jesús (prototipos)",
|
||||
"docs": [["Lo que falta: pasada 6 (21-oct, noche)", "/hardware/v56-v57-auditoria-21oct/#5-pasada-5-revisada-6458c5d-y-lo-que-falta"], ["Auditoría del 21-oct, §3.2", "/hardware/v56-v57-auditoria-21oct/#32-v57-hbox-watchdog-v57"], ["Re-auditoría del 19-oct, §4", "/hardware/re-auditoria-19oct/#4-v57-punto-por-punto"], ["ECO V5.7: fuente a bordo", "/hardware/v57-eco-fuente/"]]
|
||||
"pasos": [["hecho", "13 oct"], ["hecho", "Pasada 6", "21 oct, Claude"], ["hecho", "DRC y paridad", "D-21-2: falta el OK"], ["ahora", "Con la V5.6", "Leo: push, B + DRC"], ["falta", "5 prototipos", "con las 5 de la V5.6"], ["falta", "9 a 32 V", "Jesús: polaridad, calor y U25"], ["falta", "327"]],
|
||||
"detalle": "Cobre listo para Gerber (21-oct, noche). La pasada 6 la hizo Claude con T36 y T37: RS1 Kelvin como en la V5.6, 2.ª vía de U7, vía de GND de C129 y fuera un tramo suelto de /24V; DRC limpio, como en la V5.6. C149/C150 se quedan como están si Leo aprueba D-21-2.",
|
||||
"siguiente": "Leo: OK a D-21-2 (C149/C150 se quedan y se mide en el banco) y el mismo push de la V5.6 → B y DRC → Gerber, BOM y CPL → 5 prototipos, pedidos con el piloto de la V5.6 (ECO §6: 9, 12, 24 y 32 V, polaridad invertida, temperatura y timbre de U25) → pedido de 327. Entrada de 9 a 32 V.",
|
||||
"quien": "Leo (D-21-2, push y pedido) · Claude (revisión, hecha; procedimiento de banco) · Jesús (prototipos)",
|
||||
"docs": [["Por qué C149/C150 se quedan (D-21-2)", "/hardware/v56-v57-auditoria-21oct/#62-v57-por-que-c149c150-se-quedan-como-estan"], ["Pasada 6 hecha por Claude (T34 a T37)", "/hardware/v56-v57-auditoria-21oct/#6-pasada-6-hecha-por-claude-t34-a-t37"], ["Auditoría del 21-oct, §3.2", "/hardware/v56-v57-auditoria-21oct/#32-v57-hbox-watchdog-v57"], ["Re-auditoría del 19-oct, §4", "/hardware/re-auditoria-19oct/#4-v57-punto-por-punto"], ["ECO V5.7: fuente a bordo", "/hardware/v57-eco-fuente/"]]
|
||||
},
|
||||
{
|
||||
"version": "v6", "clave": "v6", "estado": "No lista", "tono": "bloqueado",
|
||||
"nuevo": "Lo nuevo: GNSS y 4G en la tarjeta. Pedido de 130",
|
||||
"imagen": "/img/tarjetas/v6.webp", "alt": "Cobre de la EinarBoard v6; el GNSS y el 4G, en blanco",
|
||||
"pasos": [["hecho", "28 sep"], ["bloqueado", "6 bloqueantes", "T28 cerró 2; D34 lejos de U25"], ["falta", "25 oct y 1 nov", "Claude"], ["falta", "1 nov", "Claude (G1)"], ["falta", "20, el 2 nov", "Aldair y Leo"], ["falta", "21–22 nov", "Jesús y Leo (G2)"], ["falta", "130", "después de G2"]],
|
||||
"detalle": "No lista (21-oct): T25 y T28 ya están aplicados (falta el push de Leo) y T28 cerró 2 de los 8 bloqueantes. Michelle adelantó la fuente U25, pero D34 quedó a 13 mm del pin SW (lazo caliente de ≈ 115 mm², antes ≈ 47) y MH8 cae encima de D34.",
|
||||
"siguiente": "Leo: commit y push de T25 y T28; Michelle hace git pull --ff-only antes de abrir la v6. Sáb 24: Michelle regresa D34 junto a U25.8 (moviendo R53 y R54 junto a U6), libera MH8, MH4 y MH6, y sigue con los bloques A y B. Dom 25: bloques C, D y E (módem, GNSS y U12); en la noche, re-auditoría de Claude. Sáb 31: bloques F, G y H1 (pares, puertos serie e In1). Dom 1-nov: H2 y DRC final; en la tarde, re-auditoría final y Gerber (G1). Lun 2-nov: pedido del piloto de 20. Firmware los fines de semana del 7–8 y del 14–15 nov; banco del piloto el 21–22 nov (G2). No habrá v6.1.",
|
||||
"pasos": [["hecho", "28 sep"], ["bloqueado", "6 bloqueantes", "T38: courtyards y barrenos; falta el bloque A"], ["falta", "25 oct y 1 nov", "Claude"], ["falta", "1 nov", "Claude (G1)"], ["falta", "20, el 2 nov", "Aldair y Leo"], ["falta", "21–22 nov", "Jesús y Leo (G2)"], ["falta", "130", "después de G2"]],
|
||||
"detalle": "No lista (21-oct): T25, T28 y T38 ya están aplicados (falta el push de T38). T28 cerró 2 de los 8 bloqueantes; T38 corrigió los courtyards de 11 huellas, liberó MH6 y apartó el plano /3V3 de los barrenos. Michelle adelantó la fuente U25, pero D34 quedó a 13 mm del pin SW (lazo caliente de ≈ 115 mm², antes ≈ 47) y MH8 cae encima de D34.",
|
||||
"siguiente": "Leo: commit y push de T38 y OK a la propuesta α del bloque A (D-21-3); Michelle hace git pull --ff-only antes de abrir la v6. Desde el 22-oct y a más tardar el sáb 24: Michelle coloca D34 entre U25 y L22 con la propuesta α (primero R53 y R54 junto a U6), libera MH8 y MH4, y sigue con los bloques A y B. Dom 25: bloques C, D y E (módem, GNSS y U12); en la noche, re-auditoría de Claude. Sáb 31: bloques F, G y H1 (pares, puertos serie e In1). Dom 1-nov: H2 y DRC final; en la tarde, re-auditoría final y Gerber (G1). Lun 2-nov: pedido del piloto de 20. Firmware los fines de semana del 7–8 y del 14–15 nov; banco del piloto el 21–22 nov (G2). No habrá v6.1.",
|
||||
"quien": "Michelle (layout) · Leo (scripts y pedido) · Claude (re-auditoría y Gerber) · Aldair (pedido) · Jesús (banco)",
|
||||
"docs": [["Estado de la v6 (21-oct)", "/hardware/v6-estado-21oct/"], ["Plan de fines de semana (rev. 1.1)", "/hardware/v6-plan-fines-de-semana/"], ["Auditoría de la v6 (16-oct)", "/hardware/v6-auditoria-produccion-16oct/"], ["Re-auditoría del 19-oct, §8", "/hardware/re-auditoria-19oct/#8-v6"], ["Plan de corrección (28-sep)", "/hardware/v6-plan-correccion/"], ["Manual v6", "/manuales/manual-tarjeta-v6/"], ["Plan de cobre", "/hardware/EinarBoard-v6_Plan-de-Cobre_ES.html"]]
|
||||
"docs": [["Estado de la v6 (21-oct)", "/hardware/v6-estado-21oct/"], ["Plan de fines de semana (rev. 1.3)", "/hardware/v6-plan-fines-de-semana/"], ["Auditoría de la v6 (16-oct)", "/hardware/v6-auditoria-produccion-16oct/"], ["Re-auditoría del 19-oct, §8", "/hardware/re-auditoria-19oct/#8-v6"], ["Plan de corrección (28-sep)", "/hardware/v6-plan-correccion/"], ["Manual v6", "/manuales/manual-tarjeta-v6/"], ["Plan de cobre", "/hardware/EinarBoard-v6_Plan-de-Cobre_ES.html"]]
|
||||
}
|
||||
],
|
||||
"hitos": [
|
||||
{ "fecha": null, "cuando": "Por fijar", "texto": "Pedido del piloto: 5 V5.6 y 5 V5.7, después de la pasada 6", "quien": ["leo"] },
|
||||
{ "fecha": "2026-10-22", "cuando": "≈ 22 oct", "texto": "Gerber y pedido del piloto: 5 V5.6 y 5 V5.7", "quien": ["leo"] },
|
||||
{ "fecha": "2026-10-24", "fin": "2026-10-25", "cuando": "24 y 25 oct", "texto": "v6, primer fin de semana de layout", "quien": ["michelle", "leo"] },
|
||||
{ "fecha": "2026-10-31", "fin": "2026-11-01", "cuando": "31 oct y 1 nov", "texto": "v6, segundo fin de semana y Gerber (G1)", "quien": ["michelle", "leo", "claude"] },
|
||||
{ "fecha": "2026-11-02", "cuando": "2 nov", "texto": "Pedido del piloto de 20 v6", "quien": ["aldair", "leo"] },
|
||||
@ -56,13 +56,13 @@
|
||||
{ "fecha": "2026-11-21", "fin": "2026-11-22", "cuando": "21 y 22 nov", "texto": "Banco del piloto de la v6 (G2)", "quien": ["jesus", "leo"] }
|
||||
],
|
||||
"enfoque_inicial": {
|
||||
"titulo": "Esta semana: la pasada 6 y el piloto de V5.6 y V5.7.",
|
||||
"titulo": "Esta semana: Gerber y pedido del piloto de V5.6 y V5.7; la v6 empieza por D34.",
|
||||
"puntos": [
|
||||
"La pasada 5 dejó bien C129 y C130 en las dos tarjetas. Falta la pasada 6 (≈ 30 min por tarjeta): sacar la vía de RS1 de encima del hilo de sensado, la 2.ª vía de RS1 y de U7 y, en la V5.7, girar C149/C150.",
|
||||
"Después, Claude revisa el cobre (sobre todo el sensado de RS1), Gerber y un solo pedido de 5 y 5.",
|
||||
"v6: T25 y T28 ya están aplicados; falta el push de Leo. El sábado 24 se empieza por D34, que quedó lejos de U25, y por MH8."
|
||||
"La pasada 6 quedó hecha (Claude, T34–T37): RS1 Kelvin otra vez y DRC limpio en las dos tarjetas. El cobre está listo para Gerber.",
|
||||
"Leo: OK a D-21-2 (C149/C150 de la V5.7 se quedan), push de kicad/, B + DRC en KiCad, Gerber/BOM/CPL y un solo pedido de 5 y 5.",
|
||||
"v6: T25 y T28 van en el mismo push. Michelle empieza por D34 junto a U25 y por dejar libre MH8."
|
||||
],
|
||||
"nota": "Texto inicial tomado de la [auditoría del 21-oct](/hardware/v56-v57-auditoria-21oct/) y del [estado de la v6](/hardware/v6-estado-21oct/). Leo y Adriana lo cambian con «Editar»."
|
||||
"nota": "Texto inicial tomado de la [auditoría del 21-oct, §6](/hardware/v56-v57-auditoria-21oct/#6-pasada-6-hecha-por-claude-t34-a-t37) y del [estado de la v6](/hardware/v6-estado-21oct/). Leo y Adriana lo cambian con «Editar»."
|
||||
},
|
||||
"sitios": [
|
||||
{ "id": "icave", "nombre": "ICAVE", "ciudad": "Veracruz", "x": 68.27, "y": 73.55, "etiqueta": "der", "nota": "se compara con el CalAmp" },
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
title: "Guía del ingeniero"
|
||||
project: Project Drogheda
|
||||
date: 2026-09-29
|
||||
version: "1.1"
|
||||
version: "1.2"
|
||||
tags: [project-drogheda, procedimientos, sitio, ingeniero]
|
||||
---
|
||||
|
||||
@ -19,6 +19,19 @@ Acceso: usuario y contraseña te los da Leo; cámbiala la primera vez en [Mi cue
|
||||
5. Si algo no cuadra con el documento (valor fuera de rango, paso imposible, la tarjeta no coincide), no improvises: detente, anótalo en Observaciones y avisa a Leo.
|
||||
6. No edites procedimientos. Si un paso está mal, escríbele a Leo con la página, el número de paso, qué esperabas y qué encontraste. Las páginas se corrigen en el repositorio `Drogheda`; tú no tocas Git.
|
||||
|
||||
## El sitio es el centro de mando { #el-sitio-es-el-centro-de-mando }
|
||||
|
||||
**Si no está en el sitio, no pasó.** Todo el proyecto se coordina aquí: los pendientes, los documentos, las actas y la ruta a producción. Una tarea está terminada cuando el sitio lo dice, no antes.
|
||||
|
||||
Al terminar una tarea:
|
||||
|
||||
1. **El pendiente.** Muévelo a *Hecho*, o a *Bloqueado* con la razón en un comentario, y abre los que siguen, con responsable y fecha. Si la tarea no tenía pendiente, créalo y ciérralo: así queda en la Actividad.
|
||||
2. **El documento.** Si la tarea cambió un procedimiento, una tarjeta o una decisión, el documento se corrige el mismo día. Lo hacen Leo o Claude en el repositorio; tú avísales qué cambió (regla 6).
|
||||
3. **El acta.** Si tocaste una tarjeta o una unidad, llena su acta (regla 3).
|
||||
4. **La mejora.** Si el sitio te estorbó (una liga rota, un dato viejo, algo que no encontraste), abre un pendiente para Leo con la página y lo que esperabas encontrar.
|
||||
|
||||
Claude trabaja igual: cada tarea suya termina con sus cambios al tablero (en un lote que Leo revisa y aplica), sus documentos y la ruta a producción al día. Lo ves en la **Actividad** de la página de inicio, con su nombre.
|
||||
|
||||
## Qué abrir
|
||||
|
||||
- [Producción](produccion/index.md): de la tarjeta nueva a la unidad lista para embarcar.
|
||||
@ -50,7 +63,9 @@ Cada una vive en la página donde se aplica:
|
||||
cd ~/Documents/Claude/Projects/Drogheda/hbox-docs
|
||||
./scripts/sync-docs.sh # copia desde el repo lo que está en la lista MAP (política de publicación)
|
||||
mkdocs build --strict # tiene que terminar sin WARNING
|
||||
./scripts/deploy.sh # rsync del sitio + backend al servidor
|
||||
./scripts/deploy.sh # rsync del sitio + backend al servidor; baja la foto del tablero a _estado/
|
||||
./scripts/tablero.sh revisar # lotes de Claude: qué cambiaría cada uno en el tablero (no escribe)
|
||||
./scripts/tablero.sh aplicar # los aplica (lo ya aplicado se salta) y baja la foto nueva
|
||||
# nuevos tipos de acta: actualizar ACTAS_REPETIBLES en /etc/systemd/system/actas-hbox.service
|
||||
# (ya incluye prueba-ethernet-serie en backend/actas-hbox.service y DESPLIEGUE.md), luego
|
||||
# sudo systemctl daemon-reload && sudo systemctl restart actas-hbox
|
||||
@ -63,5 +78,6 @@ git add -A && git commit -m "docs: ..." && git push
|
||||
|---|---|---|
|
||||
| 1.0 | 2026-09-29 | Primera versión. |
|
||||
| 1.1 | 2026-09-29 | Edición breve; recibe las reglas de la portada; la tabla por situación queda en las portadas de sección. |
|
||||
| 1.2 | 2026-10-21 | «El sitio es el centro de mando»: cómo se cierra una tarea (pendiente, documento, acta y mejora); lotes del tablero en el anexo. |
|
||||
|
||||
Versiones anteriores en git.
|
||||
|
||||
@ -6,19 +6,24 @@ title: "Tarjetas y hardware"
|
||||
|
||||
Estado de cada versión de la EinarBoard al **21-oct-2026**, con sus documentos vigentes, los históricos y el siguiente paso. ¿No sabes qué tarjeta tienes? [Manuales por tarjeta](../manuales/index.md) te la identifica por serigrafía, conectores y LEDs. Estado vivo (tareas, decisiones, responsables): [Tablero de ingeniería](../tableros/ingenieria.md).
|
||||
|
||||
!!! warning "21-oct, noche: pasada 6 de V5.6/V5.7 hecha por Claude (T34–T37) y T25 + T28 aplicados en la v6"
|
||||
!!! warning "21-oct, noche: pasada 6 de V5.6/V5.7 hecha por Claude (T34–T37); en la v6, T25, T28 y T38 aplicados"
|
||||
- **V5.6 — cobre listo para Gerber.** T34 + T35: RS1 otra vez Kelvin (2 vías de entrada y el hilo de U6.6 entra al pad por su lado), 2.ª vía de la entrada de U7 y exclusiones de MOD1. DRC: solo los 3 de MOD1, ya excluidos; 0 sin conectar. Falta el push de Leo, B + DRC en KiCad y Gerber/BOM/CPL ([pasada 6](v56-v57-auditoria-21oct.md#6-pasada-6-hecha-por-claude-t34-a-t37)).
|
||||
- **V5.7 — cobre listo para Gerber.** T36 + T37: lo mismo en RS1 y U7, la vía de GND de C129 y un tramo suelto de `/24V`. C149/C150 se quedan como están: propuesta D-21-2, falta el OK de Leo ([por qué](v56-v57-auditoria-21oct.md#62-v57-por-que-c149c150-se-quedan-como-estan)).
|
||||
- **v6 — T25 y T28 aplicados** (falta el push). Michelle adelantó la fuente U25, pero D34 quedó lejos del pin SW (lazo de ≈ 115 mm²) y MH8 cae encima de D34: [**▶ estado de la v6 al 21-oct**](v6-estado-21oct.md).
|
||||
- **v6 — T25, T28 y T38 aplicados** (falta el push de T38). Michelle adelantó la fuente U25, pero D34 quedó lejos del pin SW (lazo de ≈ 115 mm²) y MH8 cae encima de D34.
|
||||
- T38: courtyards reales en 11 huellas (1 encime real: C153 bajo L22), MH6 libre y una regla nueva de barrenos. El plano `/3V3` de B.Cu quedaba desnudo bajo los separadores de los 5 barrenos.
|
||||
- Para Michelle: colocación de D34 y L22 que sí cabe (propuesta α) y metas del bloque A corregidas.
|
||||
|
||||
Detalle: [**▶ Auditoría del 21-oct (pasadas 4 a 6)**](v56-v57-auditoria-21oct.md). Antes: [re-auditoría del 19-oct](re-auditoria-19oct.md) · [qué es crítico en la V5.6](v56-clasificacion-19oct.md) · [plan v6 (rev. 1.1)](v6-plan-fines-de-semana.md).
|
||||
[**▶ Estado de la v6 al 21-oct (rev. 1.1)**](v6-estado-21oct.md).
|
||||
- **V5.6 y V5.7:** desde T38, su DRC muestra 2 y 4 avisos de «no coincide con la biblioteca». Es solo el courtyard: no actualicen esas huellas antes de los Gerber ([por qué](v56-v57-auditoria-21oct.md#61-que-falta-antes-del-gerber)).
|
||||
|
||||
Detalle: [**▶ Auditoría del 21-oct (pasadas 4 a 6)**](v56-v57-auditoria-21oct.md). Antes: [re-auditoría del 19-oct](re-auditoria-19oct.md) · [qué es crítico en la V5.6](v56-clasificacion-19oct.md) · [plan v6 (rev. 1.3)](v6-plan-fines-de-semana.md).
|
||||
|
||||
| Tarjeta | Estado | Pedido | Proyecto KiCad | Firmware |
|
||||
|---|---|---|---|---|
|
||||
| <span class="badge-v55">V5.5</span> | En campo (15 pzas, 22-ago). No se fabrica más. | — | `hbox-watchdog-v5` (tag `v5.5-as-fabbed`) | `hbox-v5-bringup_0.6.0` CAMPO · 0.6.3 banco |
|
||||
| <span class="badge-v56">V5.6</span> | Pasada 6 hecha por Claude el 21-oct en la noche (T34 + T35): **cobre listo para Gerber**. RS1 Kelvin otra vez, 2.ª vía de U7, DRC limpio. Falta push, B + DRC en KiCad y Gerber/BOM/CPL ([pasada 6](v56-v57-auditoria-21oct.md#6-pasada-6-hecha-por-claude-t34-a-t37)). | 113 (piloto de 5 primero) | `hbox-watchdog-v5` | 0.7.0 (`board_v56.h`), no existe |
|
||||
| <span class="badge-v57">V5.7</span> | Pasada 6 hecha por Claude el 21-oct en la noche (T36 + T37): **cobre listo para Gerber**. Lo mismo que la V5.6, más la vía de GND de C129; C149/C150 se quedan (D-21-2, falta el OK de Leo) ([pasada 6](v56-v57-auditoria-21oct.md#6-pasada-6-hecha-por-claude-t34-a-t37)). Entrada 9–32 V. | 327 (5 prototipos primero) | `hbox-watchdog-v57` (convive con v5) | el mismo 0.7.0 (mismos pines de MCU) |
|
||||
| <span class="badge-v6">v6</span> | Revisada el 21-oct (`9255b84` + T25/T28): **NO LISTA**. T28 cerró 2 de los 8 bloqueantes de la [auditoría v6](v6-auditoria-produccion-16oct.md) y T25 regresó los barrenos; Michelle adelantó la fuente U25, pero D34 quedó lejos del pin SW y MH8 cae encima ([estado del 21-oct](v6-estado-21oct.md)). Plan: [fines de semana 24-oct → 22-nov](v6-plan-fines-de-semana.md). | 130 + reemplazos (piloto de 20, pedido previsto el 2-nov) | `hbox-watchdog-v6` | 0.7.x (`board_v6.h` + NTRIP), no existe |
|
||||
| <span class="badge-v6">v6</span> | Revisada el 21-oct (`9255b84` + T25/T28/T38): **NO LISTA**. T28 cerró 2 de los 8 bloqueantes de la [auditoría v6](v6-auditoria-produccion-16oct.md), T25 regresó los barrenos y T38 corrigió courtyards, MH6 y el `/3V3` bajo los barrenos. Michelle adelantó la fuente U25, pero D34 quedó lejos del pin SW y MH8 cae encima ([estado del 21-oct](v6-estado-21oct.md)). Plan: [fines de semana 24-oct → 22-nov](v6-plan-fines-de-semana.md). | 130 + reemplazos (piloto de 20, pedido previsto el 2-nov) | `hbox-watchdog-v6` | 0.7.x (`board_v6.h` + NTRIP), no existe |
|
||||
| <span class="badge-v4">V4</span> | En prueba y campo. Sin producción nueva. | — | `hbox-watchdog-v4` | `hbox-tarjetaV4_0.6.0` CAMPO |
|
||||
| <span class="badge-v3">V3</span> | 3 unidades en campo. Registro congelado. | — | `hbox-watchdog-v3-PROD` (no editar) | `hbox-tarjetaV3_4.0.0` |
|
||||
|
||||
@ -108,22 +113,30 @@ Lleva Mean Well SD-15B-05 externa (igual que V5.5).
|
||||
|
||||
El sensado de RS1 ya está bien (hay que conservarlo).
|
||||
|
||||
**21-oct (`9255b84`, Michelle):** adelantó el bloque A (L22 a 2.2 mm del pin SW de U25, 6 vías en su EP) y llevó `/VEH5V` de L22 a RS1 todo por F.Cu. Pero D34 quedó a 13 mm del pin SW: el lazo caliente pasó de ≈ 47 a ≈ 115 mm². **T25 y T28 ya están aplicados** (falta el push): cierran los bloqueantes 1 y 8 y regresan los barrenos; el DRC da 22 errores en MH4 y MH8, que Michelle libera. Detalle: [**▶ estado de la v6 al 21-oct**](v6-estado-21oct.md).
|
||||
**21-oct (`9255b84`, Michelle):** adelantó el bloque A (L22 a 2.2 mm del pin SW de U25, 6 vías en su EP) y llevó `/VEH5V` de L22 a RS1 todo por F.Cu. Pero D34 quedó a 13 mm del pin SW: el lazo caliente pasó de ≈ 47 a ≈ 115 mm². **T25 y T28 ya están aplicados:** cierran los bloqueantes 1 y 8 y regresan los barrenos.
|
||||
|
||||
**21-oct, noche (T38, Claude):**
|
||||
|
||||
- Courtyards reales en las 11 huellas de easyeda2kicad: el DRC no veía encimes en U19, U20, U23–U26, D22, D27, J12, J13 ni L22. Salió uno real: C153 debajo del cuerpo de L22.
|
||||
- `/3V3` de MH6 por B.Cu, a 4.06 mm del centro.
|
||||
- Regla nueva: en F.Cu y B.Cu, solo GND a menos de 4 mm del centro de un barreno. El plano `/3V3` de B.Cu llegaba a 1.85 mm, desnudo bajo los separadores.
|
||||
|
||||
DRC: 52 errores, todos conocidos (MH4 28 · MH8 10 · courtyards 14), 0 sin conectar. Para el bloque A, D34 no cabe abajo del pin SW: la propuesta α pone D34 entre U25 y L22. Detalle: [**▶ estado de la v6 al 21-oct (rev. 1.1)**](v6-estado-21oct.md).
|
||||
|
||||
El firmware que sustituye al 4G NTRIP Master no existe. Decisión vigente: **no habrá v6.1**.
|
||||
|
||||
| | Documentos |
|
||||
|---|---|
|
||||
| Vigentes | [**▶ Estado al 21-oct (T25 + T28)**](v6-estado-21oct.md) · [**▶ Auditoría de producción v6 (16-oct)**](v6-auditoria-produccion-16oct.md) · [**▶ Plan de fines de semana (rev. 1.1: 24-oct → 22-nov)**](v6-plan-fines-de-semana.md) · [Re-auditoría 19-oct, §8](re-auditoria-19oct.md#8-v6) · [Plan de corrección y lista de liberación (28-sep, el porqué de cada punto)](v6-plan-correccion.md) · [Esquemático final — resumen de 3 pasadas](v6-cambios-resumen.md) · [Mapa de pines MCU para `board_v6.h`](v6-pinmap.md) · [Plan de cobre y colocación EMC](EinarBoard-v6_Plan-de-Cobre_ES.html) ([inglés](EinarBoard-v6_Copper-Plan_EN.html)) · [Manual v6 (marcador)](../manuales/manual-tarjeta-v6.md) |
|
||||
| Vigentes | [**▶ Estado al 21-oct (T25, T28 y T38)**](v6-estado-21oct.md) · [**▶ Auditoría de producción v6 (16-oct)**](v6-auditoria-produccion-16oct.md) · [**▶ Plan de fines de semana (rev. 1.3: 24-oct → 22-nov)**](v6-plan-fines-de-semana.md) · [Re-auditoría 19-oct, §8](re-auditoria-19oct.md#8-v6) · [Plan de corrección y lista de liberación (28-sep, el porqué de cada punto)](v6-plan-correccion.md) · [Esquemático final — resumen de 3 pasadas](v6-cambios-resumen.md) · [Mapa de pines MCU para `board_v6.h`](v6-pinmap.md) · [Plan de cobre y colocación EMC](EinarBoard-v6_Plan-de-Cobre_ES.html) ([inglés](EinarBoard-v6_Copper-Plan_EN.html)) · [Manual v6 (marcador)](../manuales/manual-tarjeta-v6.md) |
|
||||
| Detalle (28-sep) | [Auditoría completa](v6-auditoria-completa.md) ([inglés](v6-full-audit.md)) · [Pasada 1 — cambios al esquemático](v6-cambios-esquematico.md) · [Pasada 2 — puertos (inglés)](v6-puertos-sin-nueva-version.md) · [Pasada 3 — revisión final (inglés)](v6-revision-final.md) |
|
||||
| Históricos | [Próximos pasos para ordenar (29-sep)](v6-proximos-pasos.md) — superado por el plan de fines de semana · [Auditoría V5.6 · V5.7 · v6 del 16-oct (mañana), §8](auditoria-produccion-16oct.md#8-v6-que-falta) · [Revisión contra lecciones V5.5](v6-revision-lecciones-v55.md) — superada por la auditoría completa · [Auditoría pre-fab 25-sep](v6-auditoria-prefab.md) ([inglés](v6-prefab-audit.md)) — superada por la del 28-sep |
|
||||
| Cantidad | La auditoría de septiembre hablaba de **5 prototipos**; el plan del 13-oct son **130 unidades y todos los reemplazos futuros**. Recomendado: piloto de 20 que pase el banco con el firmware 0.7.x antes de las 130. |
|
||||
|
||||
**Siguiente paso** ([plan de fines de semana, rev. 1.1](v6-plan-fines-de-semana.md); recorrido una semana el 19-oct):
|
||||
**Siguiente paso** ([plan de fines de semana, rev. 1.3](v6-plan-fines-de-semana.md); recorrido una semana el 19-oct):
|
||||
|
||||
- **Lun 19 o mar 20:** pre-órdenes en JLC (THVD1410DR, TPS7A2033PDBVR, J14). No se recorren.
|
||||
- **21-oct:** T25 y T28 ya aplicados; falta el push de Leo.
|
||||
- **Sáb 24:** Michelle regresa D34 junto a U25, libera MH4, MH6 y MH8, y sigue con la potencia ([estado del 21-oct, §3](v6-estado-21oct.md#3-lo-que-tiene-que-corregir-michelle-antes-de-seguir)).
|
||||
- **21-oct:** T25, T28 y T38 aplicados; falta el push de T38.
|
||||
- **Sáb 24:** Michelle coloca D34 entre U25 y L22 (propuesta α), libera MH4 y MH8, y sigue con la potencia ([estado del 21-oct, §3](v6-estado-21oct.md#3-lo-que-tiene-que-corregir-michelle-antes-de-seguir)).
|
||||
- **Dom 25:** RF del módem, U20, GNSS y U12.
|
||||
- **Sáb 31 y dom 1-nov:** pares, puertos, In1, vías de tierra (T29), serigrafía y DRC final. P7/P8 se quitan del esquemático (decisión del 9-oct).
|
||||
- **Lun 2-nov:** pedido del piloto de 20.
|
||||
|
||||
@ -95,8 +95,9 @@ hide:
|
||||
</section>
|
||||
<section class="in-caja" aria-labelledby="como-h">
|
||||
<div class="in-caja-cab"><h2 id="como-h">Cómo trabajamos</h2><a href="/tableros/ingenieria/#filosofia">Con detalle</a></div>
|
||||
<p class="in-nota">Antes de proponer un cambio, revisa que no choque con ninguna de estas reglas.</p>
|
||||
<p class="in-nota">Las reglas con las que trabajamos. Antes de proponer un cambio, revisa que no choque con ninguna.</p>
|
||||
<ul class="in-reglas">
|
||||
<li><b>Si no está en el sitio, no pasó.</b> <span>Cada tarea termina aquí: su pendiente movido, su documento al día y, si algo estorbó, un pendiente de mejora (<a href="/guia-ingeniero/#el-sitio-es-el-centro-de-mando">cómo</a>).</span></li>
|
||||
<li><b>Primero la seguridad en campo.</b> <span>La unidad vive a más de 45 °C y en batería; todo se prueba en banco antes de llevarlo a campo.</span></li>
|
||||
<li><b>Una etapa probada vale más que una nueva.</b> <span>Los zócalos se quedan; la fuente de la V5.7 viene del bloque ya auditado de la v6.</span></li>
|
||||
<li><b>Redundancia solo con causa.</b> <span>Por eso salió P12 y entró el acelerómetro U26.</span></li>
|
||||
|
||||
@ -27,6 +27,7 @@ Las páginas que abres para saber **dónde está el proyecto hoy**: qué pendien
|
||||
3. Los tableros de Ingeniería y Producción son **vistas** de los mismos pendientes filtrados por pista, más sus bloques fijos (versiones, cantidades).
|
||||
4. Las cantidades del plan de producción (versión → objetivo → fecha) las edita un administrador en el propio tablero; los conteos reales salen de las actas.
|
||||
5. Los pendientes sustituyen al *Task Tracker* de Notion, que queda como histórico (sus tareas se importaron una vez).
|
||||
6. Claude también mueve pendientes: sus cambios llegan en **lotes** que Leo revisa y aplica, y se ven en la Actividad con su nombre y una nota que dice por qué. Toda tarea termina en el tablero, sea de quien sea ([el sitio es el centro de mando](../guia-ingeniero.md#el-sitio-es-el-centro-de-mando)).
|
||||
|
||||
Las direcciones viejas (`tableros/kanban.html`, `unidad.html?serie=…`, `sitios/sitio.html?id=…` y las demás) siguen sirviendo: llevan solas a la página nueva.
|
||||
|
||||
|
||||
@ -37,6 +37,9 @@ hide:
|
||||
<h2>Últimas decisiones</h2>
|
||||
<p class="sub">Lo decidido manda hasta que otra decisión con fecha posterior lo cambie. Fuente: notas del proyecto; detalle en la sección de hardware.</p>
|
||||
<ul class="decisiones">
|
||||
<li><b>21-oct (20:45)</b> — <b>v6: T38 aplicado por Claude.</b> Courtyards reales en las 11 huellas de easyeda2kicad: el DRC no veía encimes en ellas, y salió uno real, C153 debajo de L22. MH6 libre. Regla nueva de barrenos: el plano <code>/3V3</code> de B.Cu quedaba desnudo bajo los separadores de los 5 barrenos; con insertos o separadores metálicos, el tornillo lo une a GND. <b>D-21-3, propuesta (falta el OK de Leo):</b> el bloque A se hace con D34 entre U25 y L22, y L22 al este (propuesta α). Metas corregidas: cátodo ≤ 3.5 mm, L22.1 ≤ 9 mm, nodo SW ≤ 10 mm² fuera de los pads, lazo ≤ 60 mm². En V5.6 y V5.7 salen 2 y 4 avisos de biblioteca: es solo el courtyard, no actualicen esas huellas antes del Gerber. <a href="/hardware/v6-estado-21oct/#5-t38-21-oct-noche-courtyards-mh6-y-regla-de-barrenos">T38</a> · <a href="/hardware/v6-estado-21oct/#31-bloque-a-propuesta-corrige-la-rev-10">Propuesta α</a></li>
|
||||
<li><b>21-oct (20:00)</b> — <b>El sitio es el centro de mando</b> (regla de Leo): toda tarea del proyecto termina aquí, con sus pendientes movidos, sus documentos al día y, si algo del sitio estorbó, una mejora. Los cambios de Claude al tablero llegan en <b>lotes</b> que Leo revisa y aplica (<code>./scripts/tablero.sh</code>) y se ven en la Actividad como cualquier otro cambio. <a href="/guia-ingeniero/#el-sitio-es-el-centro-de-mando">Cómo se cierra una tarea</a></li>
|
||||
<li><b>21-oct (19:30)</b> — <b>Pasada 6 hecha por Claude</b> (T34–T37; Leo pidió avanzar sin esperar a Michelle): <b>la V5.6 y la V5.7 quedan con el cobre listo para Gerber</b>. RS1 otra vez Kelvin (2 vías de entrada unidas en F.Cu y B.Cu; el hilo de U6.6 entra al pad por su lado), 2.ª vía de la entrada de U7 y, en la V5.7, la vía de GND de C129. DRC: solo los 3 errores de MOD1 (excluidos), 0 sin conectar, paridad solo PF*. <b>D-21-2, propuesta (falta el OK de Leo):</b> C149/C150 de la V5.7 se quedan como están y el timbre de U25 se mide en el banco. Falta: push de Leo, B + DRC en KiCad y Gerber/BOM/CPL. <a href="/hardware/v56-v57-auditoria-21oct/#6-pasada-6-hecha-por-claude-t34-a-t37">Pasada 6</a> · <a href="/hardware/v56-v57-auditoria-21oct/#62-v57-por-que-c149c150-se-quedan-como-estan">D-21-2</a></li>
|
||||
<li><b>21-oct (noche)</b> — <b>Pasada 5 revisada</b> (<code>6458c5d</code>): C129 y C130 quedaron bien en las dos, pero la vía de entrada de RS1 quedó encima del hilo de sensado de U6.6: <b>el sensado ya no es Kelvin</b> (el límite de entrada baja ≈ 3–4 %). Falta la <b>pasada 6</b>, ≈ 30 min por tarjeta; en la V5.7, además, girar C149/C150 (lazo de ≈ 56 mm²). <b>v6:</b> T25 y T28 aplicados sobre <code>9255b84</code> (falta el push de Leo); T28 cerró 2 de los 8 bloqueantes, pero D34 quedó a 13 mm del pin SW de U25 (lazo caliente de ≈ 115 mm², antes ≈ 47) y MH8 cae encima de D34. <a href="/hardware/v56-v57-auditoria-21oct/#5-pasada-5-revisada-6458c5d-y-lo-que-falta">Pasada 5 revisada</a> · <a href="/hardware/v6-estado-21oct/">Estado de la v6</a></li>
|
||||
<li><b>21-oct (tarde)</b> — <b>Auditoría de la pasada 4 de Michelle</b> (HBox_PCB <code>85e7602</code> V5.6, <code>1416dcb</code> V5.7). <b>V5.6:</b> la pasada dejó un corto nuevo (<code>/MODEM_EN</code> a GND en R106) y una pista de <code>/SYS5V</code> a 0.09 mm de una vía; el giro de U7 mejoró el nodo SW (L7 a 4 mm de U7). <b>V5.7:</b> ya sin el bloqueante de K1. <b>D-21-1, aprobada por Leo:</b> C130 de 100 nF en la entrada de U7, en las dos. En la tarde quedaron <b>T32</b> (C130 en los esquemáticos) y <b>T33</b> (el corto, la separación, la 2.ª vía de VSYS y las vías de potencia): DRC solo con los 3 errores de MOD1. Falta la <b>pasada 5</b> de Michelle (V5.6 ≈ 1 h, V5.7 ≈ medio día); después, revisión del cobre y Gerber. <a href="/hardware/v56-v57-auditoria-21oct/">Auditoría del 21-oct</a></li>
|
||||
<li><b>19-oct (19:30)</b> — <b>V5.6: qué es crítico y qué se puede quedar</b> (archivo de trabajo de las 18:59, sobre T30). <b>Un solo crítico, nuevo:</b> U6 (LTC4040) no tiene capacitor en sus pines VSYS; el más cercano, C34, está a 11.5 mm (viene desde la V5.5). La hoja de datos lo pone por encima de todo en el layout. Se agrega <b>C129</b> (10 µF 0603, C19702) junto a los pines 1/24, sin mover piezas. Regresar 2 vías de <code>/MAIN5V</code> y 2 de <code>/3V3_MODEM</code> que se perdieron al guardar. L7 lejos de U7 y lo heredado de la V5.5 (L6, L8, Dbk1) se quedan para el piloto, y se miden en el banco. <a href="/hardware/v56-clasificacion-19oct/">Clasificación</a></li>
|
||||
|
||||
@ -152,7 +152,7 @@ nav:
|
||||
- hardware/index.md
|
||||
- Manuales por tarjeta: manuales/index.md
|
||||
- Auditorías y pasadas:
|
||||
- 21 oct · Estado v6 (T25 + T28): hardware/v6-estado-21oct.md
|
||||
- 21 oct · Estado v6 (T25, T28, T38): hardware/v6-estado-21oct.md
|
||||
- 21 oct · V5.6/V5.7, pasadas 4 a 6: hardware/v56-v57-auditoria-21oct.md
|
||||
- 19 oct · Críticos V5.6: hardware/v56-clasificacion-19oct.md
|
||||
- 19 oct · Re-auditoría: hardware/re-auditoria-19oct.md
|
||||
|
||||
@ -17,8 +17,13 @@ echo "Sitio publicado en $REMOTE:$REMOTE_PATH (puerto $PORT)"
|
||||
# Sin --delete (ahí junto al app.py viven el venv y __pycache__).
|
||||
BACKEND_PATH="$(dirname "$REMOTE_PATH")/backend"
|
||||
rsync -az -e "ssh -p $PORT" backend/app.py backend/requirements.txt backend/seed_1013.py \
|
||||
backend/parche-nginx-api.py backend/actas-hbox.service "$REMOTE:$BACKEND_PATH/"
|
||||
backend/parche-nginx-api.py backend/actas-hbox.service backend/tablero.py "$REMOTE:$BACKEND_PATH/"
|
||||
echo "Backend copiado en $REMOTE:$BACKEND_PATH"
|
||||
|
||||
# Tablero (21-oct): baja la foto a _estado/ (para que Claude lea el tablero) y avisa si hay lotes
|
||||
# de Claude sin aplicar. Solo lee; los lotes se aplican con ./scripts/tablero.sh aplicar.
|
||||
REMOTE="$REMOTE" SSH_PORT="$PORT" BACKEND_PATH="$BACKEND_PATH" ./scripts/tablero.sh estado \
|
||||
|| echo "(no se pudo revisar el tablero; el sitio sí quedó publicado)"
|
||||
echo
|
||||
echo "FALTA reiniciar el servicio (pide sudo, por eso no se hace aquí):"
|
||||
echo "FALTA reiniciar el servicio si cambió backend/app.py (pide sudo, por eso no se hace aquí):"
|
||||
echo " ssh $REMOTE 'sudo systemctl restart actas-hbox'"
|
||||
|
||||
2
scripts/sync-docs.sh
Executable file → Normal file
2
scripts/sync-docs.sh
Executable file → Normal file
@ -136,6 +136,8 @@ declare -a MAP=(
|
||||
"03-Architecture-Hardware/img/2026-10-21_Drogheda_Pasada-5-V56-V57_v1.0.png|docs/hardware/img/2026-10-21_Drogheda_Pasada-5-V56-V57_v1.0.png"
|
||||
# 2026-10-21 (noche): pasada 6 hecha por Claude con T34–T37 (V5.6 y V5.7)
|
||||
"03-Architecture-Hardware/img/2026-10-21_Drogheda_Pasada-6-T34-T37_v1.0.png|docs/hardware/img/2026-10-21_Drogheda_Pasada-6-T34-T37_v1.0.png"
|
||||
# 2026-10-21 (noche): T38 en la v6 (courtyards, MH6, regla de barrenos) y propuesta α del bloque A
|
||||
"03-Architecture-Hardware/img/2026-10-21_Drogheda_v6-T38-Courtyards-U25-Barrenos_v1.0.png|docs/hardware/img/2026-10-21_Drogheda_v6-T38-Courtyards-U25-Barrenos_v1.0.png"
|
||||
)
|
||||
|
||||
n=0
|
||||
|
||||
49
scripts/tablero.sh
Executable file
49
scripts/tablero.sh
Executable file
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
# El tablero de pendientes desde la Mac (21-oct-2026).
|
||||
# Claude no llega al servidor: deja sus cambios al tablero en lotes (backend/lotes/*.json) y lee el
|
||||
# tablero en la foto (_estado/, fuera de git). Este script lleva los lotes y trae la foto.
|
||||
#
|
||||
# ./scripts/tablero.sh foto baja pendientes, enfoque y actividad a _estado/ (solo lee)
|
||||
# ./scripts/tablero.sh revisar sube los lotes y muestra qué cambiaría cada uno (no escribe)
|
||||
# ./scripts/tablero.sh aplicar los aplica (lo ya aplicado se salta) y baja la foto nueva
|
||||
# ./scripts/tablero.sh estado foto + una línea con lo que falta aplicar (lo llama deploy.sh)
|
||||
#
|
||||
# Variables: REMOTE (leo@git.helgen.online), SSH_PORT (22), BACKEND_PATH (…/hbox-docs/backend).
|
||||
# Formato de los lotes y reglas: backend/lotes/README.md.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
REMOTE="${REMOTE:-leo@git.helgen.online}"
|
||||
PORT="${SSH_PORT:-22}"
|
||||
BACKEND_PATH="${BACKEND_PATH:-/srv/projects/drogheda/hbox-docs/backend}"
|
||||
PY="$BACKEND_PATH/venv/bin/python3" # el mismo Python del servicio; tablero.py solo usa la biblioteca estándar
|
||||
|
||||
subir() {
|
||||
rsync -az -e "ssh -p $PORT" backend/tablero.py "$REMOTE:$BACKEND_PATH/"
|
||||
rsync -az -e "ssh -p $PORT" backend/lotes/ "$REMOTE:$BACKEND_PATH/lotes/" # sin --delete
|
||||
}
|
||||
|
||||
remoto() { # remoto <acción> → corre tablero.py en el servidor
|
||||
ssh -p "$PORT" "$REMOTE" "$PY $BACKEND_PATH/tablero.py $1 $BACKEND_PATH/lotes"
|
||||
}
|
||||
|
||||
foto() {
|
||||
mkdir -p _estado
|
||||
if ssh -p "$PORT" "$REMOTE" "$PY $BACKEND_PATH/tablero.py foto" > _estado/tablero.json.nuevo; then
|
||||
mv _estado/tablero.json.nuevo _estado/tablero.json
|
||||
python3 backend/tablero.py resumen _estado/tablero.json > _estado/tablero.md
|
||||
echo "Foto del tablero en _estado/tablero.md ($(grep -c '^- ' _estado/tablero.md || true) líneas)"
|
||||
else
|
||||
rm -f _estado/tablero.json.nuevo
|
||||
echo "No se pudo bajar la foto del tablero (¿el servicio actas-hbox está arriba?)" >&2
|
||||
return 2
|
||||
fi
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
foto) subir; foto ;;
|
||||
revisar) subir; remoto revisar ;;
|
||||
aplicar) subir; remoto aplicar; foto ;;
|
||||
estado) subir; foto; remoto pendientes ;;
|
||||
*) sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'; exit 1 ;;
|
||||
esac
|
||||
Loading…
Reference in New Issue
Block a user