# Tareas pendientes (hoja de ruta)

**29-10-2026 (actualizado 8-11-2026).** Este archivo existe para no perder el hilo al saltar entre tareas.
Aquí está el **qué**, el **estado de partida** y las **decisiones tomadas**. El
detalle operativo sigue en [`../AGENTS.md`](../AGENTS.md).

---

## 0. Estado de partida

Lo que ya funciona y **no hay que rehacer**:

| Pieza | Qué hace | Estado |
|---|---|---|
| `Engine/batch_poses_chars.py` | N personajes × 3 niveles × 1 tema c/u, pose única por lote | ✅ 2 lotes tirados |
| `Engine/pack_builder.py` | **1** personaje × 5 stages × tema, 9/stage = 45 | ✅ probado |
| `Engine/pose_bank.py` | banco de poses (`.jsonl`) + `avoid.jsonl` | 72 poses |
| `Engine/pose_calibrate.py` | renderiza poses candidatas, 1 semilla por pose | ✅ |
| `Engine/regen_batch_items.py` | regenera 1 imagen concreta (job exacto + semilla nueva) | ✅ |
| `Engine/audit_batch_sheets.py` | hojas de contacto `audit_N.jpg` + `contact_all.jpg` | ✅ |
| `Engine/character_keys.py` | 216 claves, `--list/--random/--check` | ✅ |
| `Engine/pixiv_tags.py` | personaje → tag Pixiv JP/EN + obra + accesos | ✅ verificado |

**Datos**

- `Archivos/personajes.txt`: **216 líneas = 216 claves**, 0 colisiones.
- LoRAs en disco: **146** de esas 216 (37 sin `<lora:>` o con el fichero ausente).
- Banco de poses: **72** → sugerente **23**, spicy **28**, explícito **21**
  (+16 el 2-nov, `pb0057`–`pb0072`). Utilizables **70** (`pb0005` y `pb0012` blacklisteadas).
- Temas: **27** en `Archivos/danbooru/themes.json`. Solo **2** tienen
  `outfit_story` (`oficina`, `onsen_xilonen`); quedan **25** por curar.
- Stages con cámara propia: `Archivos/stage_cameras.json` (sugerente, spicy1,
  spicy2, explicit1, explicit2).

**Lotes tirados**

| Lote | Contenido | Estado |
|---|---|---|
| `Output/pilots/Diferent poses/BATCH_20261026_174422/` | 36 img · 12 personajes · 36 poses | auditado (30 OK / 5 flojas / 1 defecto aceptado) |
| `Output/pilots/Diferent poses/BATCH_20261028_051456/` | 36 img · 12 personajes **nuevos** · 36 poses | **aprobado sin auditar** |

**Dependencias nuevas instaladas (29-10-2026):** `pixivpy3`, `gppt 5.1.0`,
`python-dotenv` (ya estaba).

---

## 1. Tarea 1 — Cadena de 10 tags de Pixiv por personaje

### Objetivo

Dado un personaje, tener **su** cadena de 10 tags lista para pegar en Pixiv. No
se publica por API (no existe): se prepara el texto.

### DECIDIDO

| ID | Decisión |
|---|---|
| D1.1 | **Cadena base por personaje** + 2-3 tags que se añaden según el prompt de la imagen |
| D1.2 | Japonés **e inglés**, pero el inglés **solo** para nombre de personaje y franquicia |
| D1.3 | **10 tags** = 2 personaje (JP+EN) + 2 franquicia (JP+EN) + 5 NSFW/escena + 1 libre. `R-18` y `AIイラスト` **fuera de la cuenta** (van como casilla/flag) |
| D1.4 | **Co-ocurrencia real** (requiere token de Pixiv) → hay que montar el login |
| D1.5 | Personajes sin tag (Tsugumi Mikamori): etiquetar por género — `NTR`, `eroge`, `オリジナル`… |
| D1.6 | Solo los que se vayan a publicar. **Prioridad**: gachas / RPG / juegos / anime (Genshin, Nikke, ZZZ, Wuthering Waves…) |

> **Nota técnica (una vez):** `R-18` **sí es un tag real** de Pixiv, además de la
> casilla de restricción de edad; sin él las búsquedas R-18 no encuentran la obra.
> Como has decidido que va fuera de la cuenta de 10, el generador lo **añade
> aparte** al final de la cadena publicable (quedarían 11 al pegar). Si prefieres
> quitarlo del todo, se cambia en una línea.

### Fuentes (todas sin token)

| Endpoint | Devuelve |
|---|---|
| `/rpc/cps.php?keyword=X&lang=ja\|en` | `tag_name` (JP), `tag_translation` (EN), `access_count` |
| `/ajax/search/tags/{tag}` | descripción pixpedia (de qué obra es) + ambiguo sí/no |
| `AppPixivAPI.search_illust` | **solo con token** → co-ocurrencia real de tags en obras R-18 |

Truco verificado: preguntando en **romaji** (`genshin`, `zenless`, `dandadan`)
`cps.php` devuelve el tag japonés **y** el inglés de la franquicia. Eso resuelve
el par JP/EN de la obra sin diccionario a mano.

### Salida

```
Archivos/pixiv_tags/<clave>.json      ← una ficha por personaje, revisable
Archivos/pixiv_tags_index.md          ← tabla generada (char, JP, EN, obra, accesos)
Archivos/pixiv_nsfw_pool.json         ← pool curado de generales NSFW en japonés
Archivos/pixiv_series.json            ← mapa JP→EN de franquicia (curado)
```

```json
{
  "char": "Raiden_shogun",
  "tags": ["雷電将軍", "Raiden Shogun", "原神", "Genshin Impact",
           "○○", "○○", "○○", "○○", "○○", "○○"],
  "extra": ["R-18"],
  "verified": true,
  "access": 12345678
}
```

### Puesta en marcha del token (una vez, manual)

`gppt` es interactivo (abre el navegador y pide pegar un código), así que ese
paso lo tienes que hacer tú:

```powershell
python -m gppt configure          # perfil: nombre de usuario de Pixiv
python -m gppt login --oauth -j   # abre el navegador, pegas el código
```

El `refresh_token` que imprime va a `.env` en la raíz de `SD` como
`PIXIV_REFRESH_TOKEN=...`. Sin ese archivo, `--r18` y la co-ocurrencia no
funcionan, pero el resto de la cadena sí (con pool curado en vez de real).

### Done

Un comando devuelve, para N personajes, su cadena de 10 tags verificada (JP+EN de
personaje y obra + 5 generales + 1 libre), con la obra y el nº de accesos, y avisa
de los que no tienen tag.

### Estado: ✅ CERRADA (29-10-2026)

```bash
python Engine/pixiv_tags.py --from-batch "<run>/plan.json" --chain --r18
python Engine/pixiv_per_image.py                  # D1.1: tags de cada PNG
python Engine/pixiv_nsfw_pool.py      # regenera el pool midiendo qué tags existen
```

Artefactos: `Archivos/pixiv_tags/<clave>.json` (con `fijos` y `por_imagen`
separados para D1.1), `Archivos/pixiv_tags/_index.md`,
`Archivos/pixiv_nsfw_pool.json` (**77 tags** verificados con nº de obras),
`Archivos/pixiv_series.json` (16 franquicias JP→EN),
`Archivos/pixiv_tag_map.json` (**113** tags de prompt → tag JP que existe) y,
por lote, `<run>/pixiv_tags.json` + `<run>/pixiv_tags.md`.

Probado con los 12 del lote del 28-10: **11 cadenas, las 11 con `5/5` generales
medidos** (Tsugumi no tiene tag). Los generales ya son del personaje, no del
pool: Kokomi → `極上の女体, 巨乳, 魅惑のふともも, 拘束, 中出し`; Raikou →
`巨乳, 爆乳, おっぱい, NTR, 中出し`; Chun-Li → `巨乳, 魅惑のふともも, 爆乳, 全裸, 拘束`.

**Token: hecho** (29-10-2026, `pixiv_login.bat`). Queda registrado en
`Logs/pixiv_login.log`.

**D1.1 (tags por imagen): hecho.** `Engine/pixiv_per_image.py` lee el prompt de
cada PNG del lote y sustituye los `por_imagen` por 2-3 tags de la imagen, con
prioridad `acto > estado > ropa > encuadre > cuerpo` y guardas de contradicción.
Del lote del 28-10: **33 imágenes, 22 NSFW / 11 SFW, media 8,6 tags reales**; las
imágenes vestidas y sin acto **no llevan `R-18`** (`--r18-all` lo fuerza). La
auditoría visual de 3 imágenes confirmó los tags y añadió `精液` a mano en una
(`--add "03_explicito=精液"`), porque el checkpoint pinta fluidos que el prompt
no pide.

**Lo único que queda vivo de esta tarea** (no bloquea): mapear más franquicias
— `--chain` avisa `obra sin mapear` y se añade una línea a
`Archivos/pixiv_series.json`. Hoy hay 16. Y cada personaje nuevo necesita su
`--chain --r18` (≈10 s con token).

---

## 2. Tarea 2 — Interfaz visual para generar batches

### Objetivo

Dejar de escribir comandos: una pantalla donde elegir **qué** se genera, verlo
antes de gastar GPU, y lanzarlo.

### DECIDIDO

| ID | Decisión |
|---|---|
| D2.1 | **Flask + una sola página HTML**, lo importante es que hable **directo con SD** (Forge `127.0.0.1:7860`) |
| D2.2 | La UI **lanza**, pero antes enseña una **vista previa** de lo que hará + botón **limpiar** y botón **regenerar** |
| D2.3 | La UI **avisa** del límite de poses, y deja **ver y editar el pool** (añadir / limpiar poses) desde la propia pantalla |
| D2.4 | **Unificar a 5 stages**: sugerente / spicy1 / spicy2 / explicit1 / explicit2 |
| D2.5 | **Dos pestañas**: elegir a mano (buscador + checkboxes) y sortear N. Con el filtro `--exclude-used` |
| D2.6 | Solo `personajes.txt` como universo (se relee en cada arranque). **Más adelante**: dividirlo por franquicias |
| D2.7 | **Validar el prompt en la preview** (dry-run visual) |
| D2.8 | Sí: **regenerar una imagen suelta** desde la UI (ya existe `regen_batch_items.py`) |

### Los dos casos

1. **Lote largo de 1 personaje** — 35 / 45 / 50 / personalizado.
2. **Lote de grupo** — N personajes × stages marcados × un tema cada uno.

Y en los dos: casillas de stages (se puede marcar solo explícito, o solo
sugerente, etc.).

### Tensión a resolver (decidido: avisar, no bloquear)

Con 16 poses sugerentes, 45 imágenes implican **repetir pose**. La UI calcula
cuántas caben sin repetir y **avisa** ("45 con repetición / 16 sin repetir"),
además de decir qué poses faltan. Ampliar el pool es la tarea 3.

### Arquitectura prevista

```
Engine/ui/
  app.py            Flask: /  /api/plan  /api/start  /api/progress  /api/regen  /api/bank
  templates/index.html   una sola página (vanilla JS, sin build)
Output/ui_runs/<stamp>/   mismos artefactos que hoy (plan.json, manifest, hojas)
```

La UI **no reimplementa** la generación: llama a `pack_builder.generate()` y
`batch_poses_chars` como librería, para no tener dos verdades.

### Done

Se puede lanzar el lote del 28-10 y uno de 45 de un personaje desde la UI, sin
tocar consola, con preview previa, y el resultado queda con los mismos artefactos.

### Estado (29-10-2026): construida, pendiente el pase con GPU

```
Engine/ui/
  app.py       Flask: /  /api/meta  /api/plan  /api/start  /api/stop  /api/progress
               /api/runs  /api/run/<name>  /api/image/...  /api/open  /api/regen
               /api/bank (+ /add, /blacklist, /unblacklist)
  planner.py   peticion -> Jobs reales + preview serializable (sin GPU)
  runner.py    artefactos del lote + regen de una imagen
  templates/index.html · static/{app.js,style.css}
Output/ui_runs/<stamp>_<id>/   plan.json, manifest_all.json, ui_spec.json,
                               summary.json, audit_N.jpg, contact_all.jpg
```

Verificado **sin GPU** (Forge caído todo el rato):

- pack 45 (oficina) → **45 poses únicas**, cámaras `from above/from front/from
  side`, stages 9/9/9/9/9, sin bloqueo (`no_repeat_max` 45).
- grupo (4 personajes × 3 stages) → 12 poses únicas, un tema por personaje.
- límite: 40 sugerente → `blocked`; con `allow_repeat` se desbloquea; en grupo
  el número útil es **personajes** (`no_repeat_max` 9 con 5 stages).
- runner + regen con `pb.generate` simulado: todos los artefactos, hojas,
  `_reemplazadas/`, los 3 ficheros actualizados y `drift:false`.
- todos los endpoints responden 200 (`/api/start` da 409 con Forge caído, que
  es lo correcto).

**Pendiente:** el pase real (lanzar 3 stages de grupo y un pack de 45 con Forge
arriba, auditar los PNG). No se ha gastado GPU todavía.

### Estado (30-10-2026): pase real + arreglos de la lista y las miniaturas

Dos fallos reportados por el usuario, los dos corregidos y verificados **con
GPU** (Forge arriba, `waiIllustriousSDXL_v170`):

1. **Miniaturas invisibles**: `/api/progress` empujaba `img/x.png` y
   `/api/image/<lote>/<rel>` sirve desde la raíz del lote → 404 en todas.
   Ahora llevan el prefijo de la carpeta. Ensayado con un lote real de 2
   imágenes de Reze: `/api/progress`, `/api/image`, `/api/run` y el regen
   devuelven 200.
2. **Nombres de personaje**: la lista no venía de la carpeta de LoRAs (venía
   de `personajes.txt`), pero el nombre visible era la clave técnica
   (`Yelan夜蘭原神`), la casilla "solo con LoRA" ocultaba a Reze/Rias (no
   llevan `<lora:>`) y tres Yelan parecían duplicadas. Ahora:
   - `name` sale de la línea (tag de personaje del CSV `type=4` → tag que casa
     con la clave → clave limpia): 54 / 67 / 95 de 216.
   - `lora_state` (`ok`/`missing`/`none`) sustituye al binario `has_lora`; el
     filtro oculta **solo `missing`**, así Reze y Rias vuelven a salir.
   - nombres repetidos se desambiguan con lo que sobra de la clave:
     `Yelan`, `Yelan (夜蘭原神)`, `Makima (V2)`.
   - `Lotes` en la cabecera: navegador de lotes con imágenes y hojas.

**Bug nuevo descubierto en el pase real:** `reze \(chainsaw man\)` lleva una
barra invertida y `run_dir / clave` reventaba con `WinError 3`. El plan ahora
lleva `dir` (carpeta segura, `planner.fs_name()`), el manifiesto guarda
`char_dir` = carpeta y `char` = clave, y el regen busca por clave y escribe en
la carpeta. Los lotes viejos siguen funcionando.

**Mojibake de `personajes.txt`** (3 líneas con el `<lora:>` roto) reparado byte
a byte y `character_keys.py --check` ya no muere por el macrón de Magik. Quedan
2 líneas sin reparar a propósito (102 Magik, 203 `거유프린세스최면ix`).

**Auditoría visual de las 2 imágenes del pase real** (Reze, `sugerente`, gimnasio):
las dos pasan — 5 dedos y pulgar en la mano que sujeta la botella, ninguna
extremidad de más, ojos verdes coherentes, choker + pelo morado recogido
(identidad), ropa deportiva y taquillas consecuentes con el tema. En la de
espaldas el brazo izquierdo queda oculto tras el torso: ambiguo pero verosímil.

**Pase final con el script corregido: `RESULTADO: TODO OK`** (lote
`20261030_215533_c19c60d735`, 2 imágenes + regen). Comprueba catálogo (216,
Reze/Rias visibles, 3 Yelan con nombre distinto), plan 200 con nombre de
`personajes.txt`, start 200, 2/2 generadas, rutas con prefijo de carpeta,
2 miniaturas 200, `/api/run` con 2 registros y 2 hojas servibles, regen 200 con
fichero nuevo, miniatura nueva 200 y manifiesto actualizado.

**Auditoría de ese segundo par** (Reze, `sugerente`, balcón al atardecer /
de noche): la primera pasa limpia (mano con 5 dedos, sandalias correctas, vestido
y luz del tema). La segunda (`pb0038` = `kneeling, seiza, from_side,
hands_on_own_thighs, cowboy_shot`) **falla en la mitad inferior**: el muslo
delantero se agranda y se funde con la rodilla, el pie de la sandalia queda
girado y las manos se pierden en el regazo. No es un fallo de la UI ni del
prompt de vestuario: es `cowboy_shot` + `from_side` en vertical sobre un seiza.
Candidata a **mapear a un pase horizontal** antes que a regenerar (mismo
criterio que `Output/pilots/Diferent poses/BATCH_20261026_174422/AUDITORIA.md`).

### Estado (8-11-2026): pase real completo con el pack de Velina Airgid — descartado por vestuario

El 7-nov se generó el primer pack completo de principio a fin: `Output/packs/velina airgid_playa/`
(45/45, 0 fallos mecánicos, 9/stage), organizado en `social/{Pixiv,X,Instagram,Facebook}/`
(X partido en `post_01..17` + `preview/`). **Resultado no prometedor: no publicar.**
Todos los Jobs salieron con `outfit_fallback: true` porque `playa` no tiene
`outfit_story`: 31 `bikini` + 5 `beach_skirt` + 9 sin outfit (explicit2). El tag
`bikini` renderiza como bañador blanco de una pieza en unas imágenes y como
bikini azul de tirantes en otras, y `beach_skirt` se coló en explicit1
(`pb0032`, `pb0036`, `pb0050` y un `explicit1_new`) — la progresión de vestuario
por stage no se respetó. La mecánica (UI → pack → organización social) funciona;
el problema es de datos: **ningún pack se lanza sin `outfit_story` curado para
su tema** (25 pendientes, Tarea 3 / D3.5).

### Decisiones tomadas al construirla

| ID | Decisión |
|---|---|
| D2.9 | La **preview y el lanzamiento son el mismo objeto**: `/api/plan` compone los Jobs y los guarda en memoria; `/api/start` solo ejecuta esos. Lo que se ve es lo que se genera |
| D2.10 | **Cámara por índice de imagen dentro del personaje** (pool general `cameras.txt`: from above / from front / from side), igual que el piloto del 28-10; **no** `stage_cameras.json` |
| D2.11 | Una llamada a `compose_theme_jobs` **por (personaje, stage)**: con `bank_ids` la pose se fija por *nivel*, y spicy1/spicy2 en la misma llamada saldrían con la misma pose |
| D2.12 | El lote de grupo admite `allow_repeat` explícito; por defecto **bloquea** si hay repetición (en pack y en grupo) |
| D2.13 | **El nombre visible sale de `personajes.txt`** (nunca de la carpeta de LoRAs); la clave sigue siendo el ID y se pinta como texto secundario. Nombres repetidos se desambiguan con lo que sobra de la clave |
| D2.14 | **Carpeta ≠ clave**: `planner.fs_name()` sanea la clave para el directorio (`reze \(chainsaw man\)` → `reze (chainsaw man)`); el manifiesto guarda `char_dir` y `char` |

---

## 3. Tarea 3 — Ampliar el catálogo

### Agujeros medidos

| Agujero | Número | Consecuencia |
|---|---|---|
| Poses sugerentes | ~~16~~ **23** (8-nov) | objetivo ≥20 alcanzado (2-nov: `pb0057`–`pb0072`) |
| Poses totales | **70 útiles** (72 − `pb0005`/`pb0012`) | un lote de 45 sí cabe sin repetir |
| Temas con `outfit_story` | **2** de 27 | quedan **25** por curar |
| Personajes con LoRA en disco | **146** de 216 | 37 líneas no pueden generar |

### DECIDIDO

| ID | Decisión |
|---|---|
| D3.1 | Orden: **poses → temas/outfit → personajes** |
| D3.2 | Los personajes nuevos los **busca y cura el usuario** manualmente antes de entrar en la lista |
| D3.3 | Poses: **otra pasada automática** del corpus + **calibración** en GPU |
| D3.4 | Se mantiene "personaje base desnudo, cabeza = identidad". **No se descarta el outfit**: al crear franquicias se usarán los **outfits base** del personaje (lista la dará el usuario) |
| D3.5 | Primero **curar los 25 temas** restantes; después, temas nuevos **con `outfit_story` desde el principio** |
| D3.6 | Arreglar de paso `pb0009` (cowgirl sin hombre) y `pb0025` (mangas) |

### Done

Banco con ≥20 poses sugerentes (✅ 23, 2-nov), los 27 temas con `outfit_story`, y el catálogo
ampliado con personajes ya curados por el usuario. El pack de Velina Airgid
(7-nov, ver Tarea 2 → Estado 8-11) confirma que D3.5 es el cuello de botella
real: sin `outfit_story` el vestuario no es consistente entre stages.

---

## 4. Orden y dependencias

```
Tarea 1 (tags)      → independiente, no toca imágenes      [CERRADA 29-10]
Tarea 2 (UI)        → necesita el aviso de poses (D2.3)    [siguiente]
Tarea 3 (catálogo)  → alimenta a la 2; se hace con la UI ya limpia
```

La 1 va primero porque no compite por GPU. La 2 antes que la 3 por decisión
explícita: primero la interfaz limpia, luego se llena.

---

## 5. Registro de decisiones

| ID | Decisión | Fecha |
|---|---|---|
| — | El lote `BATCH_20261028_051456` se aprueba **sin auditar** | 29-10-2026 |
| — | La API de Pixiv se usa **solo para leer tags**, nunca para publicar | 29-10-2026 |
| D1.1 | Cadena base por personaje + 2-3 tags según el prompt | 29-10-2026 |
| D1.2 | JP + EN, inglés solo para personaje y franquicia | 29-10-2026 |
| D1.3 | 10 = 2 personaje + 2 franquicia + 5 NSFW + 1 libre; `R-18`/`AI` fuera | 29-10-2026 |
| D1.4 | Co-ocurrencia real con token (`gppt`) | 29-10-2026 |
| D1.5 | Sin tag → etiquetar por género (`NTR`, `eroge`, `オリジナル`) | 29-10-2026 |
| D1.6 | Solo personajes a publicar; prioridad gachas/RPG/anime | 29-10-2026 |
| D2.1 | Flask + 1 HTML, conexión directa a Forge | 29-10-2026 |
| D2.2 | Preview antes de lanzar + botones limpiar/regenerar | 29-10-2026 |
| D2.3 | La UI avisa del límite y permite ver/editar el pool | 29-10-2026 |
| D2.4 | Unificar a 5 stages | 29-10-2026 |
| D2.5 | Dos pestañas (manual / sorteo) + filtro de usados | 29-10-2026 |
| D2.6 | Solo `personajes.txt`; dividir por franquicias más adelante | 29-10-2026 |
| D2.7 | Validar el prompt en la preview | 29-10-2026 |
| D2.8 | Regenerar imagen suelta desde la UI | 29-10-2026 |
| D2.9 | Preview y lanzamiento comparten los mismos Jobs (se guardan en memoria) | 29-10-2026 |
| D2.10 | Cámara por índice de imagen dentro del personaje (`cameras.txt`) | 29-10-2026 |
| D2.11 | Una `compose_theme_jobs` por (personaje, stage) | 29-10-2026 |
| D2.12 | Repetición de poses: bloquea salvo `allow_repeat` explícito | 29-10-2026 |
| D3.1 | Poses → temas/outfit → personajes | 29-10-2026 |
| D3.2 | Personajes: búsqueda y curación manual del usuario | 29-10-2026 |
| D3.3 | Poses: pasada automática + calibración | 29-10-2026 |
| D3.4 | Base desnudo; outfits base por franquicia cuando toque | 29-10-2026 |
| D3.5 | Curar los 25 temas y luego nuevos con story | 29-10-2026 |
| D3.6 | Arreglar `pb0009` y `pb0025` de paso | 29-10-2026 |
