# 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)