Files
balam/planeacion/Plan-Webhook-Jira-2026-09-29.md
T

83 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)