83 lines
6.9 KiB
Markdown
83 lines
6.9 KiB
Markdown
# 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)
|