TTS masivo del backlog histórico por autor (Fray Marcos primero): cron cada 5 h con gate de cuota MiniMax + reporte diario #188

Open
opened 2026-08-01 11:40:36 +00:00 by rafa · 6 comments
Owner

Aprovechar la cuota ociosa de MiniMax para locutar el backlog histórico de artículos con voz clonada, empezando por Fray Marcos (lo que falta de 2026 + todo 2025) y siguiendo por el resto de años y por los otros autores ya clonados (Pagola, Sicre, Arregi — ver #152).

Hoy la cuota está prácticamente sin usar (MiniMax 5h 0 %, semanal 7 %), así que es capacidad que se pierde cada semana.

1. Inventario medido (WP local, 2026-08-01)

Solo posts ES (term_taxonomy_id 1404 de polylang) en estado publish. Los draft y trash quedan fuera a propósito.

Autor ID 2026 faltan 2025 faltan Total ES Con audio Faltan (todos los años)
Fray Marcos 382 24 de 34 62 de 62 1.032 10 1.022
Pagola 383 22 de 31 53 de 53 398 9 389
Sicre 774 21 de 30 54 de 54 637 10 627
Arregi 386 3 de 7 13 de 13 447 4 443
2.514 33 2.481

Lote 1 pedido por Rafa = Fray Marcos 2026 + 2025 = 86 artículos.

Rango de Fray Marcos: 2008–2026, ~60 artículos/año salvo 2013 (58) y 2009/2008 (9 y 11). Solo 1 artículo en todo el inventario baja de 400 caracteres (Fray Marcos 2021) → prácticamente nada se va a descartar por «sin contenido».

2. Diseño

Se reutiliza el pipeline que ya existe (tts_produce.py + minimax_tts.py + fea_post_io.php, #152/#163). Lo único que falta es de dónde sale la cola: hoy se construye a partir de una lista de cartas (FEA_TTS_CARTAS) o de un CSV de IDs a mano, y eso no sirve para un backlog de 2.481.

2.1 La cola se deriva de la BD, no de un fichero de estado

Nueva acción en scripts/fea_post_io.php:

listpending <author_id> <anio_desde> <anio_hasta> <limite>

que devuelve los IDs de posts que cumplen todo:

  • post_author = <author_id>, post_type = post, post_status = publish
  • idioma es (join con wp_term_relationships, tt_id 1404 — no hardcodear: resolver por slug)
  • CHAR_LENGTH(post_content) >= 400
  • sin meta fea_audio_done = 1 y sin meta fea_audio_skip = 1

ordenados por post_date DESC (lo más reciente primero, que es lo que más se lee).

Esa consulta es la idempotencia. No hay fichero de estado que se pueda desincronizar: si un post ya tiene audio, sencillamente no vuelve a salir en la cola. Relanzar el script, solaparlo o reiniciar la máquina no duplica nada ni gasta cuota de más.

2.2 tts_produce.py gana 4 flags

--autor 382 --desde 2025 --hasta 2026 --max 15
  • --autor/--desde/--hasta → construyen la cola con listpending en vez de con cartas.
  • --max Npara después de N audios OK en esta ejecución (acota la ventana de 5 h).
  • Sin flags, el comportamiento actual (cola de cartas) no cambia — retrocompatible con el flujo de la carta semanal, que sigue teniendo prioridad.

El resto ya está y no se toca: voz por autor vía AUTHOR_VOICES (#152), pausas dinámicas, setaudio con fea_audio_voice, freno ante rc 2056/1039 (cuota/rate limit) y INTERVAL=180 s entre audios.

2.3 Cron de ventana: scripts/tts_backlog_cron.sh

Mismo patrón que el backfill de ytsummaries (scripts/backfill_cron.sh), que ya está probado:

  1. flock -n → si una ventana se alarga, la siguiente se salta en vez de solaparse.
  2. Gate de cuota MiniMax con ~/ytsummaries/scripts/quota.py --json --no-local:
    • semanal ≥ 85 % → aborta la ventana.
    • 5 h ≥ 25 % → aborta (alguien más está usando MiniMax: la carta semanal, el backfill de summaraise…). El trabajo de fondo nunca debe comerse la cuota del trabajo con dueño.
  3. tts_produce.py --autor $AUTOR --desde $DESDE --hasta $HASTA --max $BATCH
  4. Log por día en /tmp/fea-tts-backlog/cron-YYYY-MM-DD.log.

Cola configurable por variables (FEA_TTS_AUTOR, FEA_TTS_DESDE, FEA_TTS_HASTA, FEA_TTS_BATCH) para poder pasar de Fray Marcos a Pagola cambiando una línea, sin tocar código.

Crontab:

0 */5 * * 1,5,6,0  /home/rafa/joomla-migration/scripts/tts_backlog_cron.sh

Cada 5 h lunes, viernes, sábado y domingo (fuera martes, miércoles y jueves, como pidió Rafa). Son 5 disparos/día × 4 días = 20 ventanas/semana.

2.4 Dimensionar el lote (BATCH)

Dato real del 2026-07-08: 22 audios llevaron la ventana de 5 h del 0 % al 89 %. Con eso, BATCH=15 deja ~35 % de margen para que un TTS de la carta semanal quepa encima sin chocar. A 15/ventana × 20 ventanas = 300/semana teóricos, pero el techo real lo pondrá la cuota semanal, no las ventanas — por eso el gate semanal es el que manda.

Con ese ritmo: el lote 1 (86) cae en el primer fin de semana; los 2.481 de los 4 autores, del orden de 2–3 meses de fondo. Empezar con BATCH=10 la primera semana y subirlo cuando se vea el consumo semanal real.

3. Reporte diario

Job de Hermes en modo no-agent (stdout se entrega directo a Rafa), igual que feadulta-feedback-daily:

hermes cron create --name feadulta-tts-backlog-daily --schedule "30 7 * * *" --script fea_tts_backlog_report.py

~/.hermes/scripts/fea_tts_backlog_report.pysolo lectura, no genera nada:

  • audios generados en las últimas 24 h (por post_meta + mtime de uploads/tts/), desglosados por autor y voz;
  • cuánto queda por autor/año (la misma consulta de listpending, contando);
  • cuota MiniMax 5 h y semanal en ese momento;
  • ventanas que se saltaron por gate y fea_audio_error nuevos, si los hay.

4. ⚠️ Interacción con el cutover a Hetzner (#180, lunes 03-ago)

  • Generar en local es inocuo: el pipeline solo escribe en el WordPress local (Docker) y en wordpress/wp-content/uploads/tts/. No toca producción. El cron puede arrancar ya.
  • Publicar en prod NO: sync_audio_to_prod.py apunta a CDMON (134.0.10.170) y lleva el workaround de ssh 'cat > ruta' por la glibc rota de ese servidor (#163). Después del cutover el destino es Hetzner, donde ese workaround sobra. No lanzar el sync antes del 03-ago — el delta de uploads del lunes está medido en 0 ficheros y meter cientos de mp3 nuevos ahí dentro invalida esa medición y alarga la ventana.
  • Fase 3 (publicar el backlog en prod) queda bloqueada hasta que el cutover cierre y sync_audio_to_prod.py esté repuntado a Hetzner y revalidado. Los audios se van acumulando en local mientras tanto, sin prisa.

5. Fases

  • F1 — Fray Marcos 2026 + 2025 (86). listpending + flags de tts_produce.py + tts_backlog_cron.sh + entrada de crontab. Validar la primera ventana a mano antes de dejarla sola.
  • F2 — Reporte diario (fea_tts_backlog_report.py + job de Hermes).
  • F3 — Publicación en prod. Bloqueada por #180; repuntar sync_audio_to_prod.py a Hetzner y revalidar.
  • F4 — Resto del histórico de Fray Marcos (2008–2024, 936 más).
  • F5 — Pagola, Sicre, Arregi (1.459 más). Solo cambiar FEA_TTS_AUTOR.

6. Decisiones abiertas

  1. ¿Ordenar por fecha descendente o ascendente? Propuesta: descendente (lo reciente se lee más). Alternativa: priorizar por tráfico real de GA4 — más trabajo, mejor retorno.
  2. ¿Los draft de 2026 entran? Ahora mismo no. Son 4 por autor; si son cartas en preparación, ya se locutan por el flujo semanal.
  3. Regeneración de los 33 audios viejos hechos con Nico antes de tener las voces clonadas: en #152 se decidió no tocarlos. Con cuota de sobra quizá ahora sí compense; son solo 33.

Medición y diseño: sesión Claude Code 2026-08-01. El inventario del §1 sale de una consulta de solo lectura contra el WP local.

Aprovechar la cuota ociosa de MiniMax para locutar el **backlog histórico** de artículos con voz clonada, empezando por **Fray Marcos** (lo que falta de 2026 + todo 2025) y siguiendo por el resto de años y por los otros autores ya clonados (Pagola, Sicre, Arregi — ver #152). Hoy la cuota está prácticamente sin usar (MiniMax 5h **0 %**, semanal **7 %**), así que es capacidad que se pierde cada semana. ## 1. Inventario medido (WP local, 2026-08-01) Solo posts **ES** (`term_taxonomy_id` 1404 de polylang) en estado `publish`. Los `draft` y `trash` quedan fuera a propósito. | Autor | ID | 2026 faltan | 2025 faltan | Total ES | Con audio | **Faltan (todos los años)** | |---|---|---|---|---|---|---| | Fray Marcos | 382 | 24 de 34 | 62 de 62 | 1.032 | 10 | **1.022** | | Pagola | 383 | 22 de 31 | 53 de 53 | 398 | 9 | **389** | | Sicre | 774 | 21 de 30 | 54 de 54 | 637 | 10 | **627** | | Arregi | 386 | 3 de 7 | 13 de 13 | 447 | 4 | **443** | | | | | | **2.514** | **33** | **2.481** | **Lote 1 pedido por Rafa = Fray Marcos 2026 + 2025 = 86 artículos.** Rango de Fray Marcos: 2008–2026, ~60 artículos/año salvo 2013 (58) y 2009/2008 (9 y 11). Solo 1 artículo en todo el inventario baja de 400 caracteres (Fray Marcos 2021) → prácticamente nada se va a descartar por «sin contenido». ## 2. Diseño Se reutiliza el pipeline que ya existe (`tts_produce.py` + `minimax_tts.py` + `fea_post_io.php`, #152/#163). Lo único que falta es **de dónde sale la cola**: hoy se construye a partir de una lista de cartas (`FEA_TTS_CARTAS`) o de un CSV de IDs a mano, y eso no sirve para un backlog de 2.481. ### 2.1 La cola se deriva de la BD, no de un fichero de estado Nueva acción en `scripts/fea_post_io.php`: ``` listpending <author_id> <anio_desde> <anio_hasta> <limite> ``` que devuelve los IDs de posts que cumplen **todo**: - `post_author = <author_id>`, `post_type = post`, `post_status = publish` - idioma **es** (join con `wp_term_relationships`, tt_id 1404 — no hardcodear: resolver por slug) - `CHAR_LENGTH(post_content) >= 400` - **sin** meta `fea_audio_done = 1` y **sin** meta `fea_audio_skip = 1` ordenados por `post_date DESC` (lo más reciente primero, que es lo que más se lee). **Esa consulta es la idempotencia.** No hay fichero de estado que se pueda desincronizar: si un post ya tiene audio, sencillamente no vuelve a salir en la cola. Relanzar el script, solaparlo o reiniciar la máquina no duplica nada ni gasta cuota de más. ### 2.2 `tts_produce.py` gana 4 flags ``` --autor 382 --desde 2025 --hasta 2026 --max 15 ``` - `--autor/--desde/--hasta` → construyen la cola con `listpending` en vez de con cartas. - `--max N` → **para después de N audios OK** en esta ejecución (acota la ventana de 5 h). - Sin flags, el comportamiento actual (cola de cartas) no cambia — retrocompatible con el flujo de la carta semanal, que sigue teniendo prioridad. El resto ya está y no se toca: voz por autor vía `AUTHOR_VOICES` (#152), pausas dinámicas, `setaudio` con `fea_audio_voice`, freno ante `rc` 2056/1039 (cuota/rate limit) y `INTERVAL=180 s` entre audios. ### 2.3 Cron de ventana: `scripts/tts_backlog_cron.sh` Mismo patrón que el backfill de ytsummaries (`scripts/backfill_cron.sh`), que ya está probado: 1. `flock -n` → si una ventana se alarga, la siguiente se salta en vez de solaparse. 2. **Gate de cuota MiniMax** con `~/ytsummaries/scripts/quota.py --json --no-local`: - semanal **≥ 85 %** → aborta la ventana. - 5 h **≥ 25 %** → aborta (alguien más está usando MiniMax: la carta semanal, el backfill de summaraise…). El trabajo de fondo nunca debe comerse la cuota del trabajo con dueño. 3. `tts_produce.py --autor $AUTOR --desde $DESDE --hasta $HASTA --max $BATCH` 4. Log por día en `/tmp/fea-tts-backlog/cron-YYYY-MM-DD.log`. **Cola configurable por variables** (`FEA_TTS_AUTOR`, `FEA_TTS_DESDE`, `FEA_TTS_HASTA`, `FEA_TTS_BATCH`) para poder pasar de Fray Marcos a Pagola cambiando una línea, sin tocar código. **Crontab:** ``` 0 */5 * * 1,5,6,0 /home/rafa/joomla-migration/scripts/tts_backlog_cron.sh ``` Cada 5 h **lunes, viernes, sábado y domingo** (fuera martes, miércoles y jueves, como pidió Rafa). Son 5 disparos/día × 4 días = **20 ventanas/semana**. ### 2.4 Dimensionar el lote (`BATCH`) Dato real del 2026-07-08: **22 audios llevaron la ventana de 5 h del 0 % al 89 %**. Con eso, `BATCH=15` deja ~35 % de margen para que un TTS de la carta semanal quepa encima sin chocar. A 15/ventana × 20 ventanas = **300/semana teóricos**, pero el techo real lo pondrá la **cuota semanal**, no las ventanas — por eso el gate semanal es el que manda. Con ese ritmo: el **lote 1 (86) cae en el primer fin de semana**; los 2.481 de los 4 autores, del orden de **2–3 meses** de fondo. Empezar con `BATCH=10` la primera semana y subirlo cuando se vea el consumo semanal real. ## 3. Reporte diario Job de Hermes en modo `no-agent` (stdout se entrega directo a Rafa), igual que `feadulta-feedback-daily`: ``` hermes cron create --name feadulta-tts-backlog-daily --schedule "30 7 * * *" --script fea_tts_backlog_report.py ``` `~/.hermes/scripts/fea_tts_backlog_report.py` — **solo lectura**, no genera nada: - audios generados en las últimas 24 h (por `post_meta` + `mtime` de `uploads/tts/`), desglosados por autor y voz; - cuánto queda por autor/año (la misma consulta de `listpending`, contando); - cuota MiniMax 5 h y semanal en ese momento; - ventanas que se saltaron por gate y `fea_audio_error` nuevos, si los hay. ## 4. ⚠️ Interacción con el cutover a Hetzner (#180, lunes 03-ago) - **Generar en local es inocuo**: el pipeline solo escribe en el WordPress local (Docker) y en `wordpress/wp-content/uploads/tts/`. No toca producción. El cron puede arrancar ya. - **Publicar en prod NO**: `sync_audio_to_prod.py` apunta a CDMON (`134.0.10.170`) y lleva el workaround de `ssh 'cat > ruta'` por la glibc rota de ese servidor (#163). Después del cutover el destino es Hetzner, donde ese workaround sobra. **No lanzar el sync antes del 03-ago** — el delta de `uploads` del lunes está medido en 0 ficheros y meter cientos de mp3 nuevos ahí dentro invalida esa medición y alarga la ventana. - **Fase 3 (publicar el backlog en prod) queda bloqueada** hasta que el cutover cierre y `sync_audio_to_prod.py` esté repuntado a Hetzner y revalidado. Los audios se van acumulando en local mientras tanto, sin prisa. ## 5. Fases - [ ] **F1 — Fray Marcos 2026 + 2025 (86).** `listpending` + flags de `tts_produce.py` + `tts_backlog_cron.sh` + entrada de crontab. Validar la primera ventana a mano antes de dejarla sola. - [ ] **F2 — Reporte diario** (`fea_tts_backlog_report.py` + job de Hermes). - [ ] **F3 — Publicación en prod.** Bloqueada por #180; repuntar `sync_audio_to_prod.py` a Hetzner y revalidar. - [ ] **F4 — Resto del histórico de Fray Marcos** (2008–2024, 936 más). - [ ] **F5 — Pagola, Sicre, Arregi** (1.459 más). Solo cambiar `FEA_TTS_AUTOR`. ## 6. Decisiones abiertas 1. **¿Ordenar por fecha descendente o ascendente?** Propuesta: descendente (lo reciente se lee más). Alternativa: priorizar por tráfico real de GA4 — más trabajo, mejor retorno. 2. **¿Los `draft` de 2026 entran?** Ahora mismo no. Son 4 por autor; si son cartas en preparación, ya se locutan por el flujo semanal. 3. **Regeneración de los 33 audios viejos hechos con Nico** antes de tener las voces clonadas: en #152 se decidió no tocarlos. Con cuota de sobra quizá ahora sí compense; **son solo 33**. --- _Medición y diseño: sesión Claude Code 2026-08-01. El inventario del §1 sale de una consulta de solo lectura contra el WP local._
Author
Owner

F1 implementada y corriendo (2026-08-01)

Rama feat/tts-backlog-autor, commit 7c5330a, sin mergear. Tres ficheros:

  • scripts/fea_post_io.php → acción listpending <autor> <desde> <hasta> <limite> [voz].
  • scripts/tts_produce.py → flags --autor/--desde/--hasta/--max/--dry-run. Sin flags, el modo cartas de la carta semanal es idéntico al de antes (verificado: la cola de --cartas 45018 sigue dando los mismos 15 IDs).
  • scripts/tts_backlog_cron.sh → wrapper con flock + gate de cuota.

Entrada de crontab instalada:

0 */5 * * 1,5,6,0 /home/rafa/joomla-migration/scripts/tts_backlog_cron.sh

De paso entran al historial los cambios de tts_produce.py que estaban sin commitear desde el 24-jul (argparse --ids/--cartas y QUOTA_OR_RATE_ERRORS para no reintentar ante rc 2056/1039).

Validado end-to-end

Prueba Resultado
php -l · py_compile · bash -n OK
listpending 382 2025 2026 86 = 24 (2026) + 62 (2025), cuadra con el inventario del §1
Orden post_date DESC real: #44204 (2026-05-21) → #44184 (05-14) → #44164 (05-07)
Tanda real de 2 audios #44204 y #44184 generados con FrayMarcosFeadulta2026, mp3 en uploads/tts/, metas fea_audio_url/voice/done correctas
Idempotencia tras la tanda, esos 2 IDs desaparecen de la cola y el pendiente baja 86 → 84
Ventana completa vía tts_backlog_cron.sh gate leyó 5h=9% semana=8%, generó #44164, log dejó Pendientes: 83

Quedan 83 de los 86 del lote 1.

Corrección al §6.3: no hay nada que regenerar

Escribí que había 33 audios viejos locutados con Nico antes de tener las voces clonadas. Es falso. Los 37 audios existentes de los 4 autores tienen todos ya su voz clonada correcta, y los mp3 están fechados el 9-jul por la mañana — o sea que se regeneraron enteros al día siguiente de #152, no solo el artículo de la carta 734 por autor como decía mi nota.

listpending mantiene igualmente la comprobación de voz (sale en la cola lo locutado con una voz que no es la del autor). Hoy devuelve 0 por ese motivo; queda como red de seguridad si en el futuro se clona una voz nueva o se cambia una existente.

Ajuste sobre el diseño del §2.4

El gate de la ventana de 5 h sube de 25 % a 55 %. Medido en las pruebas de hoy: 3 audios = 9 % de la ventana, o sea ~3 % por audio, coherente con el dato del 8-jul (22 audios = 89 %). Con BATCH=10 una tanda pide ~35 %, así que un gate al 25 % se bloquearía a sí mismo casi siempre. A 55 % cabe la tanda y sigue frenando cuando la ventana la está usando otro — la carta semanal son ~28 audios de golpe. Si se sube BATCH, hay que subir también FEA_TTS_MAX_5H.

BATCH arranca en 10 (no 15) hasta ver una semana de consumo real.

Ritmo real medido

La síntesis de un artículo de ~4.000 caracteres tarda ~10 s; el grueso del tiempo es el INTERVAL=180 s entre audios. Una tanda de 10 son ~30 min, muy holgada dentro de la ventana de 5 h.

Limitación conocida

El cron es del crontab de la WSL: si Windows está apagado o la WSL no ha arrancado, la ventana no se dispara. No se pierde nada (la cola se recalcula sola), simplemente el backlog avanza más despacio.

Siguiente

F2, el reporte diario. Sin empezar.

## F1 implementada y corriendo (2026-08-01) Rama **`feat/tts-backlog-autor`**, commit `7c5330a`, sin mergear. Tres ficheros: - **`scripts/fea_post_io.php`** → acción `listpending <autor> <desde> <hasta> <limite> [voz]`. - **`scripts/tts_produce.py`** → flags `--autor/--desde/--hasta/--max/--dry-run`. Sin flags, el modo cartas de la carta semanal es idéntico al de antes (verificado: la cola de `--cartas 45018` sigue dando los mismos 15 IDs). - **`scripts/tts_backlog_cron.sh`** → wrapper con `flock` + gate de cuota. Entrada de crontab instalada: ``` 0 */5 * * 1,5,6,0 /home/rafa/joomla-migration/scripts/tts_backlog_cron.sh ``` De paso entran al historial los cambios de `tts_produce.py` que estaban sin commitear desde el 24-jul (argparse `--ids/--cartas` y `QUOTA_OR_RATE_ERRORS` para no reintentar ante rc 2056/1039). ### Validado end-to-end | Prueba | Resultado | |---|---| | `php -l` · `py_compile` · `bash -n` | OK | | `listpending 382 2025 2026` | 86 = 24 (2026) + 62 (2025), cuadra con el inventario del §1 | | Orden | `post_date DESC` real: #44204 (2026-05-21) → #44184 (05-14) → #44164 (05-07) | | Tanda real de 2 audios | #44204 y #44184 generados con `FrayMarcosFeadulta2026`, mp3 en `uploads/tts/`, metas `fea_audio_url`/`voice`/`done` correctas | | **Idempotencia** | tras la tanda, esos 2 IDs **desaparecen de la cola** y el pendiente baja 86 → 84 | | Ventana completa vía `tts_backlog_cron.sh` | gate leyó `5h=9% semana=8%`, generó #44164, log dejó `Pendientes: 83` | **Quedan 83 de los 86 del lote 1.** ### Corrección al §6.3: no hay nada que regenerar Escribí que había 33 audios viejos locutados con Nico antes de tener las voces clonadas. **Es falso.** Los 37 audios existentes de los 4 autores tienen **todos** ya su voz clonada correcta, y los mp3 están fechados el **9-jul por la mañana** — o sea que se regeneraron enteros al día siguiente de #152, no solo el artículo de la carta 734 por autor como decía mi nota. `listpending` mantiene igualmente la comprobación de voz (sale en la cola lo locutado con una voz que no es la del autor). Hoy devuelve 0 por ese motivo; queda como red de seguridad si en el futuro se clona una voz nueva o se cambia una existente. ### Ajuste sobre el diseño del §2.4 El gate de la ventana de 5 h sube de **25 % a 55 %**. Medido en las pruebas de hoy: **3 audios = 9 % de la ventana**, o sea ~3 % por audio, coherente con el dato del 8-jul (22 audios = 89 %). Con `BATCH=10` una tanda pide ~35 %, así que un gate al 25 % se bloquearía a sí mismo casi siempre. A 55 % cabe la tanda y sigue frenando cuando la ventana la está usando otro — la carta semanal son ~28 audios de golpe. **Si se sube `BATCH`, hay que subir también `FEA_TTS_MAX_5H`.** `BATCH` arranca en **10** (no 15) hasta ver una semana de consumo real. ### Ritmo real medido La síntesis de un artículo de ~4.000 caracteres tarda ~10 s; el grueso del tiempo es el `INTERVAL=180 s` entre audios. Una tanda de 10 son ~30 min, muy holgada dentro de la ventana de 5 h. ### Limitación conocida El cron es del crontab de la WSL: **si Windows está apagado o la WSL no ha arrancado, la ventana no se dispara**. No se pierde nada (la cola se recalcula sola), simplemente el backlog avanza más despacio. ### Siguiente F2, el reporte diario. Sin empezar.
Author
Owner

F2 implementada — y dos fallos del F1 cazados en la primera ventana real (2026-08-02)

Rama feat/tts-backlog-autor subida a gitea (c5dcbdb), sin mergear. Tres commits.

🔴 Las 4 ventanas del 2-ago no corrieron: el script se quedó sin bit +x

tts_backlog_cron.sh acabó en -rw-r--r--. Lo dejé ejecutable al probarlo, pero dos ediciones posteriores del fichero reescribieron el modo y se llevaron el +x por delante. Cron lo invocaba directamente → permission deniedni log, ni aviso (no hay MTA en la WSL, así que el correo de cron se pierde). Confirmado en /var/log/syslog: las 4 ventanas de hoy (00:00, 05:00, 10:00, 15:00) sí se dispararon, y ninguna dejó rastro en /tmp/fea-tts-backlog/.

Arreglado por partida triple, porque un fallo mudo no puede depender de acordarse de un chmod:

  1. chmod +x y modo 100755 en el índice de git (git update-index --chmod=+x), que en el primer commit había entrado como 100644.
  2. La entrada de crontab pasa a **/bin/bash** /home/rafa/.../tts_backlog_cron.sh → deja de depender del modo del fichero.
  3. El reporte diario avisa explícitamente cuando en 24 h no hubo ni una ventana ejecutada ni una saltada, que es la firma exacta de este fallo.

🔴 El gate de cuota abortaba justo cuando había toda la cuota libre

Al relanzar una ventana de recuperación:

[2026-08-02 18:50:43] MiniMax 5h=100% semana=10%
[2026-08-02 18:50:43] ABORT: ventana de 5h al 100% >= 55%, no cabe la tanda; salto.

Pero la API decía five_h_pct: 0.0. El parser hacía int(m.get("five_h_pct") or 100) y 0.0 es falsy en Python, así que la ventana entera libre se leía como ventana entera llena. El caso peor posible: el gate bloqueaba precisamente las ventanas más aprovechables. Ahora distingue 0.0 de None con una comprobación explícita, y el comentario del código lo deja escrito para que no vuelva.

Verificado tras el arreglo — misma ventana, lectura correcta y tanda en marcha:

[2026-08-02 18:52:03] MiniMax 5h=0% semana=10%
[2026-08-02 18:52:03] tts_produce --autor 382 --desde 2025 --hasta 2026 --max 10 ...
[2026-08-02 18:52:14] #44068 OK «JESÚS ES ÉL MISMO DIOS-VIDA» [FrayMarcosFeadulta2026]

Lección para el §2.3: un gate de cuota que falla en cerrado es invisible. Aborta, escribe una línea de log que parece normal y nadie se entera. Los dos fallos de hoy tenían el mismo síntoma —no pasa nada— y por eso el aviso de "ninguna ventana dejó rastro" del reporte es parte del arreglo, no un adorno.

F2 — Reporte diario

scripts/fea_tts_backlog_report.py, solo lectura: no genera audio ni escribe en la BD.

Vive en el repo y ~/.hermes/scripts/ lo ve por symlink, no por copia — el problema de la skill del webmaster duplicada y desincronizada no se repite.

hermes cron create "30 7 * * *" --name feadulta-tts-backlog-daily \
  --script fea_tts_backlog_report.py --no-agent --deliver origin

Job 9bcb2f913cbd, primera entrega el 3-ago a las 07:30. Salida real de hoy:

Fe Adulta — backlog TTS (últimas 24 h): 1 audios
  Fray Marcos: 1  (#44068)

Pendientes:
  Fray Marcos: 1017  ← en curso, 82 del lote 2025-2026
  Pagola: 389
  Sicre: 627
  Arregi: 443

Cuota MiniMax: 5h 5%  ·  semana 10%
Ventanas 24 h: 2 ejecutadas, 1 saltadas por cuota

Avisos:
  ventana de 5h al 100% >= 55%, no cabe la tanda; salto.

Los audios se atribuyen a su autor por fea_audio_voice (cada autor clonado tiene la suya), y el recuento de las 24 h sale del mtime de los mp3, no de las metas: es lo que se acaba de escribir de verdad, sin que lo enturbie una sincronización antigua.

Estado

  • Lote 1: 82 pendientes de 86. Tanda de 10 en curso ahora mismo.
  • Sigue sin tocarse producción. F3 continúa bloqueada por #180.
## F2 implementada — y dos fallos del F1 cazados en la primera ventana real (2026-08-02) Rama **`feat/tts-backlog-autor`** subida a gitea (`c5dcbdb`), **sin mergear**. Tres commits. ### 🔴 Las 4 ventanas del 2-ago no corrieron: el script se quedó sin bit `+x` `tts_backlog_cron.sh` acabó en `-rw-r--r--`. Lo dejé ejecutable al probarlo, pero **dos ediciones posteriores del fichero reescribieron el modo** y se llevaron el `+x` por delante. Cron lo invocaba directamente → *permission denied* → **ni log, ni aviso** (no hay MTA en la WSL, así que el correo de cron se pierde). Confirmado en `/var/log/syslog`: las 4 ventanas de hoy (00:00, 05:00, 10:00, 15:00) sí se dispararon, y ninguna dejó rastro en `/tmp/fea-tts-backlog/`. Arreglado por partida triple, porque un fallo mudo no puede depender de acordarse de un `chmod`: 1. `chmod +x` y **modo 100755 en el índice de git** (`git update-index --chmod=+x`), que en el primer commit había entrado como 100644. 2. La entrada de crontab pasa a `**/bin/bash** /home/rafa/.../tts_backlog_cron.sh` → deja de depender del modo del fichero. 3. El reporte diario **avisa explícitamente** cuando en 24 h no hubo *ni una ventana ejecutada ni una saltada*, que es la firma exacta de este fallo. ### 🔴 El gate de cuota abortaba justo cuando había toda la cuota libre Al relanzar una ventana de recuperación: ``` [2026-08-02 18:50:43] MiniMax 5h=100% semana=10% [2026-08-02 18:50:43] ABORT: ventana de 5h al 100% >= 55%, no cabe la tanda; salto. ``` Pero la API decía `five_h_pct: 0.0`. El parser hacía `int(m.get("five_h_pct") or 100)` y **`0.0` es falsy en Python**, así que la ventana entera libre se leía como ventana entera llena. El caso peor posible: el gate bloqueaba precisamente las ventanas más aprovechables. Ahora distingue `0.0` de `None` con una comprobación explícita, y el comentario del código lo deja escrito para que no vuelva. Verificado tras el arreglo — misma ventana, lectura correcta y tanda en marcha: ``` [2026-08-02 18:52:03] MiniMax 5h=0% semana=10% [2026-08-02 18:52:03] tts_produce --autor 382 --desde 2025 --hasta 2026 --max 10 ... [2026-08-02 18:52:14] #44068 OK «JESÚS ES ÉL MISMO DIOS-VIDA» [FrayMarcosFeadulta2026] ``` **Lección para el §2.3: un gate de cuota que falla en cerrado es invisible.** Aborta, escribe una línea de log que parece normal y nadie se entera. Los dos fallos de hoy tenían el mismo síntoma —no pasa nada— y por eso el aviso de "ninguna ventana dejó rastro" del reporte es parte del arreglo, no un adorno. ### F2 — Reporte diario `scripts/fea_tts_backlog_report.py`, **solo lectura**: no genera audio ni escribe en la BD. Vive en el repo y `~/.hermes/scripts/` lo ve **por symlink**, no por copia — el problema de la skill del webmaster duplicada y desincronizada no se repite. ``` hermes cron create "30 7 * * *" --name feadulta-tts-backlog-daily \ --script fea_tts_backlog_report.py --no-agent --deliver origin ``` Job `9bcb2f913cbd`, primera entrega el 3-ago a las 07:30. Salida real de hoy: ``` Fe Adulta — backlog TTS (últimas 24 h): 1 audios Fray Marcos: 1 (#44068) Pendientes: Fray Marcos: 1017 ← en curso, 82 del lote 2025-2026 Pagola: 389 Sicre: 627 Arregi: 443 Cuota MiniMax: 5h 5% · semana 10% Ventanas 24 h: 2 ejecutadas, 1 saltadas por cuota Avisos: ventana de 5h al 100% >= 55%, no cabe la tanda; salto. ``` Los audios se atribuyen a su autor por `fea_audio_voice` (cada autor clonado tiene la suya), y el recuento de las 24 h sale del **mtime de los mp3**, no de las metas: es lo que se acaba de escribir de verdad, sin que lo enturbie una sincronización antigua. ### Estado - Lote 1: **82 pendientes de 86**. Tanda de 10 en curso ahora mismo. - Sigue sin tocarse producción. F3 continúa bloqueada por #180.
Author
Owner

Cierre de sesión 2026-08-02: reparto semanal + respuesta al §6.1 (ordenar por tráfico)

Rama feat/tts-backlog-autor al día en gitea, 4 commits, sin mergear (e1a14ec).

El tamaño de tanda fijo era un error, y el reparto semanal también hacía falta

Rafa señaló dos cosas seguidas, las dos correctas.

1. Con la ventana de 5 h libre caben 20, no 10. Coste medido con dos tandas reales: 4,4 puntos de la ventana de 5 h y 0,4 de la semanal por audio. El BATCH=10 escrito a mano dejaba media ventana sin usar. Ahora cada corrida mide y calcula.

2. Pero maximizar cada ventana de 5 h agota la semana antes del fin de semana. 20 audios por ventana × 20 ventanas activas = 160 puntos de semanal para un presupuesto de 85. Simulado, el tope duro del 85 % se seca en la ventana 12 de 20 — o sea el viernes, dejando sábado y domingo a cero.

La solución no es un tope, es repartir: cada ventana gasta la parte proporcional de lo que quede de semana, dividido entre las ventanas que faltan hasta el reset.

Con reparto Con tope duro al 85 %
Audios/semana 204 205
Se seca en no se seca ventana 12 de 20
Sábado y domingo ~10 por ventana nada

El total es idéntico porque la cuota semanal es el único cuello de botella real: caben 400 audios/semana por las ventanas de 5 h y solo ~205 por la semanal. Sobra capacidad de 5 h por todas partes.

Consecuencias que conviene no olvidar:

  • Añadir días al cron no aumenta el throughput, solo reparte lo mismo entre más ventanas (con jueves serían tandas de 8 en vez de 10). Martes y miércoles siguen fuera porque son los días de la carta.
  • La única palanca real es OBJ_SEM: 85 % → ~205 audios/semana, 95 % → ~230. El colchón que deja el 85 % es justo para el TTS de una carta (~28 audios ≈ 11 puntos).
  • En la última ventana de la semana el reparto vale todo lo que sobre, así que no queda cuota sin gastar. Eso retira el apaño anterior de "apurar en las últimas 12 h".
  • El gate binario desaparece: si otro está usando MiniMax, se hace una tanda pequeña en vez de saltarse la ventana entera.

Validado en vivo: reset semanal en 166h, 20 ventanas por delante · Caben: 10 por la de 5h, 10 por el reparto semanal.

§6.1 RESUELTO a medias: ordenar por tráfico gana, pero menos de lo esperado

Cruzado GA4 (14-oct-2025 → 02-ago-2026) con la BD, vía pagePath → id de K2 → meta _fgj2wp_old_k2_id.

El archivo antiguo se consume mucho: 1,02 M de páginas vistas en URLs de artículo (49 % del tráfico del portal), 14.217 artículos antiguos distintos con visitas. Y desde que el archivo vive aparte en antiguo.feadulta.com, tiene más sesiones que la web viva (20.337 vs 12.757 en 28 días).

Filtrar por "vigente" no sirve: de los ~2.458 pendientes de los cuatro autores clonados, 2.311 tienen tráfico medible. No hay un subconjunto muerto que descartar; la palanca es el orden, no la selección.

Ordenar por tráfico vs por fecha, con los primeros 200 audios:

Orden Vistas capturadas
Por tráfico real 149.272 (75 %)
Por fecha (lo actual) 116.553 (59 %)

Mejora real de 16 puntos, no un cambio de juego: la fecha ya es buen proxy porque lo reciente es lo más leído. Decisión aplazada — el orden por fecha se queda de momento; cambiarlo es sustituir el ORDER BY de listpending por una tabla de tráfico precalculada.

Tráfico muy repartido: top 50 = 22 % de las vistas, top 250 = 46 %, top 1.000 = 72 %.

⚠️ Gotcha del cruce, por si alguien lo repite: las URLs de listado de K2 tienen la misma forma que las de artículo (/es/buscadoravanzado/itemlist/user/43-fraymarcos.html). Sin excluir /itemlist/, la página de autor de Fray Marcos (24.117 vistas) se atribuye al artículo con k2 id 43 («LA PASCUA», 33 vistas reales) y encabeza la cola. Hay que filtrarlas.

El hallazgo más valioso no era de este issue y se ha llevado a #189: los autores sin voz clonada tienen tanto tráfico como los clonados. Enrique Martínez Lozano, 60.442 vistas y 827 artículos sin locutar, sin voz. Arregi, que sí tiene voz, es el 14º por tráfico.

Estado al cierre

  • Lote 1 (Fray Marcos 2025-2026): 52 pendientes de 86. 34 audios hechos hoy.
  • Cron activo 0 */5 * * 1,5,6,0 con /bin/bash explícito. Reporte diario a las 07:30 por Hermes.
  • Sigue sin tocarse producción. F3 (publicar en prod) continúa bloqueada por #180 — mañana lunes es el cutover.
## Cierre de sesión 2026-08-02: reparto semanal + respuesta al §6.1 (ordenar por tráfico) Rama `feat/tts-backlog-autor` al día en gitea, **4 commits, sin mergear** (`e1a14ec`). ### El tamaño de tanda fijo era un error, y el reparto semanal también hacía falta Rafa señaló dos cosas seguidas, las dos correctas. **1. Con la ventana de 5 h libre caben 20, no 10.** Coste medido con dos tandas reales: **4,4 puntos de la ventana de 5 h y 0,4 de la semanal por audio**. El `BATCH=10` escrito a mano dejaba media ventana sin usar. Ahora cada corrida mide y calcula. **2. Pero maximizar cada ventana de 5 h agota la semana antes del fin de semana.** 20 audios por ventana × 20 ventanas activas = 160 puntos de semanal para un presupuesto de 85. Simulado, el tope duro del 85 % **se seca en la ventana 12 de 20** — o sea el viernes, dejando sábado y domingo a cero. La solución no es un tope, es **repartir**: cada ventana gasta la parte proporcional de lo que quede de semana, dividido entre las ventanas que faltan hasta el reset. | | Con reparto | Con tope duro al 85 % | |---|---|---| | Audios/semana | 204 | 205 | | Se seca en | no se seca | ventana 12 de 20 | | Sábado y domingo | ~10 por ventana | nada | El total es idéntico porque **la cuota semanal es el único cuello de botella real**: caben 400 audios/semana por las ventanas de 5 h y solo ~205 por la semanal. Sobra capacidad de 5 h por todas partes. **Consecuencias que conviene no olvidar:** - Añadir días al cron **no aumenta el throughput**, solo reparte lo mismo entre más ventanas (con jueves serían tandas de 8 en vez de 10). Martes y miércoles siguen fuera porque son los días de la carta. - **La única palanca real es `OBJ_SEM`**: 85 % → ~205 audios/semana, 95 % → ~230. El colchón que deja el 85 % es justo para el TTS de una carta (~28 audios ≈ 11 puntos). - En la última ventana de la semana el reparto vale todo lo que sobre, así que no queda cuota sin gastar. Eso retira el apaño anterior de "apurar en las últimas 12 h". - El gate binario desaparece: si otro está usando MiniMax, se hace una tanda pequeña en vez de saltarse la ventana entera. Validado en vivo: `reset semanal en 166h, 20 ventanas por delante · Caben: 10 por la de 5h, 10 por el reparto semanal`. ### §6.1 RESUELTO a medias: ordenar por tráfico gana, pero menos de lo esperado Cruzado GA4 (14-oct-2025 → 02-ago-2026) con la BD, vía `pagePath` → id de K2 → meta `_fgj2wp_old_k2_id`. **El archivo antiguo se consume mucho:** 1,02 M de páginas vistas en URLs de artículo (49 % del tráfico del portal), **14.217 artículos antiguos distintos** con visitas. Y desde que el archivo vive aparte en `antiguo.feadulta.com`, tiene **más sesiones que la web viva** (20.337 vs 12.757 en 28 días). **Filtrar por "vigente" no sirve:** de los ~2.458 pendientes de los cuatro autores clonados, **2.311 tienen tráfico medible**. No hay un subconjunto muerto que descartar; la palanca es el orden, no la selección. **Ordenar por tráfico vs por fecha**, con los primeros 200 audios: | Orden | Vistas capturadas | |---|---| | Por tráfico real | 149.272 (75 %) | | Por fecha (lo actual) | 116.553 (59 %) | Mejora real de 16 puntos, no un cambio de juego: la fecha ya es buen proxy porque lo reciente es lo más leído. **Decisión aplazada** — el orden por fecha se queda de momento; cambiarlo es sustituir el `ORDER BY` de `listpending` por una tabla de tráfico precalculada. Tráfico muy repartido: top 50 = 22 % de las vistas, top 250 = 46 %, top 1.000 = 72 %. ⚠️ **Gotcha del cruce, por si alguien lo repite:** las URLs de listado de K2 tienen la misma forma que las de artículo (`/es/buscadoravanzado/itemlist/user/43-fraymarcos.html`). Sin excluir `/itemlist/`, la página de autor de Fray Marcos (24.117 vistas) se atribuye al artículo con k2 id 43 («LA PASCUA», 33 vistas reales) y encabeza la cola. Hay que filtrarlas. **El hallazgo más valioso no era de este issue y se ha llevado a #189:** los autores sin voz clonada tienen tanto tráfico como los clonados. Enrique Martínez Lozano, 60.442 vistas y 827 artículos sin locutar, sin voz. Arregi, que sí tiene voz, es el 14º por tráfico. ### Estado al cierre - **Lote 1 (Fray Marcos 2025-2026): 52 pendientes de 86.** 34 audios hechos hoy. - Cron activo `0 */5 * * 1,5,6,0` con `/bin/bash` explícito. Reporte diario a las 07:30 por Hermes. - Sigue sin tocarse producción. **F3 (publicar en prod) continúa bloqueada por #180** — mañana lunes es el cutover.
Author
Owner

Fallo del informe diario en su primera ejecución real (2026-08-03 07:30)

⚠️ Cron 'feadulta-tts-backlog-daily' failed: Blocked: script path resolves
outside the scripts directory (/home/rafa/.hermes/scripts): 'fea_tts_backlog_report.py'

Causa. Puse ~/.hermes/scripts/fea_tts_backlog_report.py como symlink al script del repo, para no tener dos copias que se desincronicen. Hermes resuelve los symlinks antes de comprobar que el path caiga dentro de su directorio de scripts, así que lo rechaza: para él el script está en /home/rafa/joomla-migration/.

Arreglo. Symlink sustituido por un wrapper fino que llama al script del repo por subproceso — el mismo patrón que ya seguían feadulta_ga4_daily.py y feadulta_ga4_weekly.py en ese directorio, que apuntan a /mnt/c/Users/Chia/feadulta-git/. Sigue habiendo un único sitio donde se edita la lógica (el repo); el wrapper solo reenvía stdout/stderr y el código de salida.

Verificado: hermes cron run 9bcb2f913cbdLast run: 2026-08-03T07:36 ok, con el informe correcto.

Regla para futuros jobs de Hermes que ejecuten código del repo: wrapper, nunca symlink.

Nota documentada en el docstring de scripts/fea_tts_backlog_report.py (commit en feat/tts-backlog-autor).

De paso, primer dato real del backlog

Fe Adulta — backlog TTS (últimas 24 h): 49 audios
Pendientes: Fray Marcos 969 (34 del lote 2025-2026) · Pagola 389 · Sicre 627 · Arregi 443
Cuota MiniMax: 5h 0% · semana 12%
Ventanas 24 h: 7 ejecutadas, 1 saltadas por cuota

49 audios en 24 h con 12% de cuota semanal consumida — coherente con los 0,4 puntos/audio medidos en §3. La ventana saltada lo fue por cuota (5h al 100% en ese momento), que es el comportamiento correcto, no un fallo.

## Fallo del informe diario en su primera ejecución real (2026-08-03 07:30) ``` ⚠️ Cron 'feadulta-tts-backlog-daily' failed: Blocked: script path resolves outside the scripts directory (/home/rafa/.hermes/scripts): 'fea_tts_backlog_report.py' ``` **Causa.** Puse `~/.hermes/scripts/fea_tts_backlog_report.py` como **symlink** al script del repo, para no tener dos copias que se desincronicen. Hermes resuelve los symlinks *antes* de comprobar que el path caiga dentro de su directorio de scripts, así que lo rechaza: para él el script está en `/home/rafa/joomla-migration/`. **Arreglo.** Symlink sustituido por un **wrapper fino** que llama al script del repo por subproceso — el mismo patrón que ya seguían `feadulta_ga4_daily.py` y `feadulta_ga4_weekly.py` en ese directorio, que apuntan a `/mnt/c/Users/Chia/feadulta-git/`. Sigue habiendo un único sitio donde se edita la lógica (el repo); el wrapper solo reenvía stdout/stderr y el código de salida. Verificado: `hermes cron run 9bcb2f913cbd` → `Last run: 2026-08-03T07:36 ok`, con el informe correcto. **Regla para futuros jobs de Hermes que ejecuten código del repo:** wrapper, nunca symlink. Nota documentada en el docstring de `scripts/fea_tts_backlog_report.py` (commit en `feat/tts-backlog-autor`). ### De paso, primer dato real del backlog ``` Fe Adulta — backlog TTS (últimas 24 h): 49 audios Pendientes: Fray Marcos 969 (34 del lote 2025-2026) · Pagola 389 · Sicre 627 · Arregi 443 Cuota MiniMax: 5h 0% · semana 12% Ventanas 24 h: 7 ejecutadas, 1 saltadas por cuota ``` 49 audios en 24 h con 12% de cuota semanal consumida — coherente con los 0,4 puntos/audio medidos en §3. La ventana saltada lo fue por cuota (5h al 100% en ese momento), que es el comportamiento correcto, no un fallo.
Author
Owner

8-ago-2026 — el cron llevaba 3 días caído, y ya no depende de la rama

Qué pasó. El 5-ago el checkout principal (~/joomla-migration) pasó a la rama
docs/rescate-gap-194. Los scripts del backlog solo viven en feat/tts-backlog-autor,
así que scripts/tts_backlog_cron.sh dejó de existir en el working tree y el cron pasó
a invocar un fichero inexistente: fallo mudo, sin log, sin aviso. 7 ventanas perdidas
(las 5 del viernes 7 y las 2 de la madrugada del sábado 8). El informe diario de Hermes
estaba roto por lo mismo, y por la misma razón que el symlink de agosto: el script
estaba bien y la instalación mal
.

Arreglo (2fa50a8). El cron ya no depende de qué rama tenga puesta el repo:

  • Worktree fijo ~/worktrees/fea-tts-backlog clavado a feat/tts-backlog-autor.
    Checkout directo no era opción: esa rama versiona wordpress/ y habría machacado
    la instalación local de WordPress.
  • uploads/tts y tts-samples son symlinks a los directorios reales del repo
    principal, así que todos los mp3 siguen en un único sitio.
  • tts_backlog_cron.sh deduce su repo de la ubicación del propio script
    (FEA_TTS_REPO sigue disponible como override) en vez de llevarlo a fuego.
  • Crontab y wrapper de Hermes apuntan al worktree.

Fray Marcos 2025-2026: cerrado. Los 4 que quedaban (#16620, #16614, #16613, #16612),
locutados. El lote queda a 0.

Nuevo lote: Pagola (383), todos los años — 389 pendientes. Primera tanda de 12 hecha,
quedan 377.

Recalibrado el coste por audio. Los textos de Pagola son bastante más cortos que los
de Fray Marcos (media 2.516 car — min 2.275, max 2.885 — frente a ~7.800), y el gasto
por audio lo refleja:

5 h semanal
calibrado con Fray Marcos 4,4 pts 0,40 pts
medido con Pagola (12 audios) 2,58 pts 0,25 pts

Con la calibración vieja el cron creía que cabía la mitad de lo que cabe y dejaba cuota
sin gastar. Los nuevos valores van en el crontab, junto al lote — el script conserva los
de Fray Marcos por defecto, así que la calibración viaja con el autor, no con el código:

FEA_TTS_AUTOR=383 FEA_TTS_DESDE=2000 FEA_TTS_HASTA=2026 FEA_TTS_COSTE_5H=28 FEA_TTS_COSTE_SEM=3

(28 y 3 en décimas, un pelo por encima de lo medido para no pasarse del objetivo.)

Pendiente de decidir: al cambiar de autor conviene volver a medir un par de tandas.
Sicre (627) y Arregi (443) siguen sin tocar.

## 8-ago-2026 — el cron llevaba 3 días caído, y ya no depende de la rama **Qué pasó.** El 5-ago el checkout principal (`~/joomla-migration`) pasó a la rama `docs/rescate-gap-194`. Los scripts del backlog solo viven en `feat/tts-backlog-autor`, así que `scripts/tts_backlog_cron.sh` dejó de existir en el working tree y el cron pasó a invocar un fichero inexistente: fallo mudo, sin log, sin aviso. **7 ventanas perdidas** (las 5 del viernes 7 y las 2 de la madrugada del sábado 8). El informe diario de Hermes estaba roto por lo mismo, y por la misma razón que el symlink de agosto: *el script estaba bien y la instalación mal*. **Arreglo (2fa50a8).** El cron ya no depende de qué rama tenga puesta el repo: - Worktree fijo `~/worktrees/fea-tts-backlog` clavado a `feat/tts-backlog-autor`. Checkout directo no era opción: esa rama versiona `wordpress/` y habría machacado la instalación local de WordPress. - `uploads/tts` y `tts-samples` son symlinks a los directorios reales del repo principal, así que todos los mp3 siguen en un único sitio. - `tts_backlog_cron.sh` deduce su repo de la ubicación del propio script (`FEA_TTS_REPO` sigue disponible como override) en vez de llevarlo a fuego. - Crontab y wrapper de Hermes apuntan al worktree. **Fray Marcos 2025-2026: cerrado.** Los 4 que quedaban (#16620, #16614, #16613, #16612), locutados. El lote queda a 0. **Nuevo lote: Pagola (383), todos los años — 389 pendientes.** Primera tanda de 12 hecha, quedan 377. **Recalibrado el coste por audio.** Los textos de Pagola son bastante más cortos que los de Fray Marcos (media 2.516 car — min 2.275, max 2.885 — frente a ~7.800), y el gasto por audio lo refleja: | | 5 h | semanal | |---|---|---| | calibrado con Fray Marcos | 4,4 pts | 0,40 pts | | medido con Pagola (12 audios) | 2,58 pts | 0,25 pts | Con la calibración vieja el cron creía que cabía la mitad de lo que cabe y dejaba cuota sin gastar. Los nuevos valores van en el crontab, junto al lote — el script conserva los de Fray Marcos por defecto, así que la calibración viaja con el autor, no con el código: ``` FEA_TTS_AUTOR=383 FEA_TTS_DESDE=2000 FEA_TTS_HASTA=2026 FEA_TTS_COSTE_5H=28 FEA_TTS_COSTE_SEM=3 ``` (28 y 3 en décimas, un pelo por encima de lo medido para no pasarse del objetivo.) **Pendiente de decidir:** al cambiar de autor conviene volver a medir un par de tandas. Sicre (627) y Arregi (443) siguen sin tocar.
Author
Owner

Corrección del informe con ceros falsos (2026-08-28)

El informe diario convertía silenciosamente cualquier error de docker exec wordpress-web php /tmp/fea_post_io.php listpending … en cadena vacía y la contaba como 0 pendientes. Por eso pudo informar 0/0 pese a existir cola: no era un estado del backlog, era una lectura fallida camuflada.

Fix publicado: commit bda4a75 (feat/tts-backlog-autor). Ahora una lectura WP fallida entrega explícitamente ⚠️ informe TTS inválido y sale con código 1; nunca comunica cero. También queda ajustado el informe para sumar todas las líneas activas de crontab y señalar Sicre 774, rango 2000–2026, como autor/lote actual.

Verificación real posterior:

  • Fray Marcos: 935 pendientes
  • Pagola: 0
  • Sicre: 334 pendientes en el lote activo
  • Arregi: 443
  • última ventana: 6 audios Sicre generados correctamente.
### Corrección del informe con ceros falsos (2026-08-28) El informe diario convertía silenciosamente cualquier error de `docker exec wordpress-web php /tmp/fea_post_io.php listpending …` en cadena vacía y la contaba como `0 pendientes`. Por eso pudo informar 0/0 pese a existir cola: no era un estado del backlog, era una lectura fallida camuflada. Fix publicado: commit `bda4a75` (`feat/tts-backlog-autor`). Ahora una lectura WP fallida entrega explícitamente `⚠️ informe TTS inválido` y sale con código 1; nunca comunica cero. También queda ajustado el informe para sumar todas las líneas activas de crontab y señalar Sicre `774`, rango `2000–2026`, como autor/lote actual. Verificación real posterior: - Fray Marcos: 935 pendientes - Pagola: 0 - Sicre: 334 pendientes en el lote activo - Arregi: 443 - última ventana: 6 audios Sicre generados correctamente.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rafa/feadulta#188