---
name: comfyui_compose_capabilities
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: pure
signature: "def comfyui_compose_capabilities(base_workflow: dict, *, loras: list[dict] | None = None, controlnet: dict | None = None, ipadapter: dict | None = None, hires: dict | None = None, facedetailer: dict | None = None) -> dict"
description: "Mezclador de capacidades ComfyUI: toma un workflow base en API format (skill o txt2img) y aplica EN ORDEN las capacidades activadas (cada arg None = desactivada), componiendo los inyectores/builders encadenables del registry: loras (inject_multi_lora) -> controlnet (inject_controlnet) -> ipadapter (inject_ipadapter) -> facedetailer (build_facedetailer_workflow) -> hires (inject_hires_fix), reconectando MODEL/CLIP/positive/IMAGE. Cada capacidad es opcional e independiente; sin ninguna devuelve el base intacto. Pura: no muta el dict de entrada."
tags: [comfyui, comfyui-skill, ml, mixer, lora, controlnet, ipadapter, facedetailer, hires, workflow]
uses_functions: [comfyui_inject_multi_lora_py_ml, comfyui_inject_controlnet_py_ml, comfyui_inject_ipadapter_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: base_workflow
    desc: "dict en API format (salida de comfyui_build_skill_workflow o comfyui_build_txt2img_workflow). No se muta; se devuelve una copia."
  - name: loras
    desc: "Lista de dicts {name, strength_model?, strength_clip?} para inject_multi_lora. None o vacia = sin LoRAs. keyword-only."
  - name: controlnet
    desc: "Dict para inject_controlnet: {control_image (obligatoria), cn_name (obligatoria), strength?, positive_node?}. None = sin ControlNet. keyword-only."
  - name: ipadapter
    desc: "Dict para inject_ipadapter: {ref_image (obligatoria), mode ('style'|'faceid'), weight?, ...}. None = sin IPAdapter. keyword-only."
  - name: hires
    desc: "Dict de kwargs para inject_hires_fix (upscale_by, denoise, steps, cfg, seed, upscale_model, ...). {} = hires con defaults. None = sin hires. keyword-only."
  - name: facedetailer
    desc: "Dict de overrides para build_facedetailer_workflow. ckpt_name/positive/negative se detectan del workflow si faltan; resto = params del builder (denoise, steps, bbox_model, ...). {} = detect + defaults. None = sin facedetailer. keyword-only."
output: "copia del base con las capacidades activadas encadenadas en orden (loras -> controlnet -> ipadapter -> facedetailer -> hires). Sin ninguna activada, copia del base intacta. Tras facedetailer deja un unico SaveImage (el del detailer)."
tested: true
tests: ["sin capacidades devuelve el base intacto (mismos nodos)", "solo loras encadena los LoraLoader", "loras + facedetailer: cadena de loras + FaceDetailer + un solo SaveImage", "ipadapter + lora: IPAdapter toma el MODEL del ultimo LoraLoader", "hires anade UltimateSDUpscale", "controlnet sin control_image propaga ValueError", "ipadapter sin ref_image propaga ValueError", "no muta el dict de entrada (pureza)", "api format valido en todas las combinaciones", "activar una capacidad cambia el conjunto de class_types"]
test_file_path: "python/functions/ml/tests/test_comfyui_compose_capabilities.py"
file_path: "python/functions/ml/comfyui_compose_capabilities.py"
---

## Ejemplo

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

base = comfyui_build_txt2img_workflow("IMG_dreamshaper_8.safetensors", "a hero, 3d render style")

# 3 capacidades a la vez: 2 LoRAs + FaceDetailer (activar/desactivar = cambiar args)
mixed = comfyui_compose_capabilities(
    base,
    loras=[
        {"name": "SD15_3d_render_redmond.safetensors", "strength_model": 0.9},
        {"name": "SD15_detail_tweaker.safetensors", "strength_model": 0.5},
    ],
    facedetailer={"denoise": 0.45},
    # controlnet=..., ipadapter=..., hires=...  -> None = desactivadas
)
```

## Cuando usarla

Cuando quieras **mezclar varias capacidades de generacion** (LoRAs + ControlNet +
IPAdapter + FaceDetailer + hires) sobre un mismo workflow base y poder
activar/desactivar cada una para iterar y mejorar. Es el "mixer" del grupo
`comfyui-skill`: una sola funcion en vez de encadenar los inyectores a mano. La
salida va directa a `comfyui_submit_workflow` (o usa el one-shot
`comfyui_generate_mixed_oneshot` para submit + juicio).

## Gotchas

- Pura: no muta el `base_workflow` y NO valida que checkpoints/loras/modelos
  existan en el servidor. Las imagenes de control/referencia (ControlNet,
  IPAdapter) deben estar en el `input/` del servidor antes de submit.
- **Orden fijo**: loras -> controlnet -> ipadapter -> facedetailer -> hires. El
  IPAdapter se aplica sobre el MODEL ya modificado por los LoRAs (orden correcto).
- **hires + facedetailer NO encadenan** con las piezas actuales: ambos toman su
  imagen del VAEDecode del render base, asi que combinarlos deja a uno sin efecto
  sobre la salida final (con los dos activos, hires "gana" y facedetailer queda
  sin consumidor). Usa uno U otro por workflow. Es la limitacion documentada del
  mixer; el resto de combinaciones (loras+controlnet+ipadapter+uno de los dos
  post-procesos) encadenan limpio.
- Cada capacidad apila coste de VRAM. En 8GB lowvram con SD1.5 entran ~2-3
  capacidades modestas (p.ej. 2 LoRAs + FaceDetailer a 512px). Apilar IPAdapter
  FaceID + ControlNet + hires + facedetailer a la vez puede dar OOM: baja
  resolucion o desactiva capacidades.
- Errores de incompatibilidad (controlnet sin `control_image`, ipadapter sin
  `ref_image`, mode invalido) se propagan como `ValueError` del inyector, no
  petan en silencio.
