Registra la recuperación técnica del 1-ago (#61), refresca pendientes, borrador de status para Erika y script de pruebas Jira de lectura

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Johann
2026-08-01 20:21:02 -06:00
parent 2b862ee769
commit 8d320edfa7
4 changed files with 145 additions and 3 deletions
@@ -32,3 +32,19 @@ Johann Velázquez
## 3. Confirmación al recibir el repositorio
> Listo, ya recibí la invitación y confirmé que tengo acceso de escritura al repositorio. Muchas gracias. Voy a publicar la estructura inicial del backend y les aviso cuando quede disponible.
## 4. WhatsApp a Erika — status lunes 3-ago + lo que falta de Balam (preparado 1-ago)
> Hola Erika, buen día. Te comparto avance del fin de semana:
>
> ✅ Cierre técnico de la Etapa 1 (lo que no estaba bloqueado): sincronización automática cada 15 min, datos de demostración, y suite de pruebas del API (61 pruebas en verde).
> ✅ Adelanté el frontend de la Etapa 2 (parte de consulta): inicio de sesión por roles, cartera por moneda, listado de facturas con filtros y búsqueda, detalle con descarga del PDF del CFDI y catálogo de clientes con exportación a Excel/CSV. Todo el código ya está en el repositorio de Balam.
> ✅ La base técnica de la integración con Jira (lectura) quedó lista y probada.
>
> Para seguir avanzando solo me falta de su lado:
> 1. ☁️ El acceso a Azure que quedó con el inge Noé (grupo de recursos con Contributor) — es lo que libera el ambiente de pruebas y el despliegue.
> 2. 📅 La fecha de la sesión del flujo de facturación — desbloquea la capa de escritura (cotización→factura) y las pantallas de emisión.
> 3. 🧾 Tres definiciones de la integración Jira→BIND (pueden salir en una sola sesión con Pedro y Arturo): ¿qué tipo de solicitud se automatiza (Facturación o Facturación adicional)?, ¿qué estatus dispara la cotización?, y ¿de dónde tomamos monto y conceptos del ticket?
>
> Si te parece, el viernes les muestro todo funcionando en la demo semanal.
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env bash
# Pruebas de SOLO LECTURA contra la instancia real de Jira (FAC) — Bloques 2,
# 3 (inventario), 4 y 5 de Investigacion-API-Jira.md. Ninguna llamada muta nada.
#
# Requisitos: el token en propuesta/token-jira.txt (gitignoreado) o en $JIRA_TOKEN.
# Uso: ./pruebas-jira-lectura.sh [directorio_salida]
# La salida VA FUERA DEL REPO por default (los tickets traen datos reales de
# clientes): /tmp/jira-probes-<fecha>. Al repo solo se llevan los hallazgos
# agregados, transcritos a mano a Investigacion-API-Jira.md.
set -euo pipefail
SITE="https://balam-jsm-temp.atlassian.net"
EMAIL="pedro.ayala@balamtalentoestrategico.com"
REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
TOKEN="${JIRA_TOKEN:-$(cat "$REPO_DIR/propuesta/token-jira.txt" 2>/dev/null || true)}"
if [ -z "$TOKEN" ]; then
echo "ERROR: token no encontrado (propuesta/token-jira.txt o \$JIRA_TOKEN)." >&2
exit 1
fi
OUT="${1:-/tmp/jira-probes-$(date +%Y%m%d-%H%M%S)}"
mkdir -p "$OUT"
AUTH="$EMAIL:$TOKEN"
req() { # req <nombre> <ruta> — guarda body y headers, imprime status + rate limit
local name="$1" path="$2"
local hdr="$OUT/$name.headers" body="$OUT/$name.json"
local status
status=$(curl -sS -u "$AUTH" -H "Accept: application/json" \
-D "$hdr" -o "$body" -w "%{http_code}" "$SITE$path")
local rate
rate=$(grep -i "x-ratelimit" "$hdr" | tr -d '\r' | paste -sd' ' - || true)
echo "[$status] $name ${rate:-(sin headers de rate limit)}"
}
echo "== Bloque 0 · auth =="
req myself "/rest/api/3/myself"
echo "== Bloque 4 · búsqueda vigente, fields acotados y tope de maxResults =="
req search-recientes "/rest/api/3/search/jql?jql=$(python3 -c 'import urllib.parse;print(urllib.parse.quote("project = FAC ORDER BY updated DESC"))')&maxResults=10&fields=summary,status,updated,created,issuetype,reporter"
req search-tope "/rest/api/3/search/jql?jql=$(python3 -c 'import urllib.parse;print(urllib.parse.quote("project = FAC"))')&maxResults=5000&fields=id"
req search-delta "/rest/api/3/search/jql?jql=$(python3 -c 'import urllib.parse;print(urllib.parse.quote("project = FAC AND updated >= \"-20m\" ORDER BY updated ASC"))')&fields=updated"
echo "== Bloque 5 · origen de los tickets (changelog de los 8 más recientes) =="
python3 - "$OUT" <<'PY'
import json, sys, subprocess, os
out = sys.argv[1]
data = json.load(open(f"{out}/search-recientes.json"))
keys = [(i["id"], i["key"]) for i in data.get("issues", [])][:8]
open(f"{out}/ids-muestra.txt", "w").write("\n".join(f"{i} {k}" for i, k in keys))
print("muestra:", ", ".join(k for _, k in keys))
PY
while read -r id key; do
req "issue-$key" "/rest/api/3/issue/$id?expand=changelog&fields=summary,status,issuetype,created,reporter,attachment,description,resolution"
done < "$OUT/ids-muestra.txt"
echo "== Bloque 3 · transiciones con campos obligatorios (2 tickets de la muestra) =="
head -2 "$OUT/ids-muestra.txt" | while read -r id key; do
req "transitions-$key" "/rest/api/3/issue/$id/transitions?expand=transitions.fields"
done
echo "== Bloque 2 · request types faltantes (12 Facturación, 389 Automatización) =="
req requesttype-12 "/rest/servicedeskapi/servicedesk/35/requesttype/12/field"
req requesttype-389 "/rest/servicedeskapi/servicedesk/35/requesttype/389/field"
echo "== Resumen para transcribir a Investigacion-API-Jira.md =="
python3 - "$OUT" <<'PY'
import json, sys, glob, os
out = sys.argv[1]
def load(n):
try: return json.load(open(f"{out}/{n}.json"))
except Exception: return None
tope = load("search-tope")
if tope: print(f"- maxResults efectivo con 5000 pedidos: {len(tope.get('issues', []))} issues devueltos; isLast={tope.get('isLast')}")
delta = load("search-delta")
if delta is not None: print(f"- delta 20m: {len(delta.get('issues', []))} tickets actualizados en la ventana")
moves = 0
for f in sorted(glob.glob(f"{out}/issue-*.json")):
d = load(os.path.basename(f)[:-5])
if not d: continue
key = d.get("key", "?")
fields = d.get("fields", {})
hist = (d.get("changelog") or {}).get("histories", []) or (d.get("changelog") or {}).get("values", [])
key_changes = [i for h in hist for i in h.get("items", []) if i.get("field") in ("Key", "project")]
if key_changes: moves += 1
att = fields.get("attachment") or []
desc = fields.get("description")
desc_len = len(json.dumps(desc)) if desc else 0
total_hist = (d.get("changelog") or {}).get("total", len(hist))
print(f"- {key}: type={ (fields.get('issuetype') or {}).get('name') } · status={(fields.get('status') or {}).get('name')} · "
f"resolution={(fields.get('resolution') or {}).get('name') if fields.get('resolution') else None} · "
f"adjuntos={len(att)} · descripcion_adf={desc_len}B · changelog(total={total_hist}, en_expand={len(hist)})"
f"{' · ⚠️ MOVE/KEY-CHANGE detectado' if key_changes else ''}")
print(f"- tickets con move/cambio de llave en la muestra: {moves}")
print(f"\nBodies completos en: {out} (NO copiar al repo: contienen datos reales)")
PY