diff --git a/bitacora/PENDIENTES.md b/bitacora/PENDIENTES.md index a1146cc..5b789d4 100644 --- a/bitacora/PENDIENTES.md +++ b/bitacora/PENDIENTES.md @@ -2,7 +2,20 @@ Acciones vivas del proyecto. Formato: `[ ]` abierta · `[x]` cerrada (no se borran, dejan rastro). Cada una con responsable y, si aplica, fecha. Ver contexto en [REGISTRO.md](REGISTRO.md). -_Última actualización: **2026-09-15** — corte de horas 10-ago→15-sep reconstruido y huecos de registro listados (seis sesiones sin bitácora). Anterior: **2026-08-21, mediodía** — ⭐ primera cotización real emitida (folio 1026; la 2.ª, corregida, pendiente de confirmar). Anterior: **2026-08-20, noche.** Cruce completo del buzón contra la bitácora (MCP ms365): se incorporaron 9 comunicaciones faltantes y se corrigieron 2 errores de registro. **Hay sesión mañana viernes 21-ago, 7:00 am** con Noé, Ara y Pedro sobre el formulario modificado y `FAC-106`. Ver [REGISTRO #65](REGISTRO.md)–[#68](REGISTRO.md)._ +_Última actualización: **2026-09-29** — plan del webhook Jira FAC → vista previa de cotización ([#71](REGISTRO.md)). Anterior: **2026-09-15** — corte de horas 10-ago→15-sep reconstruido y huecos de registro listados (seis sesiones sin bitácora). Anterior: **2026-08-21, mediodía** — ⭐ primera cotización real emitida (folio 1026; la 2.ª, corregida, pendiente de confirmar). Anterior: **2026-08-20, noche.** Cruce completo del buzón contra la bitácora (MCP ms365): se incorporaron 9 comunicaciones faltantes y se corrigieron 2 errores de registro. **Hay sesión mañana viernes 21-ago, 7:00 am** con Noé, Ara y Pedro sobre el formulario modificado y `FAC-106`. Ver [REGISTRO #65](REGISTRO.md)–[#68](REGISTRO.md)._ + +## ⭐ 29-sep — webhook Jira FAC → vista previa de cotización ([REGISTRO #71](REGISTRO.md), [plan](../planeacion/Plan-Webhook-Jira-2026-09-29.md)) + +- [ ] 📄 **Archivar en `fuentes/` la autorización escrita de Pedro** para crear el webhook/regla en Jira; anotar su alcance (¿incluye crear un ticket de prueba en FAC?). +- [ ] 🔎 **Fase 1 (solo GET):** `myself` + `mypermissions` (ADMINISTER / ADMINISTER_PROJECTS en FAC) + inventario de webhooks → decide Vía A (webhook de sistema) o Vía B (regla de Automation). +- [ ] 🔴 **Decidir el evento:** creación del ticket, transición a *En proceso de facturación* (dura ~3 min), o ambos. +- [ ] 🔧 **Fase 2:** `POST /api/jira/webhook` con secreto, relectura del ticket, idempotencia y solo dry-run; pruebas con `FakeHandler`. Poller de respaldo. +- [ ] 🌐 **Fase 3:** ngrok con traffic policy de una sola ruta; API en ReadOnly mientras el túnel esté abierto. +- [ ] ✍️ **Fase 4:** alta del webhook/regla como operación numerada (id, URL, fecha, autorización). +- [ ] 🧪 **Fase 5:** ticket de prueba → "lista para revisar" → verificar que BIND no recibió escritura. Reversa documentada. +- [ ] ☁️ **Destino productivo del webhook:** ngrok con dominio fijo (de pago) vs Azure (sigue bloqueado por el cliente). +- [ ] ⚠️ **Una sola sesión de Claude Code dueña de la API** en sesiones de escritura (el 29-sep otra sesión la relanzó en ReadOnly). +- [ ] 📝 **Hueco de bitácora:** registrar el trabajo del 9/10-sep (puente a la UI de BIND, prefactura 2463, sonda #12 / remisión 1004) — hoy solo vive en `balam-plataforma/CLAUDE.md`. ## 🚨 Huecos de registro (detectados en el corte del 15-sep) diff --git a/bitacora/REGISTRO.md b/bitacora/REGISTRO.md index eb48b3a..62624fe 100644 --- a/bitacora/REGISTRO.md +++ b/bitacora/REGISTRO.md @@ -1610,6 +1610,32 @@ Se aplicó el protocolo del guion §6: parar, anunciar el diagnóstico, pivotear --- +## 71 · Sep 29, 2026 · Sesión de trabajo · App en modo escritura, token de Jira y plan del webhook FAC → vista previa de cotización ⭐ + +> Contexto: sesión de Johann con Claude Code. Nota: la bitácora no tiene entradas entre el #70 (26-ago) y este; el trabajo del 9/10-sep (puente a la UI de BIND para la prefactura) está documentado solo en `balam-plataforma/CLAUDE.md` y se sube al repo en esta misma fecha. + +**1. Levantar la app en modo escritura.** Se arrancó Docker Desktop (estaba apagado) → Postgres `balam-postgres-dev` en 5438, frontend en 4200 y API en 5078 con `Bind__Mode=Write`, `Bind__Ui__Enabled=true` y `Bind__Ui__Mode=Write` como **variables de entorno de esa corrida** (los user-secrets siguen en `Bind:Mode=ReadOnly`, así que al apagar vuelve sola a solo lectura). +- ⚠️ **Choque entre dos sesiones de Claude Code:** otra sesión en la misma máquina levantó su propia API a las 13:24 (y de nuevo a las 15:28, `--no-build`) **sin** las variables de escritura. La API de esta sesión murió sin excepción en el log y el reinicio falló con `address already in use`. La API que quedó viva en 5078 era la de la otra sesión, en `Bind:Mode=ReadOnly`. +- Evidencia en pantalla: el paso 3 del wizard (*Vista previa y creación (ADR-001)*) generó la **operación #15 · `POST /api/Quotes`** como dry-run registrado en `WriteOperations`, con el botón *Crear cotización en BIND* deshabilitado: *"el servidor está en modo ReadOnly"*. **No se escribió nada en BIND.** +- Lección: una sola sesión debe ser dueña de la API durante una sesión de escritura; verificar el dueño del puerto (no solo que esté escuchando). + +**2. Token de Jira.** Existe `Jira:ApiToken` en user-secrets de `Balam.Api` (junto con `Bind:ApiKey`, `Jwt:SigningKey`, etc.); configuración en `appsettings.Development.json`: `balam-jsm-temp.atlassian.net`, usuario `pedro.ayala@…` (token personal de **Pedro**), proyecto FAC, `Mode=ReadOnly`. No se verificó el valor (el clasificador de permisos bloqueó leerlo, correctamente). Prueba práctica de vigencia: que la bandeja de Solicitudes FAC cargue. + +**3. ¿Se puede leer la creación de un ticket y automatizar la cotización?** +- **Leer: sí, hoy.** Polling con JQL delta (`SearchJqlAsync` + `SyncSchedulerService` + checkpoint con solape, ADR-002) o webhook. Recordatorio del 10-ago: el disparador acordado (*En proceso de facturación*) dura ~3 min, así que el poller debe leer el changelog, no el estatus actual. +- **Crear la cotización sin humano: técnicamente sí, pero rompe ADR-001** (dry-run + confirmación humana en cada escritura a BIND producción). Propuesta aceptada como camino: **el webhook/poller deja lista la vista previa y un humano confirma con un clic.** Automatización total solo con autorización escrita de Balam y enmienda a ADR-001. + +**4. Webhook / Jira Automation — qué se necesita.** URL pública (ngrok ya instalado en la máquina de Johann; productivo = Azure o dominio fijo), endpoint nuestro `POST /api/jira/webhook` (no existe aún), y el disparador en Jira. Con el token actual: construir el endpoint sí (no requiere permisos de Jira); crear el webhook/regla en Jira solo si el usuario de Pedro es admin **y** con autorización expresa, porque es escribir en la configuración del Jira productivo a su nombre. +- **Johann confirma que existe autorización escrita de Pedro** para hacerlo. ⚠️ Aún no archivada en `fuentes/`. + +**Resultado:** plan en [`../planeacion/Plan-Webhook-Jira-2026-09-29.md`](../planeacion/Plan-Webhook-Jira-2026-09-29.md): 5 fases (verificar permisos por GET → endpoint con secreto/idempotencia/relectura → ngrok con traffic policy de una sola ruta → una escritura auditada en Jira, preferentemente webhook de sistema → ticket de prueba y reversa). + +**Repo de la plataforma:** se suben los cambios pendientes (puente de prefactura por la UI de BIND: `BindUiClient`, `PrefacturaUiEndpoints`, tarjeta del wizard y pruebas). Verificado antes del push: 107 pruebas en verde y frontend compilando; escaneo del diff sin secretos. + +> **Lectura estratégica:** el webhook no cambia el protocolo de escritura: lo que acelera es la *captura*, no la *decisión*. Y el caso más valioso no es la creación del ticket sino el cruce de 3 minutos por *En proceso de facturación*, que el polling de 15 min pierde. + +--- + ## Resumen ejecutivo del hilo (para contexto rápido) ### Datos duros confirmados por Balam diff --git a/planeacion/Plan-Webhook-Jira-2026-09-29.md b/planeacion/Plan-Webhook-Jira-2026-09-29.md new file mode 100644 index 0000000..38be102 --- /dev/null +++ b/planeacion/Plan-Webhook-Jira-2026-09-29.md @@ -0,0 +1,82 @@ +# Plan — Webhook Jira (FAC) → vista previa automática de cotización BIND + +_Creado: 2026-09-29 · Responsable: Johann · Contexto: [REGISTRO #71](../bitacora/REGISTRO.md) · Antecedente técnico: [Investigacion-API-Jira.md §Bloque 3–4](Investigacion-API-Jira.md)_ + +## Objetivo + +Que la plataforma se entere **en el momento** en que se crea (o avanza) un ticket de FAC y deje **preparada la vista previa (dry-run) de la cotización en BIND**, para que un humano solo revise y confirme con un clic. + +**Fuera de alcance:** crear la cotización en BIND sin confirmación humana. ADR-001 sigue vigente: toda escritura en BIND producción = dry-run + confirmación humana + `Bind:Mode=Write` encendido solo durante la operación. La automatización total requeriría autorización escrita de Balam **y** enmienda de ADR-001, con reglas (solo clientes conocidos, IVA dentro del catálogo, tope de monto, interruptor de apagado). + +## Decisiones previas (antes de escribir en Jira) + +1. **¿Qué evento dispara?** Hay dos candidatos y no son equivalentes: + - `jira:issue_created`: el ticket nace. Es lo que se pidió el 29-sep. + - Transición a **`En proceso de facturación`**: es el disparador acordado en julio/agosto. Dura **~3 minutos** (FAC-100: 2m44s), así que un poller de 15 min se lo pierde; el webhook lo resuelve de raíz. + - **Propuesta:** escuchar `created` **y** `updated` (filtrado a cambio de estatus) en un solo webhook; la plataforma decide qué hacer con cada uno leyendo el changelog. +2. **Tickets de `Automation for Jira`** (10 de 69 al 10-ago, sin request type ni campos estructurados): el endpoint los recibe, pero los marca *"sin datos para cotizar"* en lugar de armar una vista previa. +3. **Autorización escrita de Pedro:** Johann confirma que existe (29-sep). **Falta archivarla en `fuentes/`** con fecha, y confirmar su alcance: ¿cubre solo crear el webhook/regla, o también crear un ticket de prueba en FAC? + +## Fases + +### Fase 1 — Verificación de permisos (solo lectura, riesgo cero) + +- `GET /rest/api/3/myself`: el token es de Pedro y está vivo. +- `GET /rest/api/3/mypermissions?projectKey=FAC&permissions=ADMINISTER,ADMINISTER_PROJECTS` + - `ADMINISTER` = admin del sitio → **Vía A** (webhook de sistema). + - Solo `ADMINISTER_PROJECTS` → **Vía B** (regla de Automation del proyecto). + - Ninguno → Pedro lo crea en pantalla; nosotros solo damos los datos. +- `GET /rest/webhooks/1.0/webhook` (si Vía A): inventario de webhooks existentes, para no duplicar y para saber qué hay antes de tocar. + +### Fase 2 — Endpoint en la plataforma (código nuestro, no toca Jira) + +`POST /api/jira/webhook` en `Balam.Api/Jira/`: + +- **Autenticación:** secreto compartido (Vía A: firma HMAC `X-Hub-Signature` si el sitio la ofrece; Vía B: header propio). Sin secreto válido → 401, sin leer el cuerpo. Secreto en user-secrets (`Jira:WebhookSecret`), nunca en el repo. +- **No confía en el payload:** solo extrae `issueKey`, valida que sea `FAC-\d+`, y **relee el ticket** con `JiraClient` (ReadOnly). +- **Idempotencia:** tabla/registro por `(issueKey, evento, timestamp)`; un reintento de Jira no genera una segunda vista previa. +- **Acción:** arma el body de `POST /api/Quotes` con el mismo mapeo del wizard (cliente del combo 11598, servicio, cantidad, IVA como tasa, moneda, días de crédito), lo registra en `WriteOperations` como `DryRun` y marca el ticket como **"Cotización lista para revisar"** en la bandeja. **Nunca llama a BIND en escritura.** +- **Responde rápido** (202) y procesa en segundo plano: Jira reintenta si el webhook tarda. +- **Pruebas** xUnit con `FakeHandler`: secreto inválido → 401; payload con clave ajena → 400; reintento → una sola vista previa; ticket de Automation sin campos → "sin datos"; jamás se invoca un método mutante de `BindClient`. +- El **poller** (JQL delta con solape, ADR-002) queda como respaldo: si la laptop está apagada y el webhook falla, el ticket se recupera en la siguiente pasada. + +### Fase 3 — Exposición temporal con ngrok + +- `ngrok http 5078` con **traffic policy** que solo deja pasar `POST /api/jira/webhook`; cualquier otra ruta → 403 en el borde. Login, cartera y emisión **no** quedan expuestos. +- Mientras el túnel esté abierto: API en `Bind:Mode=ReadOnly`. +- ⚠️ Con ngrok gratuito la URL cambia en cada reinicio y depende de la máquina de Johann encendida. Es **piloto**, no productivo. Productivo = dominio fijo de ngrok (de pago) o Azure (bloqueado por el cliente). + +### Fase 4 — Alta del disparador en Jira (UNA escritura, auditada) + +Operación puntual, numerada en la bitácora; **no** se cambia `Jira:Mode` de la app. + +- **Vía A (preferida):** `POST /rest/webhooks/1.0/webhook` con `events: ["jira:issue_created", "jira:issue_updated"]`, `filters: { "issue-related-events-section": "project = FAC" }`, URL de ngrok y secreto. Reproducible, sin consumir cuota de Automation, reversible con `DELETE /rest/webhooks/1.0/webhook/{id}`. +- **Vía B:** regla de Automation del proyecto FAC: *Work item created* (y/o *Work item transitioned → En proceso de facturación*) → *Send web request* (POST, header del secreto, body `{"issueKey":"{{issue.key}}","evento":"{{webhookEvent}}"}`). Se crea en la UI de Automation (la API de reglas es menos estable). Consume cuota mensual de ejecuciones: verificar el plan con Pedro. +- Anotar en la bitácora: id del webhook/regla, URL, fecha, quién autorizó. + +### Fase 5 — Prueba y reversa + +- **Prueba:** un ticket de prueba en FAC (lo crea alguien de Balam, o Johann si la autorización lo cubre). Criterio de éxito: llega el aviso → aparece "lista para revisar" → `WriteOperations` tiene un `DryRun` → **BIND no recibió ninguna escritura** (verificar por lectura). +- **Reversa:** borrar webhook/regla (id anotado) + cerrar ngrok. El endpoint queda inerte sin secreto configurado. + +## Riesgos + +| Riesgo | Mitigación | +|---|---| +| API local expuesta a internet | Traffic policy de ngrok (una sola ruta), secreto, `Bind:Mode=ReadOnly` con túnel abierto | +| Escritura en el Jira productivo del cliente | Una sola operación, autorizada por escrito, numerada y reversible | +| Payload falsificado | Secreto/HMAC + relectura del ticket por API | +| Pérdida de eventos (laptop apagada, URL de ngrok cambió) | Poller de respaldo con checkpoint y solape | +| Cuota de Automation (Vía B) | Verificar plan; preferir Vía A | +| Dos sesiones de Claude Code levantando la API a la vez (29-sep) | Una sola sesión dueña de la API durante la prueba | + +## Checklist + +- [ ] Archivar la autorización escrita de Pedro en `fuentes/` y anotar su alcance +- [ ] Fase 1 — verificar identidad y permisos del token (solo GET) +- [ ] Decidir evento(s): creación, transición a *En proceso de facturación*, o ambos +- [ ] Fase 2 — endpoint + idempotencia + pruebas +- [ ] Fase 3 — ngrok con traffic policy de una sola ruta +- [ ] Fase 4 — alta del webhook/regla (operación numerada) +- [ ] Fase 5 — ticket de prueba, verificación y registro en bitácora +- [ ] Definir el destino productivo (ngrok de pago vs Azure)