auto-sync 2026-09-19 18:00

This commit is contained in:
Leopoldo Rodríguez 2026-09-19 18:00:14 -06:00
parent 7465a9e1fb
commit dd9c962471
22 changed files with 596 additions and 48 deletions

View File

@ -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)

View File

@ -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

View File

@ -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")))

View File

@ -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).

View File

@ -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

View File

@ -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.

View File

@ -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||[]);

View File

@ -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}

View File

@ -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' ? '⛔ <b>' + s + ' está dada de BAJA</b>' : '⚠ <b>' + s + ' está marcada como DAÑADA</b>')
+ (a.fecha ? ' desde el ' + a.fecha : '') + (a.clasificacion ? ' — ' + a.clasificacion : '')
+ (a.resumen ? '<br>' + a.resumen : '')
+ '<br><a href="../unidad.html?serie=' + encodeURIComponent(s) + '">Ver su expediente</a>'
+ (j.marca === 'BAJA' ? '. No debe usarse ni instalarse.' : '. Revisa el acta de daño antes de continuar.');
}).join('<hr style="border:0;border-top:1px solid currentColor;opacity:.3;margin:8px 0">');
}
// 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 <b>seguí el procedimiento completo</b> 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.');

102
docs/formularios/dano.html Normal file
View File

@ -0,0 +1,102 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Acta de daño o incidente — HBOX</title>
<link rel="stylesheet" href="acta.css">
</head>
<body>
<div id="acta"></div>
<script src="flota.js"></script>
<script src="series.js"></script>
<script src="acta.js"></script>
<script>
const energizada = v => String(v.energizada || '').startsWith('Sí');
Acta.render({
tipo: 'dano',
repetible: true,
resultadoFijo: 'REGISTRADO',
tecnicoDesdeUsuario: true,
nombres: { alan: 'Alán Basurto', daniel: 'Daniel Martínez', leo: 'Leo Rodríguez' },
unidades: window.SERIES,
etiquetaSerie: 'Tarjeta, módulo o unidad dañada',
titulo: 'Acta de daño o incidente',
subtitulo: 'Helgen Technologies · cualquier cosa con número de serie: tarjeta, módulo o unidad completa.',
intro: '<b>Para qué sirve.</b> Hoy una tarjeta con un corto puede acabar dentro de un ensamble sin que nadie '
+ 'lo sepa, porque no queda registro. Esta acta <b>marca</b> la serie: desde que se envía, cualquier '
+ 'formulario que la elija muestra el aviso, y su expediente cuenta qué pasó. La marca se levanta con '
+ 'otra acta de daño cuya disposición sea <b>«Reparada»</b>, o se vuelve permanente con <b>«Baja»</b>. '
+ 'No es para asignar culpas: es para que el daño no se pierda de vista.',
secciones: [
{ titulo: '0 · Qué se dañó y dónde',
items: [
{ tipo:'fecha', clave:'fecha', req:true, guardaEn:'fecha', txt:'Fecha del daño (o de cuando se detectó)' },
{ tipo:'radio', clave:'que', req:true, txt:'¿Qué es?',
opciones:['Tarjeta EinarBoard','Módulo ArduSimple','Módem 4G','Unidad HBOX completa','Otro (cable, antena, gabinete…)'] },
{ tipo:'radio', clave:'donde', req:true, txt:'¿Dónde ocurrió?',
opciones:['En el banco (prueba o flasheo)','Durante el ensamble','En transporte o almacén','En campo, instalada','No se sabe — así se encontró'] },
{ tipo:'texto', clave:'quien_detecto', rows:'1', txt:'¿Quién lo detectó y cómo?', ph:'Daniel, al medir continuidad antes de energizar' },
] },
{ titulo: '1 · Qué pasó',
sub:'Cuenta lo que viste, no lo que supones. "No se sabe" es una respuesta válida.',
items: [
{ tipo:'radio', clave:'clasificacion', req:true, guardaEn:'clasificacion', txt:'Tipo de daño',
opciones:[
'Corto a tierra',
'Sobrevoltaje o polaridad invertida',
'Componente quemado o sobrecalentado',
'Daño mecánico (golpe, pin doblado, conector roto)',
'Agua o corrosión',
'Batería dañada',
'Error de ensamble o soldadura',
'Falla sin causa aparente',
'Otro'] },
{ tipo:'radio', clave:'energizada', req:true, txt:'¿Estaba energizada cuando ocurrió?',
opciones:['Sí','No','No se sabe'] },
{ tipo:'texto', clave:'fuente', si:energizada, rows:'1', txt:'¿Con qué fuente y a cuánto?', ph:'Fuente de banco 24 V, límite 500 mA' },
{ tipo:'texto', clave:'haciendo', rows:'2', txt:'¿Qué se estaba haciendo en ese momento?',
ph:'Se conectaba el arnés del GX16 con la fuente encendida.' },
{ tipo:'texto', clave:'resumen', req:true, guardaEn:'resumen', rows:'2',
txt:'Resumen en una línea (es lo que verán los demás formularios como aviso)',
ph:'Corto VEH5V a GND en la zona de Q10; no energizar.' },
{ tipo:'texto', clave:'descripcion', req:true, rows:'4', txt:'Descripción completa',
ph:'Qué se midió, qué se vio, qué se olió, en qué orden pasó.' },
] },
{ titulo: '2 · Evidencia',
items: [
{ tipo:'foto', clave:'f_dano', req:true, etiqueta:'dano', txt:'El daño, de cerca.' },
{ tipo:'foto', clave:'f_general', etiqueta:'general', txt:'La pieza completa, para ubicar el daño.' },
{ tipo:'medida', clave:'r_corto', txt:'Si fue corto: resistencia medida a tierra', u:'Ω' },
{ tipo:'texto', clave:'componentes', rows:'2', txt:'Componentes afectados (designadores si los conoces)', ph:'Q10, D5, pista bajo U6' },
{ tipo:'archivo', clave:'log', etiqueta:'dano', txt:'Captura o log relacionado, si lo hay' },
] },
{ titulo: '3 · Disposición — esto es lo que MARCA la serie',
sub:'Lo último que se registre aquí es lo que vale. Para levantar una marca, registra una nueva acta con «Reparada».',
items: [
{ tipo:'radio', clave:'accion', req:true, guardaEn:'accion', txt:'¿Cómo queda?',
opciones:[
'Marcada como dañada — en espera de diagnóstico o reparación',
'Enviada a Querétaro para evaluación',
'Canibalizada — sus módulos buenos pasan a otra unidad',
'Reparada — vuelve a servicio (levanta la marca)',
'Baja definitiva (marca permanente)'],
aviso:'<b>Reparada</b> quita el aviso de los formularios. <b>Baja</b> lo vuelve permanente y la serie no debe volver a usarse ni instalarse.' },
{ tipo:'texto', clave:'reparacion', si:v=>String(v.accion||'').startsWith('Reparada'), rows:'2', req:true,
txt:'¿Qué se reparó y quién lo verificó?' },
{ tipo:'texto', clave:'modulos_rescatados', si:v=>String(v.accion||'').startsWith('Canibalizada'), rows:'2',
txt:'¿Qué módulos se rescataron y a dónde fueron?', ph:'ARD-010 y RTK-0006 quedan libres para otro ensamble' },
{ tipo:'check', clave:'etiquetada', txt:'La pieza quedó <b>etiquetada físicamente</b> como dañada (cinta roja o marcador) y separada de las buenas.' },
{ tipo:'texto', clave:'leccion', rows:'2', txt:'¿Qué habría evitado esto?',
ph:'Medir continuidad antes de energizar; la acta de banco ya lo pide en el paso 2.' },
] },
]
});
</script>
</body>
</html>

View File

@ -0,0 +1,83 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Acta de envío — HBOX</title>
<link rel="stylesheet" href="acta.css">
</head>
<body>
<div id="acta"></div>
<script src="flota.js"></script>
<script src="series.js"></script>
<script src="acta.js"></script>
<script>
const conBateria = v => String(v.bateria || '').startsWith('Sí');
Acta.render({
tipo: 'envio',
repetible: true,
resultadoFijo: 'ENVIADA',
tecnicoDesdeUsuario: true,
nombres: { alan: 'Alán Basurto', daniel: 'Daniel Martínez', leo: 'Leo Rodríguez' },
unidades: window.SERIES,
etiquetaSerie: 'Unidad que se envía',
titulo: 'Acta de envío',
subtitulo: 'Helgen Technologies · se llena al cerrar la caja, no después.',
intro: '<b>Para qué sirve.</b> Fija el estado exacto en que salió la unidad: qué firmware traía, qué SIM, '
+ 'qué iba en la caja y cómo se veía. Cuando llegue, el acta de <b>recepción</b> se compara contra '
+ 'esta. Si algo falta o llegó golpeado, se sabe en qué tramo pasó.',
secciones: [
{ titulo: '0 · Qué se manda y a dónde',
items: [
{ tipo:'fecha', clave:'fecha', req:true, guardaEn:'fecha', txt:'Fecha de envío' },
{ tipo:'radio', clave:'destino', req:true, guardaEn:'clasificacion', txt:'Destino',
opciones:['ICAVE (Veracruz)','TIMSA','EIT','LCT','Taller Veracruz','Querétaro (regreso)','Otro'] },
{ tipo:'texto', clave:'maquina_destino', rows:'1', guardaEn:'maquina_id', txt:'Máquina a la que va, si ya se sabe', ph:'FL-JUN-060' },
{ tipo:'radio', clave:'medio', req:true, txt:'¿Cómo viaja?',
opciones:['Paquetería (guía abajo)','La lleva alguien de Helgen','La recoge el cliente'] },
{ tipo:'texto', clave:'guia', rows:'1', txt:'Número de guía o quién la lleva' },
] },
{ titulo: '1 · Antes de cerrar la caja — requisitos',
sub:'Si alguno de estos no se cumple, la unidad NO se envía.',
items: [
{ tipo:'check', clave:'prueba_final', req:true, txt:'Tiene <b>acta de prueba final aprobada</b> en el sitio (no vale "ya la probé").' },
{ tipo:'check', clave:'sin_marca', req:true, txt:'Al elegir la serie arriba <b>no apareció aviso de daño</b>.' },
{ tipo:'texto', clave:'fw', rows:'1', req:true, txt:'Firmware que trae (línea BOOT o banner de consola)', ph:'fw=3.0.4' },
{ tipo:'texto', clave:'iccid', rows:'1', req:true, txt:'ICCID de la SIM instalada' },
{ tipo:'texto', clave:'imei', rows:'1', txt:'IMEI del módem' },
{ tipo:'texto', clave:'id_estacion', rows:'1', txt:'ID de estación del módem (AS+K)' },
{ tipo:'check', clave:'config_flash', txt:'Configuración del ZED-F9P guardada en <b>FLASH</b> (no solo RAM).' },
] },
{ titulo: '2 · Lo que va en la caja',
items: [
{ tipo:'check', clave:'c_unidad', req:true, txt:'Unidad HBOX cerrada y etiquetada con su serie.' },
{ tipo:'check', clave:'c_ant_gnss', txt:'Antena GNSS con su cable.' },
{ tipo:'check', clave:'c_ant_lte', txt:'2 antenas LTE.' },
{ tipo:'check', clave:'c_gx16', txt:'Arnés GX16 hacia el gabinete.' },
{ tipo:'check', clave:'c_fuente', txt:'Fuente Mean Well y material de gabinete (si aplica).' },
{ tipo:'check', clave:'c_docs', txt:'Hoja impresa con serie, máquina destino y contacto de Helgen.' },
{ tipo:'texto', clave:'extras', rows:'2', txt:'Otras cosas que van (tornillería, cinchos, repuestos…)' },
{ tipo:'radio', clave:'bateria', req:true, txt:'¿Viaja con batería instalada?',
opciones:['Sí','No — se retiró','No tiene batería'],
aviso:'Una batería de litio <b>no viaja en avión</b> como carga normal. Si va por paquetería, confirma con ellos que va por tierra.' },
{ tipo:'medida', clave:'v_bat', si:conBateria, txt:'Voltaje de la batería al empacar', esperado:'3.6 – 4.0 V', u:'V' },
] },
{ titulo: '3 · Evidencia',
items: [
{ tipo:'foto', clave:'f_abierta', req:true, etiqueta:'caja-abierta', txt:'La caja abierta con todo adentro, antes de cerrar.' },
{ tipo:'foto', clave:'f_unidad', req:true, etiqueta:'unidad', txt:'La unidad con su etiqueta legible.' },
{ tipo:'foto', clave:'f_cerrada', etiqueta:'caja-cerrada', txt:'La caja cerrada y rotulada.' },
{ tipo:'radio', clave:'accion', req:true, guardaEn:'accion', txt:'Estado',
opciones:['Enviada','Lista pero detenida (explica abajo)'] },
{ tipo:'texto', clave:'observaciones', rows:'3', txt:'Observaciones' },
] },
]
});
</script>
</body>
</html>

View File

@ -0,0 +1,91 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Acta de recepción — HBOX</title>
<link rel="stylesheet" href="acta.css">
</head>
<body>
<div id="acta"></div>
<script src="flota.js"></script>
<script src="series.js"></script>
<script src="acta.js"></script>
<script>
const enciende = v => String(v.enciende || '').startsWith('Sí');
Acta.render({
tipo: 'recepcion',
repetible: true,
tecnicoDesdeUsuario: true,
nombres: { alan: 'Alán Basurto', daniel: 'Daniel Martínez', leo: 'Leo Rodríguez' },
unidades: window.SERIES,
etiquetaSerie: 'Unidad que se recibe',
titulo: 'Acta de recepción',
subtitulo: 'Helgen Technologies · se llena al abrir la caja, ANTES de entrar al puerto.',
textoOK: 'LISTA PARA ENTRAR',
intro: '<b>Para qué sirve.</b> Es la última revisión antes de que la unidad cruce la puerta del puerto, '
+ 'donde ya no se puede regresar por un cable. Se compara contra el <b>acta de envío</b>: mismo '
+ 'firmware, misma SIM, mismo contenido. Lo que no coincida, aquí se detecta. Historial de la unidad: '
+ 'elige la serie y mira sus visitas anteriores arriba.',
secciones: [
{ titulo: '0 · Qué llega y dónde',
items: [
{ tipo:'fecha', clave:'fecha', req:true, guardaEn:'fecha', txt:'Fecha de recepción' },
{ tipo:'radio', clave:'donde', req:true, guardaEn:'clasificacion', txt:'¿Dónde la recibes?',
opciones:['Taller Veracruz','Oficina ICAVE','Otra terminal','Querétaro'] },
{ tipo:'texto', clave:'guia', rows:'1', txt:'Número de guía o quién la entregó' },
{ tipo:'texto', clave:'maquina_destino', rows:'1', guardaEn:'maquina_id', txt:'Máquina a la que va', ph:'FL-JUN-060' },
] },
{ titulo: '1 · El paquete, antes de abrir',
items: [
{ tipo:'foto', clave:'f_paquete', req:true, etiqueta:'paquete', txt:'La caja cerrada tal como llegó.' },
{ tipo:'radio', clave:'paquete', req:true, txt:'Estado del paquete',
opciones:['Intacto','Golpeado o aplastado','Abierto o con cinta distinta','Mojado'] },
{ tipo:'foto', clave:'f_dano_paquete', etiqueta:'paquete-dano', si:v=>!String(v.paquete||'').startsWith('Intacto'), req:true,
txt:'Foto del daño del paquete, antes de abrirlo.' },
] },
{ titulo: '2 · Contenido contra el acta de envío',
sub:'Abre el acta de envío de esta unidad (expediente) y compara punto por punto.',
items: [
{ tipo:'check', clave:'c_unidad', req:true, txt:'Unidad HBOX presente, etiqueta legible, <b>la serie coincide</b> con la del envío.' },
{ tipo:'check', clave:'c_ant_gnss', txt:'Antena GNSS con cable.' },
{ tipo:'check', clave:'c_ant_lte', txt:'2 antenas LTE.' },
{ tipo:'check', clave:'c_gx16', txt:'Arnés GX16.' },
{ tipo:'check', clave:'c_docs', txt:'Hoja con serie, máquina y contacto.' },
{ tipo:'texto', clave:'faltante', rows:'2', txt:'¿Falta algo o llegó algo de más?' },
{ tipo:'foto', clave:'f_contenido', req:true, etiqueta:'contenido', txt:'Todo el contenido desplegado.' },
] },
{ titulo: '3 · Prueba rápida de vida (5 minutos, en el taller)',
sub:'Con fuente de 24 V y las antenas conectadas. No hace falta cielo abierto para esto.',
items: [
{ tipo:'radio', clave:'enciende', req:true, txt:'¿Enciende y el LED parpadea?',
opciones:['Sí','No — LED apagado','Enciende pero se apaga'] },
{ tipo:'texto', clave:'fw', rows:'1', si:enciende, req:true, txt:'Firmware que reporta (línea BOOT)', ph:'fw=3.0.4' },
{ tipo:'check', clave:'fw_coincide', si:enciende, txt:'El firmware <b>coincide con el del acta de envío</b>.' },
{ tipo:'radio', clave:'modem', si:enciende, txt:'LED de red del módem 4G tras 3 minutos',
opciones:['Lento o fijo (registró)','Rápido (no registra)','Apagado'] },
{ tipo:'check', clave:'sim_ok', si:enciende, txt:'La SIM es la del acta de envío (ICCID coincide).' },
{ tipo:'archivo', clave:'log', etiqueta:'recepcion', si:enciende, txt:'Captura de la consola (opcional, pero útil si algo se ve raro)' },
] },
{ titulo: '4 · Veredicto',
items: [
{ tipo:'radio', clave:'accion', req:true, guardaEn:'accion', txt:'¿Entra al puerto?',
opciones:[
'Lista para entrar al puerto',
'Entra con observaciones (anótalas)',
'No entra — se queda en el taller para revisión',
'Rechazada — regresa a Querétaro'],
aviso:'Si no enciende o el firmware no coincide, <b>no entra</b>. Instalarla y descubrirlo arriba de una máquina cuesta una visita más y un permiso más.' },
{ tipo:'texto', clave:'observaciones', rows:'3', txt:'Observaciones' },
] },
]
});
</script>
</body>
</html>

View File

@ -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, <b>detente</b>: un binario de la generación equivocada puede dejar la batería cargando cuando debería estar cortada.' },

View File

@ -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')
);
})();

View File

@ -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
---

View File

@ -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.

View File

@ -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

View File

@ -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){
? '<b>'+(u.maquina||'')+'</b> · '+(u.terminal||'')+(u.nota?'<br>'+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'?'⛔ <b>Serie dada de BAJA</b>':'⚠ <b>Serie marcada como DAÑADA</b>')
+(m.fecha?' desde el '+m.fecha:'')+(m.clasificacion?' — '+m.clasificacion:'')+(m.resumen?'<br>'+m.resumen:'')
+(m.accion?'<br>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']]

View File

@ -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

View File

@ -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"
)