{
  "flow_id": "flow_2026-05-09T22-06-17-687Z_ComfortSleep",
  "created_at": "2026-05-09T22:06:17.693996+00:00",
  "input_start": {
    "flow": "escalado_formatos",
    "product": "ComfortSleep",
    "video_url": "https://www.facebook.com/61562217471968/posts/122192975072407249/",
    "country": "ES",
    "saturated": true,
    "source": "html_dashboard",
    "timestamp": "2026-05-09T22:06:17.687Z"
  },
  "sections": {
    "_rescue_descargar": {
      "level_resolved": null,
      "winning_hypothesis": null,
      "initial_break": "EXEC_FAIL · claude exit=1",
      "qc_attempts": [
        {
          "hypothesis": {
            "id": "qc_descargar_1",
            "level": "qc",
            "idea": "yt-dlp falla porque la URL del video requiere cookies/login (FB Ads Library, Reels privados, TikTok geo-bloqueado) y el ejecutor no pasa cookies ni user-agent",
            "feedback": "Reintenta yt-dlp con: --cookies-from-browser chrome --user-agent \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0\" --no-check-certificate -o \"<ruta>.mp4\" <URL>. Si sigue fallando exit!=0, NO reintentar yt-dlp: cambia de método inmediatamente al fallback Playwright CDP (chrome --remote-debugging-port=9222 ya logueado) y captura el blob mp4 desde la pestaña con el video reproduciendo."
          },
          "result": "FAIL",
          "detail": "EXEC_FAIL · claude exit=1"
        },
        {
          "hypothesis": {
            "id": "qc_descargar_2",
            "level": "qc",
            "idea": "el binario yt-dlp no está disponible en el PATH del subproceso de Claude Code (Windows PowerShell) o está obsoleto y no soporta el extractor del dominio actual",
            "feedback": "Antes de descargar ejecuta: yt-dlp --version. Si exit!=0 o no existe, instala/actualiza con: python -m pip install -U yt-dlp (o pipx install yt-dlp). Si la URL es de un dominio no soportado por yt-dlp (FB Ads Library con ID de ad), salta directo a fallback: usar fdown.net/getfvid via WebFetch o Playwright CDP. Verifica el binario ANTES de cada intento, no asumas que existe."
          },
          "result": "FAIL",
          "detail": "EXEC_FAIL · claude exit=1"
        },
        {
          "hypothesis": {
            "id": "qc_descargar_3",
            "level": "qc",
            "idea": "la URL de input está malformada, expirada (CDN signed URL caducada) o es un ID de FB Ad Library en vez de URL de video reproducible",
            "feedback": "Antes de yt-dlp valida la URL: si empieza por facebook.com/ads/library/?id=XXXXX, NO pases eso a yt-dlp — primero abre la URL con Playwright CDP, espera a que el video cargue, extrae el src del <video> tag (regex video_hd_url|video_sd_url en el HTML) y usa ESA URL real con yt-dlp. Si la URL ya es directa pero >24h vieja, re-resuélvela navegando a la página origen primero."
          },
          "result": "FAIL",
          "detail": "EXEC_FAIL · claude exit=1"
        }
      ],
      "master_attempts": [
        {
          "hypothesis": {
            "id": "master_descargar_1",
            "level": "master",
            "idea": "La URL de input es un POST de Facebook (facebook.com/<page_id>/posts/<post_id>) — NO un video reproducible directo. yt-dlp y la mayoría de extractores fallan en /posts/ porque esperan /watch/, /reel/, /videos/, o un asset CDN. La estación INPUT (aguas arriba) aceptó una URL que no es procesable por DESCARGAR sin pre-resolución. Problema de contrato cross-estación.",
            "feedback": "FIX: insertar mini-estación pre-DESCARGAR `resolve_fb_url` que toma la URL de /posts/ y resuelve al asset real vía Playwright Chrome CDP (sesión logueada de Fer en facebook.com): navegar a la URL → esperar `<video>` o `data-video-id` en DOM → extraer src del .mp4 (Blob URL o CDN signed URL fbcdn.net) → pasar ESA URL resuelta a DESCARGAR. Si es ad de Ad Library, resolver vía Apify actor `curious_coder/facebook-ads-library-scraper` (REGLA #46) usando page_id+post_id para obtener `media_url` directo. Idempotente: cachear resolved_url en la caja del flow."
          },
          "result": "FAIL",
          "detail": "EXEC_FAIL · claude exit=1"
        },
        {
          "hypothesis": {
            "id": "master_descargar_2",
            "level": "master",
            "idea": "Modo degradado fallback global: cuando los 3 caminos primarios (yt-dlp, Playwright CDP scrape, fdown.net) fallan, NO marcar EXEC_FAIL — disparar handoff humano vía Telegram CEO con el screenshot del post + URL original + pedido explícito 'descarga manualmente este mp4 y súbelo a la caja del flow'. El flow continúa en estado WAITING_HUMAN_DOWNLOAD en vez de morir. Patrón usado en `agente-fer` y REGLA #19 cuando GetHookd cae.",
            "feedback": "FIX: modificar el supervisor de DESCARGAR para que tras 3 reintentos primarios + 2 fallbacks (yt-dlp con cookies de Chrome export → fdown.net → getfvid → savefrom) → en lugar de exit=1 escriba `_descargar_pending_human.json` en la caja con {url_original, screenshot_path, error_chain} + ping Telegram CEO bot 6002482241 + pause flow. Cuando Fer responda con mp4 attachment, hook detecta y resume DESCARGAR con ese archivo. ANTI-pattern: matar flow saturado por blocker técnico de 1 video."
          },
          "result": "FAIL",
          "detail": "EXEC_FAIL · claude exit=1"
        },
        {
          "hypothesis": {
            "id": "master_descargar_3",
            "level": "master",
            "idea": "El supervisor de DESCARGAR está calibrado para URLs canónicas (Reels, TikTok, Twitter) pero NO trata FB posts con `saturated:true` como caso especial. Producto saturado (REGLA #109) implica que el competidor pone hostia anti-scraping (login wall, rate limit por IP, Cloudflare challenge). El contrato del supervisor asume timeout uniforme de ~30s y 1 user-agent — insuficiente para FB en 2026.",
            "feedback": "FIX: re-arquitectura del supervisor DESCARGAR con branch condicional por dominio + flag `saturated`. Si `host == facebook.com` Y `saturated:true` → usar Playwright connect_over_cdp a Chrome con sesión logueada (puerto 9222), esperar 60s, hacer scroll para forzar lazy-load del video, capturar el blob via page.evaluate fetching el src del <video>, descargar con cookies de la sesión activa. Loggear `download_strategy_used` en la caja para que el QC distinga 'falló por contrato malo' vs 'falló por contenido inexistente'. Sin esta diferenciación, los 3 hipótesis del QC seguirán fallando porque atacan el mismo sub-problema (extractor) en vez del problema raíz (contrato no contempla FB+saturated)."
          },
          "result": "FAIL",
          "detail": "EXEC_FAIL · claude exit=1"
        }
      ],
      "fer_pinged": true,
      "ts": "2026-05-09T22:07:59.998272+00:00"
    }
  },
  "history": [
    {
      "ts": "2026-05-09T22:07:59.998272+00:00",
      "actor": "rescue-ladder",
      "section": "_rescue_descargar",
      "mode": "replace",
      "bytes_after": 5617
    }
  ]
}