hbox-docs/backend
2026-10-13 20:36:43 -06:00
..
actas-hbox.service Equipo y usuarios (roster, accesos desde el sitio), responsable obligatorio, pendiente ferretería EIT/TIMSA (14-oct) 2026-10-13 20:25:59 -06:00
app.py Equipo y usuarios (roster, accesos desde el sitio), responsable obligatorio, pendiente ferretería EIT/TIMSA (14-oct) 2026-10-13 20:25:59 -06:00
parche-nginx-api.py diagnostico en campo y banco: actas con fotos y logs, reflasheo, portada e indice 2026-09-18 11:27:40 -06:00
README.md Equipo y usuarios (roster, accesos desde el sitio), responsable obligatorio, pendiente ferretería EIT/TIMSA (14-oct) 2026-10-13 20:25:59 -06:00
requirements.txt actas: formularios de prueba V5/V4 + checklist de ensamble + backend 2026-09-16 18:05:16 +00:00
seed_1013.py Cabecera indigo fija (legible en modo oscuro) en tableros y actas; seed: gibranlp 2026-10-13 20:36:43 -06:00
tests-1013.sh Equipo y usuarios (roster, accesos desde el sitio), responsable obligatorio, pendiente ferretería EIT/TIMSA (14-oct) 2026-10-13 20:25:59 -06:00
tests-1014.sh Equipo y usuarios (roster, accesos desde el sitio), responsable obligatorio, pendiente ferretería EIT/TIMSA (14-oct) 2026-10-13 20:25:59 -06:00
TESTS-1013.md Reorganizacion del sitio (8 pestanas, Historia), tableros kanban/ingenieria/produccion, inventario, sitios, acta de instalacion, backend nuevo (13-oct) 2026-10-13 19:44:07 -06:00
TESTS-1014.md Equipo y usuarios (roster, accesos desde el sitio), responsable obligatorio, pendiente ferretería EIT/TIMSA (14-oct) 2026-10-13 20:25:59 -06:00

Backend de actas — configuración en el servidor (una sola vez)

Servicio Flask que recibe los formularios firmados de los técnicos y los guarda como JSON por número de serie en /srv/projects/drogheda/actas/. Corre detrás de nginx en 127.0.0.1:8081; nginx expone /api/ en el mismo dominio, con la misma contraseña.

1. Instalar (en el servidor, como leo)

cd /srv/projects/drogheda/hbox-docs/backend
python3 -m venv venv
./venv/bin/pip install -r requirements.txt

# carpeta donde se guardan las actas
mkdir -p /srv/projects/drogheda/actas

2. Servicio systemd (arranque automático)

sudo cp actas-hbox.service /etc/systemd/system/actas-hbox.service
sudo systemctl daemon-reload
sudo systemctl enable --now actas-hbox
systemctl status actas-hbox            # debe decir "active (running)"
curl -s http://127.0.0.1:8081/api/salud # {"ok":true,...}

3. nginx — exponer /api/ en el mismo dominio

Dentro del bloque server { ... } de drogheda.helgen.online (el mismo archivo /etc/nginx/sites-enabled/drogheda.helgen.online.conf, en la parte de listen 443), agrega antes del location / {:

    location /api/ {
        proxy_pass http://127.0.0.1:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        # hereda el auth_basic del server; si quieres, puedes repetirlo aquí.
    }

Luego:

sudo nginx -t && sudo systemctl reload nginx
curl -u leo:TU_PASS https://drogheda.helgen.online/api/salud   # {"ok":true,...}

4. Ver las actas recibidas

  • Índice rápido por tipo: /srv/projects/drogheda/actas/<tipo>/_indice.csv (ábrelo en Excel).
  • Listado JSON: https://drogheda.helgen.online/api/actas
  • Un acta concreta: https://drogheda.helgen.online/api/actas/prueba-v5/V5-07_....json
  • Respaldo: basta con copiar la carpeta /srv/projects/drogheda/actas/.

Actualizar el backend

Tras un git pull que cambie app.py:

sudo systemctl restart actas-hbox

5. Colecciones del 13-oct — tableros, plan, inventario y sitios

Mismo esquema que las actas: un JSON por registro más un _indice.csv que se regenera en cada cambio. Carpetas (variables de entorno, con su valor por omisión junto a ACTAS_DIR):

Variable Omisión Qué guarda
TAREAS_DIR /srv/projects/drogheda/tareas pendientes del tablero (kanban)
INVENTARIO_DIR /srv/projects/drogheda/inventario items/ y movimientos/
SITIOS_DIR /srv/projects/drogheda/sitios un JSON por sitio (icave.json…)
PLAN_FILE /srv/projects/drogheda/plan-produccion.json el plan del pedido (570 → 113/327/130)
EQUIPO_DIR /srv/projects/drogheda/equipo miembros del equipo (14-oct) + _accesos.csv
HTPASSWD_LOCK /srv/projects/drogheda/.htpasswd.lock candado para escribir el htpasswd desde Equipo

Reglas comunes: toda escritura exige el usuario autenticado (X-Remote-User; sin él, 403); cada registro guarda creado_por/creado_en/modificado_por/modificado_en y un historial de {quien, cuando, cambio}; solo un administrador borra (ACTAS_ADMINS). Un POST puede traer su propio id (lo usa seed_1013.py para ser idempotente): si ya existe → 409.

Siembra inicial (plan, 4 sitios, ítems de inventario, pendientes del 13-oct), idempotente:

cd /srv/projects/drogheda/hbox-docs && ./backend/venv/bin/python3 backend/seed_1013.py
# agrega --usuario leo (omisión) para firmar la siembra; --extra tareas.json para importar más pendientes

Pendientes (tablero kanban) — tareas

Campos: id, titulo, descripcion, pista ∈ ingenieria·produccion·mecanica·compras·campo, estado ∈ por-hacer·en-curso·bloqueado·hecho, responsable, fecha_limite (AAAA-MM-DD o null), enlaces [{titulo,url}], bloqueada_por [ids], orden, etiquetas [], version_tarjeta ∈ V5.5·V5.6·V5.7·v6·—.

U=leo:PASS; D=https://drogheda.helgen.online
curl -s -u $U "$D/api/tareas?pista=ingenieria&estado=en-curso&responsable=michelle"
curl -s -u $U -H 'Content-Type: application/json' -X POST $D/api/tareas \
     -d '{"titulo":"V5.6 pasada 2","pista":"ingenieria","responsable":"michelle","estado":"en-curso",
          "version_tarjeta":"V5.6","fecha_limite":"2026-10-17","enlaces":[{"titulo":"Pasada 2","url":"/hardware/v56-layout-pasada-2/"}]}'
# mover de columna (y posición): PATCH parcial; el historial anota quién y qué cambió
curl -s -u $U -H 'Content-Type: application/json' -X PATCH "$D/api/tarea?id=T261013-ab12" \
     -d '{"estado":"hecho","orden":0,"_nota":"commit d9f1 subido"}'
# renumerar una columna después de arrastrar (no escribe historial)
curl -s -u $U -H 'Content-Type: application/json' -X POST $D/api/tareas/orden \
     -d '{"estado":"en-curso","ids":["T261013-ab12","t-v57-fuente"]}'
curl -s -u $U -X DELETE "$D/api/tarea?id=T261013-ab12"        # solo admin → 403 para los demás
curl -s -u $U $D/api/tareas/resumen                            # conteo pista×estado + vencidas

Plan de producción — plan

curl -s -u $U $D/api/plan        # si no existe el archivo devuelve el plan por omisión con "_default": true
curl -s -u $U -H 'Content-Type: application/json' -X PUT $D/api/plan -d '{
  "pedido_total": 570,
  "versiones": [{"version":"V5.6","objetivo":113,"nota":"con Mean Well SD-15B","serie_prefijo":"V56","fecha":"2026-11-15"},
                {"version":"V5.7","objetivo":327,"nota":"fuente a bordo","serie_prefijo":"V57"},
                {"version":"v6","objetivo":130,"nota":"+ reemplazos futuros","serie_prefijo":"V6"}]}'   # solo admin

etapas (embudo) se puede omitir: se conserva la que ya había. Cada etapa dice de qué tipos de acta se cuenta (tipos), si solo cuentan las aprobadas (ok) y si solo cuentan unidades HBOX (solo_unidades). El tablero de producción hace el conteo en el navegador con /api/actas.

Inventario — inventario + movimientos

El stock no se edita a mano: es la suma de los movimientos del ítem (entrada +n, salida −n, ajuste = conteo físico; el servidor calcula el delta). Una salida mayor al stock registrado → 409.

curl -s -u $U "$D/api/inventario?categoria=fuente&aplica_a=V5.6&alertas=1"   # alerta = stock < minimo
curl -s -u $U -H 'Content-Type: application/json' -X POST $D/api/inventario \
     -d '{"codigo":"SD-15B-05","descripcion":"Mean Well SD-15B-05","categoria":"fuente","aplica_a":["V5.6"],
          "unidad":"pza","minimo":113,"ubicacion":"Anaquel B1","proveedor":"Mouser","codigo_proveedor":"709-SD15B05","stock_inicial":0}'
curl -s -u $U -H 'Content-Type: application/json' -X PATCH "$D/api/inventario/item?id=SD-15B-05" -d '{"ubicacion":"Anaquel B2","minimo":120}'
curl -s -u $U -H 'Content-Type: application/json' -X POST $D/api/inventario/movimiento \
     -d '{"item_id":"SD-15B-05","tipo":"entrada","cantidad":50,"motivo":"llegó pedido Mouser","referencia":"OC-0042"}'
curl -s -u $U -H 'Content-Type: application/json' -X POST $D/api/inventario/movimiento \
     -d '{"item_id":"EB-V56","tipo":"salida","cantidad":1,"motivo":"ensamble","referencia":"HBOX5-E07-A010-R0006"}'
curl -s -u $U -H 'Content-Type: application/json' -X POST $D/api/inventario/movimiento \
     -d '{"item_id":"EB-V56","tipo":"ajuste","cantidad":12,"motivo":"conteo físico 13-oct"}'
curl -s -u $U "$D/api/inventario/movimientos?item_id=EB-V56&n=50"
curl -s -u $U -o inventario.csv $D/api/inventario/export.csv

Sitios — sitios

curl -s -u $U "$D/api/sitios"                 # activos; &todos=1 incluye los inactivos
curl -s -u $U -H 'Content-Type: application/json' -X POST $D/api/sitios -d '{"id":"icave","nombre":"ICAVE",
     "cliente":"Hutchison Ports","ubicacion":"Veracruz, Ver.","alias":["Veracruz"],
     "contactos":[{"nombre":"por confirmar","rol":"supervisor de patio","telefono":"","correo":""}],"notas":""}'   # solo admin
curl -s -u $U -H 'Content-Type: application/json' -X PATCH "$D/api/sitio?id=icave" -d '{"activo":true,"ubicacion":"Veracruz, Ver."}'   # solo admin
curl -s -u $U "$D/api/sitio/unidades?id=icave"

/api/sitio/unidades junta las actas de envío, recepción, instalación, diagnóstico y daño cuyo campo sitio sea el slug del sitio (coincidencia: "sitio"), y también las que solo coinciden por texto (terminal de la flota, destino del envío) con el nombre o un alias (coincidencia: "texto"), agrupadas por unidad con su marca de daño vigente.

6. Equipo y accesos (14-oct)

Dos cosas distintas: un miembro del equipo (EQUIPO_DIR, omisión /srv/projects/drogheda/equipo, un JSON por persona) es quien puede ser responsable de un pendiente; un acceso es una línea en el htpasswd de nginx. tiene_login se deduce del htpasswd al leer; es_admin de ACTAS_ADMINS. Los accesos se crean y quitan desde la página Equipo (admin), con la misma rutina $apr1$ y la misma escritura atómica que /api/password, bajo un flock (HTPASSWD_LOCK, omisión /srv/projects/drogheda/.htpasswd.lock). Bitácora de altas/bajas en equipo/_accesos.csv. Requisito en el servidor: el servicio debe poder escribir el htpasswd (chown leo:www-data + chmod 640, §7 de DESPLIEGUE.md).

Campos del miembro: id (= usuario del sitio si lo tiene), nombre, rol, area ∈ direccion·operaciones· ingenieria·layout·produccion·pruebas·fabricacion·software·campo·asistente, correo, telefono, activo.

curl -s -u $U "$D/api/equipo"                 # activos, con tiene_login, es_admin y pendientes {abiertos, vencidas, por estado}
curl -s -u $U "$D/api/equipo?todos=1"         # incluye inactivos; para admin trae logins_sin_miembro
curl -s -u $U -H 'Content-Type: application/json' -X POST $D/api/equipo \
     -d '{"id":"adriana","nombre":"Adriana","rol":"Operaciones; lleva el PM","area":"operaciones"}'          # admin; sin acceso al sitio
curl -s -u $U -H 'Content-Type: application/json' -X PATCH "$D/api/equipo/miembro?id=aldair" -d '{"rol":"Fabricación y ensamble"}'   # admin
curl -s -u $U -H 'Content-Type: application/json' -X PATCH "$D/api/equipo/miembro?id=aldair" -d '{"activo":false}'   # desactivar (nunca se borra)

# acceso al sitio: crea la línea en el htpasswd (409 si existe; 400 si la contraseña es más corta que PASS_MIN)
# y crea/actualiza al miembro. La contraseña inicial se pasa en persona; la cambia en /cuenta.html.
curl -s -u $U -H 'Content-Type: application/json' -X POST $D/api/usuarios \
     -d '{"usuario":"adriana","password_inicial":"operaciones-2026","nombre":"Adriana","rol":"Operaciones","area":"operaciones","correo":"adriana@helgen.tech"}'
# quitar el acceso: borra la línea y deja al miembro activo=false. Se niega a quitarte a ti mismo (400)
# y a quitar al último administrador con acceso (409).
curl -s -u $U -X DELETE "$D/api/usuario?u=adriana"
curl -s -u $U "$D/api/usuarios"               # sigue igual: lista de cuentas del htpasswd (admin)

Pendientes (cambio del 14-oct): responsable es obligatorio al crear (400 si falta). Acepta ids de miembros separados por coma, espacio o / ("leo, Jesús" → responsable_ids: ["leo","jesus"]; los acentos no importan) y también texto libre si la persona aún no está dada de alta: se guarda y la respuesta trae aviso. GET /api/tareas?responsable=jesus filtra por id (o por texto, para los libres) y la respuesta incluye equipo (id → nombre) para pintar nombres. GET /api/tareas/resumen agrega por_responsable {id: {por-hacer, en-curso, bloqueado, hecho, abiertos, vencidas, nombre}}; los pendientes sin miembro registrado cuentan bajo _sin_equipo.