#!/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-. 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 — 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