From 0e828510a95d64e4aa81d372607c3fdbecd27ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leopoldo=20Rodr=C3=ADguez?= Date: Thu, 22 Oct 2026 13:59:27 -0600 Subject: [PATCH] =?UTF-8?q?Modo=20crisis:=20sem=C3=A1foro,=20sitio=20por?= =?UTF-8?q?=20pendiente,=20juntas;=20lote=20de=20crisis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 3 + DESPLIEGUE.md | 28 + backend/app.py | 52 +- backend/lotes/2026-10-22_auditoria-final.json | 6 +- backend/lotes/2026-10-22_crisis.json | 493 ++++++++++++++++++ backend/lotes/2026-10-22_t39-v6.json | 2 +- backend/lotes/README.md | 11 +- backend/tablero.py | 72 ++- docs/bitacora.md | 2 + docs/datos/proyecto.json | 68 ++- docs/hardware/index.md | 4 +- docs/index.md | 4 +- docs/inicio/inicio.css | 42 ++ docs/inicio/inicio.js | 48 +- docs/stylesheets/helgen.css | 9 + docs/tableros/index.md | 4 +- docs/tableros/ingenieria.md | 2 + docs/tableros/juntas.md | 61 +++ docs/tableros/kanban.js | 126 ++++- docs/tableros/pendientes.md | 3 +- docs/tableros/tablero.css | 24 +- hooks/iconos.py | 1 + mkdocs.yml | 1 + 23 files changed, 990 insertions(+), 76 deletions(-) create mode 100644 backend/lotes/2026-10-22_crisis.json create mode 100644 docs/tableros/juntas.md diff --git a/CLAUDE.md b/CLAUDE.md index b1f838f..ed22ec5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,6 +23,9 @@ Adriana, Michelle, Jesús and the field technicians work from it; Claude closes |---|---|---|---| | **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 …» | +| **Semáforo** (home, 22-oct) and per-site color | `docs/datos/proyecto.json` → `semaforo` (color rojo/amarillo/verde, titulo, texto, causas with **bold**/[links](/…), destraba, actualizado) + `estado`/`nota`/`meta` on each `sitios[]` item and on `oficina` | Leo decides the color; Claude or Leo edit | update it in every crisis or junta hand-off; map pins and the site list link to `/tableros/pendientes/?sitio=` | +| **Sitio of each task** (22-oct) | task field `sitios` = list of icave · timsa · eit · lzc · qro (no field = qro, Querétaro: fabricación e ingeniería) | everyone in the UI (site checkboxes); Claude through lotes (`"sitios": ["eit"]`) | filter `?sitio=eit,timsa`, view «Por sitio» (`?vista=sitio`); `GET /api/tareas` advertises the list; a lote with `sitios` needs the restarted backend | +| **Juntas** (standup + quick wins, 22-oct) | `docs/tableros/juntas.md` (process, agenda, cadence, register) | Claude or Leo | Leo records with Gemini → Claude turns the transcript into a lote → Leo applies; add a row to «Registro» each time; tags `crisis`, `quick-win`; track `gestion` | | **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 `
  • DD-mmm (hora) — …
  • ` at the top, with the decision number (D-xx-n) and links | diff --git a/DESPLIEGUE.md b/DESPLIEGUE.md index 00dfda8..8e64e08 100644 --- a/DESPLIEGUE.md +++ b/DESPLIEGUE.md @@ -325,6 +325,34 @@ dice por qué antes de escribir nada. | «los cambios N ya no son los que se aplicaron» | alguien editó un lote ya aplicado | un lote aplicado no se edita: Claude escribe otro | | un cambio «se salta» y no debía | el título del pendiente real es distinto | pásale a Claude la línea de `revisar`; escribe un lote nuevo con el id correcto | +## A6 · 22-oct: modo crisis — semáforo, sitio de cada pendiente y juntas + +Qué cambia: la página de inicio muestra el **semáforo del proyecto** (rojo) y el color de cada sitio (`docs/datos/proyecto.json`); +cada pendiente lleva su **sitio** (`sitios`: icave, timsa, eit, lzc, qro; sin sitio = Querétaro), con filtro y vista **Por sitio** +en Pendientes; pista nueva **Gestión**; página nueva **Juntas**. `backend/tablero.py` acepta `sitios` en los lotes, salta un lote +con errores sin frenar los demás y recorta notas de más de 300 caracteres (lo avisa). + +**Cambió `backend/app.py`: hay que reiniciar el servicio ANTES de aplicar lotes con `sitios`.** Con el backend viejo, `revisar` +marca esos cambios como inválidos («el backend todavía no conoce «sitios»: reinicia actas-hbox») y no los manda. + +```bash +cd ~/Documents/Claude/Projects/Drogheda/hbox-docs +mkdocs build --strict +git add -A && git commit -m "Modo crisis: semáforo, sitio por pendiente, juntas" && git push +cd .. && git add hbox-docs && git commit -m "hbox-docs: modo crisis" && git push && cd hbox-docs # sube el submódulo +./scripts/deploy.sh # sitio + backend (rsync); la foto todavía con el backend viejo +ssh leo@git.helgen.online 'sudo systemctl restart actas-hbox' +curl -s -u leo:PASS 'https://drogheda.helgen.online/api/tareas?sitio=eit' | head -c 300 # «sitios» en la respuesta +./scripts/tablero.sh revisar # ya con sitios +./scripts/tablero.sh aplicar +``` + +| Síntoma | Causa | Arreglo | +|---|---|---| +| En Pendientes no aparecen los botones de sitio | navegador con el `kanban.js` viejo | recarga (los `.js` llevan `?v=` desde el 21-oct) | +| `Sitio inválido: …` al guardar | sitio que no existe | icave, timsa, eit, lzc o qro (también acepta Veracruz, Manzanillo, Ensenada, LCT, Querétaro) | +| `revisar` dice «el backend todavía no conoce «sitios»» | no se reinició el servicio | el `restart` de arriba | + --- ## B · Verificación después de desplegar diff --git a/backend/app.py b/backend/app.py index abe1bd0..fa996e0 100644 --- a/backend/app.py +++ b/backend/app.py @@ -29,7 +29,7 @@ de TIPOS_UNICOS un módulo no puede aparecer en dos actas reales (409). Colecciones agregadas el 13-oct-2026 (tableros, inventario, sitios) — un JSON por registro + _indice.csv, campos de auditoría creado_por/creado_en/modificado_por/modificado_en/historial; cualquier usuario autenticado crea y modifica, solo un administrador borra: - GET/POST /api/tareas · PATCH/DELETE /api/tarea?id= · GET /api/tareas/resumen · POST /api/tareas/orden + GET/POST /api/tareas (?sitio=eit,timsa filtra por sitio; 22-oct) · PATCH/DELETE /api/tarea?id= · GET /api/tareas/resumen · POST /api/tareas/orden GET /api/plan · PUT /api/plan (admin) GET/POST /api/inventario · PATCH /api/inventario/item?id= · POST /api/inventario/movimiento GET /api/inventario/movimientos?item_id= · GET /api/inventario/export.csv @@ -769,7 +769,16 @@ EQUIPO_DIR = os.environ.get("EQUIPO_DIR", os.path.join(DATOS_BASE, "equipo")) # 2 workers). Vive en la carpeta de datos porque /etc/nginx no es escribible por el servicio. HTPASSWD_LOCK = os.environ.get("HTPASSWD_LOCK", os.path.join(DATOS_BASE, ".htpasswd.lock")) -PISTAS = ("ingenieria", "produccion", "mecanica", "compras", "campo") +PISTAS = ("ingenieria", "produccion", "mecanica", "compras", "campo", "gestion") # gestion: finanzas, trámites, juntas (22-oct) +# Sitio de cada pendiente (22-oct): dónde pasa el trabajo. Un pendiente puede tocar varios sitios. +# (id, nombre, corto). qro = Querétaro: fabricación e ingeniería (oficina). +SITIOS_TAREA = (("icave", "ICAVE · Veracruz", "ICAVE"), ("timsa", "TIMSA · Manzanillo", "TIMSA"), + ("eit", "EIT · Ensenada", "EIT"), ("lzc", "LCT · Lázaro Cárdenas", "LCT"), + ("qro", "Querétaro · fabricación e ingeniería", "QRO")) +SITIOS_TAREA_IDS = tuple(s[0] for s in SITIOS_TAREA) +SITIO_OMISION = ["qro"] # pendientes sin sitio (los de antes del 22-oct) cuentan como Querétaro +ALIAS_SITIO = {"lct": "lzc", "lazarocardenas": "lzc", "lazaro-cardenas": "lzc", "veracruz": "icave", + "manzanillo": "timsa", "ensenada": "eit", "queretaro": "qro", "oficina": "qro"} AREAS = ("direccion", "operaciones", "ingenieria", "layout", "produccion", "pruebas", "fabricacion", "software", "campo", "asistente") ESTADOS = ("por-hacer", "en-curso", "bloqueado", "hecho") @@ -1184,7 +1193,7 @@ def usuario_borrar(): # ------------------------------------------------------------------ tareas (tablero) -COLS_TAREAS = ["id", "titulo", "pista", "estado", "responsable", "responsable_ids", "fecha_limite", "version_tarjeta", +COLS_TAREAS = ["id", "titulo", "pista", "estado", "responsable", "responsable_ids", "fecha_limite", "version_tarjeta", "sitios", "orden", "etiquetas", "bloqueada_por", "creado_por", "creado_en", "modificado_por", "modificado_en"] @@ -1269,9 +1278,36 @@ def _tarea_valida(d, base=None): if v not in VERSIONES_TARJETA: return None, f"Versión de tarjeta inválida: {v}. Usa una de {', '.join(VERSIONES_TARJETA)}." pon("version_tarjeta", v) + if alta or "sitios" in d: + ids, err = _sitios_validos(d.get("sitios"), alta) + if err: + return None, err + pon("sitios", ids) return t, None +def _sitios_validos(v, alta): + """['eit', 'Manzanillo'] → (['eit', 'timsa'], None). Vacío: en un alta, Querétaro; en un cambio, error.""" + if v in (None, "", []): + if alta: + return list(SITIO_OMISION), None + return None, "Elige al menos un sitio (" + ", ".join(SITIOS_TAREA_IDS) + ")." + if isinstance(v, str): + v = [x for x in re.split(r"[,;/\s]+", v) if x] + if not isinstance(v, list): + return None, "«sitios» es una lista de ids: " + ", ".join(SITIOS_TAREA_IDS) + "." + ids = [] + for x in v: + k = _norm_id(x) + k = ALIAS_SITIO.get(k, k) + if k not in SITIOS_TAREA_IDS: + return None, (f"Sitio inválido: {x}. Usa: icave (Veracruz), timsa (Manzanillo), eit (Ensenada), " + "lzc (LCT, Lázaro Cárdenas) o qro (Querétaro).") + if k not in ids: + ids.append(k) + return ids, None + + def _guardar_tarea(t): _escribir_json(_ruta_registro(TAREAS_DIR, t["id"]), t) _reescribir_indice(TAREAS_DIR, _listar_json(TAREAS_DIR), COLS_TAREAS) @@ -1289,12 +1325,17 @@ def tareas_listar(): resp = _texto(request.args.get("responsable"), 80).lower() resp_id = _norm_id(resp) version = _texto(request.args.get("version"), 10) + sitio = {ALIAS_SITIO.get(_norm_id(x), _norm_id(x)) for x in _texto(request.args.get("sitio"), 80).split(",") if x.strip()} hoy = datetime.date.today().isoformat() miembros = _miembros() out = [] for t in _listar_json(TAREAS_DIR): if "responsable_ids" not in t: # pendientes guardados antes del 14-oct t["responsable_ids"] = _ids_responsable(t.get("responsable"), miembros)[0] + if not t.get("sitios"): # pendientes de antes del 22-oct: Querétaro + t["sitios"] = list(SITIO_OMISION) + if sitio and not sitio.intersection(t["sitios"]): + continue if pista and t.get("pista") != pista: continue if estado and t.get("estado") != estado: @@ -1309,7 +1350,8 @@ def tareas_listar(): t.get("orden", 0), t.get("creado_en", ""))) equipo = {k: {"nombre": m.get("nombre"), "activo": m.get("activo", True), "area": m.get("area")} for k, m in miembros.items()} return jsonify(ok=True, total=len(out), tareas=out, pistas=PISTAS, estados=ESTADOS, equipo=equipo, - versiones=VERSIONES_TARJETA, admin=is_admin(), user=current_user()) + versiones=VERSIONES_TARJETA, sitios=[{"id": i, "nombre": n, "corto": c} for i, n, c in SITIOS_TAREA], + admin=is_admin(), user=current_user()) @app.post("/api/tareas") @@ -1354,6 +1396,8 @@ def tarea_modificar(): base = _leer_json(ruta) if not base: return jsonify(ok=False, error=f"No existe el pendiente {rid}."), 404 + if not base.get("sitios"): # pendiente de antes del 22-oct: es de Querétaro + base["sitios"] = list(SITIO_OMISION) d = _json_body() if d is None: return jsonify(ok=False, error="JSON inválido"), 400 diff --git a/backend/lotes/2026-10-22_auditoria-final.json b/backend/lotes/2026-10-22_auditoria-final.json index 155d1e0..6151c42 100644 --- a/backend/lotes/2026-10-22_auditoria-final.json +++ b/backend/lotes/2026-10-22_auditoria-final.json @@ -10,7 +10,7 @@ "poner": { "estado": "hecho" }, - "nota": "Cerrado por la auditoría final del 22-oct: los paquetes HBOX_v5_Rev6_PROD (V5.6) y HBOX_v5_Rev7_PROD (V5.7) son idénticos a una regeneración con relleno fresco (0.000 mm² de diferencia en el cobre; máscaras, pasta, serigrafía y taladros iguales); DRC solo con los 3 errores de MOD1, 0 sin conectar, paridad solo PWR_FLAG; RS1 Kelvin en VIN y CLN en las dos; BOM y CPL cuadran con el PCB (226 / 229 piezas, todas con LCSC). Lo que sigue es el pedido (t-v56-piloto) y P-1 (t-p1-relleno-commit). Ver /hardware/auditoria-final-22oct/" + "nota": "Cerrado por la auditoría final del 22-oct: los paquetes HBOX_v5_Rev6_PROD (V5.6) y HBOX_v5_Rev7_PROD (V5.7) son idénticos a una regeneración con relleno fresco (0.000 mm² de diferencia en el cobre; máscaras, pasta, serigrafía y taladros iguales); DRC solo con los 3 errores de MOD1, 0 sin conectar, paridad solo PWR_FLAG; RS1 Kelvin en VIN y CLN en las dos; BOM y CPL cuadran con el PCB (226 / 229 piezas, todas con LCSC). Lo que sigue es el pedido (t-v56-piloto) y P-1 (t-p1-relleno-commit). Ver /hardware/auditoria-final-22oct/ Re-verificado a las 13:30 sobre los archivos de las 13:00 (P-1 cerrado): mismo cobre, mismo paquete." }, { "id": "t-v56-piloto", @@ -89,13 +89,13 @@ { "crear": { "id": "t-p1-relleno-commit", - "titulo": "P-1: abrir V5.6 y V5.7 en KiCad, B (rellenar), guardar y commit — el repo trae el relleno viejo", + "titulo": "P-1: commit de kicad/ y production/ con el relleno fresco (el relleno ya quedó en los archivos a las 13:00)", "pista": "ingenieria", "estado": "por-hacer", "responsable": "leo", "version_tarjeta": "V5.6", "fecha_limite": "2026-10-22", - "descripcion": "Los dos .kicad_pcb del repo traen el relleno de zonas de la pasada 5. El Gerber del Fabrication Toolkit sí lleva el relleno fresco (verificado: 0.000 mm² de diferencia), pero una exportación sin rellenar (kicad-cli, o el GUI sin B) manda cobre de GND sobre la vía de /SYS5V de T35 y las vías de RS1 de T34. Son 5 min: abrir cada tarjeta, B, guardar, DRC (solo los 3 de MOD1), commit «V5.6/V5.7: relleno fresco (P-1)», push y bump del submódulo. Regla nueva (D-22-3): todo script T que toque cobre termina con rellenado en el GUI antes del commit.", + "descripcion": "Hecho a las 13:00: Leo rellenó las zonas, guardó los dos .kicad_pcb y regeneró HBOX_v5_Rev6_PROD / Rev7_PROD. Re-verificado a las 13:30: relleno guardado = relleno fresco (0.000 mm² en las dos), mismo cobre y mismo paquete que en la mañana, BOM/CPL iguales, DRC igual. Falta: commit «V5.6/V5.7: relleno fresco (P-1) + paquetes 13:00», push y bump del submódulo. Regla D-22-3: todo script T que toque cobre termina con rellenado en el GUI antes del commit; después de cualquier cambio, scripts/verify_mac.sh + gcmp_b.py (5 min) antes de pagar.", "enlaces": [ { "titulo": "P-1 en la auditoría final (§2)", diff --git a/backend/lotes/2026-10-22_crisis.json b/backend/lotes/2026-10-22_crisis.json new file mode 100644 index 0000000..7f6de77 --- /dev/null +++ b/backend/lotes/2026-10-22_crisis.json @@ -0,0 +1,493 @@ +{ + "lote": "2026-10-22_crisis", + "autor": "claude", + "fecha": "2026-10-22", + "motivo": "Modo crisis (Leo, 22-oct): semáforo rojo. Sin pago del mes no hay compras; meta de 1–2 unidades por sitio con TIMSA, EIT e ICAVE bloqueados; software: retranslator de Wialon y desconexiones súbitas; juntas con Gemini; clasificación por sitio; instalaciones sin vehículo ni prueba", + "fuente": "/", + "cambios": [ + { + "crear": { + "id": "t-cobranza-octubre", + "titulo": "Cobranza: conseguir el pago del mes — sin él no hay compras", + "pista": "gestion", + "estado": "en-curso", + "responsable": "leo, adriana", + "version_tarjeta": "—", + "sitios": [ + "qro" + ], + "descripcion": "22-oct: no se ha recibido ningún pago este mes. Operamos muy justos y no se han hecho compras significativas. Es la causa principal del semáforo rojo.\nCuando paguemos a proveedores se destraban la mayoría de las tareas: material para las instalaciones, ferretería de EIT y TIMSA, pre-órdenes de la v6 y el resto de las compras del pedido de 570.", + "etiquetas": [ + "crisis", + "finanzas" + ] + }, + "si_no_hay": [ + "pago del mes", + "cobranza" + ] + }, + { + "crear": { + "id": "t-compras-prioridad", + "titulo": "Lista de compras priorizada, lista para el día que llegue el pago", + "pista": "compras", + "estado": "por-hacer", + "responsable": "adriana, leo", + "version_tarjeta": "—", + "sitios": [ + "qro" + ], + "descripcion": "Se puede hacer hoy, sin dinero (quick win). Orden propuesto, a confirmar en la junta: 1) lo que destraba instalaciones (ferretería de EIT y TIMSA, material en sitio); 2) piloto 5 V5.6 + 5 V5.7 si no se pagó antes; 3) pre-órdenes de la v6 (stock limitado de THVD1410DR, TPS7A2033 y J14); 4) material del pedido de 570. Con monto y proveedor por renglón.", + "etiquetas": [ + "quick-win", + "finanzas" + ] + }, + "si_no_hay": [ + "compras priorizada", + "lista de compras" + ] + }, + { + "id": "t-compras-materiales", + "poner": { + "estado": "bloqueado", + "bloqueada_por": [ + "t-cobranza-octubre" + ] + }, + "nota": "22-oct, modo crisis: sin el pago del mes no hay compras. Bloqueado hasta la cobranza (t-cobranza-octubre); el orden de compra queda en t-compras-prioridad." + }, + { + "id": "t-preordenes-v6", + "poner": { + "estado": "bloqueado", + "bloqueada_por": [ + "t-cobranza-octubre" + ] + }, + "nota": "22-oct: vencido el 20-oct y sin pago del mes: bloqueado hasta la cobranza. Ojo: el stock de THVD1410DR, TPS7A2033 y J14 es limitado; es de lo primero en cuanto haya pago." + }, + { + "id": "t-ferreteria-eit-timsa", + "poner": { + "estado": "bloqueado", + "bloqueada_por": [ + "t-cobranza-octubre" + ], + "sitios": [ + "eit", + "timsa" + ] + }, + "nota": "22-oct: sigue sin cerrarse (vencido el 14-oct) y sin pago del mes no hay compras. Es de lo primero en la lista: destraba las instalaciones de EIT y TIMSA." + }, + { + "id": "t-preorden-108-322-bom", + "poner": { + "bloqueada_por": [ + "t-v56-banco-piloto", + "t-v57-banco-prototipos", + "t-cobranza-octubre" + ] + }, + "nota": "22-oct, modo crisis: además de los bancos del piloto, la pre-orden espera el pago del mes (t-cobranza-octubre)." + }, + { + "id": "d-22-1-go-piloto", + "nota": "Con el semáforo en rojo, decidir también si el piloto 5 + 5 se paga ya o espera la cobranza del mes (t-cobranza-octubre). Se ve en la junta del 22-oct." + }, + { + "crear": { + "id": "t-asignar-campo", + "titulo": "Asignar un responsable de campo por sitio (ICAVE, TIMSA, EIT, LCT)", + "pista": "gestion", + "estado": "por-hacer", + "responsable": "leo", + "version_tarjeta": "—", + "fecha_limite": "2026-10-22", + "sitios": [ + "icave", + "timsa", + "eit", + "lzc" + ], + "descripcion": "En la junta del 22-oct. Los pendientes de instalación por sitio están a nombre de Leo mientras tanto; al asignar, cambiar el responsable de cada uno.", + "etiquetas": [ + "quick-win" + ] + }, + "si_no_hay": [ + "responsable de campo" + ] + }, + { + "crear": { + "id": "t-registrar-instaladas", + "titulo": "Registrar las 3–4 unidades ya instaladas: serie, sitio, vehículo (tipo y número económico), fecha y técnico", + "pista": "campo", + "estado": "por-hacer", + "responsable": "leo", + "version_tarjeta": "—", + "fecha_limite": "2026-10-24", + "sitios": [ + "icave", + "timsa", + "eit", + "lzc" + ], + "descripcion": "22-oct: hay 3 o 4 unidades instaladas y no sabemos en qué vehículo está cada una. Llenar el acta de instalación de cada una con la fecha real (serie, sitio, tipo de equipo y número económico, técnico), para que aparezcan en el tablero de Instalaciones con su vehículo y su estado. Lo hace quien estuvo en la instalación; responsable por sitio: t-asignar-campo.", + "enlaces": [ + { + "titulo": "Instalaciones por sitio", + "url": "/tableros/instalaciones/" + }, + { + "titulo": "Acta de instalación", + "url": "/formularios/instalacion.html" + } + ], + "etiquetas": [ + "quick-win", + "instalación" + ] + }, + "si_no_hay": [ + "ya instaladas", + "registrar las unidades" + ] + }, + { + "crear": { + "id": "t-prueba-en-sitio", + "titulo": "Prueba en sitio después de instalar: hacerla y registrarla en cada unidad instalada", + "pista": "campo", + "estado": "por-hacer", + "responsable": "leo, claude", + "version_tarjeta": "—", + "sitios": [ + "icave", + "timsa", + "eit", + "lzc" + ], + "descripcion": "22-oct: hay metas de instalación pero no hay prueba. Cada unidad instalada pasa una prueba en sitio con su registro: fix RTK visto en el monitor, reporta en el monitor y en Wialon, posición coherente en el mapa y fotos. Una unidad cuenta para la meta de 1–2 por sitio solo con la prueba aprobada. El registro nuevo vive en el tablero de Instalaciones.", + "enlaces": [ + { + "titulo": "Instalaciones por sitio", + "url": "/tableros/instalaciones/" + } + ], + "etiquetas": [ + "instalación", + "prueba" + ] + }, + "si_no_hay": [ + "prueba en sitio" + ] + }, + { + "crear": { + "id": "t-timsa-tramite-vehiculo", + "titulo": "TIMSA: resolver el trámite administrativo del vehículo en Manzanillo", + "pista": "gestion", + "estado": "bloqueado", + "responsable": "adriana, leo", + "version_tarjeta": "—", + "sitios": [ + "timsa" + ], + "descripcion": "22-oct: TIMSA está detenido por un trámite administrativo del vehículo que tenemos en Manzanillo. Qué falta, quién lo resuelve y para cuándo: por confirmar en la junta (en septiembre el bloqueo fueron las placas y el permiso del vehículo).", + "etiquetas": [ + "crisis", + "trámite" + ] + }, + "si_no_hay": [ + "trámite administrativo del vehículo", + "trámite del vehículo" + ] + }, + { + "crear": { + "id": "t-timsa-instalar", + "titulo": "TIMSA (Manzanillo): instalar 1–2 unidades", + "pista": "campo", + "estado": "bloqueado", + "responsable": "leo", + "version_tarjeta": "—", + "sitios": [ + "timsa" + ], + "bloqueada_por": [ + "t-timsa-tramite-vehiculo" + ], + "descripcion": "Meta inmediata: 1 o 2 unidades instaladas por sitio. Bloqueado por el trámite del vehículo. Al instalar: checklist de puesta en marcha, acta de instalación con el vehículo y prueba en sitio (t-prueba-en-sitio). Responsable de campo: t-asignar-campo.", + "enlaces": [ + { + "titulo": "Checklist de puesta en marcha", + "url": "/procedimientos/v5-checklist-puesta-en-marcha/" + }, + { + "titulo": "Acta de instalación", + "url": "/formularios/instalacion.html" + } + ], + "etiquetas": [ + "meta-sitio" + ] + }, + "si_no_hay": [ + "TIMSA (Manzanillo): instalar", + "instalar en TIMSA" + ] + }, + { + "crear": { + "id": "t-eit-material", + "titulo": "EIT: seguir el material enviado desde Manzanillo (guía, fecha de llegada y acta de recepción)", + "pista": "campo", + "estado": "en-curso", + "responsable": "adriana", + "version_tarjeta": "—", + "sitios": [ + "eit", + "timsa" + ], + "descripcion": "22-oct: EIT (Ensenada) espera el material que salió de Manzanillo. Registrar la guía y la fecha estimada aquí; al llegar, acta de recepción. Con eso se destraba la instalación de EIT.", + "enlaces": [ + { + "titulo": "Acta de recepción", + "url": "/formularios/recepcion.html" + } + ], + "etiquetas": [ + "crisis", + "envío" + ] + }, + "si_no_hay": [ + "material enviado desde Manzanillo" + ] + }, + { + "crear": { + "id": "t-eit-instalar", + "titulo": "EIT (Ensenada): instalar 1–2 unidades", + "pista": "campo", + "estado": "bloqueado", + "responsable": "leo", + "version_tarjeta": "—", + "sitios": [ + "eit" + ], + "bloqueada_por": [ + "t-eit-material" + ], + "descripcion": "Meta inmediata: 1 o 2 unidades instaladas por sitio. Espera el material que viene de Manzanillo. Al instalar: checklist de puesta en marcha, acta de instalación con el vehículo y prueba en sitio (t-prueba-en-sitio). Responsable de campo: t-asignar-campo.", + "enlaces": [ + { + "titulo": "Checklist de puesta en marcha", + "url": "/procedimientos/v5-checklist-puesta-en-marcha/" + }, + { + "titulo": "Acta de instalación", + "url": "/formularios/instalacion.html" + } + ], + "etiquetas": [ + "meta-sitio" + ] + }, + "si_no_hay": [ + "EIT (Ensenada): instalar", + "instalar en EIT" + ] + }, + { + "crear": { + "id": "t-icave-campo", + "titulo": "ICAVE (Veracruz): investigar en campo las fallas abiertas en la próxima ventana de buen clima", + "pista": "campo", + "estado": "bloqueado", + "responsable": "leo", + "version_tarjeta": "—", + "sitios": [ + "icave" + ], + "descripcion": "22-oct: el mal clima en Veracruz deja poco tiempo en campo y hay fallas que solo se pueden investigar ahí. Preparar desde hoy la lista de unidades y qué medir en cada una, para aprovechar la ventana. Regla: nada se reinicia antes de capturar la evidencia; acta de diagnóstico en campo con fotos.", + "enlaces": [ + { + "titulo": "Diagnóstico en campo", + "url": "/diagnostico/procedimiento-diagnostico-campo/" + }, + { + "titulo": "Acta de diagnóstico en campo", + "url": "/formularios/diagnostico.html" + } + ], + "etiquetas": [ + "crisis", + "clima" + ] + }, + "si_no_hay": [ + "ventana de buen clima" + ] + }, + { + "crear": { + "id": "t-icave-unidades", + "titulo": "ICAVE (Veracruz): confirmar 1–2 unidades instaladas y reportando bien", + "pista": "campo", + "estado": "por-hacer", + "responsable": "leo", + "version_tarjeta": "—", + "sitios": [ + "icave" + ], + "descripcion": "Meta inmediata: 1 o 2 unidades instaladas por sitio. En ICAVE ya hay unidades; confirmar cuántas reportan bien en el monitor y en Wialon, y cuáles van a la lista de t-icave-campo.", + "etiquetas": [ + "meta-sitio" + ] + }, + "si_no_hay": [ + "ICAVE (Veracruz): confirmar" + ] + }, + { + "crear": { + "id": "t-lct-instalar", + "titulo": "LCT (Lázaro Cárdenas): instalar 1–2 unidades — estado por confirmar en la junta", + "pista": "campo", + "estado": "por-hacer", + "responsable": "leo", + "version_tarjeta": "—", + "sitios": [ + "lzc" + ], + "descripcion": "Meta inmediata: 1 o 2 unidades instaladas por sitio. El estado de LCT no se revisó el 22-oct: confirmar en la junta qué hay en sitio y qué falta.", + "enlaces": [ + { + "titulo": "Acta de instalación", + "url": "/formularios/instalacion.html" + } + ], + "etiquetas": [ + "meta-sitio" + ] + }, + "si_no_hay": [ + "LCT (Lázaro Cárdenas): instalar", + "instalar en LCT" + ] + }, + { + "id": "t-sitios-alta", + "poner": { + "titulo": "Dar de alta los 4 sitios (ICAVE, LCT, EIT, TIMSA) con contactos y unidades instaladas", + "sitios": [ + "icave", + "timsa", + "eit", + "lzc" + ] + }, + "nota": "Clasificado por sitio (22-oct). LCT es la sigla correcta (21-oct)." + }, + { + "id": "t-acta-instalacion", + "poner": { + "sitios": [ + "icave", + "timsa", + "eit", + "lzc" + ] + }, + "nota": "Es para las instalaciones de los cuatro puertos: hace falta para la meta de 1–2 unidades por sitio (22-oct)." + }, + { + "crear": { + "id": "t-wialon-retranslator", + "titulo": "Software: retranslator de Wialon hacia nuestro servidor para comparar en el monitor RTK contra el GPS legado", + "pista": "ingenieria", + "estado": "por-hacer", + "responsable": "gibranlp, leo", + "version_tarjeta": "—", + "sitios": [ + "qro", + "icave" + ], + "descripcion": "Que Wialon reenvíe (retranslator) las posiciones del GPS legado (CalAmp) a nuestro servidor, para ver en el monitor la posición del RTK contra la del legado en la misma máquina. Sirve para justificar con datos los viajes de ICAVE. No requiere compras (quick win).", + "etiquetas": [ + "software", + "quick-win" + ] + }, + "si_no_hay": [ + "retranslator de Wialon" + ] + }, + { + "crear": { + "id": "t-desconexiones", + "titulo": "Software: investigar de inmediato las desconexiones súbitas", + "pista": "ingenieria", + "estado": "por-hacer", + "responsable": "gibranlp, claude", + "version_tarjeta": "—", + "fecha_limite": "2026-10-23", + "sitios": [ + "qro" + ], + "descripcion": "Pedido de Leo (22-oct): inmediato. Juntar la lista de desconexiones (unidad, sitio, hora), revisar los registros del monitor y de la unidad (Grafana/Loki) y descartar primero lo barato (backend, SIM, caster) antes de culpar al hardware. Un pendiente por causa encontrada.", + "enlaces": [ + { + "titulo": "Protocolo de diagnóstico remoto", + "url": "/diagnostico/protocolo-diagnostico-remoto/" + } + ], + "etiquetas": [ + "software", + "crisis" + ] + }, + "si_no_hay": [ + "desconexiones súbitas" + ] + }, + { + "crear": { + "id": "t-juntas", + "titulo": "Juntas periódicas (standup + quick wins) grabadas con Gemini; Claude actualiza el tablero", + "pista": "gestion", + "estado": "en-curso", + "responsable": "leo, adriana, claude", + "version_tarjeta": "—", + "sitios": [ + "qro", + "icave", + "timsa", + "eit", + "lzc" + ], + "descripcion": "Cada junta: Leo pasa la transcripción o las notas de Gemini a Claude; Claude escribe un lote (cierra, abre y comenta, con responsable y fecha); Leo lo revisa y lo aplica, y queda en la Actividad. Agenda fija: semáforo, cada sitio, bloqueos, quick wins y acuerdos. Frecuencia: se acuerda en la junta del 22-oct.", + "enlaces": [ + { + "titulo": "Juntas", + "url": "/tableros/juntas/" + } + ], + "etiquetas": [ + "proceso" + ] + }, + "si_no_hay": [ + "juntas periódicas" + ] + } + ] +} diff --git a/backend/lotes/2026-10-22_t39-v6.json b/backend/lotes/2026-10-22_t39-v6.json index f036618..30002d7 100644 --- a/backend/lotes/2026-10-22_t39-v6.json +++ b/backend/lotes/2026-10-22_t39-v6.json @@ -58,7 +58,7 @@ ] }, "si_no_hay": [ - "T39" + "push de T39" ] }, { diff --git a/backend/lotes/README.md b/backend/lotes/README.md index 89c98db..eeb986a 100644 --- a/backend/lotes/README.md +++ b/backend/lotes/README.md @@ -22,10 +22,11 @@ y queda en el historial del pendiente con su autor y su nota. En la página de i 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 + caracteres; si es más larga, se recorta a 300 y `revisar` lo avisa). Es lo que la gente lee en el historial. +6. **Un lote con errores de formato se salta** (y `revisar` dice por qué); los demás lotes sí se aplican. +7. **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 +8. **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 @@ -71,9 +72,9 @@ y queda en el historial del pendiente con su autor y su nota. En la página de i | `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 · +Valores: `pista` ∈ ingenieria · produccion · mecanica · compras · campo · gestion (desde el 22-oct); `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. +[Equipo](https://drogheda.helgen.online/tableros/equipo/) separados por coma; `fecha_limite` AAAA-MM-DD; `sitios` (desde el 22-oct) = lista de icave (Veracruz) · timsa (Manzanillo) · eit (Ensenada) · lzc (LCT) · qro (Querétaro: fabricación e ingeniería) — un pendiente puede tener varios; sin `sitios`, cuenta como Querétaro. Con un backend sin reiniciar, `revisar` marca los cambios con `sitios` como inválidos. ## La foto diff --git a/backend/tablero.py b/backend/tablero.py index aad7a69..d1c6e43 100644 --- a/backend/tablero.py +++ b/backend/tablero.py @@ -45,7 +45,7 @@ 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") + "bloqueada_por", "version_tarjeta", "sitios") 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") @@ -110,7 +110,8 @@ def llamar(metodo, ruta, usuario, cuerpo=None): "¿Está arriba el servicio actas-hbox?") -VALORES = {} # pista y version_tarjeta válidas, tal como las publica el backend en GET /api/tareas +VALORES = {} # pista, version_tarjeta y sitios válidos, tal como los publica el backend en GET /api/tareas +SITIOS_INFO = [] # [{id, nombre, corto}] del backend (desde el 22-oct) def leer_tareas(): @@ -118,15 +119,31 @@ def leer_tareas(): 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 [])) + SITIOS_INFO[:] = [x for x in (j.get("sitios") or []) if isinstance(x, dict) and x.get("id")] + VALORES["sitios"] = [x["id"] for x in SITIOS_INFO] 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.""" + """Lo que el backend rechazaría (o ignorarí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: + if k == "sitios" or k not in campos: + continue + if validos and campos[k] not in validos: malos.append(f"{k} «{campos[k]}» no existe (usa {', '.join(validos)})") + if "sitios" in campos: + validos = VALORES.get("sitios") or [] + if not validos: + # un backend sin reiniciar guardaría el pendiente y tiraría «sitios» sin avisar + malos.append("el backend todavía no conoce «sitios»: reinicia actas-hbox") + else: + lista = campos["sitios"] if isinstance(campos["sitios"], list) else [campos["sitios"]] + fuera = [str(x) for x in lista if str(x) not in validos] + if not lista: + malos.append("«sitios» vacío: pon al menos uno") + elif fuera: + malos.append(f"sitios {', '.join(fuera)} no existen (usa {', '.join(validos)})") return malos @@ -193,8 +210,7 @@ def validar_cambio(c): 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") + # más de 300 caracteres: el backend guarda 300; se recorta y «revisar» lo dice (no bloquea el lote) p = c.get("poner", {}) if not isinstance(p, dict): e.append("«poner» debe ser un objeto") @@ -265,6 +281,9 @@ def igual(campo, actual, nuevo): 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 == "sitios": + lst = lambda v: v if isinstance(v, list) else ([v] if v else []) + return sorted({norm(x) for x in lst(actual)}) == sorted({norm(x) for x in lst(nuevo)}) 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)] @@ -309,8 +328,12 @@ def _cambio_a(t, c): 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"]) + nota = c["nota"].strip() + aviso = "" + if len(nota) > 300: + nota, aviso = nota[:299].rstrip() + "…", f" (nota de {len(c['nota'].strip())} caracteres, recortada a 300)" + detalle = f"{_quien_es(t)}: " + ("; ".join(partes) + " · " if partes else "") + f"nota «{corto(nota, 80)}»{aviso}" + return detalle, ("PATCH", "/api/tarea?id=" + urllib.parse.quote(t["id"]), dict(poner, _nota=nota), t["id"]) def planear(c, tareas): @@ -333,6 +356,10 @@ def planear(c, tareas): datos = [cuerpo["pista"], NOMBRE.get(cuerpo["estado"], cuerpo["estado"]), cuerpo["responsable"]] if cuerpo.get("fecha_limite"): datos.append("vence " + fecha_corta(cuerpo["fecha_limite"])) + if cuerpo.get("sitios"): # 22-oct: el sitio también se revisa antes de aplicar + cortos = {x["id"]: x.get("corto") or x["id"] for x in SITIOS_INFO} + sit = cuerpo["sitios"] if isinstance(cuerpo["sitios"], list) else [cuerpo["sitios"]] + datos.append("sitio " + " · ".join(cortos.get(x, x) for x in sit)) return "crear", f"nuevo {cuerpo['id']} «{corto(cuerpo['titulo'], 70)}» ({', '.join(datos)})", \ [("POST", "/api/tareas", cuerpo, cuerpo["id"])] if c.get("id"): @@ -382,7 +409,13 @@ def simular(tareas, peticion): 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 + # un lote con errores de formato se salta (y se dice por qué); los demás sí se revisan y aplican + lotes, malos = [], [] + for a in archivos: + try: + lotes.append((a, cargar_lote(a))) + except ValueError as ex: + malos.append(str(ex)) ids = [lote["lote"] for _, lote in lotes] if len(ids) != len(set(ids)): raise ValueError("Hay dos lotes con el mismo «lote».") @@ -390,6 +423,9 @@ def correr(rutas, aplicar, silencioso=False): tareas, _ = leer_tareas() por_aplicar = errores = 0 di = (lambda *a: None) if silencioso else print + for m in malos: + di("✗ " + m + "\n → este lote no se aplica hasta corregirlo (un lote nuevo); los demás sí.") + errores += len(malos) if not lotes: di("No hay lotes en " + ", ".join(rutas) + ".") for archivo, lote in lotes: @@ -473,7 +509,7 @@ def correr(rutas, aplicar, silencioso=False): def foto(): tareas, equipo = leer_tareas() - out = {"tomada": ahora(), "api": API, "tareas": tareas, "equipo": equipo} + out = {"tomada": ahora(), "api": API, "tareas": tareas, "equipo": equipo, "sitios": list(SITIOS_INFO)} 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) @@ -492,6 +528,8 @@ def resumen(ruta): 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} + sitios_info = F.get("sitios") or [] + sit_corto = {x["id"]: x.get("corto") or x["id"] for x in sitios_info} 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} @@ -515,6 +553,8 @@ def resumen(ruta): espera = [x for x in (t.get("bloqueada_por") or []) if estado_de.get(x) != "hecho"] if espera: bits.append("espera a " + ", ".join(espera)) + if sit_corto and t.get("sitios"): + bits.append("sitio: " + ", ".join(sit_corto.get(x, x) for x in t["sitios"])) 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") @@ -525,6 +565,14 @@ def resumen(ruta): 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)] + [""] + if sitios_info: + o += ["## Abiertos por sitio", ""] + for x in sitios_info: + lst = [t for t in abiertos if x["id"] in (t.get("sitios") or [])] + bl = sum(1 for t in lst if t.get("estado") == "bloqueado") + o += [f"### {x.get('nombre') or x['id']} ({len(lst)}" + (f", {bl} bloqueado{'s' if bl > 1 else ''}" if bl else "") + ")", ""] + o += [linea(t) for t in sorted(lst, key=orden)] or ["- (nada abierto)"] + o += [""] 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) @@ -565,8 +613,8 @@ def main(): 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.") + print((f"Tablero: {n} cambio(s) de lotes por aplicar" if n else "Tablero: no hay lotes por aplicar") + + (f"; {err} con errores" if err else "") + (" → ./scripts/tablero.sh revisar" if n or err else ".")) return 3 if err else 0 n, err = correr(rutas, aplicar=(a.accion == "aplicar")) return 3 if err else 0 diff --git a/docs/bitacora.md b/docs/bitacora.md index fff10c0..0acf71f 100644 --- a/docs/bitacora.md +++ b/docs/bitacora.md @@ -23,6 +23,8 @@ Arriba, lo que pasó en el tablero de pendientes y en las actas en los últimos ### Octubre de 2026 **22 oct** +: [Juntas: standup y quick wins](tableros/juntas.md): **modo crisis, semáforo en rojo** (sin pago este mes y compras detenidas; TIMSA bloqueado por el trámite del vehículo; EIT espera material de Manzanillo; ICAVE con mal clima). El tablero ahora tiene **sitio** por pendiente (ICAVE, TIMSA, EIT, LCT, Querétaro) con filtro y vista *Por sitio*, y la pista **Gestión**. Las juntas se graban con Gemini y Claude pasa los acuerdos al tablero con un lote. +: [Re-verificación de las 13:30](hardware/auditoria-final-22oct.md#2-que-se-reviso-metodo-ag): Leo rellenó las zonas, guardó y regeneró los paquetes a las 13:00 → **P-1 cerrado** (relleno guardado = fresco, 0.000 mm²; mismo cobre, mismo paquete, BOM/CPL iguales; DRC igual). El veredicto no cambia; falta el commit. Presentación e informe reemitidos con la re-verificación; `verify_mac.sh` + `gcmp_b.py` para repetirla en 5 min después de cualquier cambio. : [Estado de la v6, rev. 1.2: T39, el bloque A por script](hardware/v6-estado-21oct.md#6-t39-22-oct-el-bloque-a-por-script): D34 entre U25 y L22, con el cátodo a 3.52 mm del pin SW y un lazo de 56.5 mm² con C149. In1 e In2 sin pistas bajo la fuente y MH8 libre. DRC: 44 errores (≈ 33 de MH4 y 11 encimes de margen), 0 sin conectar. Falta el push de T39; lote `2026-10-22_t39-v6`. : [Plan de fines de semana de la v6, rev. 1.4](hardware/v6-plan-fines-de-semana.md#31-fuente-u25-bloque-a): el bloque A ya quedó; el sábado 24 empieza con Rig1, el `/24V` sin montar y el bloque B. : [Auditoría final de producción de V5.6 y V5.7](hardware/auditoria-final-22oct.md): **GO al piloto de 5 + 5 hoy** con tres condiciones de pedido (vías *Epoxy Filled & Capped* + rayos X en U6, rotación/polaridad en el visor de JLC, D10 → SMBJ33CA); **NO-GO para las 108/322** hasta que el piloto pase el banco con 0.7.0. Paquetes `HBOX_v5_Rev6_PROD` / `HBOX_v5_Rev7_PROD` verificados contra una regeneración con relleno fresco; 30 hallazgos, 16 riesgos, 6 easy wins; lote `2026-10-22_auditoria-final`. [PDF, 33 pp.](hardware/2026-10-22_Drogheda_Auditoria-Final-Produccion_V5.6-V5.7_ES_v1.0.pdf) · [presentación, 19 láminas](hardware/2026-10-22_Drogheda_Auditoria-Final-Produccion_V5.6-V5.7_Presentacion_ES_v1.0.pdf). diff --git a/docs/datos/proyecto.json b/docs/datos/proyecto.json index 234ac3f..30966ba 100644 --- a/docs/datos/proyecto.json +++ b/docs/datos/proyecto.json @@ -1,9 +1,24 @@ { - "_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»).", + "_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»). Semáforo (22-oct): «semaforo» con color rojo/amarillo/verde, titulo, texto, causas (admite **negritas** y [ligas](/ruta/)), destraba y actualizado; cada sitio y la oficina llevan «estado» (rojo/amarillo/verde), «nota» y «meta».", "actualizado": "2026-10-22", "fase": { - "nombre": "Pre-producción", - "texto": "V5.6 y V5.7 auditadas: GO al piloto de 5 + 5 (pedido hoy); la v6 en layout" + "nombre": "Pre-producción · modo crisis", + "texto": "semáforo rojo: sin pago este mes y compras detenidas; V5.6 y V5.7 auditadas (GO al piloto de 5 + 5); la v6 en layout" + }, + "semaforo": { + "color": "rojo", + "titulo": "Rojo — modo crisis", + "texto": "Sin pago este mes y tres sitios frenados. La meta inmediata es tener 1–2 unidades instaladas por sitio.", + "causas": [ + "**Finanzas:** no hemos recibido pago este mes; operamos muy ajustados y sin compras significativas.", + "**TIMSA (Manzanillo):** bloqueado por un trámite administrativo del vehículo. [Pendientes de TIMSA](/tableros/pendientes/?sitio=timsa)", + "**EIT (Ensenada):** esperando el material que se envió desde Manzanillo. [Pendientes de EIT](/tableros/pendientes/?sitio=eit)", + "**ICAVE (Veracruz):** mal clima; ventanas cortas en campo y fallas que hay que investigar ahí. [Pendientes de ICAVE](/tableros/pendientes/?sitio=icave)", + "**Software:** falta el retranslator de Wialon hacia nuestro servidor para comparar en el monitor el RTK contra el GPS legado, y hay desconexiones súbitas que investigar de inmediato." + ], + "destraba": "Cuando paguemos a proveedores se destraban la mayoría de las tareas.", + "actualizado": "2026-10-22", + "quien": "Leo" }, "pedido_nota": "primero los pilotos: 5 V5.6 y 5 V5.7 (pedido del 22-oct, 3 condiciones), luego 20 v6", "gestion": { @@ -121,8 +136,8 @@ "después del banco" ] ], - "detalle": "Auditoría final del 22-oct (Claude, a petición de Leo): el paquete HBOX_v5_Rev6_PROD es idéntico a una regeneración con relleno fresco (0.000 mm²), DRC solo con los 3 de MOD1, 0 sin conectar, paridad limpia, RS1 Kelvin; BOM y CPL cuadran. GO al piloto de 5 con tres condiciones de pedido: vías Epoxy Filled & Capped + rayos X en U6, rotación/polaridad en el visor de JLC, D10 → SMBJ33CA. NO-GO para las 108 hasta el banco con 0.7.0.", - "siguiente": "Leo: OK a D-22-1 → pedido de 5 + 5 hoy con la lista de §7 (BOM D10, ENIG, vías tapadas, rayos X en U6, visor) → P-1: abrir, B, guardar, commit de kicad/ y production/ → 0.7.0-pre antes de que lleguen (≈ 29-oct) → banco del piloto (8 criterios) → las 108, con SD-15B.", + "detalle": "Auditoría final del 22-oct (Claude, a petición de Leo): el paquete HBOX_v5_Rev6_PROD es idéntico a una regeneración con relleno fresco (0.000 mm²), DRC solo con los 3 de MOD1, 0 sin conectar, paridad limpia, RS1 Kelvin; BOM y CPL cuadran. GO al piloto de 5 con tres condiciones de pedido: vías Epoxy Filled & Capped + rayos X en U6, rotación/polaridad en el visor de JLC, D10 → SMBJ33CA. NO-GO para las 108 hasta el banco con 0.7.0. Re-verificada a las 13:30 sobre los archivos de las 13:00: P-1 cerrado, mismo cobre.", + "siguiente": "Leo: OK a D-22-1 → pedido de 5 + 5 hoy con la lista de §7 (BOM D10, ENIG, vías tapadas, rayos X en U6, visor) → P-1 hecho a las 13:00 (re-verificado): commit de kicad/ y production/ → 0.7.0-pre antes de que lleguen (≈ 29-oct) → banco del piloto (8 criterios) → las 108, con SD-15B.", "quien": "Leo (D-22-1, pedido, P-1) · Claude (auditoría hecha; 0.7.0 con Leo) · Jesús (banco del piloto) · Aldair (pedido e inventario)", "docs": [ [ @@ -207,8 +222,8 @@ "después del banco" ] ], - "detalle": "Auditoría final del 22-oct: paquete HBOX_v5_Rev7_PROD verificado igual que el de la V5.6. La fuente U25 (TPS54360B, 5.32 V / 600 kHz) cubre 9–32 V sin la SD-15B; C149/C150 se quedan (D-21-2 se sostiene) y el timbre se mide en banco. D10 → SMBJ33CA es obligatorio: con el arnés al revés el SMBJ33A abre F2, que es SMD. GO a los 5 prototipos con las mismas condiciones; NO-GO para las 322 hasta el banco.", - "siguiente": "Mismo pedido que la V5.6 (hoy) → P-1 → prototipos en banco: 9/12/24/32 V con 2.8 A, polaridad invertida, U25 < 110 °C y D34 < 100 °C a 32 V, cámara térmica en las vías de entrada a 9 V, ignición a 12 V por ADC, timbre de SW/VIN (D-21-2) → pedido de 327.", + "detalle": "Auditoría final del 22-oct: paquete HBOX_v5_Rev7_PROD verificado igual que el de la V5.6. La fuente U25 (TPS54360B, 5.32 V / 600 kHz) cubre 9–32 V sin la SD-15B; C149/C150 se quedan (D-21-2 se sostiene) y el timbre se mide en banco. D10 → SMBJ33CA es obligatorio: con el arnés al revés el SMBJ33A abre F2, que es SMD. GO a los 5 prototipos con las mismas condiciones; NO-GO para las 322 hasta el banco. Re-verificada a las 13:30: P-1 cerrado, mismo cobre.", + "siguiente": "Mismo pedido que la V5.6 (hoy) → commit (P-1 ya está en el archivo) → prototipos en banco: 9/12/24/32 V con 2.8 A, polaridad invertida, U25 < 110 °C y D34 < 100 °C a 32 V, cámara térmica en las vías de entrada a 9 V, ignición a 12 V por ADC, timbre de SW/VIN (D-21-2) → pedido de 327.", "quien": "Leo (pedido) · Claude (auditoría hecha; procedimiento de banco) · Jesús (prototipos en banco)", "docs": [ [ @@ -397,14 +412,15 @@ } ], "enfoque_inicial": { - "titulo": "Esta semana: pedido del piloto de V5.6 y V5.7 (hoy) y 0.7.0-pre antes de que lleguen; la v6 empieza por D34.", + "titulo": "Modo crisis: destrabar pago, trámites y material para tener 1–2 unidades instaladas por sitio.", "puntos": [ - "Auditoría final (22-oct): GO al piloto de 5 + 5 con tres condiciones de pedido — vías Epoxy Filled & Capped + rayos X en U6, rotación/polaridad en el visor de JLC, D10 → SMBJ33CA. NO-GO para las 108/322 hasta el banco con 0.7.0.", - "Leo: OK a D-22-1, pedido de 5 + 5 (≈ 40 min con la lista de §7), P-1 (abrir, B, guardar, commit) y OK a D-21-2 y D-21-3.", - "Firmware: board_v56.h ya existe; 0.7.0-pre (módem, NTC, LFP, ignición por ADC) en 1 día, antes de que lleguen las tarjetas (≈ 29-oct).", - "v6: T25, T28 y T38 van en el mismo push. Michelle empieza por D34 junto a U25 y por dejar libre MH8." + "**Finanzas:** sin pago recibido este mes, las compras están detenidas; al pagar a proveedores se destraban la mayoría de las tareas.", + "**Sitios:** TIMSA bloqueado por el trámite del vehículo; EIT espera el material de Manzanillo; ICAVE con mal clima y fallas por investigar en campo; LCT por confirmar.", + "**Software:** retranslator de Wialon hacia nuestro servidor (RTK contra GPS legado) y desconexiones súbitas: atención inmediata.", + "**Piloto V5.6 + V5.7:** GO de la auditoría final del 22-oct; 0.7.0-pre antes de que lleguen las tarjetas.", + "**Juntas:** standup y revisión de quick wins grabadas con Gemini; Claude pasa los acuerdos al tablero con un lote ([cómo](/tableros/juntas/))." ], - "nota": "Texto inicial tomado de la [auditoría final del 22-oct](/hardware/auditoria-final-22oct/) y del [estado de la v6](/hardware/v6-estado-21oct/). Leo y Adriana lo cambian con «Editar»." + "nota": "Texto inicial del 22-oct (modo crisis, Leo) y de la [auditoría final](/hardware/auditoria-final-22oct/). Leo y Adriana lo cambian con «Editar»." }, "sitios": [ { @@ -414,7 +430,9 @@ "x": 68.27, "y": 73.55, "etiqueta": "der", - "nota": "se compara con el CalAmp" + "nota": "mal clima: ventanas cortas en campo y fallas por investigar · se compara con el CalAmp", + "estado": "amarillo", + "meta": "1–2 unidades instaladas" }, { "id": "lzc", @@ -422,7 +440,10 @@ "ciudad": "Lázaro Cárdenas", "x": 49.6, "y": 80.34, - "etiqueta": "der" + "etiqueta": "der", + "estado": "amarillo", + "nota": "estado por confirmar en la junta del 22-oct", + "meta": "1–2 unidades instaladas" }, { "id": "eit", @@ -430,7 +451,10 @@ "ciudad": "Ensenada", "x": 9.34, "y": 7.92, - "etiqueta": "der" + "etiqueta": "der", + "estado": "rojo", + "nota": "esperando el material que se envió desde Manzanillo", + "meta": "1–2 unidades instaladas" }, { "id": "timsa", @@ -438,13 +462,19 @@ "ciudad": "Manzanillo", "x": 43.06, "y": 74.67, - "etiqueta": "izq" + "etiqueta": "izq", + "estado": "rojo", + "nota": "bloqueado por un trámite administrativo del vehículo", + "meta": "1–2 unidades instaladas" } ], "oficina": { "nombre": "Helgen", "ciudad": "Querétaro", "x": 55.08, - "y": 67.1 + "y": 67.1, + "id": "qro", + "estado": "amarillo", + "nota": "sin compras hasta recibir el pago" } -} \ No newline at end of file +} diff --git a/docs/hardware/index.md b/docs/hardware/index.md index 130b0ff..f410b61 100644 --- a/docs/hardware/index.md +++ b/docs/hardware/index.md @@ -10,7 +10,7 @@ Estado de cada versión de la EinarBoard al **22-oct-2026**, con sus documentos - **V5.6 y V5.7 — paquetes verificados.** `HBOX_v5_Rev6_PROD` y `HBOX_v5_Rev7_PROD` son idénticos a una regeneración con relleno fresco (0.000 mm² de diferencia); DRC solo con los 3 de MOD1, 0 sin conectar, paridad solo PWR_FLAG, RS1 Kelvin en las dos; BOM y CPL cuadran con el PCB. Todos los puertos funcionan en hardware; la V5.7 va de 9 a 32 V sin la SD-15B; el apagón del módem de la V5.5 desapareció por diseño. - **Condiciones del pedido (no tocan el cobre):** (1) vías *Epoxy Filled & Capped* + rayos X en U6 (3 vías dentro de la pasta del QFN); (2) rotación y polaridad en el visor de JLC (U6, U7, U25, U26, L22, Q7, U16, D34, D10, electrolíticos: huellas easyeda2kicad nunca fabricadas); (3) BOM: D10 SMBJ33A → SMBJ33CA. - **NO-GO para las 108 / 322** hasta que el piloto pase el banco con el firmware 0.7.0: hoy ningún firmware enciende el módem de estas tarjetas. `board_v56.h` ya existe; 0.7.0-pre (módem, NTC, LFP, ignición por ADC) es 1 día y va antes de que lleguen las tarjetas. - - **P-1, Leo, 5 min:** los `.kicad_pcb` del repo traen el relleno viejo (el Gerber sí lleva el fresco): abrir, B, guardar, commit. Regla D-22-3: todo script T que toque cobre termina con rellenado en el GUI. + - **P-1 cerrado a las 13:00:** Leo rellenó, guardó y regeneró los paquetes; re-verificado a las 13:30 (relleno guardado = fresco, 0.000 mm²; mismo cobre y mismo paquete que en la mañana; BOM/CPL iguales). Falta el commit de `kicad/` y `production/`. Regla D-22-3: todo script T que toque cobre termina con rellenado en el GUI ([re-verificación](auditoria-final-22oct.md#2-que-se-reviso-metodo-ag)). - **v6:** T39 (22-oct, Claude) hizo el bloque A con la propuesta α. D34 quedó entre U25 y L22, In1/In2 sin pistas bajo la fuente y MH8 libre. DRC: 44 errores (≈ 33 de MH4 y 11 encimes de margen), 0 sin conectar. Falta el push de T39 y MH4 (T40) → [estado de la v6, §6](v6-estado-21oct.md#6-t39-22-oct-el-bloque-a-por-script). [**▶ Auditoría final (veredicto, hallazgos, riesgos)**](auditoria-final-22oct.md) · [PDF completo, 33 pp.](2026-10-22_Drogheda_Auditoria-Final-Produccion_V5.6-V5.7_ES_v1.0.pdf) · [presentación, 19 láminas](2026-10-22_Drogheda_Auditoria-Final-Produccion_V5.6-V5.7_Presentacion_ES_v1.0.pdf) · [protocolo](auditoria-final-protocolo.md). Antes: [auditoría del 21-oct (pasadas 4 a 6)](v56-v57-auditoria-21oct.md) · [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.4)](v6-plan-fines-de-semana.md). @@ -18,7 +18,7 @@ Estado de cada versión de la EinarBoard al **22-oct-2026**, con sus documentos | Tarjeta | Estado | Pedido | Proyecto KiCad | Firmware | |---|---|---|---|---| | V5.5 | 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 | -| V5.6 | **Auditoría final del 22-oct: GO al piloto** con tres condiciones de pedido (vías tapadas + rayos X en U6, visor de JLC, D10 → SMBJ33CA). Paquete `HBOX_v5_Rev6_PROD` verificado = cobre con relleno fresco; DRC y paridad limpios. Pendiente P-1 (relleno en el repo, 5 min) ([auditoría final](auditoria-final-22oct.md)). | 113 (piloto de 5 primero, se pide hoy) | `hbox-watchdog-v5` | 0.7.0: `board_v56.h` listo; 0.7.0-pre por escribir (1 día) | +| V5.6 | **Auditoría final del 22-oct: GO al piloto** con tres condiciones de pedido (vías tapadas + rayos X en U6, visor de JLC, D10 → SMBJ33CA). Paquete `HBOX_v5_Rev6_PROD` verificado = cobre con relleno fresco; DRC y paridad limpios. P-1 cerrado a las 13:00 (relleno fresco en el repo, re-verificado; falta el commit) ([auditoría final](auditoria-final-22oct.md)). | 113 (piloto de 5 primero, se pide hoy) | `hbox-watchdog-v5` | 0.7.0: `board_v56.h` listo; 0.7.0-pre por escribir (1 día) | | V5.7 | **Auditoría final del 22-oct: GO a los 5 prototipos** con las mismas condiciones. Fuente U25 a 5.32 V / 600 kHz: 9–32 V sin la SD-15B; C149/C150 se quedan (D-21-2) y el timbre se mide en banco; D10 → SMBJ33CA es obligatorio aquí (con el arnés al revés abre F2, que es SMD) ([auditoría final](auditoria-final-22oct.md)). | 327 (5 prototipos primero, se piden hoy) | `hbox-watchdog-v57` (convive con v5) | el mismo 0.7.0 (mismos pines de MCU) | | v6 | Revisada el 22-oct (`b74b96f` + T39): **NO LISTA**. T28 cerró 2 de los 8 bloqueantes de la [auditoría v6](v6-auditoria-produccion-16oct.md), T25 regresó los barrenos, T38 corrigió courtyards, MH6 y el `/3V3` bajo los barrenos, y **T39 hizo el bloque A** (D34 entre U25 y L22, MH8 libre). Faltan MH4 y los bloques B a H ([estado, §6](v6-estado-21oct.md#6-t39-22-oct-el-bloque-a-por-script)). 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 | | V4 | En prueba y campo. Sin producción nueva. | — | `hbox-watchdog-v4` | `hbox-tarjetaV4_0.6.0` CAMPO | diff --git a/docs/index.md b/docs/index.md index bcdb902..5a17366 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,7 +9,7 @@ hide: