---
name: comfyui_build_facedetailer_workflow
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: pure
signature: "def comfyui_build_facedetailer_workflow(base_workflow_or_image, ckpt_name: str, positive: str, negative: str = \"\", *, bbox_model: str = \"face_yolov8m.pt\", denoise: float = 0.5, steps: int = 20, cfg: float = 8.0, seed: int = 0, guide_size: float = 512.0, bbox_threshold: float = 0.5, feather: int = 5, sampler_name: str = \"euler\", scheduler: str = \"normal\", filename_prefix: str = \"facedetail\") -> dict"
description: "Construye un workflow ComfyUI con FaceDetailer (Impact-Pack) en API format: detecta caras con UltralyticsDetectorProvider (YOLO bbox) y las regenera con un sampler de difusion para recuperar detalle (el pain #1 de retratos). Acepta el nombre de una imagen ya en input/ (modo str) o un workflow base como dict (modo workflow, p.ej. el de comfyui_build_txt2img_workflow): en este caso toma la imagen del VAEDecode y reutiliza el CheckpointLoaderSimple. Class_types reales verificados en /object_info. Pura, sin red ni I/O."
tags: [comfyui, ml, facedetailer, impact-pack, portrait, workflow]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
params:
  - name: base_workflow_or_image
    desc: "Nombre (str) de una imagen ya en el input/ del servidor, o un workflow base (dict en API format). Con str monta LoadImage + CheckpointLoaderSimple nuevos; con dict toma la imagen del primer VAEDecode y reutiliza su CheckpointLoaderSimple."
  - name: ckpt_name
    desc: "Checkpoint para el sampler del detailer (y para el loader nuevo en modo imagen). Debe existir en el servidor (CheckpointLoaderSimple)."
  - name: positive
    desc: "Prompt positivo para regenerar las caras (ej. 'detailed face, sharp eyes, skin texture'). Se codifica con el CLIP del checkpoint."
  - name: negative
    desc: "Prompt negativo. Por defecto ''."
  - name: bbox_model
    desc: "Modelo de deteccion Ultralytics. Acepta nombre corto ('face_yolov8m.pt') o prefijado ('bbox/face_yolov8m.pt'); si no trae prefijo se asume 'bbox/'. keyword-only."
  - name: denoise
    desc: "Fuerza de re-difusion de cada cara (0.5 por defecto; mas alto = mas cambio, mas riesgo de perder identidad). keyword-only."
  - name: steps
    desc: "Pasos de sampling del detailer. keyword-only."
  - name: cfg
    desc: "Classifier-free guidance del detailer. keyword-only."
  - name: seed
    desc: "Semilla del sampler del detailer. keyword-only."
  - name: guide_size
    desc: "Tamano (px) al que se reescala cada cara recortada antes de re-difundirla (FaceDetailer.guide_size). keyword-only."
  - name: bbox_threshold
    desc: "Umbral de confianza del detector de caras (0..1). Mas alto = menos falsos positivos, riesgo de no detectar caras pequenas. keyword-only."
  - name: feather
    desc: "Pixeles de difuminado del borde de la mascara al recomponer la cara sobre la imagen. keyword-only."
  - name: sampler_name
    desc: "Sampler del detailer (ej. 'euler'). keyword-only."
  - name: scheduler
    desc: "Scheduler del detailer (ej. 'normal'). keyword-only."
  - name: filename_prefix
    desc: "Prefijo del PNG final que escribe SaveImage. keyword-only."
output: "dict en API format listo para comfyui_submit_workflow. En modo dict contiene los nodos del workflow base mas los del detailer (node_ids prefijados 'fd_' para no colisionar); el SaveImage 'fd_save' produce la imagen con las caras regeneradas."
tested: true
tests: ["modo imagen monta UltralyticsDetectorProvider + FaceDetailer + SaveImage", "modo workflow reutiliza VAEDecode y CheckpointLoaderSimple del base y conserva sus nodos", "normaliza bbox_model corto a prefijo bbox/", "dict sin VAEDecode lanza ValueError", "determinismo: misma entrada -> mismo dict (builder puro)"]
test_file_path: "python/functions/ml/tests/test_comfyui_build_facedetailer_workflow.py"
file_path: "python/functions/ml/comfyui_build_facedetailer_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_facedetailer_workflow import comfyui_build_facedetailer_workflow
from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow

# Modo workflow: genera un retrato y le aplica FaceDetailer en el mismo grafo.
base = comfyui_build_txt2img_workflow(
    ckpt_name="IMG_dreamshaper_8.safetensors",
    positive="portrait of a woman, soft light",
    width=512, height=768, seed=7,
)
wf = comfyui_build_facedetailer_workflow(
    base,
    ckpt_name="IMG_dreamshaper_8.safetensors",
    positive="detailed face, sharp eyes, skin texture",
    negative="blurry, deformed",
    denoise=0.45,
)
# wf["fd_det"]["class_type"] == "UltralyticsDetectorProvider"
# wf["fd_det"]["inputs"]["model_name"] == "bbox/face_yolov8m.pt"
# wf["fd_face"]["class_type"] == "FaceDetailer"
# wf["fd_face"]["inputs"]["image"] == ["8", 0]   # VAEDecode del base
# wf["4"]["class_type"] == "CheckpointLoaderSimple"  # nodos del base conservados
```

O lanzable directo con: `./fn run comfyui_build_facedetailer_workflow` (imprime el JSON del workflow de ejemplo en modo imagen).

## Cuando usarla

Cuando una imagen generada tiene caras mediocres (ojos borrosos, piel plana,
rasgos deformados) y quieres regenerarlas con detalle sin rehacer toda la imagen.
Es el ADetailer/FaceDetailer "pro" del flujo de retratos. Encadénala tras
`comfyui_build_txt2img_workflow` (pásale el dict) para detail en una sola cola, o
pásale el nombre de una imagen ya en `input/` para mejorar una imagen existente.
Después: `comfyui_submit_workflow` → `comfyui_wait_result` → `comfyui_fetch_output_image`.

## Gotchas

- Es API format (nodos numerados / con prefijo `fd_`), NO el formato de la UI.
- Requiere **ComfyUI-Impact-Pack** instalado (provee `FaceDetailer` y
  `UltralyticsDetectorProvider`). Si el server responde HTTP 400 "node type not
  found: FaceDetailer", el custom node no está cargado: revísalo en el Manager.
- El modelo de detección debe estar en `models/ultralytics/bbox/` (aquí
  `face_yolov8m.pt`). El nodo lo referencia con prefijo de subcarpeta
  (`bbox/face_yolov8m.pt`); la función normaliza el nombre corto automáticamente.
- **No usa SAM** (segment-anything): `sam_model_opt` es opcional y aquí no hay
  modelo SAM instalado (`SAMLoader` reporta lista vacía). FaceDetailer funciona
  solo con el detector de bounding box, que basta para caras. Si instalas un SAM
  y quieres máscaras más finas, habría que añadir el `SAMLoader` aparte.
- En **modo workflow** (dict) se reutiliza el primer `CheckpointLoaderSimple` y el
  primer `VAEDecode` del base. Si el base usa otro loader (p.ej. un flujo SDXL con
  loaders distintos), se monta un `CheckpointLoaderSimple` propio con `ckpt_name` —
  asegúrate de que el checkpoint case con el espacio latente del base.
- El SaveImage del workflow base (si lo tenía) se conserva: el grafo produce tanto
  la imagen base como la "detailed" (`fd_save`). Si solo quieres la final, ignora
  la otra salida.
- `denoise` alto (>0.6) puede cambiar la identidad de la cara; 0.4–0.5 conserva
  rasgos y añade detalle.
