diff --git a/02-Architecture-Software/2026-10-27_Drogheda_Instrucciones-Cotizador-Integracion_ES_v1.0.md b/02-Architecture-Software/2026-10-27_Drogheda_Instrucciones-Cotizador-Integracion_ES_v1.0.md new file mode 100644 index 0000000..7684d28 --- /dev/null +++ b/02-Architecture-Software/2026-10-27_Drogheda_Instrucciones-Cotizador-Integracion_ES_v1.0.md @@ -0,0 +1,150 @@ +--- +title: "Instrucciones para el proyecto del cotizador: ligar órdenes de compra al centro de mando" +date: 2026-10-27 +tags: [project-drogheda, cotizador, compras, inventario, integracion, instrucciones] +--- + +# Instrucciones para el proyecto del cotizador + +**27-oct-2026 · v1.0.** Para la sesión de Claude que trabaja en `cotizador-helgen` (Apps Script + Sheets + Cloudflare). +Diseño completo y decisiones: [Cotizador ↔ centro de mando](integracion-cotizador.md). Aquí está **qué cambiar en el +cotizador**, en orden, con sus pruebas. Todo respeta las reglas del `CLAUDE.md` del cotizador (§7): totales en el servidor, +datos de compras solo en su libro, `CAMBIOS_API`, manual actualizado en el mismo turno, pruebas en PRUEBAS antes del real. + +## Para pegar en el proyecto del cotizador + +> Lee `CLAUDE.md` completo y después este documento (`2026-10-27_Drogheda_Instrucciones-Cotizador-Integracion_ES_v1.0.md`). +> Vamos a ligar las órdenes de compra de los proyectos ligados (hoy DROGHEDA, `PRY-2026-0002`) con el centro de mando +> drogheda.helgen.online, siguiendo el contrato de la sección «Contrato». Trabaja por pasos (1 a 7), con respaldos antes +> de tocar hojas, pruebas en Node y una ronda en PRUEBAS con `"prueba": true` antes de publicar. No mandes montos al centro +> de mando. No pongas el secreto en el código ni en el chat: va en las propiedades del script (`PORTAL_SECRETO`) y me lo +> pides a mí cuando haga falta. Al terminar, actualiza `CLAUDE.md` (§4.5.3, §4.5.4, §5, §6.12, §6.13 y una §6.15 nueva) +> y el Manual del Cotizador. + +## Lo que decidió Leo (27-oct) + +- El material se **recibe en el centro de mando** (el responsable del sitio da «Recibir»). El cotizador **cierra solo** + la requisición cuando el centro de mando dice que llegó todo. «Recibí el material» en el cotizador se queda como respaldo + para proyectos no ligados y para cuando el centro de mando no esté disponible. +- La autorización, los montos, los pagos y los proveedores **se quedan en el cotizador**. +- Cada OC de un proyecto ligado lleva un **sitio de entrega** de 3 letras: `VER` Veracruz (ICAVE) · `ZLO` Manzanillo + (TIMSA) · `ESE` Ensenada (EIT) · `LZC` Lázaro Cárdenas (LCT) · `QRO` Querétaro. + +## Paso 1 · Propiedades del script + +| Propiedad | Valor | Nota | +|---|---|---| +| `PORTAL_URL` | `https://drogheda.helgen.online` | | +| `PORTAL_SECRETO` | (lo genera Leo; el mismo que `COTIZADOR_SECRETO` en el servidor) | Nunca en el código, en `CLAUDE.md` ni en el chat | +| `PORTAL_PROYECTOS` | `{"PRY-2026-0002": "drogheda"}` | Llave interna del proyecto → proyecto del centro de mando | +| `PORTAL_SITIOS` | `VER,ZLO,ESE,LZC,QRO` | Con sus nombres en la interfaz (Veracruz · ICAVE, Manzanillo · TIMSA, …) | + +Agregar `script.external_request` (ya existe por la IA de claves SAT si está activa) y `script.scriptapp` (para el +activador del paso 6) a `appsscript.json`: el dueño (efrain@) tendrá que volver a autorizar. + +## Paso 2 · Sitio de entrega e ítem del inventario en la requisición + +- `Requisiciones`: columna nueva **`sitio_entrega`** (al final, como se agregó `req_folio`; las filas viejas quedan vacías). + Obligatoria cuando el proyecto está en `PORTAL_PROYECTOS`; el servidor lo revisa en `guardarRequisicion` (no solo la + interfaz). En la interfaz: un selector con los 5 sitios, junto al proyecto. +- `RequisicionesRenglones`: columna nueva **`inventario_item`** (código del ítem, opcional). En la interfaz, cada renglón + de un proyecto ligado tiene un buscador con los ítems del centro de mando (`GET /api/integracion/inventario/items`, + guardado en `CacheService` 6 h). Se puede dejar vacío: el centro de mando lo liga después. +- `OrdenesCompra`: columnas nuevas **`sitio_entrega`** (se copia de la requisición al autorizar), **`paqueteria`**, + **`guia`**, **`fecha_estimada`**, **`en_camino_en`**, **`en_camino_por`**. Los renglones de la OC guardan el + `inventario_item` de su renglón de requisición (si compras divide o junta renglones, conserva el ítem). + +## Paso 3 · Estado «En camino» + +- Nueva función *(UI)* **`marcarEnCamino(folio, {paqueteria, guia, fechaEstimada})`**: la puede usar **compras** o **el + solicitante de esa requisición**. Solo desde Autorizada/Emitida o Pagada; guía obligatoria salvo «lo trae el proveedor»; + fecha estimada no anterior a hoy. Con candado e idempotencia (`ENC_`), como las demás. +- Requisición y OC pasan a **«En camino»** (estado nuevo entre Pagada y Recibida; también puede venir de Autorizada si se + paga después). El solicitante lo ve en su historial con la fecha, sin montos. +- Una OC «En camino» ya no se cancela desde el cotizador sin motivo explícito (avisa al centro de mando con `oc.cancelada`). +- `CAMBIOS_API`: `marcarEnCamino` cambia `requisiciones` y `oc`. + +## Paso 4 · Avisos al centro de mando (empujar) + +Función interna **`avisarPortal_(tipo, folioOC)`**: arma el cuerpo del contrato con los datos de la OC **sin montos** +(nada de costo, importe, subtotal, IVA, total, tipo de cambio ni margen), lo firma y lo manda con `UrlFetchApp.fetch` +(`muteHttpExceptions: true`). Se llama **después** de que el cambio quedó guardado: + +| Momento | `tipo` | +|---|---| +| `autorizarRequisicion` (emite la OC) | `oc.autorizada` | +| `registrarPagoProveedor` | `oc.pagada` | +| `marcarEnCamino` | `oc.en_camino` | +| `cancelarOrdenCompra` | `oc.cancelada` | +| `cerrarRequisicionCompras` (compras cierra a mano) | `req.cerrada` | + +- Solo si el proyecto de la OC está en `PORTAL_PROYECTOS`. +- `evento_id` = `::` (n = cuántas veces se mandó ese tipo para esa OC), para que un reintento no duplique. +- Si la respuesta no es 200 `ok`, el aviso se guarda en una pestaña nueva **`PortalPendientes`** del libro de compras + (`evento_id`, `cuerpo`, `intentos`, `ultimo_error`, `creado`) y lo reintenta el activador del paso 6. La acción del + usuario **no** falla por eso: se le muestra «Guardado. El centro de mando se actualizará en unos minutos». +- Firma: `X-Hbox-Tiempo` = hora ISO con zona; `X-Hbox-Firma` = `sha256=` + hex de + `Utilities.computeHmacSha256Signature(tiempo + "\n" + cuerpo, PORTAL_SECRETO)`. + +## Paso 5 · Recepciones (consultar y cerrar solo) + +Función **`traerRecepcionesPortal()`**: `GET /api/integracion/cotizador/recepciones?desde=` +(firmada igual, cuerpo vacío). Por cada recepción: + +- `completa: true` → cierra la requisición con `cerrarReq_` (estado Cerrada, OC Recibida) y nota «Recibido en ZLO por + jesush en el centro de mando, 30-oct-2026». Si la requisición ya estaba Cerrada, no hace nada. +- `completa: false` → solo agrega una nota de recepción parcial («Llegaron 3 de 4 soportes») y deja la requisición En camino. +- Guarda `PORTAL_ULTIMA_CONSULTA` al final, solo si todo salió bien. +- Bitácora en cada cierre automático (quién: «centro de mando»). + +## Paso 6 · Activador cada 15 minutos + +`instalarActivadorPortal()` (se corre una vez desde el editor): `ScriptApp.newTrigger('cadaQuinceMinutos').timeBased() +.everyMinutes(15)`. `cadaQuinceMinutos` = reintentar `PortalPendientes` + `traerRecepcionesPortal`. Aprovecha para llenar el +caché (la idea de §6.14). + +## Paso 7 · Pruebas y publicación + +1. **Node** (`tests/portal.test.js`): el cuerpo nunca lleva montos; la firma coincide con un vector de prueba; sitio + obligatorio solo en proyectos ligados; `evento_id` estable; un 500 del portal deja el aviso en `PortalPendientes`; + recepción completa cierra, parcial solo anota, repetida no hace nada. +2. **PRUEBAS** con `"prueba": true`: requisición → autorizar → en camino → (Leo da «Recibir» en el centro de mando) → + la requisición se cierra sola en ≤ 15 min. El centro de mando guarda las de prueba aparte y no toca el inventario. +3. Respaldos de los libros (como siempre), `clasp push`, comparar el editor, `clasp deploy` (misma API_URL), + `wrangler deploy`. +4. Primera OC real de DROGHEDA: revisar juntos en el centro de mando (Compras y la pestaña del sitio en Inventario). + +## Contrato (lo que espera el centro de mando) + +```json +POST /api/integracion/cotizador/evento +{ "evento_id": "OC-2026-0012:oc.en_camino:1", + "tipo": "oc.en_camino", + "cuando": "2026-10-28T10:15:00-06:00", + "quien": "tania@helgen.tech", + "prueba": false, + "oc": { "folio": "OC-2026-0012", "req_folio": "REQ-2026-0031", + "proyecto_folio": "PRY-2026-0002", "proyecto_clave": "DRO", + "sitio": "ZLO", "proveedor": "Ferretería del Pacífico", + "fecha_entrega": "2026-10-30", "paqueteria": "Estafeta", "guia": "123456789", + "solicitante": "jesus@helgen.tech", "autorizo": "tania@helgen.tech", + "renglones": [ { "n": 1, "descripcion": "Soporte para RTG", "unidad": "pza", "cantidad": 4, "item": "SOP-RTG" }, + { "n": 2, "descripcion": "Tornillo 3/8 x 2 in", "unidad": "pza", "cantidad": 32, "item": null } ] } } + +→ 200 { "ok": true, "estado_portal": "en-camino", "tarea_id": "t-oc-2026-0012", "por_ligar": [2] } +→ 200 { "ok": true, "repetido": true } (mismo evento_id) +→ 400 { "ok": false, "error": "Sitio inválido: MZO (usa VER, ZLO, ESE, LZC o QRO)" } +→ 401 { "ok": false, "error": "Firma inválida o vencida" } + +GET /api/integracion/inventario/items +→ 200 { "ok": true, "items": [ { "codigo": "SOP-RTG", "descripcion": "Soporte para RTG", "unidad": "pza", "categoria": "instalacion" } ] } + +GET /api/integracion/cotizador/recepciones?desde=2026-10-28T00:00:00-06:00 +→ 200 { "ok": true, "hasta": "2026-10-30T12:40:00-06:00", + "recepciones": [ { "oc_folio": "OC-2026-0012", "req_folio": "REQ-2026-0031", "sitio": "ZLO", "completa": false, + "recibio": "jesush", "cuando": "2026-10-30T12:31:00-06:00", + "renglones": [ { "n": 1, "pedido": 4, "recibido": 4 }, { "n": 2, "pedido": 32, "recibido": 20 } ] } ] } +``` + +Hasta que el centro de mando publique estas rutas (fase 1 del lado del sitio), el cotizador puede programar y probar todo +contra un servidor simulado en Node con estas mismas respuestas. diff --git a/02-Architecture-Software/2026-10-27_Drogheda_Integracion-Cotizador-Inventario_ES_v1.0.md b/02-Architecture-Software/2026-10-27_Drogheda_Integracion-Cotizador-Inventario_ES_v1.0.md new file mode 100644 index 0000000..b3fbc0f --- /dev/null +++ b/02-Architecture-Software/2026-10-27_Drogheda_Integracion-Cotizador-Inventario_ES_v1.0.md @@ -0,0 +1,154 @@ +--- +title: "Cotizador ↔ centro de mando: órdenes de compra, inventario por sitio y tareas" +date: 2026-10-27 +tags: [project-drogheda, hbox, compras, inventario, cotizador, integracion, sql] +estado: propuesta con 3 decisiones tomadas (27-oct); falta construir +--- + +# Cotizador ↔ centro de mando: órdenes de compra, inventario por sitio y tareas + +**27-oct-2026 · v1.0 · Claude (CTO/CSO) con Leo.** Responde al pendiente `t-cotizador-integracion`: que la +requisición, la orden de compra y la llegada del material sean un solo hilo que termine en el inventario de cada sitio, +con su pendiente en el tablero. Las instrucciones para el proyecto del cotizador están en +[Instrucciones para el cotizador](integracion-cotizador-instrucciones.md). + +![Flujo de una orden de compra del cotizador al inventario del sitio](img/2026-10-27_Drogheda_Flujo-OC-Inventario_v1.0.png) + +## Resumen + +1. **El cotizador se queda con el dinero y la autorización.** Ya tiene requisición → autorización de compras → orden de + compra (OC) → pago → «Recibí el material», con roles y montos protegidos. No se duplica en el sitio. +2. **El centro de mando recibe lo operativo de cada OC, sin montos:** folio, proyecto, **sitio de entrega** (código de 3 + letras), proveedor, renglones con cantidad y su ítem del inventario, fechas, paquetería y guía. +3. **«En camino» es nuevo:** cuando el proveedor manda el material, compras o quien pidió lo confirma en el cotizador con + la guía. En ese momento el material aparece **en camino** en la pestaña del sitio del inventario. +4. **Se recibe en el sitio, en el centro de mando** (decisión de Leo, 27-oct): el responsable del sitio da **Recibir** con + lo que llegó de verdad; el cotizador se entera solo y cierra la requisición. +5. **Cada OC abre su pendiente** (pista Compras, su sitio, el responsable del sitio, vence en la fecha de entrega) y se + cierra solo cuando se recibe todo. +6. **Base de datos: SQLite** en el servidor del sitio (decisión de Leo, 27-oct), empezando por las órdenes de compra; el + inventario pasa después, con una prueba de que los números no cambian. + +## Decisiones + +| # | Decisión | Estado | +|---|---|---| +| D-27-1 | El material de una OC se **recibe en el centro de mando** (pestaña del sitio, botón Recibir). El cotizador cierra la requisición solo. | **Tomada** (Leo, 27-oct) | +| D-27-2 | Base de datos **SQLite** en el servidor del sitio (`/srv/projects/drogheda/drogheda.db`). PostgreSQL solo si otra aplicación tiene que escribir directo en ella. | **Tomada** (Leo, 27-oct) | +| D-27-3 | Código de 3 letras por sitio = el de su aeropuerto y su puerto (UN/LOCODE): **VER** Veracruz (ICAVE) · **ZLO** Manzanillo (TIMSA) · **ESE** Ensenada (EIT) · **LZC** Lázaro Cárdenas (LCT) · **QRO** Querétaro. Son los que vienen en guías y documentos de embarque; el sitio ya los acepta. | Propuesta de Claude (Leo pidió códigos de 3 letras) | +| D-27-4 | La **autorización** de la OC se queda en el cotizador (rol de compras). El centro de mando **nunca** muestra montos: todo el equipo lo ve. | Propuesta | +| D-27-5 | Solo las OC de proyectos ligados van al centro de mando. Hoy: DROGHEDA = `PRY-2026-0002-DRO` → proyecto `drogheda`. | Propuesta | + +## Lo que hay hoy + +| | Cotizador | Centro de mando (drogheda.helgen.online) | +|---|---|---| +| Dónde vive | Google Apps Script + Sheets + Drive; interfaz en Cloudflare; entra con cuenta @helgen.tech | Servidor de Helgen (Flask + nginx), página de entrada propia desde el 27-oct | +| Proyecto | `PRY-2026-0002-DRO` «Helgen — DROGHEDA», MXN, sin contrato | `drogheda` | +| Requisición | `REQ-AAAA-NNNN`: Solicitada → Autorizada → Pagada → Cerrada (también Devuelta, Rechazada, Cancelada). Piden sin precios: adriana@, miguel@, paola@, jesus@ (solo requisiciones) y efrain@, tania@, leo@ | — | +| Orden de compra | `OC-AAAA-NNNN`: Emitida → Pagada → Recibida, o Cancelada. Sale siempre de una requisición autorizada. **Sin recepciones parciales** | — | +| Sitio de entrega | No existe (solo «lugar de entrega» en texto) | `icave` · `timsa` · `eit` · `lzc` · `qro` en cada pendiente, movimiento y miembro | +| Material | Renglones de texto libre (descripción, unidad, cantidad) | Ítems con código, existencia por sitio, envíos «en camino» con Recibir (también parcial), kit por instalación | +| Tareas | — | Pendientes con pista, sitio, responsable y vencimiento | + +**Lo que falta para unirlos:** el sitio de entrega en la requisición, el ítem del inventario en cada renglón, el estado +«En camino», un canal entre los dos y una forma de cerrar la requisición desde la recepción. + +**Una restricción que define el diseño:** el cotizador puede llamar al centro de mando (Apps Script tiene `UrlFetchApp`), +pero el centro de mando **no** puede llamar al cotizador, porque cada llamada al cotizador exige una sesión de Google de +una persona (su regla 10, que no se quita). Por eso: el cotizador **avisa** (empuja) cada cambio de una OC, y **consulta** +cada 15 minutos lo que ya se recibió. + +## El flujo, paso por paso + +| Paso | Dónde | Quién | Qué pasa | +|---|---|---|---| +| 1. Pedir | Cotizador | Quien necesita el material | Requisición en DROGHEDA con **sitio de entrega** (obligatorio en proyectos ligados) y renglones; cada renglón puede llevar su **ítem del inventario** (lista que el cotizador baja del centro de mando). | +| 2. Autorizar | Cotizador | Compras (Efraín, Tania, Leo) | Emite la OC como hoy. Aviso `oc.autorizada` → el centro de mando crea la OC (sin montos) y el pendiente «Recibir OC-… en ZLO» para el responsable del sitio, que vence en la fecha de entrega. | +| 3. Pagar | Cotizador | Compras | Como hoy. Aviso `oc.pagada` (solo el estado). | +| 4. En camino | Cotizador | Compras o quien pidió | «El proveedor ya lo mandó»: paquetería, guía y fecha estimada. Aviso `oc.en_camino` → en la pestaña del sitio aparece cada renglón **en camino** (un envío desde «Proveedor o fuera del inventario», con el folio de la OC como referencia). Los renglones sin ítem quedan en **Compras → por ligar** del centro de mando. | +| 5. Recibir | Centro de mando | Responsable del sitio (Alán VER, Jesús Honorato ZLO, Miguel ESE, Antonio LZC, Jesús Romero QRO) | **Recibir** con lo que llegó de verdad (puede ser parcial). La OC queda «recibida» o «recibida en parte»; el pendiente se cierra solo cuando llega todo. | +| 6. Cerrar | Cotizador (solo) | — | Cada 15 min el cotizador consulta las recepciones: si llegó todo, cierra la requisición («Recibido en ZLO por jesush en el centro de mando, 30-oct») y la OC queda Recibida. Si llegó una parte, lo anota en las notas de recepción. | +| Cancelar | Cotizador | Compras | Aviso `oc.cancelada` → el centro de mando cancela los envíos abiertos y el pendiente. | + +## Datos (SQLite) + +Un archivo, `/srv/projects/drogheda/drogheda.db`, en modo WAL (aguanta a los dos procesos del servicio). Se respalda +con el resto de `/srv/projects/drogheda`. + +```sql +CREATE TABLE sitios (codigo TEXT PRIMARY KEY, -- VER ZLO ESE LZC QRO + id_portal TEXT UNIQUE NOT NULL, -- icave timsa eit lzc qro + nombre TEXT NOT NULL, responsable TEXT); +CREATE TABLE proyectos (id TEXT PRIMARY KEY, -- drogheda + pry_folio TEXT UNIQUE NOT NULL, -- PRY-2026-0002 + clave TEXT, nombre TEXT); +CREATE TABLE ordenes (folio TEXT PRIMARY KEY, -- OC-2026-0012 + req_folio TEXT, proyecto_id TEXT REFERENCES proyectos(id), + sitio TEXT REFERENCES sitios(codigo), proveedor TEXT, + estado TEXT CHECK (estado IN ('autorizada','pagada','en-camino','recibida-parcial','recibida','cancelada')), + fecha_entrega TEXT, paqueteria TEXT, guia TEXT, en_camino_en TEXT, + solicitante TEXT, autorizo TEXT, tarea_id TEXT, prueba INTEGER DEFAULT 0, + creado_en TEXT, actualizado_en TEXT); +CREATE TABLE renglones (oc_folio TEXT REFERENCES ordenes(folio), n INTEGER, + descripcion TEXT, unidad TEXT, cantidad REAL, + item_codigo TEXT, -- ítem del inventario (NULL = por ligar) + envio_id TEXT, -- el envío «en camino» que se creó en el inventario + recibido REAL DEFAULT 0, PRIMARY KEY (oc_folio, n)); +CREATE TABLE eventos (evento_id TEXT PRIMARY KEY, -- idempotencia: el mismo aviso dos veces no duplica + oc_folio TEXT, tipo TEXT, cuando TEXT, quien TEXT, + origen TEXT CHECK (origen IN ('cotizador','portal')), datos TEXT); +``` + +**Fases.** 1) Órdenes de compra en SQLite; el inventario sigue en sus JSON y la OC crea sus envíos con la API de hoy. +2) El inventario (ítems y movimientos) pasa a SQLite con una comparación de existencias por sitio antes y después. +3) **Activos**: una fila por unidad y por pieza con serie (HBOX, tarjetas, módulos) con su sitio, estado, vehículo y actas +— lo que hoy se arma leyendo las actas. + +## Cómo se hablan (API del centro de mando para el cotizador) + +**Seguridad.** Las rutas `/api/integracion/…` no usan la sesión de una persona: cada llamada va firmada con un secreto +compartido (HMAC-SHA256 del cuerpo y la hora). El secreto vive en las propiedades del script del cotizador +(`PORTAL_SECRETO`) y en el servicio del servidor (`COTIZADOR_SECRETO`); **nunca en el código ni en un chat**. Se rechaza +una firma de hace más de 5 minutos. + +``` +POST /api/integracion/cotizador/evento + Encabezados: X-Hbox-Tiempo: 2026-10-28T10:15:00-06:00 + X-Hbox-Firma: sha256= + Cuerpo: + { "evento_id": "OC-2026-0012:oc.en_camino:1", + "tipo": "oc.en_camino", // oc.autorizada · oc.pagada · oc.en_camino · oc.cancelada · req.cerrada + "cuando": "2026-10-28T10:15:00-06:00", "quien": "tania@helgen.tech", "prueba": false, + "oc": { "folio": "OC-2026-0012", "req_folio": "REQ-2026-0031", + "proyecto_folio": "PRY-2026-0002", "proyecto_clave": "DRO", "sitio": "ZLO", + "proveedor": "Ferretería del Pacífico", "fecha_entrega": "2026-10-30", + "paqueteria": "Estafeta", "guia": "123456789", "solicitante": "jesus@helgen.tech", + "renglones": [ { "n": 1, "descripcion": "Soporte para RTG", "unidad": "pza", "cantidad": 4, "item": "SOP-RTG" } ] } } + Respuesta: { "ok": true, "estado_portal": "en-camino", "tarea_id": "t-oc-2026-0012", "por_ligar": [] } + (el mismo evento_id otra vez → { "ok": true, "repetido": true }) + +GET /api/integracion/inventario/items → [{codigo, descripcion, unidad, categoria}] (para el selector) +GET /api/integracion/cotizador/recepciones?desde=… → [{oc_folio, req_folio, sitio, completa, recibio, cuando, + renglones: [{n, pedido, recibido}]}] +``` + +`"prueba": true` (rondas del proyecto de PRUEBAS del cotizador) se guarda aparte y **no** toca el inventario ni el tablero. + +## Qué se construye y quién + +| Lado | Qué | Quién | +|---|---|---| +| Centro de mando | `backend/compras.py` (SQLite, las 3 rutas firmadas, envíos en camino, pendiente por OC, cierre al recibir); página **Compras** (OC por sitio y estado, renglones por ligar, liga desde el envío en camino del inventario); ruta en nginx sin sesión para `/api/integracion/` (la firma la revisa el backend); pruebas | Claude (fase 1), Leo despliega | +| Cotizador | Sitio de entrega y su regla; ítem por renglón; estado «En camino»; avisos firmados con reintento; consulta de recepciones cada 15 min y cierre automático; pruebas en Node y en PRUEBAS | El proyecto del cotizador (Claude con Efraín) — ver [instrucciones](integracion-cotizador-instrucciones.md) | +| Los dos | El secreto compartido (Leo lo genera y lo pone en los dos lados) y una prueba de punta a punta con una OC de PRUEBA | Leo | + +## Riesgos y cómo se cubren + +- **El centro de mando no contesta** cuando el cotizador avisa → el cotizador guarda el aviso en una pestaña de + pendientes y lo reintenta; el `evento_id` evita duplicados. +- **Una OC llega antes que su proyecto o su sitio** → se rechaza con un error claro (sitio inválido, proyecto no ligado) + y el cotizador lo muestra a compras. +- **Doble captura durante la transición** → las OC de antes no se migran; solo las nuevas de proyectos ligados. +- **Fuga del secreto** → se cambia en los dos lados; las firmas viejas dejan de valer al momento. +- **Montos** → el centro de mando no los recibe; si un aviso los trae, se descartan antes de guardar. diff --git a/02-Architecture-Software/img/2026-10-27_Drogheda_Flujo-OC-Inventario_v1.0.png b/02-Architecture-Software/img/2026-10-27_Drogheda_Flujo-OC-Inventario_v1.0.png new file mode 100644 index 0000000..a5a4320 Binary files /dev/null and b/02-Architecture-Software/img/2026-10-27_Drogheda_Flujo-OC-Inventario_v1.0.png differ diff --git a/CLAUDE.md b/CLAUDE.md index 2ca28ac..5b0df4a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -473,6 +473,8 @@ lote `2026-10-22_fota-4g`. **JLCPCB costing + import (2026-10-26; Leo: «estimate the manufacturing price for V5.6, V5.7 and V6.0 … batches of 135 V5.6, 365 V5.7 and 60 V6.0 … import costs and shipping … 3 separate orders»).** BOMs from the fab packages (Rev6 226 parts / 96 lines, Rev7 229 / 105; v6 from the 26-oct PCB, 299 / 126); all 151 LCSC parts re-priced from jlcpcb.com that day (some ICs up 15–20% vs the 1-Aug local kicad-mcp DB: LTC4040 6.35 → 7.27, SC16IS752 2.69 → 5.03); PCB quoted in the JLC calculator (4 L, 140×100, ENIG, Epoxy Filled & Capped, JLC04161H-7628: 150 / 400 / 75 PCB = US$401 / 957 / 234; JLC only takes fixed PCB quantities); assembly from JLC's 22-oct fee table (THT hand soldering and feeders dominate); UPS rates for Mexico from the calculator (≈ US$10/kg; no DDP to MX). Result: to JLC ≈ **US$36,058** (V5.6 7,493 · V5.7 18,909 · v6 9,656); landed excl. IVA ≈ MX$703k → **MX$1,104 / 998 / 3,163 per board** (v6: ZED-F9P = US$76.30 of US$146.67 parts); import IVA ≈ MX$112k, creditable. Import: formal A1 in Helgen's name (each order > US$2,500; the UPS T1 courier route of Sept = 33.5% global rate, not creditable), IGI 0% (8537.10.99 / 8543.90.01 / 8517.79.04; v6 risk 8526.91.99 at 5% ≈ +MX$9k; avoid 8517.62.17: NOM-221 at customs from ≈ 10-nov), DTA 0.8%, PRV MX$350, broker MX$6,500 assumed (AA patente 3927, Grupo Lorenzo: quote pending, `t-cotizacion-pedimentos-jlc`). **12 parts short at JLC for the three orders together** (STM32F411RET6 2 for 570, AP64350 1, SC7A20 2, AO4406A / MWSA0804S-4R7 / SRP1265A 0, MWSA0804S-2R2 171, A7670SA 44 / 62, 47 µF MVK, BAT54W, two 1% resistors), priced at +15% → `t-faltantes-jlc-26oct`. The mix differs from `t-paquetes-aldair` (113 / 327 / 130). Canonical: `00-Project-Management/2026-10-26_Drogheda_Costeo-JLCPCB-Importacion_V5.6-V5.7-v6_ES_v1.0.md` + `2026-10-26_Drogheda_Costeo-JLCPCB_V5.6-V5.7-v6_v1.0.xlsx` (site `hardware/costeo-jlc-26oct/`); lote `2026-10-26_costeo-jlc`. Scratch (gitignored): `_to_delete/costeo-20261026/` — `fetch_jlc.py` re-prices the BOM from jlcpcb.com (curl from the Mac; Python urllib fails on certificates there). + +**Site entry, incidents, standup and agents (2026-10-27; Leo: «failures in authentication: after resetting passwords the users cannot log in again (Adriana, yesterday)… an issues tracker in the portal, at the bottom and at login… an agent to look for these issues daily and act on them with fixes that won't require authorization… a scrum bot that asks every engineer what they did, what is missing, and gives each one instructions»).** Read-only diagnosis on the server: the 23-oct bulk reset left 15 of 19 on the initial password; at 09:15 on 27-oct Gibran (has sudo) reinstalled `/etc/systemd/system/actas-hbox.service` by hand — an OLD copy that added `ACTAS_ADMINS=leo,gibranlp` but dropped `prueba-ethernet-serie` from `ACTAS_REPETIBLES` — and at 09:18 reset Adriana's password from Mi cuenta, which unblocked her; backend code on the server = repo (md5). Likely cause (nginx error log needs sudo; the grep is in DESPLIEGUE §A11): HTTP Basic — browsers/phones resend the old saved password, Mi cuenta had no username field so password managers never updated, «Adriana» ≠ adriana for nginx, no logout, no attempt log. **Fix:** own login page — `backend/acceso.py` (nginx `auth_request` → `/api/_auth`; signed 30-day cookie whose version = hash of the user's htpasswd line, so a reset ends old sessions and Mi cuenta re-issues the cookie; login by user or Equipo email, case/space-insensitive; no HTTP Basic; unsafe methods same-origin only; attempts logged without passwords; lockout per user+IP 8 and per IP 20 per 15 min; reviewed by an independent security pass the same day: Basic brute-force path, open redirect, lockout DoS, CSRF, secret race, public-notice leak — all fixed), `docs/entrar.html` + `docs/publico/`, nginx `backend/nginx/drogheda.helgen.online.conf` installed by `cambiar-entrada.sh` (backup outside sites-enabled, $apr1$-only pre-check, `nginx -t`, 14 checks incl. a test session minted from the service secret, auto-rollback; `deshacer`); service in repo now = `leo,gibranlp` admins + full repetibles + gthread 2×8 + `Restart=always`. **Incidents** (`backend/incidencias.py`, footer `docs/js/incidencias.js` on every page incl. forms, public report + public notices at the entry page, browser/server error capture) · **standup bot** (`backend/standup.py`, `/tableros/standup/`, banner; answers can move the person's own cards) · **vigía** (`backend/vigia.py`, leo's crontab minute 7: login failures, 5xx/exceptions, repeated JS errors, htpasswd perms, disk, installed-vs-repo service drift) · **daily agent** (Claude scheduled task on Leo's Mac, Mon–Fri 18:37; runbook `hbox-docs/agentes/agente-diario.md`, team rules `docs/agentes.md`: level A = page-only fixes published after an rsync dry-run proves the server has nothing outside main; level B = branch + exact commands for Leo; API via `scripts/agente-api.sh`). Tests `backend/tests-1027.sh` (55) + nginx-level 35; tests-1013/1023/cuentas pass; tests-1014 has 6 pre-existing failures (seed count). Equipo emails from Gibran's 23-oct list as confirmed by Leo (`backend/cuentas/2026-10-27_correos.json`; daniel = daniel@helgen.tech, jesus = jesusr@, jesush = jesus@; danielf@/jesusd@ are other people; Leo's «otro correo» ceo@ is an alias → leo@helgen.services by hand). **Cotizador integration** (`t-cotizador-integracion` closed): `02-Architecture-Software/2026-10-27_Drogheda_Integracion-Cotizador-Inventario_ES_v1.0.md` + `…_Instrucciones-Cotizador-Integracion_ES_v1.0.md` (+ flow figure) — **D-27-1** receive in the portal, **D-27-2** SQLite (both Leo); proposed D-27-3 site codes VER·ZLO·ESE·LZC·QRO (the portal already accepts them as aliases), D-27-4 no amounts on the portal, D-27-5 only PRY-2026-0002-DRO; HMAC-signed push from Apps Script + 15-min pull of receipts (the portal cannot call the cotizador: its API needs a Google user token). One command: `hbox-docs/scripts/despliegue-1027.sh`. Lote `2026-10-27_entrada-incidencias-agentes`. --- ## 1. System architecture (corrected from the BOM, 2026-06-20) @@ -547,14 +549,14 @@ these before presenting; if it breaks one, say so and propose the compliant fix. |---|---|---| | `00-Project-Management` | plan, ethos, hackathon, **financials**, **handover**, client comms, Drive tooling | [[00-Project-Management/PLAN\|PLAN]] · [[00-Project-Management/Engineering-Handover\|Engineering-Handover]] · `ethos.md` · `hackathon-plan.md` · `Financials.md` · `Drive-Upload/` · `client-comms/` · **`2026-10-26_Drogheda_Costeo-JLCPCB-Importacion_V5.6-V5.7-v6_ES_v1.0.md`** + `.xlsx` (JLC + import costing, 135 / 365 / 60) | | `01-Requirements` | what we build | `SRS.md` · `HBOX-product-spec.md` | -| `02-Architecture-Software` | backend/software analysis, ADRs | `backend-review.md` · **`2026-10-22_Drogheda_FOTA-por-4G_Factibilidad_ES_v1.0.md`** (FOTA over 4G per board; D-22-6) | +| `02-Architecture-Software` | backend/software analysis, ADRs | `backend-review.md` · **`2026-10-22_Drogheda_FOTA-por-4G_Factibilidad_ES_v1.0.md`** (FOTA over 4G per board; D-22-6) · **`2026-10-27_Drogheda_Integracion-Cotizador-Inventario_ES_v1.0.md`** (cotizador ↔ portal: POs, per-site inventory, SQLite; D-27-1…5) + `2026-10-27_Drogheda_Instrucciones-Cotizador-Integracion_ES_v1.0.md` (what the cotizador project changes) + `img/2026-10-27_Drogheda_Flujo-OC-Inventario_v1.0.png` | | `03-Architecture-Hardware` | HBOX/cabinet, wiring, BOM, **watchdog PCB v3** | `hardware-respec-and-pcb-brief.md` · `cabinet-electrical-bom.md` · `watchdog-schematic.md` · `watchdog-health-telemetry.md` · `firmware-review.md` · `BOM-master.md` (+§5 Block B/E/H/I parts) · **`watchdog-pcb-v3-design.md`** (schematic spec) · `watchdog-pcb-v3-bom.xlsx` · `watchdog-pcb-v3-kicad-guide.md` (KiCad→JLCPCB) · `watchdog-pcb-v3-blockdiagram.svg` · `watchdog-protoboard-bringup.md` + `watchdog-proto-bench-bringup-v3.md` · `watchdog-connections.xlsx` (PCB pin/net list; H1 DNP; +B/H/I rows) · `watchdog-connections-rationale.md` (+§7 additions 2026-07-02) · **`watchdog-electrical-design-state.md`** (CANONICAL — read first) · **`watchdog-production-schematic-delta.md`** (PROD build spec; LTC4040 §4 values) · `kicad/` — `hbox-watchdog-v3.kicad_sch` (**DEV/proto**) · `hbox-watchdog-v3-PROD.kicad_sch/.kicad_pcb` (**PRODUCTION**: ERC 0, layout started, DRC baseline) · `Drogheda-sym.kicad_sym` (MP1584EN · LTC4040EUFD · AO4406A) · `Drogheda_Conexiones-por-Bloque_Proto_v1.xlsx` (**proto assumptions ledger**) · `Guia-Conexion-Protoboard/` (LaTeX source; `modulos-pinout.tex` = visual parts catalog) → `Drogheda_Guia-Conexion-Protoboard_ES_v3.1.pdf` · **`2026-07-08_Drogheda_4G-Modem-Alternatives_v1.0.md`** (XBee 2.0 mm bridge + cheap-4G survey) · `circuitikz-wiring/` (sheet + Beamer v2.1, blocks 1–11; XBee-socket 3V3 fix 07-08; **`Drogheda_Diagrama-Unico-Proto`** = todo-en-una-hoja para verificación rápida, Modo B; v1.1 07-10 agrega XBee 20-pin obligatorios VREF(14)→VCC · ON/OFF(20)→GND · GND×4 + USB config + franja de pinouts para el técnico; HiRes-300dpi.png para TV; **v1.2 07-20**: letras G/D/S·B/C/E en todos los transistores + bloque 5b Q10+DzG 15V) · **Guía v3.1** (5b Q10+Zener · 9c pines obligatorios XBee + USB · disipadores · plano 10×10) · **lista de compras reconstruida DESDE CERO** (1 tarjeta ≈MX$2,087) · `watchdog-protoboard-BOM-3boards.xlsx` + `watchdog-LISTA-DE-COMPRAS-ES.xlsx` (proto BOMs; 07-10: +BOB-08276/PRT-08272, buck «MODEM 3.3V», breakout USB-C+cable, botón/LED módem, straps VREF/ON-OFF, surtido +220R/22k) · **`2026-07-06_Drogheda_EinarBoard-1.0-PreFab-Audit_v1.0.md`** (CLOSED — Rev 1.1 fixes) · `Assembly-BOM-Split.md` + `LCSC-search-list.md` (verified LCSC) + `JLCPCB-Order-Guide.md` (ordering runbook) · `harness-connector-pinout.md` + `Socket-Pinout-Silkscreen.md` · **`EinarBoard-Manual/`** (Rev 1.1 order-prep pack: `einarboard-manual.tex` → branded PDF · `helgen_einarboard_1.0_production_bom.xlsx` · `tools/generate_production_bom.py` + `tools/sync_bom.py` Excel→LaTeX BOM sync) — plus repo-root `scripts/prep_order_release.sh` (safe cleanup + release commit) · **`2026-10-16_Drogheda_Auditoria-Produccion_V5.6-V5.7-v6_ES_v1.0.md`** (production audit + comparison table) + `2026-10-16_Drogheda_Instrucciones-Michelle_V5.6-V5.7-Pasada-3_ES.md` · `kicad/tools/t25_mounting_holes_v55.py` (restores the 7 V5.5 holes) · `kicad/tools/t27_paso1_2_mh11_rs1_kelvin.py` (MH11 out + RS1 Kelvin, V5.6/V5.7) · `kicad/tools/t28_v6_mecanicos.py` (v6: U25 part code, stackup, U6 paste) · `kicad/tools/t30_v56_v57_mecanicos.py` (V5.6/V5.7 mechanical fixes, 19-oct) · `kicad/tools/t31_c129_vsys_quitar_dbk2.py` (V5.6/V5.7 schematic: C129 at U6 VSYS + Dbk2 out, 20-oct) · `kicad/tools/t32_c130_vin_u7.py` (V5.6/V5.7 schematic: C130 at U7 VIN, 21-oct) · `kicad/tools/t33_v56_v57_auditoria_21oct.py` (V5.6/V5.7 PCB fixes from the 21-oct audit) · `kicad/tools/t34_v56_pasada6.py` + `t35_v56_via_u7.py` (V5.6 pass 6) · `kicad/tools/t36_v57_pasada6.py` + `t37_v57_c129_gnd.py` (V5.7 pass 6) · `kicad/tools/t38_v6_courtyards_mh.py` (v6: courtyards of the 11 easyeda2kicad footprints + library, MH6 `/3V3` to B.Cu, 4-mm MH rule in the `.kicad_dru`) · `kicad/tools/t39_v6_bloque_a.py` (v6 block A, proposal α: D34/L22/R53/R54 moved, SW + VEH5V tracks, In1/In2 cleared, MH8 free) · `kicad/tools/t40_v6_mh4.py` (v6 MH4 free: D23/D24 moved, SIM_CLK via In1, SIM_RST above D25, NTC around the hole, TVS + U19.92/94 GND vias) · `kicad/tools/t42a_v5_quickwins.py` (V5.6/V5.7 quick wins T42a: BOM with stock for 430, /IGN_IN 0.3 mm on In1, V5.7 E-11 via; `--solo-bom`) · `2026-10-16_Drogheda_Auditoria-Produccion_v6_ES_v1.0.md` + `2026-10-16_Drogheda_Plan-Fines-de-Semana_v6-Produccion_ES.md` (v6 audit + weekend plan, rev 1.5) · **`2026-10-19_Drogheda_Re-Auditoria_V5.6-V5.7-v6_ES_v1.0.md`** (re-audit of all three) + `2026-10-19_Drogheda_Instrucciones-Michelle_V5.6-V5.7-Pasada-4_ES.md` · **`2026-10-19_Drogheda_V5.6_Clasificacion-Criticos_ES_v1.0.md`** (V5.6 critical vs. can-stay; C129) · **`2026-10-21_Drogheda_Auditoria-Pasada-4_V5.6-V5.7_ES_v1.0.md`** (audit of pass 4, T32/T33 results, pass-5 review, pass 6 done by Claude with T34–T37 + D-21-2) · **`2026-10-21_Drogheda_Estado-v6-T25-T28_ES_v1.0.md`** (v6 state, rev 1.3 of 22-oct: T25 + T28 + T38 + **T39 block A** + **T40 MH4** applied, MH rule, D-22-4; figures `img/2026-10-22_Drogheda_v6-T39-Bloque-A_v1.0.png` + `img/2026-10-22_Drogheda_v6-T40-MH4_v1.0.png`) · **`2026-10-22_Drogheda_Auditoria-Final-Produccion_V5.6-V5.7_ES_v1.0.md`** (FINAL production audit of the V5.6/V5.7 fab packages: GO pilot 5 + 5 with 3 order conditions, NO-GO 108/322 until bench with 0.7.0; 30 findings, 16 risks) + `…_Protocolo_ES_v1.0.md` (the audit protocol / "reprompt") + **`Auditoria-Final-V56-V57/`** (LaTeX sources → 33-pp PDF + 19-frame Beamer; `fig/`; `scripts/` gerb.py · gcmp.py · netparse.py · copper.py · riesgos.py) · `img/2026-10-22_Drogheda_*.png` (8 figures) · **`2026-10-22_Drogheda_Victorias-Rapidas_V5.6-V5.7_T42a_ES_v1.0.md`** (quick wins T42a + HV24 netclass finding → T42b + D-22-7; figure `img/2026-10-22_Drogheda_V5.6-V5.7-T42a_v1.0.png`) · **`2026-10-22_Drogheda_Designadores-T43_V5.6-V5.7-v6_ES_v1.0.md`** (T43 designators in order on V5.6/V5.7/v6 + D-22-9; old→new CSVs `2026-10-22_Drogheda_T43_*_viejo-nuevo.csv`; maps `img/2026-10-22_Drogheda_*-Designadores-T43_v1.0.png`) · `kicad/tools/t43_renumber.py` + `t43_check.py` + `fab_package.py` (headless Fabrication Toolkit) · **`2026-10-22_Drogheda_Documento-de-Diseno_{V5.6,V5.7,v6}_ES_v1.0.md/.pdf`** (one design document per board; images + full schematic PDF in `img/diseno-*`) · `2026-10-22_Drogheda_v6-T44-Serigrafia_ES_v1.0.md` + `kicad/tools/t44_serigrafia.py` (silk designators 0.8/0.15 by script on all three; `t44_drc.py`, `t44_diff.py`, `t44_export.py`) | | `04-Source-Code` | firmware + backend repos | `firmware/` (**own git repo `hbox-firmware`**: `blackpillrtkcode_v2/` deploy · `legacy/` v1 · `docs/LOGGING-SPEC.md` v2.1 · **`hbox_v5_bringup/board_v56.h`** (V5.6/V5.7 pin map, 22-oct; 0.7.0 base)) · `htracker/` · `notifications/` · `rtk-config/` | | `05-Analytics-Reports` | SQL views, dashboards (VERONICA/PowerBI), **observability** | `2026-06-22_Drogheda_Manual-Logs-Grafana_v1.0.pdf` (Grafana/Loki log-consultation manual) | | `06-Testing-Validation` | RCA, diagnostics, test evidence, **log analysis & tooling** | `RCA-2026-06-field-failures.md` · `remote-diagnostic-protocol.md` · `2026-06-20_Drogheda_Bench-Log-Analysis_v1.0.md` (rev-3) · `2026-06-20_Drogheda_GNSS-Findings-Summary.md` · `log-analysis/` (repo **`hbox-log-tools`**: `log-explorer.html` · `analyze_logs.py` · `build_dashboard.py` · `docs/manual.pdf`) | | `Marketing` | product-facing collateral (LaTeX→PDF) | `HBOX-Datasheet/` (industrial spec sheet) · `HBOX-Brochure-RTK/` (flyer) | | `99-Archive` | superseded raw inputs (read-only) | `situation_ORIGINAL.md` | -| `hbox-docs/` | **the command centre** (submodule): internal site drogheda.helgen.online — MkDocs + Flask backend (actas, pendientes, inventario, sitios, equipo, enfoque, actividad) | **`CLAUDE.md`** (where each piece lives) · `docs/datos/proyecto.json` (Ruta, hitos) · `docs/hardware/index.md` · `docs/tableros/ingenieria.md` (decisions) · `docs/bitacora.md` · `scripts/sync-docs.sh` (publication policy) · `scripts/deploy.sh` · **`scripts/tablero.sh` + `backend/tablero.py` + `backend/lotes/`** (board snapshot and Claude's lotes, 21-oct) · `_estado/` (snapshot, gitignored) · `DESPLIEGUE.md` | +| `hbox-docs/` | **the command centre** (submodule): internal site drogheda.helgen.online — MkDocs + Flask backend (actas, pendientes, inventario, sitios, equipo, enfoque, actividad) | **`CLAUDE.md`** (where each piece lives) · `docs/datos/proyecto.json` (Ruta, hitos) · `docs/hardware/index.md` · `docs/tableros/ingenieria.md` (decisions) · `docs/bitacora.md` · `scripts/sync-docs.sh` (publication policy) · `scripts/deploy.sh` · **`scripts/tablero.sh` + `backend/tablero.py` + `backend/lotes/`** (board snapshot and Claude's lotes, 21-oct) · `_estado/` (snapshot, gitignored) · `DESPLIEGUE.md` · **27-oct:** `backend/acceso.py` (login page) · `backend/incidencias.py` · `backend/standup.py` · `backend/vigia.py` · `backend/nginx/` · `agentes/agente-diario.md` (daily agent runbook) · `scripts/agente-api.sh` · `scripts/despliegue-1027.sh` | | `.claude/skills/` | vendored project skill | `project-drogheda/` | > Structure follows the **[[Helgen Project Template]]** (English `NN-`). The Google diff --git a/hbox-docs b/hbox-docs index 5c937e4..742e534 160000 --- a/hbox-docs +++ b/hbox-docs @@ -1 +1 @@ -Subproject commit 5c937e44bdbe1deeb52f6404b6d9458f46a90112 +Subproject commit 742e534af20f8d78c50c55ea9c5eccc6d707a746