---
name: comfyui_download_workflow
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: impure
signature: "def comfyui_download_workflow(source: str, dest: str | None = None, *, server: str = \"127.0.0.1:8188\", civitai_token: str | None = None, hf_token: str | None = None, timeout: float = 30.0) -> dict"
description: "Descarga un workflow ComfyUI desde CUALQUIER fuente (Google Drive, GitHub, Civitai, HuggingFace, URL directa o path local) y lo normaliza a API format. Dispatcher que detecta el tipo de fuente por la URL y delega: Drive via gdown/uc?export=download, GitHub via raw.githubusercontent.com, Civitai via API REST (resuelve downloadUrl, descomprime zip), HuggingFace via resolve/. Tras bajar: PNG/WebP -> comfyui_import_workflow_png; JSON -> comfyui_import_workflow_json (normaliza UI->API). Compone import_workflow_json + import_workflow_png. Impura: red + descompresion + disco."
tags: [comfyui, ml, workflow, download, dispatcher, import]
uses_functions: [comfyui_import_workflow_json_py_ml, comfyui_import_workflow_png_py_ml]
uses_types: []
returns: []
returns_optional: false
error_type: error_go_core
imports: []
params:
  - name: source
    desc: "URL (Google Drive con /d/<id> o ?id=, GitHub blob o raw, Civitai /api/download o /models/<id>, HuggingFace resolve, o URL directa a .json/.png/.webp) o ruta de un archivo local."
  - name: dest
    desc: "Ruta local donde guardar el archivo descargado. Si None, archivo temporal (se conserva y se reporta en 'path'). Para fuentes locales no copia: path = source. keyword-only por posicion 2 (acepta posicional)."
  - name: server
    desc: "host:port de ComfyUI, usado SOLO para mapear widgets cuando la fuente viene en formato UI graph (lo pasa a import_workflow_json). keyword-only."
  - name: civitai_token
    desc: "Token de Civitai (Bearer) para descargas restringidas/gated. keyword-only."
  - name: hf_token
    desc: "Token de HuggingFace (Bearer) para datasets privados. keyword-only."
  - name: timeout
    desc: "Timeout HTTP en segundos. keyword-only."
output: "dict {ok, workflow, source_type, path, format_in, error}. workflow = dict API format (vacio si ok=False); source_type = drive|github|civitai|huggingface|direct|local; path = ruta local descargada; format_in = api|ui_graph|png-prompt|png-workflow|zip. Nunca lanza: fallos devuelven ok=False con error."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/ml/comfyui_download_workflow.py"
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
from ml.comfyui_download_workflow import comfyui_download_workflow

# GitHub (cubiq, Apache-2.0) — baja el raw .json y lo deja en API format
res = comfyui_download_workflow(
    "https://raw.githubusercontent.com/cubiq/ComfyUI_Workflows/main/ComfyUI_Simple/SDXL_simple.json"
)
# res == {"ok": True, "workflow": {...}, "source_type": "github",
#         "path": "/tmp/comfy_wf_xxx.json", "format_in": "ui_graph", "error": ""}

# Google Drive por share-url (extrae el file id; usa gdown si esta, si no descarga directa)
res2 = comfyui_download_workflow("https://drive.google.com/file/d/<FILE_ID>/view", dest="/tmp/wf.json")

# El workflow resultante esta listo para validar/encolar:
# from ml.comfyui_validate_workflow import comfyui_validate_workflow
# comfyui_validate_workflow(res["workflow"])
```

Lánzalo con el python del venv (import de arriba o heredoc). `./fn run` directo no aplica: la firma usa `*` (keyword-only), no soportado por el generador de runner de `fn run`. El bloque `__main__` baja el ejemplo de cubiq cuando lo ejecutas como script.

## Cuando usarla

Cuando tengas la URL de un workflow ajeno (Drive de un creador, repo GitHub, página
de Civitai, dataset de HuggingFace) y quieras un dict en API format sin pensar en el
método de descarga ni en el formato. Es el punto de entrada único antes de
`comfyui_validate_workflow` + `comfyui_resolve_workflow_deps` + `comfyui_submit_workflow`.
Para una fuente que ya sabes que es JSON local/URL directa, `comfyui_import_workflow_json`
basta; para un PNG suelto, `comfyui_import_workflow_png`. Este dispatcher es para
"dame el workflow de esta URL, sea cual sea la fuente". Catálogo de fuentes: report 0080.

## Gotchas

- Impura: hace HTTP GET (y gdown/unzip según fuente) + escribe a disco. Cualquier
  fallo de red/IO devuelve `{ok: False, error: ...}` (no lanza).
- **Google Drive**: usa `gdown` si está instalado (maneja el aviso de virus-scan de
  archivos grandes). Sin gdown cae a `uc?export=download`, que solo sirve para
  archivos pequeños (un `.json` de workflow son KB); si Drive devuelve HTML (aviso
  de virus-scan o gated) la función pide instalar gdown. `pip install gdown` en el venv.
- **Civitai**: descargas gated/early-access exigen `civitai_token` (Bearer). Sin token
  la respuesta puede ser HTML de login → error claro. Una página `/models/<id>` se
  resuelve via `/api/v1/models/<id>` tomando el primer file; para precisión pasa el
  `downloadUrl` directo (`/api/download/models/<version_id>`).
- **GitHub**: una URL `github.com/.../blob/...` se reescribe a `raw.githubusercontent.com`
  automáticamente; si pasas la URL de la página HTML (no raw ni blob) puede bajar HTML
  → error. Mejor pasar el raw o el blob.
- **Formato de salida siempre API**: un PNG con chunk `prompt` (API) se usa directo; si
  solo trae el chunk `workflow` (UI graph) se normaliza vía import_workflow_json (necesita
  el server vivo para mapear widgets). Un UI graph `.json` se normaliza igual (best-effort:
  conexiones siempre; widgets sólo si el server responde).
- **El workflow descargado es un secreto si trae credenciales/cookies** (raro en workflows,
  común en HAR): este caso es de workflows públicos; aun así no commitear el `path` temporal.
- Fuentes con anti-bot fuerte (ComfyWorkflows.com, comfy.org/workflows con Cloudflare)
  pueden devolver 402/HTML a la descarga directa → requieren navegador (CDP). No cubiertas.
