auto-sync 2026-09-18 18:00
This commit is contained in:
parent
ade43bbc14
commit
d21963fec6
@ -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_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: 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(
|
TIPOS_REPETIBLES = {t.strip() for t in os.environ.get(
|
||||||
"ACTAS_REPETIBLES", "diagnostico-campo,instalacion-campo").split(",") if t.strip()}
|
"ACTAS_REPETIBLES", "diagnostico-campo,diagnostico-banco,instalacion-campo,reflasheo").split(",") if t.strip()}
|
||||||
|
|
||||||
# Subida de fotos
|
# Subida de fotos
|
||||||
MAX_MB = int(os.environ.get("ADJUNTO_MAX_MB", "8"))
|
MAX_MB = int(os.environ.get("ADJUNTO_MAX_MB", "8"))
|
||||||
@ -561,5 +561,98 @@ def cambiar_password():
|
|||||||
return jsonify(ok=True, usuario=destino, reset=es_reset)
|
return jsonify(ok=True, usuario=destino, reset=es_reset)
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------- expediente de una unidad
|
||||||
|
def _listar_adjuntos(serie):
|
||||||
|
"""Todas las fotos y logs subidos para esa serie, de cualquier tipo de acta."""
|
||||||
|
fotos, logs = [], []
|
||||||
|
if not os.path.isdir(ADJ):
|
||||||
|
return fotos, logs
|
||||||
|
for tipo in sorted(os.listdir(ADJ)):
|
||||||
|
base = os.path.join(ADJ, tipo, serie)
|
||||||
|
if not os.path.isdir(base):
|
||||||
|
continue
|
||||||
|
for fn in sorted(os.listdir(base)):
|
||||||
|
fp = os.path.join(base, fn)
|
||||||
|
if os.path.isfile(fp):
|
||||||
|
fotos.append({"tipo": tipo, "ruta": f"{tipo}/{serie}/{fn}",
|
||||||
|
"bytes": os.path.getsize(fp), "ts": int(os.path.getmtime(fp))})
|
||||||
|
dlogs = os.path.join(base, "logs")
|
||||||
|
if os.path.isdir(dlogs):
|
||||||
|
for fn in sorted(os.listdir(dlogs)):
|
||||||
|
fp = os.path.join(dlogs, fn)
|
||||||
|
if os.path.isfile(fp):
|
||||||
|
logs.append({"tipo": tipo, "ruta": f"{tipo}/{serie}/logs/{fn}",
|
||||||
|
"nombre": fn[16:] if len(fn) > 16 else fn,
|
||||||
|
"bytes": os.path.getsize(fp), "ts": int(os.path.getmtime(fp))})
|
||||||
|
return fotos, logs
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/unidad")
|
||||||
|
def expediente():
|
||||||
|
"""Todo lo que existe en el servidor sobre una serie: actas de cualquier tipo,
|
||||||
|
fotos, archivos de log, y las actas de otras unidades que la mencionan como componente."""
|
||||||
|
serie = slug(request.args.get("serie"), 24)
|
||||||
|
if not serie:
|
||||||
|
return jsonify(ok=False, error="falta serie"), 400
|
||||||
|
|
||||||
|
actas, relacionadas = [], []
|
||||||
|
if os.path.isdir(BASE):
|
||||||
|
for tipo in sorted(os.listdir(BASE)):
|
||||||
|
carpeta = os.path.join(BASE, tipo)
|
||||||
|
if not os.path.isdir(carpeta):
|
||||||
|
continue
|
||||||
|
for fn in sorted(os.listdir(carpeta)):
|
||||||
|
if not fn.endswith(".json"):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
with open(os.path.join(carpeta, fn), encoding="utf-8") as f:
|
||||||
|
d = json.load(f)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
fila = {"tipo": tipo, "archivo": fn, "serie": d.get("serie"),
|
||||||
|
"recibido": d.get("_recibido"), "fecha": d.get("fecha"),
|
||||||
|
"tecnico": d.get("tecnico"), "resultado": d.get("resultado"),
|
||||||
|
"clasificacion": d.get("clasificacion"), "accion": d.get("accion"),
|
||||||
|
"motivo": d.get("motivo"), "maquina": d.get("maquina_id") or d.get("maquina"),
|
||||||
|
"fotos": [f2.get("ruta") for f2 in (d.get("fotos") or []) if f2.get("ruta")],
|
||||||
|
"archivos": [{"ruta": a.get("ruta"), "nombre": a.get("nombre")}
|
||||||
|
for a in (d.get("archivos") or []) if a.get("ruta")]}
|
||||||
|
if d.get("serie") == serie:
|
||||||
|
actas.append(fila)
|
||||||
|
elif isinstance(d.get("componentes"), dict) and serie in d["componentes"].values():
|
||||||
|
fila["componentes"] = d["componentes"]
|
||||||
|
relacionadas.append(fila)
|
||||||
|
|
||||||
|
# Orden del expediente: manda la FECHA que anotó el técnico (la de la visita);
|
||||||
|
# la hora de recepción solo desempata. Un acta capturada días después no debe
|
||||||
|
# aparecer como si el trabajo se hubiera hecho ese día.
|
||||||
|
def _clave(r):
|
||||||
|
return ((r.get("fecha") or (r.get("recibido") or "")[:10]), r.get("recibido") or "")
|
||||||
|
actas.sort(key=_clave, reverse=True)
|
||||||
|
relacionadas.sort(key=_clave, reverse=True)
|
||||||
|
fotos, logs = _listar_adjuntos(serie)
|
||||||
|
return jsonify(ok=True, serie=serie, actas=actas, relacionadas=relacionadas,
|
||||||
|
fotos=fotos, logs=logs,
|
||||||
|
total=len(actas), total_fotos=len(fotos), total_logs=len(logs))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/series")
|
||||||
|
def series():
|
||||||
|
"""Todas las series que tienen algo guardado — para el buscador del expediente."""
|
||||||
|
vistas = {}
|
||||||
|
if os.path.isdir(BASE):
|
||||||
|
for tipo in sorted(os.listdir(BASE)):
|
||||||
|
carpeta = os.path.join(BASE, tipo)
|
||||||
|
if not os.path.isdir(carpeta):
|
||||||
|
continue
|
||||||
|
for fn in os.listdir(carpeta):
|
||||||
|
if fn.endswith(".json"):
|
||||||
|
ser = fn.rsplit("_", 1)[0] if "_" in fn else fn[:-5]
|
||||||
|
ser = fn.split("_2")[0] if "_2" in fn else ser
|
||||||
|
vistas.setdefault(ser, set()).add(tipo)
|
||||||
|
return jsonify(ok=True, series=[{"serie": k, "tipos": sorted(v)}
|
||||||
|
for k, v in sorted(vistas.items())])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="127.0.0.1", port=int(os.environ.get("PORT", "8081")))
|
app.run(host="127.0.0.1", port=int(os.environ.get("PORT", "8081")))
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
e1f351084e803306c376989b284ca0f9f9f1a3445e59494f8a5ff7a9652ad44c hbox-v5-bringup_0.5.1.bin
|
e1f351084e803306c376989b284ca0f9f9f1a3445e59494f8a5ff7a9652ad44c hbox-v5-bringup_0.5.1.bin
|
||||||
|
a8de74b3d7768e9d5656ae0bff05475445c0cd2599ecfdc4ab081e517ed3d6ef hbox-v5-bringup_0.4.0.bin
|
||||||
|
|||||||
BIN
docs/descargas/hbox-v5-bringup_0.4.0.bin
Normal file
BIN
docs/descargas/hbox-v5-bringup_0.4.0.bin
Normal file
Binary file not shown.
@ -3,11 +3,38 @@
|
|||||||
Archivos que el técnico necesita en el banco. Verificar el SHA-256 antes de flashear
|
Archivos que el técnico necesita en el banco. Verificar el SHA-256 antes de flashear
|
||||||
(`shasum -a 256 archivo` en macOS, `sha256sum archivo` en Linux, `certutil -hashfile archivo SHA256` en Windows).
|
(`shasum -a 256 archivo` en macOS, `sha256sum archivo` en Linux, `certutil -hashfile archivo SHA256` en Windows).
|
||||||
|
|
||||||
## Firmware de bring-up — EinarBoard v5
|
## Qué archivo va a qué cosa
|
||||||
|
|
||||||
|
Una unidad HBOX tiene **tres cerebros distintos** y cada uno se carga por su propio camino. Antes de
|
||||||
|
descargar nada, ubica cuál vas a tocar:
|
||||||
|
|
||||||
|
| Qué | Qué se le carga | Cómo | Dónde está |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **Tarjeta HBOX** (Blackpill / EinarBoard) | Firmware `.bin` | DFU por USB | [§ Firmware del HBOX](#firmware-del-hbox-por-generacion-de-tarjeta) |
|
||||||
|
| **Módem 4G NTRIP Master** | Archivo de **parámetros** `$$$AS+…` | Terminal serie por el USB *XBEE* | [§ Módem 4G](#parametros-del-modem-4g-ntrip-master) |
|
||||||
|
| **simpleRTK2B Pro (ZED-F9P)** | Configuración u-center **y** firmware u-blox | u-center por el USB *POWER+GPS* | [§ ZED-F9P](#firmware-y-configuracion-del-simplertk2b-pro-zed-f9p) |
|
||||||
|
|
||||||
|
Cada carga se registra en el **[acta de reflasheo](../formularios/reflasheo.html)**, y queda en el
|
||||||
|
[expediente de esa unidad](../unidad.html) junto con las capturas de antes y después.
|
||||||
|
|
||||||
|
!!! warning "Verifica el SHA-256 antes de flashear"
|
||||||
|
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)
|
||||||
|
|
||||||
|
| Generación | Archivo | Para qué sirve |
|
||||||
|
|---|---|---|
|
||||||
|
| **V3** — flota desplegada (RTK-003, RTK-006, RTK-014) | ⬜ hay que compilarlo | Firmware de campo: gobierna la unidad en la máquina. El código existe (`blackpillrtkcode_v3`, v3.0.3) |
|
||||||
|
| **V4** | ⬜ hay que compilarlo | `blackpillrtkcode_v4` (v4.0.0) — telemetría en banda: el `$PHLG` llega al servidor por el módem, no solo al cable USB |
|
||||||
|
| **V5** — EinarBoard v5 | ✅ `hbox-v5-bringup_0.5.1.bin` (abajo) | Bring-up de tarjeta en banco |
|
||||||
|
|
||||||
|
### Firmware de bring-up — EinarBoard v5
|
||||||
|
|
||||||
| Archivo | Versión | SHA-256 | Uso |
|
| Archivo | Versión | SHA-256 | Uso |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| [**hbox-v5-bringup_0.5.1.bin**](hbox-v5-bringup_0.5.1.bin) | **0.5.1** (2026-09-18) | `e1f351084e803306…` ([SHA256SUMS](SHA256SUMS.txt)) | 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`) |
|
| [**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.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):
|
Flasheo por DFU en **J11** (mantener **SW2**, pulsar y soltar **SW1**, soltar SW2):
|
||||||
|
|
||||||
@ -17,8 +44,14 @@ 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`.
|
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.
|
||||||
|
|
||||||
!!! note "Tarjetas ya flasheadas con cualquier versión anterior"
|
!!! note "Tarjetas ya flasheadas con cualquier versión anterior"
|
||||||
Re-flashear con 0.5.1 por el mismo procedimiento DFU. **La primera línea de la consola tras reset dice la versión**
|
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**
|
||||||
(`hbox-v5-bringup 0.5.1 built …`): si la línea `STAT` no termina en `stack=…`, la tarjeta sigue con 0.1.1.
|
(`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"
|
!!! tip "Consola 0.5.1"
|
||||||
@ -46,17 +79,19 @@ o con **STM32CubeProgrammer** → USB → Connect → Download del `.bin` en `0x
|
|||||||
LEDs, ADC) sin encender la etapa RTK ni el cargador. **Nunca flashear `blackpillrtkcode_v3`
|
LEDs, ADC) sin encender la etapa RTK ni el cargador. **Nunca flashear `blackpillrtkcode_v3`
|
||||||
(v3.0.3) en una v5.**
|
(v3.0.3) en una v5.**
|
||||||
|
|
||||||
|
**Novedades de 0.5.x:** el `$PHLG` gana 5 campos nuevos (`up_s`, `pwr`, `v5min`, `vbat_min`, `bkp_n` — `vbat_min` caza los hundimientos de 50-200 ms de las ráfagas LTE que una foto cada 30 s nunca ve) y aparece `$PHEV`, una trama por cada cambio de estado. Los analizadores viejos ignoran los campos extra.
|
||||||
|
|
||||||
Notas de compilación y cambios: [README del firmware de bring-up](hbox-v5-bringup-README.md).
|
Notas de compilación y cambios: [README del firmware de bring-up](hbox-v5-bringup-README.md).
|
||||||
|
|
||||||
## Firmware de campo — tarjetas V3 y V4
|
### Firmware de campo — tarjetas V3 y V4 (detalle)
|
||||||
|
|
||||||
Es el firmware que gobierna la unidad en la máquina (`blackpillrtkcode_v3` / `v4`), distinto del de
|
Es el firmware que gobierna la unidad en la máquina (`blackpillrtkcode_v3` / `v4`), distinto del de
|
||||||
bring-up del v5. Se usa en el [reflasheo en taller](../procedimientos/reflasheo-unidades.md).
|
bring-up del v5. Se usa en el [reflasheo en taller](../procedimientos/reflasheo-unidades.md).
|
||||||
|
|
||||||
| Generación | Archivo | Estado |
|
| Generación | Archivo | Estado |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **V3** — flota desplegada (RTK-003, RTK-006, RTK-014) | `hbox-fw-v3_X.Y.Z.bin` | ⬜ **pendiente de publicar** |
|
| **V3** — flota desplegada | `hbox-fw-v3_3.0.3.bin` | ⬜ **falta compilarlo y probarlo en banco** |
|
||||||
| **V4** | `hbox-fw-v4_X.Y.Z.bin` | ⬜ **pendiente de publicar** |
|
| **V4** | `hbox-fw-v4_4.0.0.bin` | ⬜ **falta compilarlo y probarlo en banco**. Escrito y validado en el simulador de host el 18-sep |
|
||||||
|
|
||||||
!!! danger "Mientras diga «pendiente», no hay binario aprobado"
|
!!! 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
|
No flashees una unidad V3 o V4 con un archivo que no esté publicado aquí con su SHA-256. Los
|
||||||
@ -100,15 +135,44 @@ Referencia rápida de los parámetros que más se consultan:
|
|||||||
|
|
||||||
## Firmware y configuración del simpleRTK2B Pro (ZED-F9P)
|
## Firmware y configuración del simpleRTK2B Pro (ZED-F9P)
|
||||||
|
|
||||||
| Archivo | Estado | Notas |
|
Aquí hay que separar dos cosas que se confunden:
|
||||||
|
|
||||||
|
| Qué | De dónde sale | Estado |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Configuración del ZED-F9P (u-center `.txt`) | ⬜ **pendiente de publicar** | UART1 a 115200 (es lo que lee la tarjeta) y UART2 a la velocidad del módem |
|
| **Firmware del ZED-F9P** | **Estándar de u-blox**, no es nuestro: [u-blox → ZED-F9P → Documentation & resources → Firmware Update](https://www.u-blox.com/en/product/zed-f9p-module). Serie **HPG**. Se carga con **u-center** (solo Windows) a 115200 | ✅ disponible en el sitio de u-blox |
|
||||||
| Firmware del ZED-F9P (u-blox) | Se descarga de u-blox | Anotar la versión instalada con **UBX-MON-VER** |
|
| **Configuración de Helgen** (`.txt` de u-center) | **Nuestra.** Define UART1 a 115200 (que es lo que lee la tarjeta), UART2 a la velocidad del módem y qué mensajes NMEA salen | ⬜ **pendiente de publicar — este es el que de verdad falta** |
|
||||||
|
|
||||||
|
!!! warning "No actualices el firmware del F9P sin una razón concreta"
|
||||||
|
El firmware de u-blox **no tiene nada que ver con nuestras fallas**: las correcciones llegan por
|
||||||
|
el módem 4G, no por el GNSS, y en las capturas de junio los receptores estaban sanos. Actualizarlo
|
||||||
|
es un proceso solo-Windows que puede dejar el receptor a medias, y **borra o invalida la
|
||||||
|
configuración**, que es justo la parte que sí importa.
|
||||||
|
|
||||||
|
Primero anota qué versión trae (**u-center → View → Messages → UBX-MON-VER**) y déjala así.
|
||||||
|
Solo se actualiza si u-blox publica un arreglo para un problema que estemos viendo, y siempre en
|
||||||
|
banco, nunca en una unidad instalada.
|
||||||
|
|
||||||
Para leer o cargar la configuración: u-center → *Tools → Receiver Configuration*. Después de cargar,
|
Para leer o cargar la configuración: u-center → *Tools → Receiver Configuration*. Después de cargar,
|
||||||
**guardar en FLASH** y reconectar para confirmar que sobrevive al reinicio — una configuración que
|
**guardar en FLASH** y reconectar para confirmar que sobrevive al reinicio — una configuración que
|
||||||
solo quedó en RAM se pierde al primer corte de energía, y así perdimos tiempo en junio.
|
solo quedó en RAM se pierde al primer corte de energía, y así perdimos tiempo en junio.
|
||||||
|
|
||||||
|
## Cómo se publica un firmware nuevo
|
||||||
|
|
||||||
|
Para que un archivo pueda aparecer en esta página tiene que pasar por aquí. No es burocracia: un
|
||||||
|
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`.
|
||||||
|
- [ ] 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.
|
||||||
|
|
||||||
|
Los archivos viven en `docs/descargas/` del repositorio `hbox-docs` y se publican con
|
||||||
|
`./scripts/deploy.sh`. Los `.bin` de firmware de campo están en `.gitignore` por tamaño y por
|
||||||
|
control de versiones: se copian a mano a esa carpeta antes de publicar.
|
||||||
|
|
||||||
## Herramientas
|
## Herramientas
|
||||||
|
|
||||||
- **STM32CubeProgrammer** (Windows/macOS/Linux): https://www.st.com/en/development-tools/stm32cubeprog.html
|
- **STM32CubeProgrammer** (Windows/macOS/Linux): https://www.st.com/en/development-tools/stm32cubeprog.html
|
||||||
|
|||||||
@ -132,7 +132,11 @@ function render(actas){
|
|||||||
const vs = (porUnidad[u.serie]||[]);
|
const vs = (porUnidad[u.serie]||[]);
|
||||||
const box = el('div',{class:'unidad'});
|
const box = el('div',{class:'unidad'});
|
||||||
const cab = el('div',{class:'cab'});
|
const cab = el('div',{class:'cab'});
|
||||||
cab.append(el('div',null, el('div',{class:'s',text:u.serie}), el('div',{class:'m',text:(u.maquina||'')+(u.nota?' · '+u.nota:'')})));
|
cab.append(el('div',null,
|
||||||
|
el('div',{class:'s'}, el('a',{href:'../unidad.html?serie='+encodeURIComponent(u.serie),
|
||||||
|
style:'color:inherit;text-decoration:none;border-bottom:1px dotted currentColor',
|
||||||
|
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'];
|
const [cls,txt] = vs.length ? pill(vs[0]) : ['pend','Sin diagnóstico'];
|
||||||
cab.append(el('span',{class:'pill '+cls,text:txt}));
|
cab.append(el('span',{class:'pill '+cls,text:txt}));
|
||||||
box.append(cab);
|
box.append(cab);
|
||||||
|
|||||||
149
docs/formularios/reflasheo.html
Normal file
149
docs/formularios/reflasheo.html
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Acta de reflasheo y configuración — HBOX</title>
|
||||||
|
<link rel="stylesheet" href="acta.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="acta"></div>
|
||||||
|
<script src="flota.js"></script>
|
||||||
|
<script src="acta.js"></script>
|
||||||
|
<script>
|
||||||
|
const esHBOX = v => String(v.objetivo || '').startsWith('A');
|
||||||
|
const es4G = v => String(v.objetivo || '').startsWith('B');
|
||||||
|
const esARD = v => String(v.objetivo || '').startsWith('C');
|
||||||
|
const conEvidencia = v => v.subido && v.subido('log_antes');
|
||||||
|
|
||||||
|
Acta.render({
|
||||||
|
tipo: 'reflasheo',
|
||||||
|
repetible: true,
|
||||||
|
tecnicoDesdeUsuario: true,
|
||||||
|
nombres: { alan: 'Alán Basurto', daniel: 'Daniel Martínez', leo: 'Leo Rodríguez' },
|
||||||
|
unidades: window.FLOTA,
|
||||||
|
etiquetaSerie: 'Unidad o módulo que vas a reflashear',
|
||||||
|
titulo: 'Acta de reflasheo y configuración',
|
||||||
|
subtitulo: 'Helgen Technologies · solo en taller. Nunca sobre la máquina.',
|
||||||
|
textoOK: 'QUEDÓ BIEN',
|
||||||
|
intro: '<b>Un reflasheo es una prueba, no una reparación.</b> Sirve para contestar si el problema '
|
||||||
|
+ 'desaparece con firmware nuevo. Si la unidad tiene un corto, un conector roto o una batería '
|
||||||
|
+ 'hinchada, el firmware no va a arreglar nada. Y como el flasheo <b>borra la memoria del equipo</b> '
|
||||||
|
+ '(temperatura máxima, reinicios, causa del último arranque), la captura de antes es obligatoria: '
|
||||||
|
+ 'sin ella destruyes la evidencia que explicaba la falla. '
|
||||||
|
+ 'Procedimiento: <a href="../procedimientos/reflasheo-unidades/">Reflasheo de unidades</a>.',
|
||||||
|
secciones: [
|
||||||
|
|
||||||
|
{ titulo: '0 · Qué vas a reflashear',
|
||||||
|
items: [
|
||||||
|
{ tipo:'nota', txt:'<div class="peligro"><b>Solo en taller, con la unidad en la mesa.</b> Un flasheo '
|
||||||
|
+ 'interrumpido deja el equipo muerto: en la mesa se vuelve a intentar, arriba de un RTG hay que '
|
||||||
|
+ 'bajarlo otra vez.</div>' },
|
||||||
|
{ tipo:'fecha', clave:'fecha', req:true, guardaEn:'fecha', txt:'Fecha' },
|
||||||
|
{ tipo:'radio', clave:'objetivo', req:true, guardaEn:'clasificacion', txt:'¿Qué vas a cargar?',
|
||||||
|
opciones:[
|
||||||
|
'A · Firmware del HBOX (Blackpill / EinarBoard)',
|
||||||
|
'B · Parámetros del módem 4G NTRIP Master',
|
||||||
|
'C · Firmware o configuración del ArduSimple (ZED-F9P)'] },
|
||||||
|
{ tipo:'radio', clave:'motivo', guardaEn:'motivo', txt:'¿Por qué?',
|
||||||
|
opciones:['Falla en campo — a ver si cambia con firmware nuevo','Actualización planeada',
|
||||||
|
'Unidad nueva antes de instalar','Se perdió o corrompió la configuración','Otro'] },
|
||||||
|
{ tipo:'texto', clave:'nota_previa', rows:'2', txt:'¿Qué hacía la unidad antes de tocarla?' },
|
||||||
|
] },
|
||||||
|
|
||||||
|
{ titulo: '1 · Antes de tocar nada — la evidencia',
|
||||||
|
sub:'Este paso es la razón de ser del acta. Si no lo haces, el reflasheo no sirve como prueba de nada.',
|
||||||
|
items: [
|
||||||
|
{ tipo:'archivo', clave:'log_antes', etiqueta:'antes', req:true,
|
||||||
|
txt:'Captura de ANTES del reflasheo',
|
||||||
|
ayuda:'Terminal serie a 115200, guardar a archivo, 3 minutos. Nómbrala '
|
||||||
|
+ '<code>SERIE_antes_AAAA-MM-DD.log</code>.' },
|
||||||
|
{ tipo:'texto', clave:'fw_antes', rows:'1', req:true,
|
||||||
|
txt:'Versión que traía (de la línea BOOT, o "no se pudo leer")', ph:'fw=3.0.3' },
|
||||||
|
{ tipo:'nota', si:v=>!conEvidencia(v),
|
||||||
|
txt:'<div class="peligro">⛔ <b>No sigas todavía.</b> Sube la captura de antes: el resto del '
|
||||||
|
+ 'formulario se abre cuando esté guardada en el servidor.</div>' },
|
||||||
|
] },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- HBOX
|
||||||
|
{ titulo: '🔵 A · Firmware del HBOX', si:v=>esHBOX(v)&&conEvidencia(v),
|
||||||
|
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' },
|
||||||
|
{ 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.' },
|
||||||
|
{ tipo:'check', clave:'sha_ok', req:true,
|
||||||
|
txt:'Verifiqué el <b>SHA-256</b> del archivo y coincide con el publicado.' },
|
||||||
|
{ tipo:'check', clave:'dfu_ok', txt:'Entró en DFU: apareció <b>STM32 BOOTLOADER</b> (0483:DF11).' },
|
||||||
|
{ tipo:'check', clave:'grabado', txt:'Grabado en <code>0x08000000</code>: «Download complete» y «Verify successful».' },
|
||||||
|
{ tipo:'texto', clave:'fw_despues', rows:'1', txt:'Versión que quedó (línea BOOT después)', ph:'fw=3.0.3' },
|
||||||
|
{ tipo:'radio', clave:'arranque', txt:'Después de 10 minutos con antenas, ¿a qué estado llegó?',
|
||||||
|
opciones:['RUN con link OK','RUN pero link DEGR o LIMP','Se queda en BOOT','Se reinicia solo','Ya no arranca'] },
|
||||||
|
] },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- 4G
|
||||||
|
{ titulo: '🟠 B · Parámetros del módem 4G', si:v=>es4G(v)&&conEvidencia(v),
|
||||||
|
sub:'Por el puerto USB "XBEE" del simpleRTK2B Pro, a 115200. Solo las líneas que empiezan con $$$AS+.',
|
||||||
|
items: [
|
||||||
|
{ tipo:'nota', txt:'<div class="peligro"><b>Cuidado con lo que subes.</b> El archivo de parámetros '
|
||||||
|
+ 'lleno contiene <b>usuario y contraseña</b> del caster NTRIP y de la APN. Aquí sube la versión '
|
||||||
|
+ '<b>sin credenciales</b>: borra los valores de <code>AS+U</code>, <code>AS+q</code> y '
|
||||||
|
+ '<code>AS+Q</code> antes de subirlo. Lo que nos sirve guardar es qué quedó configurado, no las claves.</div>' },
|
||||||
|
{ tipo:'texto', clave:'imei', rows:'1', txt:'IMEI del módem' },
|
||||||
|
{ tipo:'texto', clave:'iccid', rows:'1', txt:'ICCID de la SIM' },
|
||||||
|
{ tipo:'check', clave:'lineas_ok', txt:'Todas las líneas contestaron <code>OK</code> (ninguna con <code>ERROR</code>).' },
|
||||||
|
{ tipo:'texto', clave:'as_k', rows:'1', req:true, txt:'ID de estación (<code>AS+K</code>) que quedó' },
|
||||||
|
{ tipo:'texto', clave:'mountpoint', rows:'1', txt:'Mountpoint (<code>AS+M</code>)' },
|
||||||
|
{ tipo:'archivo', clave:'params_4g', etiqueta:'parametros-4g', req:true,
|
||||||
|
txt:'Archivo de parámetros que cargaste, SIN credenciales',
|
||||||
|
ayuda:'Nómbralo <code>RTK-NNNN_parametros_AAAA-MM-DD.txt</code>. Queda guardado en el expediente de este módem.' },
|
||||||
|
{ tipo:'radio', clave:'registro', txt:'¿Registró en la red?',
|
||||||
|
opciones:['Sí, LED lento o fijo','No, sigue en parpadeo rápido','El LED no enciende'] },
|
||||||
|
{ tipo:'medida', clave:'rssi', txt:'RSSI, si el módulo lo reporta' },
|
||||||
|
{ tipo:'archivo', clave:'log_4g', etiqueta:'consola-4g', txt:'Captura de la consola del módem (opcional)',
|
||||||
|
ayuda:'Recuerda: esta consola no prueba que las correcciones lleguen. Sirve para ver que enciende y registra.' },
|
||||||
|
] },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- ArduSimple
|
||||||
|
{ titulo: '🟢 C · ArduSimple / ZED-F9P', si:v=>esARD(v)&&conEvidencia(v),
|
||||||
|
sub:'Por el USB POWER+GPS con u-center. Antena con vista al cielo, o no habrá fix y parecerá falla.',
|
||||||
|
items: [
|
||||||
|
{ tipo:'radio', clave:'ard_que', req:true, txt:'¿Qué cargaste?',
|
||||||
|
opciones:['Solo la configuración (archivo .txt de Helgen)','Firmware del ZED-F9P','Las dos cosas'] },
|
||||||
|
{ tipo:'texto', clave:'f9p_antes', rows:'1', txt:'Versión del ZED-F9P ANTES (UBX-MON-VER)' },
|
||||||
|
{ tipo:'texto', clave:'f9p_despues', rows:'1', txt:'Versión DESPUÉS (UBX-MON-VER)' },
|
||||||
|
{ tipo:'texto', clave:'cfg_nombre', rows:'1', txt:'Archivo de configuración cargado' },
|
||||||
|
{ tipo:'check', clave:'flash_ok', req:true,
|
||||||
|
txt:'Guardé la configuración en <b>FLASH</b> (no solo en RAM) y sobrevive al reinicio.',
|
||||||
|
aviso:'En junio perdimos tiempo por esto: una configuración que solo quedó en RAM se borra al primer corte de energía y el receptor vuelve a su estado anterior sin avisar.' },
|
||||||
|
{ tipo:'check', clave:'uart_ok', txt:'UART1 a <b>115200</b> (es lo que lee la tarjeta) y UART2 a la velocidad del módem.' },
|
||||||
|
{ tipo:'radio', clave:'fix', txt:'Calidad de fix alcanzada a cielo abierto',
|
||||||
|
opciones:['0 — sin posición','1 — GPS solo','2 — corregido básico','4 — RTK fijo','5 — RTK flotante'] },
|
||||||
|
{ tipo:'archivo', clave:'log_ard', etiqueta:'ardusimple', txt:'Captura NMEA o log de u-center (opcional)' },
|
||||||
|
] },
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- cierre
|
||||||
|
{ titulo: '2 · Después del reflasheo', si:conEvidencia,
|
||||||
|
items: [
|
||||||
|
{ tipo:'archivo', clave:'log_despues', etiqueta:'despues', req:true,
|
||||||
|
txt:'Captura de DESPUÉS del reflasheo',
|
||||||
|
ayuda:'Las dos capturas juntas — antes y después — son la prueba de si el firmware cambió algo. Por separado no dicen nada.' },
|
||||||
|
{ tipo:'radio', clave:'accion', req:true, guardaEn:'accion', txt:'¿Cómo queda?',
|
||||||
|
opciones:[
|
||||||
|
'Lista para reinstalar',
|
||||||
|
'En observación en el taller',
|
||||||
|
'Repite la misma falla — se envía a Querétaro',
|
||||||
|
'Empeoró: ya no arranca — se envía a Querétaro',
|
||||||
|
'Pendiente de más pruebas'] },
|
||||||
|
{ tipo:'texto', clave:'observaciones', rows:'4', req:true,
|
||||||
|
txt:'¿Cambió algo? Cuéntalo con tus palabras',
|
||||||
|
ph:'Antes se reiniciaba cada 3 minutos; después de cargar 3.0.3 lleva 40 minutos en RUN sin reiniciarse.' },
|
||||||
|
{ tipo:'foto', clave:'f_final', etiqueta:'final', txt:'Foto de cómo quedó (opcional).' },
|
||||||
|
] },
|
||||||
|
]
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -41,6 +41,15 @@ Sitio interno de Helgen Technologies. **Busca abajo lo que vas a hacer hoy.**
|
|||||||
|
|
||||||
[Procedimiento de reflasheo](procedimientos/reflasheo-unidades.md)
|
[Procedimiento de reflasheo](procedimientos/reflasheo-unidades.md)
|
||||||
|
|
||||||
|
- ### 🗂 Todo sobre una unidad
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
El expediente completo de un número de serie: cada acta, cada foto y cada archivo de log
|
||||||
|
que se ha subido, en una sola línea de tiempo.
|
||||||
|
|
||||||
|
[**▶ Abrir expediente**](unidad.html){ .md-button }
|
||||||
|
|
||||||
- ### 📊 ¿Qué se ha revisado?
|
- ### 📊 ¿Qué se ha revisado?
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@ -27,6 +27,12 @@ batería, y si el problema sigue después de reflashear.
|
|||||||
> De ahí la regla que aplica a la unidad del montacargas (**RTK-014**, todavía instalada):
|
> De ahí la regla que aplica a la unidad del montacargas (**RTK-014**, todavía instalada):
|
||||||
> primero la captura por USB en sitio, y hasta después se desmonta.
|
> primero la captura por USB en sitio, y hasta después se desmonta.
|
||||||
|
|
||||||
|
!!! info "Qué tarjeta traen estas unidades"
|
||||||
|
Las tres unidades desplegadas son **EinarBoard 1.0** corriendo firmware **v2/v3** (así lo
|
||||||
|
documenta `V4-PLAN.md` del repositorio de firmware). Eso importa para dos cosas: el binario que
|
||||||
|
les toca es el de generación **V3** —nunca el de bring-up del v5—, y el control de carga usa la
|
||||||
|
polaridad de producción (`CHRG_EN` en alto = carga **apagada**).
|
||||||
|
|
||||||
## 2. Equipo necesario
|
## 2. Equipo necesario
|
||||||
|
|
||||||
| Equipo | Para qué |
|
| Equipo | Para qué |
|
||||||
|
|||||||
@ -107,26 +107,52 @@ descárgalo otra vez; si sigue sin coincidir, no lo uses y avisa.
|
|||||||
|
|
||||||
*(Esta sección no es para el técnico: es para quien publica el `.bin`.)*
|
*(Esta sección no es para el técnico: es para quien publica el `.bin`.)*
|
||||||
|
|
||||||
Hoy el repositorio **no contiene binarios compilados de V3 ni de V4** — solo el de bring-up del V5,
|
Hoy el repositorio **no contiene binarios compilados de V3 ni de V4**. Los `.bin` están en
|
||||||
que sí está publicado y versionado. Los `.bin` de firmware están en `.gitignore` a propósito. Para
|
`.gitignore` a propósito. Y hay una diferencia importante entre las dos generaciones:
|
||||||
que el procedimiento de arriba funcione, cada generación necesita un binario **compilado, probado en
|
|
||||||
banco y publicado con su SHA256**.
|
|
||||||
|
|
||||||
Fuentes: `04-Source-Code/firmware/blackpillrtkcode_v3/` y `…_v4/`.
|
| Generación | Código fuente | Qué falta |
|
||||||
|
|---|---|---|
|
||||||
|
| **V3** | ✅ Existe: `04-Source-Code/firmware/blackpillrtkcode_v3/` (v3.0.3, 1361 líneas) | Compilarlo y probarlo en banco |
|
||||||
|
| **V4** | ✅ Existe: `blackpillrtkcode_v4/` (v4.0.0, 1536 líneas) — deriva de v3 y agrega la telemetría en banda | Compilarlo y probarlo en banco |
|
||||||
|
|
||||||
|
Las dos se compilan igual. La diferencia práctica: **V3 deja la unidad como está hoy**; **V4 además hace que la unidad reporte su salud al servidor por el módem**, sin cables nuevos (K1 y `BYP_CTRL` ya están en la EinarBoard 1.0).
|
||||||
|
|
||||||
|
El buen dato es que V3 no depende de ninguna librería externa — solo `<Arduino.h>` y `<math.h>` —
|
||||||
|
así que basta el core de STM32.
|
||||||
|
|
||||||
Compilación reproducible con `arduino-cli` (verifica el FQBN contra tu instalación del core
|
Compilación reproducible con `arduino-cli` (verifica el FQBN contra tu instalación del core
|
||||||
STM32duino antes de darlo por bueno):
|
STM32duino antes de darlo por bueno):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 1. toolchain (una sola vez)
|
||||||
|
brew install arduino-cli # o descárgalo de arduino.cc
|
||||||
|
arduino-cli config init
|
||||||
|
arduino-cli config add board_manager.additional_urls \
|
||||||
|
https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json
|
||||||
|
arduino-cli core update-index
|
||||||
arduino-cli core install STMicroelectronics:stm32
|
arduino-cli core install STMicroelectronics:stm32
|
||||||
|
|
||||||
|
# 2. confirma el identificador exacto de la tarjeta ANTES de compilar
|
||||||
|
arduino-cli board listall | grep -i blackpill
|
||||||
|
|
||||||
|
# 3. compila (ajusta el FQBN a lo que haya salido en el paso 2)
|
||||||
|
cd ~/Documents/Claude/Projects/Drogheda/04-Source-Code/firmware
|
||||||
arduino-cli compile \
|
arduino-cli compile \
|
||||||
--fqbn STMicroelectronics:stm32:GenF4:pnum=BLACKPILL_F411CE \
|
--fqbn STMicroelectronics:stm32:GenF4:pnum=BLACKPILL_F411CE \
|
||||||
--build-property "compiler.cpp.extra_flags=-DTARGET_EINARBOARD=1" \
|
|
||||||
--output-dir release/ \
|
--output-dir release/ \
|
||||||
blackpillrtkcode_v3/
|
blackpillrtkcode_v3/ # o blackpillrtkcode_v4/ para la version con telemetria
|
||||||
shasum -a 256 release/*.bin >> release/SHA256SUMS
|
|
||||||
|
# 4. nómbralo por su versión y saca su huella
|
||||||
|
mv release/blackpillrtkcode_v3.ino.bin release/hbox-fw-v3_3.0.3.bin
|
||||||
|
shasum -a 256 release/hbox-fw-v3_3.0.3.bin | tee -a release/SHA256SUMS
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**El destino de tarjeta ya viene bien por omisión:** el propio `.ino` trae `TARGET_EINARBOARD 1` y
|
||||||
|
`TARGET_PROTO 0`, que es lo correcto para la flota desplegada — las unidades de campo son
|
||||||
|
**EinarBoard 1.0** corriendo v2/v3 (así lo dice el `V4-PLAN.md`). Solo habría que tocarlo si vas a
|
||||||
|
flashear una tarjeta de protoboard, y en ese caso se edita el `.ino`, no se pasa por la línea de
|
||||||
|
comandos.
|
||||||
|
|
||||||
!!! warning "El flag de destino de tarjeta no es cosmético"
|
!!! warning "El flag de destino de tarjeta no es cosmético"
|
||||||
`TARGET_EINARBOARD` y `TARGET_PROTO` **invierten la polaridad del control de carga** (en
|
`TARGET_EINARBOARD` y `TARGET_PROTO` **invierten la polaridad del control de carga** (en
|
||||||
producción `CHRG_EN` en alto = carga **apagada**; en protoboard `CHRG_RUN` en alto = carga
|
producción `CHRG_EN` en alto = carga **apagada**; en protoboard `CHRG_RUN` en alto = carga
|
||||||
|
|||||||
240
docs/unidad.html
Normal file
240
docs/unidad.html
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Expediente de la unidad — HBOX</title>
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--naranja:#e8590c; --tinta:#1d1c19; --tinta2:#5a564d; --linea:#e6e1d6;
|
||||||
|
--papel:#faf8f4; --tarjeta:#fff; --superf:#fcfcfb;
|
||||||
|
--ok:#2f9e44; --ok-bg:#ebfbee; --falla:#e03131; --falla-bg:#fff5f5;
|
||||||
|
--pend:#adb5bd; --pend-bg:#f4f2ee; --info:#1971c2; --info-bg:#e7f1fb;
|
||||||
|
}
|
||||||
|
@media(prefers-color-scheme:dark){:root:not([data-theme=light]){
|
||||||
|
--papel:#191b1e;--tinta:#e6e1d6;--tinta2:#a09a8d;--linea:#33363b;--tarjeta:#1f2226;--superf:#1b1d20;
|
||||||
|
--ok:#69db7c;--ok-bg:#1b2a1f;--falla:#ff8787;--falla-bg:#2c1d1d;--pend:#5c636a;--pend-bg:#26282c;--info:#74c0fc;--info-bg:#182734;}}
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
body{margin:0;background:var(--papel);color:var(--tinta);font:15px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
|
||||||
|
.wrap{max-width:1020px;margin:0 auto;padding:18px 16px 70px}
|
||||||
|
header.top{background:var(--tinta);color:#fff;padding:16px;border-radius:12px;margin-bottom:16px}
|
||||||
|
header.top h1{margin:0;font-size:21px}
|
||||||
|
header.top .meta{color:#d7d2c8;font-size:13.5px;margin-top:4px}
|
||||||
|
header.top a{color:#fff;font-size:13px;text-decoration:none;opacity:.85;margin-right:14px}
|
||||||
|
.buscar{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:16px}
|
||||||
|
select,input{padding:10px 12px;border:1px solid var(--linea);border-radius:9px;font-size:15px;
|
||||||
|
background:var(--tarjeta);color:var(--tinta)}
|
||||||
|
button{background:var(--naranja);color:#fff;border:0;border-radius:9px;padding:10px 18px;font-size:14px;font-weight:600;cursor:pointer}
|
||||||
|
.kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:12px;margin-bottom:18px}
|
||||||
|
.kpi{background:var(--tarjeta);border:1px solid var(--linea);border-radius:12px;padding:14px 16px}
|
||||||
|
.kpi .n{font-size:28px;font-weight:700;line-height:1}
|
||||||
|
.kpi .l{color:var(--tinta2);font-size:12.5px;margin-top:4px}
|
||||||
|
section.bloque{background:var(--tarjeta);border:1px solid var(--linea);border-radius:12px;padding:16px;margin:14px 0}
|
||||||
|
section.bloque h2{margin:0 0 4px;font-size:17px}
|
||||||
|
section.bloque .sub{color:var(--tinta2);font-size:13.5px;margin:0 0 12px}
|
||||||
|
.linea{position:relative;padding-left:20px;border-left:2px solid var(--linea);margin-left:6px}
|
||||||
|
.ev{position:relative;padding:12px 0 12px 4px;border-bottom:1px solid var(--linea)}
|
||||||
|
.ev:last-child{border-bottom:0}
|
||||||
|
.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 .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}
|
||||||
|
.ev .cl{margin-top:3px}
|
||||||
|
.pill{font-size:11px;font-weight:700;padding:2px 9px;border-radius:99px;white-space:nowrap}
|
||||||
|
.pill.ok{background:var(--ok-bg);color:var(--ok)}
|
||||||
|
.pill.falla{background:var(--falla-bg);color:var(--falla)}
|
||||||
|
.pill.info{background:var(--info-bg);color:var(--info)}
|
||||||
|
.pill.pend{background:var(--pend-bg);color:var(--tinta2)}
|
||||||
|
.fotos{display:flex;flex-wrap:wrap;gap:8px;margin-top:9px}
|
||||||
|
.fotos a{display:block;width:78px;height:78px;border-radius:8px;overflow:hidden;border:1px solid var(--linea)}
|
||||||
|
.fotos img{width:100%;height:100%;object-fit:cover;display:block;background:var(--pend-bg)}
|
||||||
|
.logs{display:flex;flex-wrap:wrap;gap:8px;margin-top:9px}
|
||||||
|
.logs a{font-size:12.5px;font-weight:600;color:var(--info);text-decoration:none;
|
||||||
|
border:1px solid var(--linea);border-radius:7px;padding:5px 10px;background:var(--superf)}
|
||||||
|
details{margin-top:8px}
|
||||||
|
summary{cursor:pointer;color:var(--naranja);font-size:13px;font-weight:600}
|
||||||
|
table.det{border-collapse:collapse;width:100%;font-size:13px;margin-top:8px}
|
||||||
|
table.det td{padding:5px 8px 5px 0;border-bottom:1px solid var(--linea);vertical-align:top}
|
||||||
|
table.det td.k{color:var(--tinta2);width:46%}
|
||||||
|
.vacio{color:var(--tinta2);font-style:italic;font-size:14px}
|
||||||
|
.err{background:var(--falla-bg);color:var(--falla);border:1px solid var(--falla);padding:10px 14px;border-radius:10px}
|
||||||
|
@media print{header.top{background:#fff;color:#000} .buscar,button{display:none}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="wrap">
|
||||||
|
<header class="top">
|
||||||
|
<div><a href="./">← Documentación</a><a href="diagnostico/tablero-campo.html">Tablero de campo</a>
|
||||||
|
<a href="#" onclick="window.print();return false">Imprimir</a></div>
|
||||||
|
<h1 id="titulo">Expediente de la unidad</h1>
|
||||||
|
<div class="meta" id="meta">Elige una unidad para ver todo su historial.</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="buscar">
|
||||||
|
<select id="sel"><option value="">— elegir unidad —</option></select>
|
||||||
|
<input id="libre" placeholder="…o escribe una serie: RTK-014, ARD-007, V5-03">
|
||||||
|
<button id="ir">Ver expediente</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="cont"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="formularios/flota.js"></script>
|
||||||
|
<script>
|
||||||
|
const el=(t,a,...k)=>{const e=document.createElement(t); if(a) for(const p in a){ if(p==='html')e.innerHTML=a[p]; else if(p==='text')e.textContent=a[p]; else e.setAttribute(p,a[p]); } k.forEach(c=>e.append(c)); return e;};
|
||||||
|
const $=i=>document.getElementById(i);
|
||||||
|
const cont=$('cont');
|
||||||
|
const fecha=s=>(s||'').replace('T',' ').slice(0,16);
|
||||||
|
const kb=n=>n>1048576?(n/1048576).toFixed(1)+' MB':Math.max(1,Math.round(n/1024))+' kB';
|
||||||
|
|
||||||
|
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'],
|
||||||
|
'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']};
|
||||||
|
|
||||||
|
// lista de unidades: la flota + lo que el servidor ya tenga guardado
|
||||||
|
(async () => {
|
||||||
|
const vistas = new Set();
|
||||||
|
(window.FLOTA||[]).forEach(u => {
|
||||||
|
vistas.add(u.serie);
|
||||||
|
$('sel').append(el('option',{value:u.serie,text:u.serie+' — '+(u.maquina||'')}));
|
||||||
|
});
|
||||||
|
try{
|
||||||
|
const j = await (await fetch('/api/series')).json();
|
||||||
|
(j.series||[]).forEach(s => { if(!vistas.has(s.serie))
|
||||||
|
$('sel').append(el('option',{value:s.serie,text:s.serie+' ('+s.tipos.join(', ')+')'})); });
|
||||||
|
}catch(e){}
|
||||||
|
const q = new URLSearchParams(location.search).get('serie');
|
||||||
|
if(q){ $('sel').value=q; $('libre').value=q; cargar(q); }
|
||||||
|
})();
|
||||||
|
|
||||||
|
$('ir').addEventListener('click',()=>{ const s=($('libre').value||$('sel').value||'').trim().toUpperCase(); if(s) irA(s); });
|
||||||
|
$('sel').addEventListener('change',()=>{ if($('sel').value){ $('libre').value=''; irA($('sel').value); } });
|
||||||
|
$('libre').addEventListener('keydown',e=>{ if(e.key==='Enter') $('ir').click(); });
|
||||||
|
function irA(s){ history.replaceState(null,'','?serie='+encodeURIComponent(s)); cargar(s); }
|
||||||
|
|
||||||
|
async function detalle(tipo, serie, archivo, caja){
|
||||||
|
caja.textContent='Cargando…';
|
||||||
|
try{
|
||||||
|
const j = await (await fetch(`/api/acta?tipo=${encodeURIComponent(tipo)}&serie=${encodeURIComponent(serie)}&archivo=${encodeURIComponent(archivo)}`)).json();
|
||||||
|
if(!j.exists) throw new Error('no encontrada');
|
||||||
|
const t=el('table',{class:'det'}); const res=j.acta.respuestas||{};
|
||||||
|
Object.keys(res).forEach(k=>{
|
||||||
|
const v=res[k]; if(v.val===''||v.val===false||v.val==null) return;
|
||||||
|
t.append(el('tr',null, el('td',{class:'k',text:(v.lbl||k).replace(/<[^>]+>/g,'')}),
|
||||||
|
el('td',{text: v.val===true?'✓':String(v.val)})));
|
||||||
|
});
|
||||||
|
caja.innerHTML=''; caja.append(t.rows.length?t:el('div',{class:'vacio',text:'El acta no tiene respuestas guardadas.'}));
|
||||||
|
}catch(e){ caja.textContent='No se pudo cargar el detalle.'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function galeria(rutas){
|
||||||
|
const g=el('div',{class:'fotos'});
|
||||||
|
rutas.forEach(r=>{
|
||||||
|
const a=el('a',{href:'/api/adjunto?ruta='+encodeURIComponent(r),target:'_blank'});
|
||||||
|
a.append(el('img',{src:'/api/adjunto?ruta='+encodeURIComponent(r),loading:'lazy'}));
|
||||||
|
g.append(a);
|
||||||
|
});
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(j){
|
||||||
|
cont.innerHTML='';
|
||||||
|
const u=(window.FLOTA||[]).find(x=>x.serie===j.serie);
|
||||||
|
$('titulo').textContent='Expediente · '+j.serie;
|
||||||
|
$('meta').innerHTML = u
|
||||||
|
? '<b>'+(u.maquina||'')+'</b> · '+(u.terminal||'')+(u.nota?'<br>'+u.nota:'')
|
||||||
|
: 'Esta serie no está en el registro de flota (flota.js).';
|
||||||
|
|
||||||
|
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']]
|
||||||
|
.forEach(([n,l])=>kp.append(el('div',{class:'kpi'},el('div',{class:'n',text:String(n)}),el('div',{class:'l',text:l}))));
|
||||||
|
cont.append(kp);
|
||||||
|
|
||||||
|
// ---- línea de tiempo ----
|
||||||
|
const b=el('section',{class:'bloque'});
|
||||||
|
b.append(el('h2',{text:'Historial'}), el('p',{class:'sub',text:'Todo lo registrado para esta serie, de lo más reciente a lo más viejo.'}));
|
||||||
|
if(!j.actas.length) b.append(el('div',{class:'vacio',text:'Todavía no hay ninguna acta para esta unidad.'}));
|
||||||
|
else{
|
||||||
|
const lt=el('div',{class:'linea'});
|
||||||
|
j.actas.forEach(a=>{
|
||||||
|
const [nom,cls]=NOMBRE[a.tipo]||[a.tipo,'pend'];
|
||||||
|
const ev=el('div',{class:'ev '+cls});
|
||||||
|
const cab=el('div',{class:'cab'});
|
||||||
|
cab.append(el('div',null, el('div',{class:'t',text:nom}),
|
||||||
|
el('div',{class:'f',text:(a.fecha?a.fecha+' · ':'')+fecha(a.recibido)+' · '+(a.tecnico||'—')+(a.maquina?' · '+a.maquina:'')})));
|
||||||
|
const r=String(a.resultado||'');
|
||||||
|
cab.append(el('span',{class:'pill '+(r==='FALLA'?'falla':r?'ok':'pend'),text:r||'—'}));
|
||||||
|
ev.append(cab);
|
||||||
|
if(a.motivo) ev.append(el('div',{class:'cl',text:'Motivo: '+a.motivo}));
|
||||||
|
if(a.clasificacion) ev.append(el('div',{class:'cl',html:'<b>'+a.clasificacion+'</b>'}));
|
||||||
|
if(a.accion) ev.append(el('div',{class:'cl',text:'→ '+a.accion}));
|
||||||
|
if(a.fotos.length) ev.append(galeria(a.fotos));
|
||||||
|
if(a.archivos.length){
|
||||||
|
const g=el('div',{class:'logs'});
|
||||||
|
a.archivos.forEach(x=>g.append(el('a',{href:'/api/adjunto?ruta='+encodeURIComponent(x.ruta),
|
||||||
|
target:'_blank',text:'📄 '+(x.nombre||'captura')})));
|
||||||
|
ev.append(g);
|
||||||
|
}
|
||||||
|
const det=el('details'); det.append(el('summary',{text:'Ver todas las respuestas'}));
|
||||||
|
const caja=el('div'); det.append(caja);
|
||||||
|
det.addEventListener('toggle',()=>{ if(det.open&&!caja.dataset.l){ caja.dataset.l='1'; detalle(a.tipo,j.serie,a.archivo,caja);} });
|
||||||
|
ev.append(det);
|
||||||
|
lt.append(ev);
|
||||||
|
});
|
||||||
|
b.append(lt);
|
||||||
|
}
|
||||||
|
cont.append(b);
|
||||||
|
|
||||||
|
// ---- todos los archivos ----
|
||||||
|
const f=el('section',{class:'bloque'});
|
||||||
|
f.append(el('h2',{text:'Archivos de log'}),
|
||||||
|
el('p',{class:'sub',text:'Todas las capturas subidas para esta serie, vengan del acta que vengan.'}));
|
||||||
|
if(!j.logs.length) f.append(el('div',{class:'vacio',text:'Sin archivos de log.'}));
|
||||||
|
else{
|
||||||
|
const g=el('div',{class:'logs'});
|
||||||
|
j.logs.forEach(l=>g.append(el('a',{href:'/api/adjunto?ruta='+encodeURIComponent(l.ruta),target:'_blank',
|
||||||
|
text:'📄 '+l.nombre+' ('+kb(l.bytes)+')'})));
|
||||||
|
f.append(g);
|
||||||
|
}
|
||||||
|
cont.append(f);
|
||||||
|
|
||||||
|
const ph=el('section',{class:'bloque'});
|
||||||
|
ph.append(el('h2',{text:'Todas las fotos'}), el('p',{class:'sub',text:j.total_fotos+' foto(s) acumuladas.'}));
|
||||||
|
if(!j.fotos.length) ph.append(el('div',{class:'vacio',text:'Sin fotos.'}));
|
||||||
|
else ph.append(galeria(j.fotos.map(x=>x.ruta)));
|
||||||
|
cont.append(ph);
|
||||||
|
|
||||||
|
if(j.relacionadas.length){
|
||||||
|
const r=el('section',{class:'bloque'});
|
||||||
|
r.append(el('h2',{text:'Aparece como componente en'}),
|
||||||
|
el('p',{class:'sub',text:'Actas de otras unidades que incluyen esta serie entre sus módulos.'}));
|
||||||
|
j.relacionadas.forEach(a=>{
|
||||||
|
const [nom]=NOMBRE[a.tipo]||[a.tipo];
|
||||||
|
r.append(el('div',{class:'ev'},
|
||||||
|
el('div',{class:'t',html:nom+' — <a href="?serie='+encodeURIComponent(a.serie)+'">'+a.serie+'</a>'}),
|
||||||
|
el('div',{class:'f',text:fecha(a.recibido)+' · '+(a.tecnico||'—')+' · '+(a.resultado||'')})));
|
||||||
|
});
|
||||||
|
cont.append(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cargar(serie){
|
||||||
|
cont.innerHTML='<p class="vacio">Cargando…</p>';
|
||||||
|
try{
|
||||||
|
const j=await (await fetch('/api/unidad?serie='+encodeURIComponent(serie),{cache:'no-store'})).json();
|
||||||
|
if(!j.ok) throw new Error(j.error||'error');
|
||||||
|
render(j);
|
||||||
|
}catch(e){
|
||||||
|
cont.innerHTML='';
|
||||||
|
cont.append(el('div',{class:'err',text:'No se pudo leer el expediente: '+e.message}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -51,6 +51,7 @@ nav:
|
|||||||
- "Cómo se diagnostica en banco": procedimientos/diagnostico-banco.md
|
- "Cómo se diagnostica en banco": procedimientos/diagnostico-banco.md
|
||||||
- "▶ Acta de diagnóstico en banco": formularios/banco.html
|
- "▶ Acta de diagnóstico en banco": formularios/banco.html
|
||||||
- "⚡ Reflasheo de unidades": procedimientos/reflasheo-unidades.md
|
- "⚡ Reflasheo de unidades": procedimientos/reflasheo-unidades.md
|
||||||
|
- "▶ Acta de reflasheo y configuración": formularios/reflasheo.html
|
||||||
|
|
||||||
- "🏭 Producción — lote v5":
|
- "🏭 Producción — lote v5":
|
||||||
- "1 · Prueba y flasheo de tarjetas": procedimientos/v5-prueba-y-flasheo.md
|
- "1 · Prueba y flasheo de tarjetas": procedimientos/v5-prueba-y-flasheo.md
|
||||||
@ -74,6 +75,7 @@ nav:
|
|||||||
- "Firmware, parámetros 4G y ZED-F9P": descargas/index.md
|
- "Firmware, parámetros 4G y ZED-F9P": descargas/index.md
|
||||||
- "README firmware bring-up v5": descargas/hbox-v5-bringup-README.md
|
- "README firmware bring-up v5": descargas/hbox-v5-bringup-README.md
|
||||||
|
|
||||||
|
- "🗂 Expediente de una unidad": unidad.html
|
||||||
- "📋 Actas — índice y cómo se usan": actas/index.md
|
- "📋 Actas — índice y cómo se usan": actas/index.md
|
||||||
|
|
||||||
- "🌿 Git y archivos de diseño":
|
- "🌿 Git y archivos de diseño":
|
||||||
|
|||||||
@ -23,6 +23,7 @@ declare -a MAP=(
|
|||||||
"03-Architecture-Hardware/kicad/docs/GIT-GUIDE_External-Collaborators_EN.md|docs/git/git-guide-en.md"
|
"03-Architecture-Hardware/kicad/docs/GIT-GUIDE_External-Collaborators_EN.md|docs/git/git-guide-en.md"
|
||||||
# Descargas para técnicos (firmware de bring-up v5)
|
# 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/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/hbox_v5_bringup/release/SHA256SUMS|docs/descargas/SHA256SUMS.txt"
|
"04-Source-Code/firmware/hbox_v5_bringup/release/SHA256SUMS|docs/descargas/SHA256SUMS.txt"
|
||||||
"04-Source-Code/firmware/hbox_v5_bringup/README.md|docs/descargas/hbox-v5-bringup-README.md"
|
"04-Source-Code/firmware/hbox_v5_bringup/README.md|docs/descargas/hbox-v5-bringup-README.md"
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user