Mismo patrón que carta_article_ids() (commit anterior): si el docker exec contra el contenedor WP local fallaba (contenedor caído/inexistente), local_meta() devolvía stdout vacío en vez de comprobar returncode. sync_one() interpretaba la cadena vacía como "sin-audio-local" (skip), no como error, así que un lote entero podía acabar "ok=0/skip=N" tras perder Docker en vez de abortar. Ahora lanza RuntimeError con el stderr si docker exec falla; se propaga por sync_one() hasta el try/except de main(), que ya cuenta err y devuelve 1 — el wrapper aborta correctamente con exit=1 y 0 sincronizaciones. Probado: FEA_WP_CONTAINER inexistente + --ids → aborta en el primer ID (exit=1, ok=0). Camino normal (--ids con contenedor real, dry-run) sigue dando el mismo resultado que antes. Revisión de Codix en el PR #215.
feadulta — WordPress
Código del sitio WordPress de feadulta.com. Plugins personalizados (mu-plugins) y scripts de migración y mantenimiento.
Estructura
mu-plugins/ — plugins WordPress cargados automáticamente (sin activar)
scripts/ — scripts Python/PHP de migración, traducción, TTS y mantenimiento
mu-plugins principales
| Plugin | Función |
|---|---|
fea-carta-portada.php |
Parser de la carta semanal → portada |
fea-homepage.php |
Tarjetas de portada |
fea-share.php |
Sección "Comparte FeAdulta" (Facebook, Instagram, Imprimir) |
fea-ui.php |
Estilos globales, menú, fondo cálido en artículos |
fea-accesibilidad.php |
Tipografía Atkinson Hyperlegible (self-hosted) + toggle claro/oscuro persistente |
fea-beta-feedback.php |
Barra de feedback beta + endpoint /fea/v1/lang/{id} |
fea-search.php |
Buscador móvil nativo |
fea-pensamientos.php |
Widget de pensamientos |
carta-semana-plugin.php |
Redirects carta vigente/semana pasada |
Scripts principales
| Script | Función |
|---|---|
import_new_k2_items.py |
Importar artículos nuevos desde Joomla/K2 |
import_new_cartas.py |
Importar cartas semanales nuevas |
translate_haiku.py |
Traducción automática vía Claude Haiku |
tts_produce.py |
Generación de audio TTS (MiniMax) |
sync_translations_to_prod.py |
Sincronizar traducciones local → producción |
sync_audio_to_prod.py |
Sincronizar UN post/carta de audio TTS local → producción (sube el mp3, verifica SHA-256 remoto y persiste fea_audio_sha256) |
sync_audio_bulk.sh |
Envoltorio de sync_audio_to_prod.py para lotes: para en el primer fallo, sin reintentos ciegos, resumen ok/skip |
detect_untranslated.php |
Detectar posts sin traducir |
Sincronizar audio TTS a producción (local → prod)
sync_audio_to_prod.py sube el mp3 de UN post/carta y, desde el #213, calcula y verifica el
SHA-256 en cada subida (reproducción verificada automática, sin backfill manual — ver #212).
Para lotes de varios posts, usar sync_audio_bulk.sh: es el envoltorio que añade "parar en el
primer fallo" (el script Python no lo hace por sí solo, recorre toda la lista y acumula errores).
Carga los IDs con mapfile/mediante array y redirige el stdin de cada invocación a /dev/null a
propósito — un while read id; do ...ssh...; done < fichero sin esa precaución se rompe en
silencio porque ssh se come el resto del fichero de IDs (ver
feedback-bash-while-read-ssh-stdin en la memoria del proyecto).
export FEA_PROD_SSH_HOST="rafa@188.40.120.157"
export FEA_PROD_DOCKER_CONTAINER="wordpress-r2ssjifwj0r0ghyoqd528uaa"
# Dry-run primero, siempre
scripts/sync_audio_bulk.sh --ids 55519,55520,822 --dry-run
# Aplicar de verdad
scripts/sync_audio_bulk.sh --ids 55519,55520,822
# Desde un fichero (un ID por línea) o toda la cola de una carta
scripts/sync_audio_bulk.sh --file /tmp/ids_pendientes.txt
scripts/sync_audio_bulk.sh --carta 54254
Rollback de un post concreto: python3 scripts/sync_audio_to_prod.py --rollback --ids <id>
(borra el meta en prod, no toca el mp3 local).
Servidor local de desarrollo
WordPress corre en Docker local, accesible vía Tailscale:
https://farmer.taild3aaf6.ts.net/fea/
Admin: eqpyk / NuevaFeAdulta2024!