Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013SfRSvDd335NPYEbBrDFGE
155 lines
13 KiB
Markdown
155 lines
13 KiB
Markdown
---
|
|
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).
|
|
|
|

|
|
|
|
## 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=<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](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.
|