Fusión de las dos sesiones del 29-sep: el registro del webhook Jira FAC pasa de #71 a #84 (los #71–#83 son las sesiones del 21-ago al 29-sep) y PENDIENTES conserva los dos bloques
This commit is contained in:
Binary file not shown.
+20
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
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-29** — ⭐ Balam entrega **login web a BIND** con permisos de prefactura; endpoint interno de la UI confirmado (*Generar Venta → Prefactura*, petición `InvoiceDetails`) y el modelo de dos endpoints corroborado con el SDK oficial ([REGISTRO #83](REGISTRO.md)). **Huecos de registro cerrados:** las sesiones del 21-ago al 10-sep se reconstruyeron desde el buzón ([#71](REGISTRO.md)–[#82](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)._
|
||||
_Última actualización: **2026-09-30** — fusión de las dos sesiones paralelas del 29-sep: la del webhook Jira FAC → vista previa de cotización pasa a ser el [#84](REGISTRO.md) (nació como #71 en la otra máquina). Anterior: **2026-09-29** — ⭐ Balam entrega **login web a BIND** con permisos de prefactura; endpoint interno de la UI confirmado (*Generar Venta → Prefactura*, petición `InvoiceDetails`) y el modelo de dos endpoints corroborado con el SDK oficial ([REGISTRO #83](REGISTRO.md)). **Huecos de registro cerrados:** las sesiones del 21-ago al 10-sep se reconstruyeron desde el buzón ([#71](REGISTRO.md)–[#82](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 — login web a BIND y endpoint interno de la UI ([REGISTRO #83](REGISTRO.md))
|
||||
|
||||
@@ -14,6 +14,25 @@ _Última actualización: **2026-09-29** — ⭐ Balam entrega **login web a BIND
|
||||
- [ ] 📧 **Pedir en paralelo a soporte BIND el campo/endpoint oficial de prefactura**, con la evidencia del #70 + el swagger del SDK (`NewInvoice` sin campo de tipo) + la solicitud pública "agregar PREFACTURA" de su foro. Replicar la UI es frágil y posiblemente ajeno a sus términos de servicio.
|
||||
- [ ] 📼 **Recuperar los informes completos de Read AI** (al menos 9, 10 y 29-sep) desde app.read.ai con sesión — los correos solo traen el extracto — y guardarlos en `../fuentes/`.
|
||||
|
||||
## ⭐ 29-sep — webhook Jira FAC → vista previa de cotización ([REGISTRO #84](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?).
|
||||
- [x] ~~🔎 **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).~~ → **HECHO (29-sep):** `ADMINISTER`=true, 0 webhooks de sitio, API de Automation accesible (50+ reglas). Vía A y Vía B disponibles.
|
||||
- [x] ~~🔴 **Decidir el evento:** creación del ticket, transición a *En proceso de facturación* (dura ~3 min), o ambos.~~ → **Creación del ticket** (`jira:issue_created`) para el piloto; la transición a *En proceso de facturación* queda para después.
|
||||
- [x] ~~🔧 **Fase 2:** `POST /api/jira/webhook` con secreto, relectura del ticket, idempotencia y solo dry-run; pruebas con `FakeHandler`. Poller de respaldo.~~ → **HECHO (29-sep, `d7238da`).** Falta el poller de respaldo.
|
||||
- [x] ~~🌐 **Fase 3:** ngrok con traffic policy de una sola ruta; API en ReadOnly mientras el túnel esté abierto.~~ → **HECHO (29-sep)**; túnel apagado al cerrar la sesión.
|
||||
- [x] ~~✍️ **Fase 4:** alta del webhook/regla como operación numerada (id, URL, fecha, autorización).~~ → **HECHO: webhook de sitio id 1** (19:05:53).
|
||||
- [x] ~~🧪 **Fase 5:** ticket de prueba → "lista para revisar" → verificar que BIND no recibió escritura. Reversa documentada.~~ → **HECHO: FAC-169** creado → detectado en 14.4 s (HMAC, verificado) → borrado. Ver adenda del [#84](REGISTRO.md).
|
||||
- [ ] 🔥 **Mover el webhook id 1 a paputec** (`https://paputec.mx/balam/api/jira/webhook`): publicar `d7238da` en paputec, poner `Jira__Webhook__Secret` en `/opt/balam/.env`, `PUT` del webhook con la URL nueva y ese secreto (solo HMAC, sin token en la URL). **Hoy Jira entrega a una URL caída** (el túnel ngrok se apagó).
|
||||
- [ ] 🔧 **Siguiente paso de la meta:** con cada `JiraTicketEvent` verificado, armar la **vista previa (dry-run) de la cotización** y marcar el ticket "lista para revisar" en la bandeja — la confirmación sigue siendo humana (ADR-001).
|
||||
- [ ] ☁️ **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).
|
||||
- [ ] 🔥 **API público de BIND en 500 en todos los endpoints** (key de Arturo) — sync detenido desde el 10-sep. Pedir a Balam la API key del usuario `CTO` o revisar el acceso de Arturo.
|
||||
- [ ] 🔥 **Prefactura 2467 ($777,777 MXN, ACUNTIA, FAC-166):** confirmar con Balam si es prueba (cancelar/borrar antes de que alguien la timbre) o real; revisar que al timbrar quede **PPD**, no el PUE que BIND le puso.
|
||||
- [ ] 📧 **Avisar a Pedro de la cotización 1079 marcada como Surtida por error** (`QuotesAPI/FillQuote` escribe por GET); sin efecto fiscal, irreversible desde la UI.
|
||||
- [ ] 🔧 Tomar el bloque `Client` de `ClientsAPI/GetClientSelectedById` (hoy `CFDI_Use` puede heredarse de la factura de OTRO cliente) y que el wizard no muestre G03/16% cuando el puente manda lo de BIND.
|
||||
- [ ] 📝 **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)
|
||||
|
||||
- [x] ~~🚨 **Seis sesiones con Balam sin entrada en REGISTRO.md**~~ → **REGISTRADAS el 29-sep** ([#72](REGISTRO.md), [#73](REGISTRO.md), [#75](REGISTRO.md), [#76](REGISTRO.md), [#78](REGISTRO.md), [#80](REGISTRO.md), [#81](REGISTRO.md); la del 29-sep en [#83](REGISTRO.md)) — ⚠️ solo con el extracto del correo de Read AI, sin transcript. Detalle original (evidencia: convocatorias de Pedro e informes de Read AI en el buzón): **21-ago 1:00 pm** "Seguimiento - PAF (2)" (homologación de estados del flujo; pendiente prueba de escritura en Jira), **24-ago 7:00 am** (validación cotización→prefactura MXN/USD, IVA 16 %/0 %; Noé condiciona Azure a que las pruebas funcionen), **26-ago 7:00 am** (permisos y flujo remisión/prefactura/factura; el POST se timbró en vivo → [#70](REGISTRO.md)), **3-sep 7:00 am** (datos del tenant de Azure; conceptos de Acuntia por headcount; definición: ítem por código y concepto = título del ticket) y **1:30 pm** (tres tickets de Pedro cotizados desde la plataforma publicada), **9-sep 1:00 pm** "Seguimiento PAF - Prefactura" (Araceli crea una prefactura en la UI de BIND con "Generar venta" mientras se capturan las peticiones: `InvoiceDetails`), **10-sep 1:00 pm** (con Pedro: cotización 1078 de Acuntia y captura de la petición de red de la prefactura).
|
||||
|
||||
@@ -1756,6 +1756,53 @@ La bandeja permite marcar varios tickets (solo los que aún no tienen cotizació
|
||||
|
||||
---
|
||||
|
||||
## 84 · 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.
|
||||
>
|
||||
> ⚠️ **Renumerado de #71 a #84 el 30-sep**, al fusionar las dos sesiones paralelas del 29-sep: la otra numeró [#71](REGISTRO.md)–[#83](REGISTRO.md) con las sesiones del 21-ago al 29-sep. Los commits `d76496f`…`e5912bf` de este repo y la documentación de la plataforma dicen "Registro #71" y se refieren a **esta** entrada. La nota de arriba sobre el hueco entre el #70 y este quedó superada por esos registros. A su vez, este registro deja atrás lo que el [#83](REGISTRO.md) da por pendiente: el endpoint interno de la UI **ya está especificado y construido** (`BindUiClient`, `AddInvoiceTransaction`, commit `4d6b3d8` del repo del cliente; ver punto 5).
|
||||
|
||||
**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/`.
|
||||
|
||||
**Fase 1 del plan — ejecutada el mismo 29-sep (solo GET, nada creado ni ejecutado):** `myself` 200 → Pedro Alberto Ayala Elizondo, activo · `mypermissions` en FAC: **`ADMINISTER` = true** (admin de Jira), `ADMINISTER_PROJECTS` = true, `CREATE_ISSUES` = true, `SYSTEM_ADMIN` = false (no se necesita) · `GET /rest/webhooks/1.0/webhook` 200 → **0 webhooks de sitio** (el endpoint exige admin: acceso confirmado) · API de Automation (`api.atlassian.com/automation/public/jira/{cloudId}/rest/v1/rule/summary`) 200 con el mismo token → **50+ reglas visibles** (primera página), casi todas de alcance sitio; relevantes para FAC: *Confirmación ticket creado - FACT*, *Factura por soporte técnico*, *Facturación Anticipada*. **Conclusión: el token de Pedro puede crear tanto el webhook de sitio (Vía A) como reglas de Automation (Vía B).** Queda archivar la autorización y decidir el evento.
|
||||
|
||||
**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).
|
||||
|
||||
**5. Hallazgos del mismo día hechos en la OTRA sesión de Claude Code** (fuente: `balam-plataforma/CLAUDE.md`, §Puente a la UI; no verificados en esta sesión):
|
||||
- ⭐ **29-sep 16:08 — PRIMERA PREFACTURA REAL DESDE LA PLATAFORMA: folio 2467** (ACUNTIA, **$777,777 MXN**, IVA 0, sin UUID, Activa), desde la cotización 1083 / ticket FAC-166, op #28, por el puente `AddInvoiceTransaction` con el usuario nuevo **`CTO`** (a nombre de Noé Rocha Castillo). BIND dejó la 1083 como Surtida.
|
||||
- Lecciones: el API público tarda más de un minuto en ver lo que crea la web app (hay reintentos); todo lo posterior al POST quedó en try/catch para que **un documento creado nunca se reporte como fallo**.
|
||||
- ⚠️ BIND dejó `CFDIPaymentTerm = PUE` aunque el cuerpo iba en PPD (igual que la 2462 hecha a mano): se define al timbrar. Choca con la salvaguarda #4 (PPD por default); revisar antes del timbrado.
|
||||
- ⚠️ **El API público (`api.bind.com.mx`) responde 500 en TODOS los endpoints** con la API key de `arturo|128469` (pasa autenticación y falla adentro; la web app sí funciona). **El sync no avanza desde el 10-sep.** Hipótesis: Balam tocó el acceso de Arturo al crear el usuario `CTO`. Salida propuesta: API key del usuario `CTO` en `Bind:ApiKey`.
|
||||
- 🔒 **Web app de BIND: una sesión por usuario.** La plataforma entra, opera y sale en cada operación; nadie debe usar el usuario `CTO` en un navegador mientras la plataforma opera.
|
||||
- ⛔ **Escritura accidental:** `QuotesAPI/FillQuote` resultó ser *"Marcar como Surtida"* y **escribe por GET**. Se invocó por error y dejó la **cotización de prueba 1079 en Surtida** (sin documento ni efecto fiscal; irreversible desde la UI). Desde entonces el lector solo permite `/Assets/*`, `/Sales/*Details` y `*API/Get*`.
|
||||
- La web app expone un API interno (66 rutas `InvoicesAPI/*`, 12 `QuotesAPI/*`, 26 `ClientsAPI/Get*`): plan B si el API público sigue caído. `GetClientSelectedById` resuelve las dos suposiciones del builder (`PaymentMethod`, `CFDI_Use`).
|
||||
|
||||
**Repo de la plataforma:** se sube el puente de prefactura por la UI de BIND (`BindUiClient`, `PrefacturaUiEndpoints`, tarjeta del wizard y pruebas) sobre 4 commits remotos de la otra máquina (26-ago: experimento de folio compartido; 15-sep: publicación en paputec.mx, catálogo de servicios, cotización en lote). Tres conflictos resueltos: **se conservó el candado 409 de `POST /api/emision/prefactura`** (el commit remoto del 26-ago noche lo había retirado para el experimento de `Number`, que el 27-ago BIND ignoró), se combinaron los campos de `NewInvoice` (`Number`/`ShowIEPS` + `PrevInvoice`/`IsCFDI`/`QuoteID`) y los usings de `Program.cs`. Verificado después del rebase: **108 pruebas en verde** y frontend compilando; escaneo del diff sin secretos. Commit `4d6b3d8`.
|
||||
|
||||
**ADENDA (29-sep, noche) — PILOTO DEL WEBHOOK VALIDADO DE PUNTA A PUNTA** (autorización expresa de Johann para las tres escrituras de prueba en Jira; Pedro autorizó el uso de su token):
|
||||
- **Plataforma** (commit `d7238da` en el repo del cliente): `POST /api/jira/webhook` con secreto (HMAC `X-Hub-Signature` o token), relectura del ticket por API (solo GET), tabla `JiraTicketEvents` con RLS e idempotencia, `GET /api/jira/eventos`; sin secreto responde 404. 8 pruebas nuevas, suite de la API en 38/38. Instancia expuesta en el puerto 5079 con `Bind:Mode=ReadOnly` y el puente UI apagado; ngrok con *traffic policy* que solo deja pasar el webhook (verificado desde internet: todo lo demás 404).
|
||||
- **Antes de escribir, inventario de Automation (solo GET):** 64 reglas (42 activas); de las 17 que se disparan al crear un ticket, **solo "Confirmación ticket creado - FACT" aplica a FAC** (proyecto 10034) y manda correo al reporter. Notificaciones de FAC al crear/borrar: reporter + watchers → con el ticket de prueba todo le llegó solo a Pedro.
|
||||
- **Jira-op 1 (19:05:53):** webhook de sitio **id 1** registrado — `jira:issue_created`, filtro `project = FAC`, con secreto HMAC, apuntando a `https://gregoria-disjoined-marcelino.ngrok-free.dev/api/jira/webhook`.
|
||||
- **Jira-op 2:** ticket de prueba **FAC-169** creado por API (asignado a Pedro, marcado como prueba). → Jira entregó **una sola vez**, **firmado HMAC**, desde "Atlassian Webhook HTTP Client"; la plataforma lo registró **verificado** (estatus Open) **14.4 s** después del timestamp del evento (la creación por API tardó ~11 s en responder; el aviso llegó ~3 s después).
|
||||
- **Jira-op 3 (19:07:44):** FAC-169 **borrado** (204; GET posterior → 404). En la BD local queda el evento id 1 como evidencia.
|
||||
- **Estado al cierre:** el webhook id 1 sigue registrado y activo, pero el túnel y la instancia 5079 se apagaron al cerrar la sesión → **Jira entrega a una URL caída** hasta moverlo. Destino estable: **`https://paputec.mx/balam/api/jira/webhook`** (Apache ya proxya `/balam/api/`). Script de operación: [`../planeacion/jira-webhook-prueba.py`](../planeacion/jira-webhook-prueba.py) (subcomandos `estado`, `mover`, `borrar-webhook`, `crear-ticket`, `borrar-ticket`).
|
||||
|
||||
> **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)
|
||||
|
||||
### Estado actual (al 29-sep-2026 — Etapa 2 en producción provisional)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# Correo — Respuesta a Erika: token de Jira, límites de consumo y estado general
|
||||
|
||||
Borrador · 29-jul-2026 — **no enviado**
|
||||
|
||||
Responder a todos → Para: Erika Chávez · CC: Noé Rocha; Pedro Ayala
|
||||
Asunto: (el del hilo, con `RE:`)
|
||||
|
||||
**Contexto:** Erika pregunta (29-jul, 1:25 pm) por comentarios sobre lo que entregó Pedro el 27-jul —token PAF por Drive + explicación de límites de consumo—, si se pudo ingresar y si se requiere algo adicional de su lado.
|
||||
|
||||
**Estrategia:** la pregunta de Erika abre la puerta para dar el panorama completo. El correo entrega dos cosas el mismo día (propuesta v1.3 y Excel de horas por etapa), reporta la prueba real del token con su resultado, y fecha los tres bloqueos que están del lado de Balam. Las fechas son el punto: el hueco en desarrollo del 28–29 existe porque la definición del flujo lleva en revisión interna desde el 24-jul y Azure desde la semana del 21-jul.
|
||||
|
||||
======================================================================
|
||||
|
||||
Buenas tardes Erika, Ing. Noé, Pedro:
|
||||
|
||||
Gracias Pedro por el token y por el detalle de los límites de consumo. Ya lo aterricé al diseño: como no hay tope mensual y lo que aplica son límites de velocidad, la integración se autorregula leyendo los headers `X-RateLimit-*` —baja el ritmo al acercarse al umbral y respeta el `Retry-After`—, así que no anticipo problema de consumo con el volumen del proceso. Que no exista dashboard no nos afecta, porque esa telemetría viaja en las respuestas.
|
||||
|
||||
Sobre el ingreso, ahí sí necesito tu apoyo: el token está siendo rechazado con 401. Ya descarté las causas de mi lado —la cadena está completa y bien formada (192 caracteres, con el prefijo que corresponde, sin espacios ni cortes), y probé con el correo que me confirmaste, tanto contra el sitio de Jira como por la ruta de `api.atlassian.com`. También lo validé contra el endpoint de cuenta de Atlassian, que no depende del sitio, y ahí también responde 401. Eso apunta a que la credencial ya no está activa.
|
||||
|
||||
¿Me ayudas a revisar en el administrador de tokens de tu cuenta si el token **PAF** sigue existiendo? Si se rotó o quedó reemplazado, con que lo regeneres y me lo compartas de nuevo lo pruebo de inmediato y les confirmo el mismo día.
|
||||
|
||||
Aprovecho una pregunta de diseño: convendría valorar si el token vive en una cuenta de servicio en lugar de en tu cuenta personal. Hoy todo lo que la plataforma escriba en Jira quedaría registrado a tu nombre, y una rotación tuya dejaría la integración sin acceso. Lo que ustedes decidan lo implemento igual, solo lo dejo sobre la mesa.
|
||||
|
||||
Aprovecho para dejarles el panorama completo. De mi lado va hoy:
|
||||
|
||||
- **La propuesta actualizada (v1.3)** con la integración Jira → BIND incorporada al alcance, que les envío en el hilo correspondiente.
|
||||
- **El Excel de horas por etapa**, con las etapas 2 y 3 desglosadas como me lo pidió Erika para el conteo de facturación.
|
||||
|
||||
Y lo que está detenido esperando definición de su lado, con fecha para que sea fácil ubicar dónde está la fila:
|
||||
|
||||
1. **La definición del flujo de facturación**, en revisión interna desde la sesión del 24-jul. De ahí sale el estatus que dispara la creación de la cotización, y es la que tiene detenidas la capa de escritura y la automatización con Jira. Es la que más pesa.
|
||||
2. **El acceso a Azure**, previsto para la semana del 21-jul. Sin él no puedo configurar el ambiente ni el CI/CD.
|
||||
3. **El token de Jira**, lo que comento arriba.
|
||||
|
||||
En cuanto se liberen retomo de inmediato; el trabajo que no depende de ellas ya está hecho y verificado. Si les ayuda, puedo entrar a una sesión corta esta semana para cerrar la definición del flujo con Arturo y avanzar sin esperar la revisión completa.
|
||||
|
||||
Cuando toque la prueba en vivo de creación les aviso con anticipación para hacerla acompañados, como lo acordamos con el Ing. Noé.
|
||||
|
||||
Quedo a sus órdenes.
|
||||
|
||||
Saludos cordiales,
|
||||
Johann Velázquez
|
||||
@@ -0,0 +1,82 @@
|
||||
# Plan — Webhook Jira (FAC) → vista previa automática de cotización BIND
|
||||
|
||||
_Creado: 2026-09-29 · Responsable: Johann · Contexto: [REGISTRO #84](../bitacora/REGISTRO.md) (nació como #71; renumerado al fusionar el 30-sep) · 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)
|
||||
@@ -0,0 +1,164 @@
|
||||
# Piloto del webhook Jira FAC → plataforma (29-sep-2026). Autorizado por Pedro
|
||||
# (vía Johann). Cada escritura en Jira es explícita, una por subcomando:
|
||||
#
|
||||
# url → lee la URL pública del túnel ngrok local (solo lectura)
|
||||
# registrar → POST /rest/webhooks/1.0/webhook (ESCRIBE: 1 webhook de sitio)
|
||||
# crear-ticket → POST /rest/api/3/issue (ESCRIBE: 1 ticket de prueba en FAC)
|
||||
# esperar CLAVE → espera la detección en la BD local (solo lectura)
|
||||
# borrar-ticket CLAVE → DELETE /rest/api/3/issue/CLAVE (ESCRIBE: borra el ticket de prueba)
|
||||
# borrar-webhook → DELETE del webhook registrado (reversa total)
|
||||
# mover URL_BASE → PUT del webhook a otra URL base (ESCRIBE), p. ej.
|
||||
# https://paputec.mx/balam (el receptor vive en /api/jira/webhook)
|
||||
# estado → webhooks de sitio actuales (solo lectura)
|
||||
#
|
||||
# Estado del piloto: webhook de sitio id 1 registrado el 29-sep apuntando al
|
||||
# túnel ngrok de Johann; prueba FAC-169 creada, detectada y borrada.
|
||||
#
|
||||
# El token de Jira y el secreto del webhook nunca se imprimen ni se versionan:
|
||||
# token → propuesta/token-jira.txt (gitignoreado) o $JIRA_TOKEN_FILE
|
||||
# secreto → $JIRA_WEBHOOK_SECRET_FILE (default: ~/jira-webhook-secret.txt)
|
||||
import base64, json, os, subprocess, sys, time, urllib.request, urllib.error
|
||||
|
||||
SITE = "https://balam-jsm-temp.atlassian.net"
|
||||
EMAIL = "pedro.ayala@balamtalentoestrategico.com"
|
||||
PEDRO = "712020:0cde8558-7794-468c-9e2c-73dfe63bd57b"
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
HOME = os.path.expanduser("~")
|
||||
TOKEN = open(os.environ.get("JIRA_TOKEN_FILE", os.path.join(REPO, "propuesta", "token-jira.txt")), encoding="utf-8").read().strip()
|
||||
SECRETO = open(os.environ.get("JIRA_WEBHOOK_SECRET_FILE", os.path.join(HOME, "jira-webhook-secret.txt")), encoding="utf-8").read().strip()
|
||||
AUTH = "Basic " + base64.b64encode(f"{EMAIL}:{TOKEN}".encode()).decode()
|
||||
ESTADO = os.environ.get("JIRA_WEBHOOK_ESTADO", os.path.join(HOME, "jira-webhook-estado.json"))
|
||||
|
||||
|
||||
def jira(method, path, body=None):
|
||||
data = json.dumps(body).encode() if body is not None else None
|
||||
req = urllib.request.Request(SITE + path, data=data, method=method, headers={
|
||||
"Authorization": AUTH, "Accept": "application/json", "Content-Type": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
raw = r.read().decode("utf-8")
|
||||
return r.status, (json.loads(raw) if raw else None)
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, e.read().decode("utf-8", "replace")[:500]
|
||||
|
||||
|
||||
def estado(**cambios):
|
||||
s = json.load(open(ESTADO, encoding="utf-8")) if os.path.exists(ESTADO) else {}
|
||||
s.update(cambios)
|
||||
json.dump(s, open(ESTADO, "w", encoding="utf-8"), indent=1)
|
||||
return s
|
||||
|
||||
|
||||
def url_publica():
|
||||
for puerto in (4040, 4041, 4042):
|
||||
try:
|
||||
with urllib.request.urlopen(f"http://127.0.0.1:{puerto}/api/tunnels", timeout=3) as r:
|
||||
for t in json.loads(r.read().decode())["tunnels"]:
|
||||
if t["public_url"].startswith("https://") and "5079" in t["config"]["addr"]:
|
||||
return t["public_url"]
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def sql(consulta):
|
||||
# Superusuario local del docker-compose: ve la tabla pese a RLS.
|
||||
out = subprocess.run(["docker", "exec", "balam-postgres-dev", "psql", "-U", "balam", "-d", "balam",
|
||||
"-At", "-F", " | ", "-c", consulta], capture_output=True, text=True, encoding="utf-8")
|
||||
return out.stdout.strip() or out.stderr.strip()
|
||||
|
||||
|
||||
cmd = sys.argv[1] if len(sys.argv) > 1 else "estado"
|
||||
|
||||
if cmd == "url":
|
||||
print(url_publica() or "Sin túnel ngrok hacia 5079.")
|
||||
|
||||
elif cmd == "registrar":
|
||||
base = url_publica()
|
||||
if not base:
|
||||
sys.exit("Sin túnel ngrok hacia 5079: no se registra nada.")
|
||||
cuerpo = {
|
||||
"name": "Balam plataforma · FAC ticket creado (piloto 29-sep)",
|
||||
"url": f"{base}/api/jira/webhook?token={SECRETO}",
|
||||
"events": ["jira:issue_created"],
|
||||
"filters": {"issue-related-events-section": "project = FAC"},
|
||||
"excludeBody": False,
|
||||
"secret": SECRETO,
|
||||
}
|
||||
st, r = jira("POST", "/rest/webhooks/1.0/webhook", cuerpo)
|
||||
if st == 400 and "secret" in str(r).lower():
|
||||
cuerpo.pop("secret")
|
||||
st, r = jira("POST", "/rest/webhooks/1.0/webhook", cuerpo)
|
||||
if st in (200, 201) and isinstance(r, dict):
|
||||
wid = r.get("self", "").rstrip("/").split("/")[-1]
|
||||
estado(webhook_id=wid, webhook_url_base=base, registrado=time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
con_secreto_hmac="secret" in cuerpo)
|
||||
print(f"[{st}] webhook registrado · id={wid} · eventos={r.get('events')} · filtro={r.get('filters')} · "
|
||||
f"secreto HMAC enviado={'secret' in cuerpo}")
|
||||
else:
|
||||
print(f"[{st}] {r}")
|
||||
|
||||
elif cmd == "crear-ticket":
|
||||
adf = {"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"type": "text", "text":
|
||||
"Ticket de PRUEBA de la plataforma Balam (29-sep-2026), autorizado por Pedro Ayala, para validar "
|
||||
"que la plataforma detecta la creación de tickets por webhook. Se elimina en cuanto se confirma."}]}]}
|
||||
st, r = jira("POST", "/rest/api/3/issue", {"fields": {
|
||||
"project": {"key": "FAC"}, "issuetype": {"id": "10073"},
|
||||
"summary": "[PRUEBA PLATAFORMA BALAM] Detección por webhook — se borra en minutos",
|
||||
"description": adf, "assignee": {"accountId": PEDRO}}})
|
||||
if st == 201:
|
||||
estado(ticket=r["key"], ticket_id=r["id"], creado=time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
print(f"[201] ticket de prueba creado: {r['key']} (id {r['id']})")
|
||||
else:
|
||||
print(f"[{st}] {r}")
|
||||
|
||||
elif cmd == "esperar":
|
||||
clave = sys.argv[2]
|
||||
for i in range(60):
|
||||
fila = sql(f"""SELECT "Id","IssueKey","Verified","AuthMethod","JiraStatus",
|
||||
to_char("JiraTimestampUtc" AT TIME ZONE 'America/Mexico_City','HH24:MI:SS.MS'),
|
||||
to_char("ReceivedAtUtc" AT TIME ZONE 'America/Mexico_City','HH24:MI:SS.MS'),
|
||||
round(extract(epoch from ("ReceivedAtUtc"-"JiraTimestampUtc"))::numeric,2),
|
||||
coalesce("Error",'') FROM "JiraTicketEvents" WHERE "IssueKey"='{clave}'""")
|
||||
if fila and "|" in fila:
|
||||
print(f"DETECTADO tras ~{i}s de espera: id | llave | verificado | auth | estatus Jira | emitido (MX) | recibido (MX) | retraso s | error")
|
||||
print(" " + fila)
|
||||
break
|
||||
time.sleep(1)
|
||||
else:
|
||||
print(f"Sin detección de {clave} en 60 s.")
|
||||
|
||||
elif cmd == "borrar-ticket":
|
||||
clave = sys.argv[2]
|
||||
st, r = jira("DELETE", f"/rest/api/3/issue/{clave}")
|
||||
st2, _ = jira("GET", f"/rest/api/3/issue/{clave}")
|
||||
estado(ticket_borrado=clave if st == 204 else None, borrado=time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
print(f"[{st}] DELETE {clave} · verificación GET → {st2} (404 = ya no existe)")
|
||||
|
||||
elif cmd == "borrar-webhook":
|
||||
wid = estado().get("webhook_id")
|
||||
st, r = jira("DELETE", f"/rest/webhooks/1.0/webhook/{wid}")
|
||||
print(f"[{st}] DELETE webhook {wid}")
|
||||
|
||||
elif cmd == "mover":
|
||||
# Destino estable (paputec): solo HMAC, sin el secreto en la URL.
|
||||
base = sys.argv[2].rstrip("/")
|
||||
wid = estado().get("webhook_id") or "1"
|
||||
st, r = jira("PUT", f"/rest/webhooks/1.0/webhook/{wid}", {
|
||||
"name": "Balam plataforma · FAC ticket creado",
|
||||
"url": f"{base}/api/jira/webhook",
|
||||
"events": ["jira:issue_created"],
|
||||
"filters": {"issue-related-events-section": "project = FAC"},
|
||||
"excludeBody": False,
|
||||
"secret": SECRETO,
|
||||
})
|
||||
if st in (200, 201):
|
||||
estado(webhook_id=wid, webhook_url_base=base, movido=time.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
print(f"[{st}] webhook {wid} → {base}/api/jira/webhook" + ("" if st in (200, 201) else f" · {r}"))
|
||||
|
||||
elif cmd == "estado":
|
||||
st, r = jira("GET", "/rest/webhooks/1.0/webhook")
|
||||
print(f"[{st}] webhooks de sitio: {len(r) if isinstance(r, list) else r}")
|
||||
for w in r if isinstance(r, list) else []:
|
||||
print(f" - {w.get('name')} · habilitado={w.get('enabled')} · eventos={w.get('events')} · filtro={w.get('filters')}")
|
||||
print("estado local:", json.dumps(estado(), ensure_ascii=False))
|
||||
@@ -0,0 +1,29 @@
|
||||
# Correo — Propuesta v1.3 (ajuste de alcance: integración Jira → BIND)
|
||||
|
||||
Borrador · 29-jul-2026 — **no enviado**
|
||||
Adjunto: `Propuesta-Balam.pdf` (v1.3, 2.8 MB)
|
||||
|
||||
Para: Ing. Noé Rocha
|
||||
CC: Araceli Sánchez; Erika Chávez; Pedro Ayala
|
||||
|
||||
**Contexto:** cierra el compromiso del 22-jul (Erika preguntó dos veces si la propuesta se ajustaría por Jira) y la re-pregunta del 28-jul 9:12 am. Mismo reparto de destinatarios que la v1.2 (2-jul). Recomendado: responder dentro del hilo **"RE: Solicitud de cotización – PRD"** para conservar la trazabilidad comercial.
|
||||
|
||||
Asunto (si se envía en el hilo): `RE: Solicitud de cotización – PRD`
|
||||
Asunto (si se envía como correo nuevo): `Balam · Propuesta v1.3 — ajuste de alcance: integración Jira → BIND`
|
||||
|
||||
======================================================================
|
||||
|
||||
Buenas tardes a todos:
|
||||
|
||||
Como quedamos en las sesiones de julio, les comparto la propuesta actualizada (v1.3, adjunta). Incorpora al alcance la integración Jira → BIND: los tickets de la bandeja de Facturación generan automáticamente su cotización en BIND, con trazabilidad ticket → cotización → factura.
|
||||
|
||||
- **Ajuste: 7 a 10 h** ($4,200 – $6,000 MXN + IVA), en línea con la estimación de ~10 h que comenté el 21 de julio.
|
||||
- **Total primera etapa: 119 a 146 h** ($71,400 – $87,600 MXN + IVA), 6 a 8 semanas a media dedicación.
|
||||
- El control fiscal no cambia: la integración crea cotizaciones, nunca facturas. Se conserva la cadena cotización → prefactura → aprobación humana → timbrado por el PAC de BIND. Tarifa, facturación semanal y garantía siguen igual.
|
||||
|
||||
La implementación arranca en cuanto quede cerrada la definición del flujo de facturación que tienen en revisión, de donde sale el estatus disparador. La conectividad ya está resuelta con el token que generó Pedro el 27 de julio.
|
||||
|
||||
Para ejecutarlo me basta su confirmación por este mismo correo. Cualquier comentario, con gusto lo revisamos antes.
|
||||
|
||||
Saludos cordiales,
|
||||
Johann Velázquez
|
||||
Reference in New Issue
Block a user