diff --git a/DESPLIEGUE.md b/DESPLIEGUE.md index 6ba6fe5..c54feca 100644 --- a/DESPLIEGUE.md +++ b/DESPLIEGUE.md @@ -54,7 +54,7 @@ El backend la deduce sola de `ACTAS_DIR`; si prefieres fijarla, agrega a Environment=ADJUNTOS_DIR=/srv/projects/drogheda/adjuntos Environment=ADJUNTO_MAX_MB=8 Environment=ARCHIVO_MAX_MB=20 -Environment=ACTAS_REPETIBLES=diagnostico-campo,instalacion-campo +Environment=ACTAS_REPETIBLES=diagnostico-campo,diagnostico-banco,instalacion-campo,reflasheo,dano,envio,recepcion ``` ### 4 · nginx — subida de fotos e identidad del técnico (una sola vez) diff --git a/backend/actas-hbox.service b/backend/actas-hbox.service index c55df89..f88b683 100644 --- a/backend/actas-hbox.service +++ b/backend/actas-hbox.service @@ -10,7 +10,7 @@ Environment=ACTAS_DIR=/srv/projects/drogheda/actas Environment=ADJUNTOS_DIR=/srv/projects/drogheda/adjuntos Environment=ADJUNTO_MAX_MB=8 Environment=ARCHIVO_MAX_MB=20 -Environment=ACTAS_REPETIBLES=diagnostico-campo,instalacion-campo +Environment=ACTAS_REPETIBLES=diagnostico-campo,diagnostico-banco,instalacion-campo,reflasheo,dano,envio,recepcion Environment=ACTAS_ADMINS=leo Environment=HTPASSWD_FILE=/etc/nginx/hbox-docs.htpasswd Environment=PASS_MIN=8 diff --git a/backend/app.py b/backend/app.py index 9483fa4..6bc0120 100644 --- a/backend/app.py +++ b/backend/app.py @@ -36,7 +36,7 @@ ADMINS = {u.strip().lower() for u in os.environ.get("ACTAS_ADMINS", "leo").split TIPOS_UNICOS = {"ensamble"} # tipos en los que un componente solo puede usarse en un acta real # Tipos REPETIBLES: una misma serie puede tener varias actas (cada visita a campo es un evento nuevo). TIPOS_REPETIBLES = {t.strip() for t in os.environ.get( - "ACTAS_REPETIBLES", "diagnostico-campo,diagnostico-banco,instalacion-campo,reflasheo").split(",") if t.strip()} + "ACTAS_REPETIBLES", "diagnostico-campo,diagnostico-banco,instalacion-campo,reflasheo,dano,envio,recepcion").split(",") if t.strip()} # Subida de fotos MAX_MB = int(os.environ.get("ADJUNTO_MAX_MB", "8")) @@ -193,9 +193,11 @@ def crear(): os.makedirs(carpeta, exist_ok=True) archivo = f"{serie}_{iso.replace(':', '-')}.json" if es_repetible(tipo): - # cada visita es un registro propio: sufijo único para que dos envíos - # en el mismo segundo (doble toque) no se pisen - archivo = f"{serie}_{iso.replace(':', '-')}_{uuid.uuid4().hex[:6]}.json" + # cada visita es un registro propio. El nombre lleva microsegundos para que el + # orden alfabético SEA el orden cronológico (dos envíos en el mismo segundo no + # se pisan ni se desordenan), y un sufijo aleatorio por si acaso. + micro = datetime.datetime.now().strftime("%f") + archivo = f"{serie}_{iso.replace(':', '-')}-{micro}_{uuid.uuid4().hex[:4]}.json" with open(os.path.join(carpeta, archivo), "w", encoding="utf-8") as f: json.dump(d, f, ensure_ascii=False, indent=2) @@ -290,7 +292,7 @@ def listar(): "resultado": d.get("resultado"), "recibido": d.get("_recibido")} if isinstance(d.get("componentes"), dict): fila["componentes"] = d["componentes"] - for k in ("clasificacion", "accion", "motivo", "maquina", "terminal", "unidad"): + for k in ("clasificacion", "accion", "motivo", "maquina", "terminal", "unidad", "resumen"): if d.get(k): fila[k] = d[k] if d.get("archivos"): @@ -631,7 +633,9 @@ def expediente(): actas.sort(key=_clave, reverse=True) relacionadas.sort(key=_clave, reverse=True) fotos, logs = _listar_adjuntos(serie) + marca = estado_serie(serie) return jsonify(ok=True, serie=serie, actas=actas, relacionadas=relacionadas, + marca=marca["marca"], marca_acta=marca["acta"], fotos=fotos, logs=logs, total=len(actas), total_fotos=len(fotos), total_logs=len(logs)) @@ -654,5 +658,64 @@ def series(): for k, v in sorted(vistas.items())]) +# ------------------------------------------------- estado de una serie (daños abiertos) +# Un acta de tipo 'dano' MARCA la serie. La marca se levanta con otra acta 'dano' cuyo +# campo 'accion' empiece por "Reparada" o "Baja": lo último que se dijo, manda. +def estado_serie(serie): + """Devuelve {'marca': None|'DANADA'|'BAJA', 'acta': {...}|None} para una serie.""" + carpeta = os.path.join(BASE, "dano") + ultimo = None + # listar_serie() ordena por nombre = cronológico (los repetibles llevan microsegundos). + # Manda la ÚLTIMA acta registrada, no la de fecha de visita más reciente: si alguien + # captura hoy un daño de la semana pasada, hoy es cuando la serie se marca. + for fn in listar_serie("dano", serie) if os.path.isdir(carpeta) else []: + try: + with open(os.path.join(carpeta, fn), encoding="utf-8") as f: + d = json.load(f) + except Exception: + continue + if "PRUEBA" in serie.upper(): + continue + ultimo = {"archivo": fn, "fecha": d.get("fecha"), "recibido": d.get("_recibido"), + "tecnico": d.get("tecnico"), "clasificacion": d.get("clasificacion"), + "accion": d.get("accion"), "resumen": d.get("resumen")} + if not ultimo: + return {"marca": None, "acta": None} + acc = str(ultimo.get("accion") or "") + if acc.startswith("Baja"): + return {"marca": "BAJA", "acta": ultimo} + if acc.startswith("Reparada"): + return {"marca": None, "acta": ultimo} + return {"marca": "DANADA", "acta": ultimo} + + +@app.get("/api/estado") +def estado(): + """Marca de daño/baja de una serie — lo consultan TODOS los formularios al elegirla.""" + serie = slug(request.args.get("serie"), 24) + if not serie: + return jsonify(ok=False, error="falta serie"), 400 + e = estado_serie(serie) + return jsonify(ok=True, serie=serie, marca=e["marca"], acta=e["acta"]) + + +@app.get("/api/danos") +def danos(): + """Todas las series con marca vigente (dañada o baja), para el tablero.""" + out = [] + carpeta = os.path.join(BASE, "dano") + if os.path.isdir(carpeta): + vistas = set() + for fn in sorted(os.listdir(carpeta)): + if fn.endswith(".json"): + ser = fn.split("_2")[0] if "_2" in fn else fn[:-5] + vistas.add(ser) + for ser in sorted(vistas): + e = estado_serie(ser) + if e["marca"]: + out.append({"serie": ser, "marca": e["marca"], "acta": e["acta"]}) + return jsonify(ok=True, total=len(out), series=out) + + if __name__ == "__main__": app.run(host="127.0.0.1", port=int(os.environ.get("PORT", "8081"))) diff --git a/docs/actas/index.md b/docs/actas/index.md index 2b6590e..456feef 100644 --- a/docs/actas/index.md +++ b/docs/actas/index.md @@ -49,6 +49,12 @@ visitas anteriores antes de empezar. Permite **subir fotos** desde el teléfono. Procedimiento: [Diagnóstico en campo](../diagnostico/procedimiento-diagnostico-campo.md) · Seguimiento: [Tablero de campo](../diagnostico/tablero-campo.html). +**[▶ Acta de daño o incidente](../formularios/dano.html)** — cualquier serie. **Marca** la serie: todos los +formularios avisan al elegirla. Se levanta con «Reparada»; «Baja» la hace permanente. + +**[▶ Acta de envío](../formularios/envio.html)** · **[▶ Acta de recepción](../formularios/recepcion.html)** — el +tramo entre el banco y el puerto. Mapa completo: [Ciclo de vida](../procedimientos/ciclo-de-vida.md). + ## Cómo se usan 1. Abre el formulario y **elige el número de serie** (o los módulos, en integración y ensamble). diff --git a/docs/descargas/SHA256SUMS.txt b/docs/descargas/SHA256SUMS.txt index 8da6d06..0c50b1e 100644 --- a/docs/descargas/SHA256SUMS.txt +++ b/docs/descargas/SHA256SUMS.txt @@ -1,4 +1,4 @@ -035ebeb348461765f099341603a9e99b94512f8477509efb78aa7caf953f4c2f hbox-fw-v3_3.0.4.bin -a68a87ff1f6b34a8284e9e5e32c95da7bb8d8adc071c5fd64412a3e744e46b28 hbox-fw-v4_4.0.0.bin +035ebeb348461765f099341603a9e99b94512f8477509efb78aa7caf953f4c2f hbox-tarjetaV3_3.0.4.bin +a68a87ff1f6b34a8284e9e5e32c95da7bb8d8adc071c5fd64412a3e744e46b28 hbox-tarjetaV3_4.0.0.bin a8de74b3d7768e9d5656ae0bff05475445c0cd2599ecfdc4ab081e517ed3d6ef hbox-v5-bringup_0.4.0.bin e1f351084e803306c376989b284ca0f9f9f1a3445e59494f8a5ff7a9652ad44c hbox-v5-bringup_0.5.1.bin diff --git a/docs/descargas/hbox-fw-v3_3.0.4.bin b/docs/descargas/hbox-tarjetaV3_3.0.4.bin similarity index 100% rename from docs/descargas/hbox-fw-v3_3.0.4.bin rename to docs/descargas/hbox-tarjetaV3_3.0.4.bin diff --git a/docs/descargas/hbox-fw-v4_4.0.0.bin b/docs/descargas/hbox-tarjetaV3_4.0.0.bin similarity index 100% rename from docs/descargas/hbox-fw-v4_4.0.0.bin rename to docs/descargas/hbox-tarjetaV3_4.0.0.bin diff --git a/docs/descargas/index.md b/docs/descargas/index.md index b2fce1c..63804ba 100644 --- a/docs/descargas/index.md +++ b/docs/descargas/index.md @@ -21,19 +21,31 @@ Cada carga se registra en el **[acta de reflasheo](../formularios/reflasheo.html Un binario incompleto o de otra generación puede dejar la tarjeta inservible. `shasum -a 256 archivo` (macOS) · `sha256sum archivo` (Linux) · `certutil -hashfile archivo SHA256` (Windows). -## Firmware del HBOX (por generación de tarjeta) +## Firmware del HBOX — qué binario va en qué tarjeta -| Generación | Archivo | Para qué sirve | -|---|---|---| -| **V3** — flota desplegada (RTK-003, RTK-006, RTK-014) | ⚠ [`hbox-fw-v3_3.0.4.bin`](hbox-fw-v3_3.0.4.bin) — compilado, **sin prueba de banco** | Mismo comportamiento que el 3.0.3 de campo, compilado con el core 3.x | -| **V4** | ⚠ [`hbox-fw-v4_4.0.0.bin`](hbox-fw-v4_4.0.0.bin) — compilado, **sin prueba de banco** | Telemetría en banda: el `$PHLG` y el `$PHEV` llegan al servidor por el módem | -| **V5** — EinarBoard v5 | ✅ `hbox-v5-bringup_0.5.1.bin` (abajo) | Bring-up de tarjeta en banco | +**La versión del firmware no dice nada de la tarjeta.** `0.3.0` no es "para V3" ni `0.4.0` "para V4": +son versiones viejas del firmware de **V5**. El binario lleva la tarjeta en el nombre y eso es lo que manda. + +| Tarjeta | MCU | Firmware (código) | Binario vigente | +|---|---|---|---| +| **V3** — EinarBoard 1.0, las 3 unidades en campo | Blackpill en zócalo (F411CE) | `blackpillrtkcode_v3` · `_v4` | [`hbox-tarjetaV3_3.0.4.bin`](hbox-tarjetaV3_3.0.4.bin) ⚠ sin prueba de banco | +| **V3** con telemetría en banda | Blackpill en zócalo | `blackpillrtkcode_v4` (4.0.0) | [`hbox-tarjetaV3_4.0.0.bin`](hbox-tarjetaV3_4.0.0.bin) ⚠ sin prueba de banco | +| **V4** — `hbox-watchdog-v4` | Blackpill en zócalo + PCF8574 · W5500 · SC16IS752 | **ninguno** | — | +| **V5** — `hbox-watchdog-v5`, lote de 15 | STM32F411RET6 soldado | `hbox_v5_bringup` | [`hbox-v5-bringup_0.5.1.bin`](hbox-v5-bringup_0.5.1.bin) ✅ **en despliegue** | + +!!! danger "Lo que NO se puede cruzar" + - Un binario de **V5** en una **V3 o V4**: es otro MCU (soldado vs Blackpill), otro reloj y otro mapa + de pines. Lo más probable es que ni enumere por USB. + - `blackpillrtkcode` en una **V4**: maneja los LEDs de carga en PB12–PB15, que en la V4 son el SPI + del W5500. La V4 **no tiene firmware todavía**; hasta que lo tenga, no se flashea con nada. + - `hbox-v5-bringup_0.4.0` (o 0.3.0) en cualquier tarjeta: son versiones **superadas** de V5, sin + watchdog, ventilador ni recuperación de enlace. Solo se conservan por trazabilidad. ### Firmware de bring-up — EinarBoard v5 | Archivo | Versión | SHA-256 | Uso | |---|---|---|---| -| [**hbox-v5-bringup_0.4.0.bin**](hbox-v5-bringup_0.4.0.bin) | **0.4.0** (2026-09-17) — **campo, sin telemetría en banda** (probado en unidad) | `a8de74b3d7768e9d…` ([SHA256SUMS](SHA256SUMS.txt)) | +| [`hbox-v5-bringup_0.4.0.bin`](hbox-v5-bringup_0.4.0.bin) | 0.4.0 (2026-09-17) — **superada**, sin telemetría en banda | `a8de74b3d7768e9d…` ([SHA256SUMS](SHA256SUMS.txt)) | | [**hbox-v5-bringup_0.5.1.bin**](hbox-v5-bringup_0.5.1.bin) | **0.5.1** (2026-09-18) — **campo + Modo B / telemetría — EN PRUEBA** (reportado: etapa RTK no enciende) | `e1f351084e803306…` | Sección 5 del [procedimiento de prueba y flasheo v5](../procedimientos/v5-prueba-y-flasheo.md) y sección 5 del [ensamble](../procedimientos/v5-ensamble.md) (modo de banco `P` / `n`) | Flasheo por DFU en **J11** (mantener **SW2**, pulsar y soltar **SW1**, soltar SW2): @@ -44,14 +56,13 @@ dfu-util -a 0 -s 0x08000000:leave -D hbox-v5-bringup_0.5.1.bin o con **STM32CubeProgrammer** → USB → Connect → Download del `.bin` en `0x08000000`. -!!! danger "18-sep, tarde: A/B entre 0.4.0 y 0.5.1" - Se reportó que con **0.5.1** la etapa RTK no enciende y con **0.4.0** sí. Hasta cerrar el diagnóstico: - **embarcar con 0.4.0**. Para diagnosticar 0.5.1 en UNA unidad: consola en J11, tap a SW1, guardar todo desde el banner - hasta 2 min después; luego teclear `s`, `v` y `B` (bloquea Modo B) y anotar si la etapa enciende con `B`. - Mandar el log completo — el diagnóstico sale de ahí, no del LED. +!!! success "A/B 0.4.0 vs 0.5.1 — cerrado el 19-sep" + El 18-sep por la tarde se reportó que con 0.5.1 la etapa RTK no encendía. **Resuelto:** 0.5.1 ensambla, + enciende y reporta telemetría completa en el monitor desplegado. **Se embarca con 0.5.1.** `0.4.0` + sigue descargable solo como referencia de versiones anteriores. !!! note "Tarjetas ya flasheadas con cualquier versión anterior" - Re-flashear con 0.4.0 (o 0.5.1 solo para la prueba A/B) por el mismo procedimiento DFU. **La primera línea de la consola tras reset dice la versión** + Re-flashear con **0.5.1** por el mismo procedimiento DFU. **La primera línea de la consola tras reset dice la versión** (`hbox-v5-bringup 0.5.1 built …`): si la línea `STAT` no termina en `stack=…`, la tarjeta sigue con 0.1.1. !!! tip "Consola 0.5.1" @@ -83,21 +94,19 @@ o con **STM32CubeProgrammer** → USB → Connect → Download del `.bin` en `0x Notas de compilación y cambios: [README del firmware de bring-up](hbox-v5-bringup-README.md). -### Firmware de campo — tarjetas V3 y V4 (detalle) +### Firmware de campo — tarjeta V3 (detalle) !!! danger "Estos binarios todavía NO se flashean en una unidad de campo" Compilaron limpio el 19-sep y pasan el simulador, pero **ninguno ha corrido sobre una EinarBoard 1.0 real**. Hasta que ingeniería los pruebe en banco (RTK-003 o RTK-006) y quite - este aviso, solo se usan en la mesa de ingeniería. Esto aplica sobre todo a **V4**, que pone + este aviso, solo se usan en la mesa de ingeniería. Esto aplica sobre todo al **4.0.0**, que pone el MCU en línea con el enlace que lleva las correcciones RTK. -!!! warning "V4 y el A/B pendiente de 0.5.1" - El fallo de **0.5.1** en v5 (la etapa RTK no enciende) tiene como sospechoso principal al - **Modo B**, y **v4 activa el Modo B por omisión**. Hasta que el A/B de v5 dé una causa, la - prueba de banco de v4 se hace **en par**: el binario publicado (Modo B encendido) y uno - compilado sin Modo B, sobre la misma unidad, comparando si la etapa enciende y qué dice - `v5min` en el momento en que K1 se energiza — una caída ahí sería la bobina de K1 hundiendo - el riel hasta el PFI del LTC4040. +!!! tip "Prueba de banco del 4.0.0: en par" + El 4.0.0 activa el Modo B por omisión. Aunque el A/B de V5 ya cerró bien, la primera prueba en + una V3 real se hace **en par**: el binario publicado y uno compilado con `-DENABLE_MODOB=0`, + sobre la misma unidad. Si el fix RTK cae al energizar K1, o `v5min` se hunde en ese instante, + el sospechoso es la bobina de K1 llevando el riel al PFI del LTC4040. ```bash arduino-cli compile --fqbn "$FQBN" --build-property "compiler.cpp.extra_flags=-DENABLE_MODOB=0" \ --output-dir build/v4-sinB blackpillrtkcode_v4 @@ -108,8 +117,8 @@ bring-up del v5. Se usa en el [reflasheo en taller](../procedimientos/reflasheo- | Generación | Archivo | Estado | |---|---|---| -| **V3** — flota desplegada | [`hbox-fw-v3_3.0.4.bin`](hbox-fw-v3_3.0.4.bin) · 62 464 B · `035ebeb3…` | ⚠ **compilado 19-sep, falta prueba de banco** | -| **V4** | [`hbox-fw-v4_4.0.0.bin`](hbox-fw-v4_4.0.0.bin) · 64 352 B · `a68a87ff…` | ⚠ **compilado 19-sep, falta prueba de banco**. Validado en el simulador de host | +| **V3** — flota desplegada | [`hbox-tarjetaV3_3.0.4.bin`](hbox-tarjetaV3_3.0.4.bin) · 62 464 B · `035ebeb3…` | ⚠ compilado 19-sep, falta prueba de banco | +| **V3** con telemetría | [`hbox-tarjetaV3_4.0.0.bin`](hbox-tarjetaV3_4.0.0.bin) · 64 352 B · `a68a87ff…` | ⚠ compilado 19-sep, falta prueba de banco. El «4» es la versión del firmware, **no** la tarjeta | !!! danger "Mientras diga «pendiente», no hay binario aprobado" No flashees una unidad V3 o V4 con un archivo que no esté publicado aquí con su SHA-256. Los @@ -182,7 +191,7 @@ binario mal compilado deja unidades muertas en el patio. - [ ] Compilado con el `TARGET_` correcto para esa generación de tarjeta. - [ ] **Probado en banco sobre hardware real**, no solo compilado sin errores. - [ ] Verificada la polaridad del control de carga sobre la tarjeta. -- [ ] Nombre con versión: `hbox-fw-v3_X.Y.Z.bin`. +- [ ] Nombre con **tarjeta y versión**: `hbox-tarjetaV3_X.Y.Z.bin`. La versión del firmware nunca se infiere de la tarjeta ni al revés. - [ ] Su línea en `SHA256SUMS.txt`. - [ ] Anotado en el `CHANGELOG.md` del repositorio de firmware, con fecha y quién lo probó. - [ ] Actualizada la tabla de esta página. diff --git a/docs/diagnostico/tablero-campo.html b/docs/diagnostico/tablero-campo.html index 519be9e..f949188 100644 --- a/docs/diagnostico/tablero-campo.html +++ b/docs/diagnostico/tablero-campo.html @@ -138,6 +138,7 @@ function render(actas){ title:'Ver el expediente completo',text:u.serie})), el('div',{class:'m',text:(u.maquina||'')+(u.nota?' · '+u.nota:'')}))); const [cls,txt] = vs.length ? pill(vs[0]) : ['pend','Sin diagnóstico']; + if(DANOS[u.serie]) cab.append(el('span',{class:'pill falla',text:DANOS[u.serie].marca==='BAJA'?'⛔ BAJA':'⚠ DAÑADA'})); cab.append(el('span',{class:'pill '+cls,text:txt})); box.append(cab); @@ -178,9 +179,11 @@ function render(actas){ }); } +let DANOS = {}; async function cargar(){ document.getElementById('meta').textContent = 'Cargando…'; try{ + try{ const d=await (await fetch('/api/danos',{cache:'no-store'})).json(); DANOS={}; (d.series||[]).forEach(x=>DANOS[x.serie]=x); }catch(e){} const r = await fetch('/api/actas',{cache:'no-store'}); const j = await r.json(); render(j.actas||[]); diff --git a/docs/formularios/acta.css b/docs/formularios/acta.css index 28552ee..732e2f4 100644 --- a/docs/formularios/acta.css +++ b/docs/formularios/acta.css @@ -129,3 +129,9 @@ button.mini-btn{background:#fff;border:1px solid var(--linea);border-radius:6px; .mini.doc .est{font-size:11.5px;flex:0 0 auto} input[type=date]{width:100%;padding:11px 12px;border:1px solid var(--linea);border-radius:9px; font-size:16px;background:#fff;color:var(--tinta)} + +/* marca de daño / baja: se muestra en cualquier acta al elegir la serie */ +.marca{display:none;margin:0 0 14px;padding:12px 14px;border-radius:10px;font-size:14.5px;line-height:1.45} +.marca.danada{display:block;background:var(--aviso-bg);border:2px solid var(--aviso);color:#7a4a00} +.marca.baja{display:block;background:var(--falla-bg);border:2px solid var(--falla);color:#8a1a1a} +.marca a{color:inherit;font-weight:700} diff --git a/docs/formularios/acta.js b/docs/formularios/acta.js index 79a38c7..a61394d 100644 --- a/docs/formularios/acta.js +++ b/docs/formularios/acta.js @@ -131,6 +131,37 @@ wrap.append(banner); const hist = $('div', { class: 'hist' }); if (cfg.repetible) wrap.append(hist); + // Marca de daño: la consultan TODOS los formularios (banco, campo, produccion). + // Una tarjeta con corto no debe poder entrar a un ensamble sin que alguien lo vea. + const marcaBox = $('div', { class: 'marca' }); + wrap.append(marcaBox); + async function consultarMarca() { + marcaBox.innerHTML = ''; marcaBox.className = 'marca'; + if (cfg.tipo === 'dano') return; + // En un acta compuesta (ensamble, integración) se revisa CADA módulo elegido, no solo + // la unidad calculada: una tarjeta con corto no debe poder entrar a un ensamble. + const series = compuesta ? Object.values(seleccion()).filter(v => v) : [serieActual()]; + const aRevisar = series.filter(v => v && !esPrueba(v)); + if (!aRevisar.length) return; + const avisos = []; + await Promise.all(aRevisar.map(async s => { + try { + const j = await (await fetch(`/api/estado?serie=${encodeURIComponent(s)}`, { cache: 'no-store' })).json(); + if (j.marca) avisos.push({ s, j }); + } catch (e) {} + })); + if (!avisos.length) return; + const baja = avisos.some(x => x.j.marca === 'BAJA'); + marcaBox.className = 'marca ' + (baja ? 'baja' : 'danada'); + marcaBox.innerHTML = avisos.map(({ s, j }) => { + const a = j.acta || {}; + return (j.marca === 'BAJA' ? '⛔ ' + s + ' está dada de BAJA' : '⚠ ' + s + ' está marcada como DAÑADA') + + (a.fecha ? ' desde el ' + a.fecha : '') + (a.clasificacion ? ' — ' + a.clasificacion : '') + + (a.resumen ? '
' + a.resumen : '') + + '
Ver su expediente' + + (j.marca === 'BAJA' ? '. No debe usarse ni instalarse.' : '. Revisa el acta de daño antes de continuar.'); + }).join('
'); + } // selección actual de componentes -> {clave: valor} function seleccion() { const s = {}; for (const k in compSel) s[k] = compSel[k].value; return s; } @@ -236,11 +267,13 @@ const firma = $('section', { class: 'paso firma' }); firma.append($('h2', { text: 'Cierre y firma' })); firma.append($('p', { class: 'sub', text: 'Al enviar, quedas registrado como responsable de esta prueba.' })); + // cfg.resultadoFijo = 'REGISTRADO': actas que no aprueban ni reprueban nada (daños, envíos). + // Se oculta el selector y el valor se manda fijo, para que el servidor siga teniendo 'resultado'. const res = $('div', { class: 'resultado' }); const rOK = $('label', { class: 'ok' }); rOK.append($('input', { type: 'radio', name: 'resultado', value: 'OK' }), document.createTextNode(cfg.textoOK || 'APROBADA')); const rNO = $('label', { class: 'falla' }); rNO.append($('input', { type: 'radio', name: 'resultado', value: 'FALLA' }), document.createTextNode('FALLA')); res.append(rOK, rNO); - firma.append($('label', { text: 'Resultado final' }), res); + if (!cfg.resultadoFijo) firma.append($('label', { text: 'Resultado final' }), res); const conf = $('label', { class: 'chk', style: 'margin-top:14px' }); conf.append($('input', { type: 'checkbox', id: 'f_conforme' })); conf.append($('span', { class: 'txt', html: 'Confirmo que seguí el procedimiento completo y que los datos anotados son verídicos.' })); @@ -545,7 +578,7 @@ }); const rr = wrap.querySelector('input[name=resultado]:checked'); const d = { tipo: cfg.tipo, serie: serieActual(), tecnico: tecnico.value, - conforme: conforme().checked, resultado: rr ? rr.value : '', respuestas: r }; + conforme: conforme().checked, resultado: cfg.resultadoFijo || (rr ? rr.value : ''), respuestas: r }; if (compuesta) { d.componentes = seleccion(); d.verificacion = Object.assign({}, verif); } if (fotos.length) d.fotos = fotos; if (archivos.length) d.archivos = archivos; @@ -587,6 +620,7 @@ blankFields(); showEditable(); if (compuesta) { if (usados === null) await cargarUsados(); await verificar(); } if (cfg.repetible) cargarHistorial(); + consultarMarca(); if (!s) { estado.textContent = compuesta ? 'Elige los tres módulos' : (flota ? 'Elige la unidad' : 'Elige el número de serie'); aplicarCondiciones(); return; @@ -637,6 +671,8 @@ if (locked) return; const d = snapshot(); if (!d.serie) return fail(compuesta ? 'Elige los tres módulos.' : 'Elige el número de serie.'); + if (compuesta && cfg.exigirAprobados && !esPrueba(d.serie) && marcaBox.classList.contains('baja')) + return fail('Uno de los módulos está dado de BAJA. No se puede ensamblar con él.'); if (compuesta && cfg.exigirAprobados && !esPrueba(d.serie) && !todoAprobado()) return fail('Hay módulos sin aprobar, sin acta o ya asignados a otra unidad. No se puede enviar.'); if (!d.tecnico.trim()) return fail('Escribe tu nombre.'); diff --git a/docs/formularios/dano.html b/docs/formularios/dano.html new file mode 100644 index 0000000..e0d22e6 --- /dev/null +++ b/docs/formularios/dano.html @@ -0,0 +1,102 @@ + + + + + +Acta de daño o incidente — HBOX + + + +
+ + + + + + diff --git a/docs/formularios/envio.html b/docs/formularios/envio.html new file mode 100644 index 0000000..560372e --- /dev/null +++ b/docs/formularios/envio.html @@ -0,0 +1,83 @@ + + + + + +Acta de envío — HBOX + + + +
+ + + + + + diff --git a/docs/formularios/recepcion.html b/docs/formularios/recepcion.html new file mode 100644 index 0000000..831a8f3 --- /dev/null +++ b/docs/formularios/recepcion.html @@ -0,0 +1,91 @@ + + + + + +Acta de recepción — HBOX + + + +
+ + + + + + diff --git a/docs/formularios/reflasheo.html b/docs/formularios/reflasheo.html index f2f7589..ffa7417 100644 --- a/docs/formularios/reflasheo.html +++ b/docs/formularios/reflasheo.html @@ -70,7 +70,7 @@ Acta.render({ sub:'DFU por USB del Blackpill. El binario se descarga del sitio, nunca de un USB que ande circulando.', items: [ { tipo:'texto', clave:'bin_nombre', rows:'1', req:true, - txt:'Archivo binario que usaste (nombre completo)', ph:'hbox-fw-v3_X.Y.Z.bin' }, + txt:'Archivo binario que usaste (nombre completo)', ph:'hbox-tarjetaV3_X.Y.Z.bin' }, { tipo:'radio', clave:'bin_origen', req:true, txt:'¿De dónde lo sacaste?', opciones:['De la página de Descargas del sitio','Me lo pasó ingeniería directo','De otro lado'], aviso:'Si no salió del sitio ni de ingeniería, detente: un binario de la generación equivocada puede dejar la batería cargando cuando debería estar cortada.' }, diff --git a/docs/formularios/series.js b/docs/formularios/series.js new file mode 100644 index 0000000..aa8ab9d --- /dev/null +++ b/docs/formularios/series.js @@ -0,0 +1,15 @@ +/* Catálogo de series conocidas para actas que aplican a CUALQUIER cosa con número + (daños, envío, recepción). Junta la flota en campo con los lotes de producción. */ +(function () { + const F = (window.FLOTA || []).map(u => ({ serie: u.serie, etiqueta: u.etiqueta, maquina: u.maquina, + terminal: 'Unidades en campo', nota: u.nota })); + const gen = (pref, n, pad, grupo, desc) => Array.from({ length: n }, (_, i) => { + const s = pref + '-' + String(i + 1).padStart(pad, '0'); + return { serie: s, etiqueta: s + ' — ' + desc, terminal: grupo }; + }); + window.SERIES = F.concat( + gen('V5', 30, 2, 'Tarjetas EinarBoard V5', 'tarjeta EinarBoard v5'), + gen('ARD', 30, 3, 'Módulos simpleRTK2B Pro', 'módulo ArduSimple'), + gen('RTK', 30, 4, 'Módems 4G NTRIP Master', 'módem 4G') + ); +})(); diff --git a/docs/index.md b/docs/index.md index 7347c7d..33ba354 100644 --- a/docs/index.md +++ b/docs/index.md @@ -41,6 +41,23 @@ Sitio interno de Helgen Technologies. **Busca abajo lo que vas a hacer hoy.** [Procedimiento de reflasheo](procedimientos/reflasheo-unidades.md) +- ### 🚨 Algo se dañó + + --- + + Tarjeta con corto, módulo golpeado, unidad mojada. Registra el daño y la serie queda + **marcada**: cualquier acta que la elija mostrará el aviso. + + [**▶ Acta de daño o incidente**](formularios/dano.html){ .md-button .md-button--primary } + +- ### 📦 Sale o llega una unidad + + --- + + Qué salió en la caja y en qué estado llegó, antes de entrar al puerto. + + [Acta de envío](formularios/envio.html) · [Acta de recepción](formularios/recepcion.html) · [Ciclo de vida](procedimientos/ciclo-de-vida.md) + - ### 🗂 Todo sobre una unidad --- diff --git a/docs/procedimientos/ciclo-de-vida.md b/docs/procedimientos/ciclo-de-vida.md new file mode 100644 index 0000000..cf8c93a --- /dev/null +++ b/docs/procedimientos/ciclo-de-vida.md @@ -0,0 +1,103 @@ +# El ciclo de vida de una unidad HBOX — qué acta en qué momento + +**Versión:** 1.0 · 19-sep-2026 · **Responsable:** Leo Rodríguez + +Una unidad pasa por nueve momentos entre la tarjeta desnuda y la máquina en el patio. Cada uno +tiene su procedimiento y su acta, y **todas las actas cuelgan del número de serie**, así que el +[expediente](../unidad.html) de cualquier tarjeta, módulo o unidad cuenta la historia completa — +incluidos los daños. + +## El mapa + +| # | Momento | Dónde | Procedimiento | Acta | Estado | +|---|---|---|---|---|---| +| 1 | **Tarjeta desnuda** llega de fábrica | Banco QRO | [Prueba y flasheo](v5-prueba-y-flasheo.md) | [Tarjeta V5](../formularios/v5.html) | ✅ | +| 2 | **Módulos** RTK y 4G, uno por uno | Banco QRO | [Módulos §2–3](v5-modulos-integracion.md) | [ArduSimple](../formularios/ardusimple.html) · [Módem 4G](../formularios/modem4g.html) | ✅ | +| 3 | **Pre-ensamble**: la pareja que irá junta | Banco QRO | [Módulos §4](v5-modulos-integracion.md) | [Integración](../formularios/integracion.html) | ✅ | +| 4 | **Ensamble** de la unidad | Banco QRO | [Ensamble](v5-ensamble.md) | [Ensamble](../formularios/ensamble.html) — no acepta módulos sin acta aprobada ni ya usados | ✅ | +| 5 | **Prueba final** antes de embarcar | Banco QRO | [Prueba final](v5-prueba-final-unidad.md) | *(se registra en la de ensamble)* | ✅ | +| 6 | **Envío** | QRO → terminal | [abajo](#6-envio) | [Envío](../formularios/envio.html) | 🆕 | +| 7 | **Recepción**, antes de entrar al puerto | Taller Veracruz | [abajo](#7-recepcion) | [Recepción](../formularios/recepcion.html) | 🆕 | +| 8 | **Instalación** en la máquina | Patio | ⬜ pendiente | ⬜ `instalacion-campo` (el servidor ya lo acepta) | ⬜ | +| 9 | **Vida en campo**: falla, diagnóstico, reflasheo | Patio / taller | [Campo](../diagnostico/procedimiento-diagnostico-campo.md) · [Banco](diagnostico-banco.md) · [Reflasheo](reflasheo-unidades.md) | [Campo](../formularios/diagnostico.html) · [Banco](../formularios/banco.html) · [Reflasheo](../formularios/reflasheo.html) | ✅ | +| ⚡ | **Daño o incidente**, en cualquier momento | Donde sea | [abajo](#dano-en-cualquier-momento) | [Daño](../formularios/dano.html) — **marca la serie** | 🆕 | + +Lo único que falta del ciclo es el **8 · Instalación**. Se escribe con el mismo motor cuando +lo decidas: checklist de gabinete, GX16, antenas, foto final y primera posición vista en el servidor. + +## Las tres reglas que cruzan todo el ciclo + +**1 · Nada avanza sin acta.** Un módulo sin acta aprobada no entra al ensamble (el formulario lo +impide). Una unidad sin prueba final aprobada no se envía (el acta de envío lo pide). Una unidad +que no coincide con su acta de envío no entra al puerto (la de recepción lo pide). + +**2 · Una serie dañada se ve desde cualquier acta.** Registrar un daño **marca** la serie. Desde +ese momento, todo formulario que la elija —banco, campo, ensamble, envío— muestra el aviso en +amarillo, con la fecha y el resumen. Así una tarjeta con un corto no puede acabar dentro de un +ensamble sin que alguien lo vea. La marca se levanta con otra acta de daño cuya disposición sea +**«Reparada»**, o se vuelve permanente con **«Baja»** (aviso en rojo). + +**3 · La serie es la llave.** Tarjeta (`V5-06`), módulo (`ARD-010`, `RTK-0006`), unidad de campo +(`RTK-014`) o unidad ensamblada (`HBOX5-E06-A010-R0006`): todo lo que se registre contra una serie +aparece en su expediente, y un módulo además aparece en el expediente de la unidad que lo lleva. + +--- + +## 6 · Envío + +Se llena **al cerrar la caja**, no después. Fija el estado exacto en que salió la unidad para que +la recepción tenga contra qué comparar. + +Requisitos que el acta exige antes de dejar enviar: + +- Acta de **prueba final aprobada** en el sitio. +- **Sin marca de daño** en la serie. +- Firmware que trae (línea `BOOT`), **ICCID** de la SIM, ID de estación del módem. +- Configuración del ZED-F9P guardada en **FLASH**. + +Contenido de la caja (cada punto es una casilla): unidad etiquetada · antena GNSS · 2 antenas LTE · +arnés GX16 · material de gabinete si aplica · hoja impresa con serie, máquina destino y contacto. + +!!! warning "Batería" + Una batería de litio **no viaja en avión** como carga normal. Si va por paquetería, confirmar + que va por tierra, y anotar su voltaje al empacar. + +Fotos: la caja abierta con todo adentro, la unidad con su etiqueta legible, la caja cerrada y rotulada. + +## 7 · Recepción + +Se llena **al abrir la caja, antes de entrar al puerto** — la última oportunidad de regresar por un +cable. Se compara contra el acta de envío de esa unidad (está en su expediente). + +1. **Paquete, antes de abrir:** foto tal como llegó; si viene golpeado, mojado o abierto, foto del daño. +2. **Contenido contra el envío:** misma serie, mismas piezas. Lo que falte o sobre, se anota. +3. **Prueba rápida de vida** (5 min, en el taller, fuente de 24 V): enciende y el LED parpadea · + `BOOT` reporta el **mismo firmware** que el envío · el módem registra en Telcel (LED lento o fijo) · + la SIM es la misma (ICCID). +4. **Veredicto:** lista para entrar · entra con observaciones · se queda en el taller · rechazada. + +!!! danger "Si no enciende o el firmware no coincide, no entra" + Instalarla y descubrirlo arriba de una máquina cuesta una visita más y un permiso más de ICAVE. + +## ⚡ Daño en cualquier momento + +Para cualquier cosa con número de serie: tarjeta, módulo, unidad completa, o incluso un cable o +antena. No es para asignar culpas; es para que el daño **no se pierda de vista**. + +Qué registra: qué es y dónde ocurrió · tipo de daño (corto, sobrevoltaje, quemado, golpe, agua, +batería, error de ensamble, sin causa aparente) · si estaba energizada y con qué · **resumen en +una línea** (es lo que ven los demás formularios) · descripción · foto del daño · componentes +afectados · **disposición**. + +La disposición es lo que marca: + +| Disposición | Efecto en la serie | +|---|---| +| Marcada como dañada — en espera | ⚠ aviso amarillo en todos los formularios | +| Enviada a Querétaro para evaluación | ⚠ aviso amarillo | +| Canibalizada — sus módulos buenos pasan a otra unidad | ⚠ aviso amarillo; anotar qué módulos quedan libres | +| **Reparada** — vuelve a servicio | levanta la marca | +| **Baja** definitiva | ⛔ aviso rojo, permanente | + +Y una casilla que conviene no saltarse: **la pieza quedó etiquetada físicamente** (cinta roja) y +separada de las buenas. El sitio marca la serie; la cinta marca la tarjeta que alguien tiene en la mano. diff --git a/docs/procedimientos/reflasheo-unidades.md b/docs/procedimientos/reflasheo-unidades.md index c6ee1c8..f0a3968 100644 --- a/docs/procedimientos/reflasheo-unidades.md +++ b/docs/procedimientos/reflasheo-unidades.md @@ -37,9 +37,9 @@ cuando debería estar cortada. | Generación de tarjeta | Binario | Estado | |---|---|---| -| **V3** (flota desplegada: RTK-003, RTK-006, RTK-014) | `hbox-fw-v3_*.bin` | ⬜ pendiente de publicar | -| **V4** | `hbox-fw-v4_*.bin` | ⬜ pendiente de publicar | -| **V5** (EinarBoard v5, banco) | `hbox-v5-bringup_0.4.0.bin` | ✅ publicado | +| **V3** — EinarBoard 1.0 (RTK-003, RTK-006, RTK-014), Blackpill | `hbox-tarjetaV3_3.0.4.bin` · `hbox-tarjetaV3_4.0.0.bin` (telemetría) | ⚠ publicados, sin prueba de banco | +| **V4** — Blackpill + PCF8574/W5500/SC16IS752 | **ninguno** | ⛔ sin firmware: `blackpillrtkcode` choca en PB12–15 con el W5500 | +| **V5** — MCU soldado, lote de 15 | `hbox-v5-bringup_0.5.1.bin` | ✅ en despliegue | Si el renglón dice *pendiente*, **no hay binario aprobado todavía**: el flasheo de esa generación está detenido hasta que ingeniería lo publique (§6). @@ -55,9 +55,9 @@ Verifica el archivo antes de flashear: ```bash # macOS / Linux -shasum -a 256 hbox-fw-v3_X.Y.Z.bin +shasum -a 256 hbox-tarjetaV3_X.Y.Z.bin # Windows (PowerShell) -Get-FileHash .\hbox-fw-v3_X.Y.Z.bin -Algorithm SHA256 +Get-FileHash .\hbox-tarjetaV3_X.Y.Z.bin -Algorithm SHA256 ``` Debe coincidir **carácter por carácter** con el que publica `SHA256SUMS.txt`. Si no coincide, @@ -76,7 +76,7 @@ descárgalo otra vez; si sigue sin coincidir, no lo uses y avisa. Si no aparece: repite la secuencia; si sigue sin salir, el cable no lleva datos o el MCU no arranca. 4. **Graba el binario** en la dirección **`0x08000000`**: - *STM32CubeProgrammer* → conectar en **USB/DFU** → *Open file* → *Download*. - - o bien: `dfu-util -a 0 -s 0x08000000:leave -D hbox-fw-v3_X.Y.Z.bin` + - o bien: `dfu-util -a 0 -s 0x08000000:leave -D hbox-tarjetaV3_X.Y.Z.bin` 5. Espera a **«Download complete»** y **«Verify successful»**. Si falla a la mitad, **no desconectes**: repite el paso 3 y vuelve a grabar. 6. Presiona **RESET**. @@ -143,8 +143,8 @@ arduino-cli compile \ blackpillrtkcode_v3/ # o blackpillrtkcode_v4/ para la version con telemetria # 4. nómbralo por su versión y saca su huella -mv release/blackpillrtkcode_v3.ino.bin release/hbox-fw-v3_3.0.4.bin -shasum -a 256 release/hbox-fw-v3_3.0.4.bin | tee -a release/SHA256SUMS +mv release/blackpillrtkcode_v3.ino.bin release/hbox-tarjetaV3_3.0.4.bin +shasum -a 256 release/hbox-tarjetaV3_3.0.4.bin | tee -a release/SHA256SUMS ``` **El destino de tarjeta ya viene bien por omisión:** el propio `.ino` trae `TARGET_EINARBOARD 1` y diff --git a/docs/unidad.html b/docs/unidad.html index 8999c24..13b7bc4 100644 --- a/docs/unidad.html +++ b/docs/unidad.html @@ -38,7 +38,7 @@ section.bloque .sub{color:var(--tinta2);font-size:13.5px;margin:0 0 12px} .ev::before{content:"";position:absolute;left:-27px;top:18px;width:11px;height:11px;border-radius:50%; background:var(--pend);border:2px solid var(--papel)} .ev.campo::before{background:var(--naranja)} .ev.banco::before{background:var(--info)} -.ev.flash::before{background:#7048e8} .ev.prod::before{background:var(--ok)} +.ev.flash::before{background:#7048e8} .ev.prod::before{background:var(--ok)} .ev.falla::before{background:var(--falla)} .ev .cab{display:flex;flex-wrap:wrap;gap:8px;align-items:baseline;justify-content:space-between} .ev .t{font-weight:700;font-size:15px} .ev .f{color:var(--tinta2);font-size:12.5px} @@ -92,6 +92,7 @@ const kb=n=>n>1048576?(n/1048576).toFixed(1)+' MB':Math.max(1,Math.round(n/1024) const NOMBRE={'diagnostico-campo':['Diagnóstico en campo','campo'],'diagnostico-banco':['Diagnóstico en banco','banco'], 'reflasheo':['Reflasheo','flash'],'instalacion-campo':['Instalación en sitio','campo'], + 'dano':['⚡ Daño o incidente','falla'],'envio':['📦 Envío','prod'],'recepcion':['📦 Recepción','prod'], 'prueba-v5':['Prueba de tarjeta V5','prod'],'prueba-v4':['Prueba de tarjeta V4','prod'], 'modulo-ard':['Módulo ArduSimple','prod'],'modulo-4g':['Módem 4G','prod'], 'integracion':['Integración RTK + 4G','prod'],'ensamble':['Ensamble de unidad','prod']}; @@ -150,6 +151,13 @@ function render(j){ ? ''+(u.maquina||'')+' · '+(u.terminal||'')+(u.nota?'
'+u.nota:'') : 'Esta serie no está en el registro de flota (flota.js).'; + if(j.marca){ + const m=j.marca_acta||{}; + cont.append(el('div',{class:'err',style:j.marca==='BAJA'?'':'background:#fff9db;color:#7a4a00;border-color:#f08c00', + html:(j.marca==='BAJA'?'⛔ Serie dada de BAJA':'⚠ Serie marcada como DAÑADA') + +(m.fecha?' desde el '+m.fecha:'')+(m.clasificacion?' — '+m.clasificacion:'')+(m.resumen?'
'+m.resumen:'') + +(m.accion?'
Disposición: '+m.accion:'')})); + } const kp=el('div',{class:'kpis'}); [[j.total,'Actas registradas'],[j.total_fotos,'Fotos'],[j.total_logs,'Archivos de log'], [j.relacionadas.length,'Actas que la mencionan']] diff --git a/mkdocs.yml b/mkdocs.yml index 268a0cf..312b69b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -75,6 +75,12 @@ nav: - "Firmware, parámetros 4G y ZED-F9P": descargas/index.md - "README firmware bring-up v5": descargas/hbox-v5-bringup-README.md + - "📦 Ciclo de vida — envío, recepción, daños": + - "El ciclo de vida de una unidad (mapa)": procedimientos/ciclo-de-vida.md + - "▶ Acta de envío": formularios/envio.html + - "▶ Acta de recepción (antes de entrar al puerto)": formularios/recepcion.html + - "▶ Acta de daño o incidente": formularios/dano.html + - "🗂 Expediente de una unidad": unidad.html - "📋 Actas — índice y cómo se usan": actas/index.md diff --git a/scripts/sync-docs.sh b/scripts/sync-docs.sh index ce27132..8ec77c3 100644 --- a/scripts/sync-docs.sh +++ b/scripts/sync-docs.sh @@ -24,8 +24,8 @@ declare -a MAP=( # Descargas para técnicos (firmware de bring-up v5) "04-Source-Code/firmware/hbox_v5_bringup/release/hbox-v5-bringup_0.5.1.bin|docs/descargas/hbox-v5-bringup_0.5.1.bin" "04-Source-Code/firmware/hbox_v5_bringup/release/superseded/hbox-v5-bringup_0.4.0.bin|docs/descargas/hbox-v5-bringup_0.4.0.bin" - "04-Source-Code/firmware/release/hbox-fw-v3_3.0.4.bin|docs/descargas/hbox-fw-v3_3.0.4.bin" - "04-Source-Code/firmware/release/hbox-fw-v4_4.0.0.bin|docs/descargas/hbox-fw-v4_4.0.0.bin" + "04-Source-Code/firmware/release/hbox-tarjetaV3_3.0.4.bin|docs/descargas/hbox-tarjetaV3_3.0.4.bin" + "04-Source-Code/firmware/release/hbox-tarjetaV3_4.0.0.bin|docs/descargas/hbox-tarjetaV3_4.0.0.bin" "04-Source-Code/firmware/hbox_v5_bringup/README.md|docs/descargas/hbox-v5-bringup-README.md" )