---
name: comfyui_build_skill_workflow
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: pure
signature: "def build_skill_workflow(recipe: dict, subject: str, *, seed: int = 0) -> dict"
description: "Compila una receta de skill ComfyUI (dict recipe.json del grupo comfyui-skill) a un workflow en API format listo para comfyui_submit_workflow. Despacha al builder base segun recipe['base_workflow'] (txt2img|flux|sdxl_refiner), sustituye {subject} y los trigger_words en el prompt_scaffold, encadena los loras (inject_lora) y aplica los blocks de post-proceso (facedetailer, hires_fix) en orden. Pura: solo compone builders puros del registry, sin red ni I/O. base_workflow desconocido o que requiere imagen -> SkillWorkflowError."
tags: [comfyui, comfyui-skill, ml, workflow, stable-diffusion, skill]
uses_functions:
  - comfyui_build_txt2img_workflow_py_ml
  - comfyui_build_flux_workflow_py_ml
  - comfyui_build_sdxl_refiner_workflow_py_ml
  - comfyui_inject_lora_py_ml
  - comfyui_build_facedetailer_workflow_py_ml
  - comfyui_inject_hires_fix_py_ml
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
params:
  - name: recipe
    desc: "Dict de la receta (schema comfyui-skill). Campos usados: base_workflow (txt2img|flux|sdxl_refiner), checkpoint, loras [{name, strength_model, strength_clip}], params (steps/cfg/width/height/sampler_name/scheduler/...), prompt_scaffold (positive/negative con {subject} + trigger_words), blocks [{type, params}]."
  - name: subject
    desc: "El sujeto concreto que sustituye {subject} en el scaffold del prompt (ej. 'a woman with red hair')."
  - name: seed
    desc: "Semilla de generacion; se pasa al builder base y por defecto a cada bloque que la acepte. keyword-only."
output: "dict en API format (nodos numerados con class_type + inputs) listo para comfyui_submit_workflow."
tested: true
tests: ["golden: txt2img + 1 lora + facedetailer -> API format valido con LoraLoader + FaceDetailer y subject sustituido", "edge: sin loras ni blocks -> workflow base minimo (6 class_types)", "params seed/steps/cfg/width + trigger_words reflejados", "error: base_workflow desconocido -> SkillWorkflowError", "error: base que requiere imagen (img2img) -> SkillWorkflowError", "error: recipe no dict -> SkillWorkflowError"]
test_file_path: "python/functions/ml/tests/test_comfyui_build_skill_workflow.py"
file_path: "python/functions/ml/comfyui_build_skill_workflow.py"
---

## Ejemplo

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

recipe = {
    "schema_version": 1,
    "slug": "portrait_cinematic_sdxl",
    "version": "1.0.0",
    "base_workflow": "txt2img",
    "checkpoint": "IMG_juggernaut_xl_v11.safetensors",
    "loras": [{"name": "add_detail.safetensors", "strength_model": 0.6, "strength_clip": 0.6}],
    "params": {"steps": 30, "cfg": 5.5, "sampler_name": "dpmpp_2m",
               "scheduler": "karras", "width": 832, "height": 1216},
    "prompt_scaffold": {"positive": "cinematic portrait of {subject}, sharp focus",
                        "negative": "blurry, lowres", "trigger_words": []},
    "blocks": [{"type": "facedetailer", "params": {"denoise": 0.45}}],
}

wf = build_skill_workflow(recipe, "a woman with red hair", seed=42)
# wf tiene CheckpointLoaderSimple + KSampler + LoraLoader + FaceDetailer + SaveImage.
# Pasalo a comfyui_submit_workflow para encolarlo.
```

O lanzable directo con `./fn run comfyui_build_skill_workflow` (imprime el JSON del workflow de ejemplo).

## Cuando usarla

- Cuando tengas una **receta de skill** (de `comfyui_load_skill`) y quieras convertirla en un
  workflow concreto para un `subject` dado, sin montar el grafo a mano. Es el paso "receta →
  workflow" del flujo del grupo `comfyui-skill`: `load_skill` → `build_skill_workflow` →
  `submit_workflow` → `wait_result`.
- Cuando reutilices una configuración probada (checkpoint + LoRAs + params + post-proceso)
  cambiando solo el sujeto y la semilla.

## Gotchas

- **Pura, NO valida contra el servidor**: igual que los builders hermanos, no comprueba que el
  checkpoint/LoRA/modelo de upscale existan en ComfyUI. Si faltan, el servidor rechaza el
  workflow con HTTP 400 al enviarlo. Valida antes con `comfyui_validate_workflow` si hace falta.
- **Solo bases de texto**: soporta `base_workflow` ∈ {`txt2img`, `flux`, `sdxl_refiner`}. Las
  bases que requieren una imagen de entrada (`img2img`, `inpaint`, `controlnet`) lanzan
  `SkillWorkflowError` — `build_skill_workflow` arranca de un `subject` de texto, no de una imagen.
- **`sdxl_refiner` requiere `params['refiner_ckpt']`**; sin él lanza `SkillWorkflowError`.
- **Flux ignora el negativo**: el builder de Flux usa un negativo vacío fijo y la guía va por
  `FluxGuidance`; en Flux el campo `checkpoint` de la receta se mapea a `unet`.
- **El orden de los `blocks` importa**: se aplican secuencialmente sobre el dict. `facedetailer`
  toma la imagen del `VAEDecode`; `hires_fix` re-difunde y repunta el `SaveImage`. Encadénalos en
  el orden lógico (p.ej. hires_fix tras facedetailer).
- **Excepción tipada**: todos los errores de compilación son `SkillWorkflowError` (subclase de
  `ValueError`), exportada por el módulo.
