Drogheda/02-Architecture-Software/2026-10-27_Drogheda_Integracion-Cotizador-Inventario_ES_v1.0.md

13 KiB

title date tags estado
Cotizador ↔ centro de mando: órdenes de compra, inventario por sitio y tareas 2026-10-27
project-drogheda
hbox
compras
inventario
cotizador
integracion
sql
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.

Flujo de una orden de compra del cotizador al inventario del sitio

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.

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=<hex de HMAC(secreto, tiempo + "\n" + cuerpo)>
  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
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.