Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013SfRSvDd335NPYEbBrDFGE
151 lines
9.9 KiB
Markdown
151 lines
9.9 KiB
Markdown
---
|
|
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_<idSolicitud>`), 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` = `<folio OC>:<tipo>:<n>` (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=<PORTAL_ULTIMA_CONSULTA>`
|
|
(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.
|