diff --git a/docs/actualizar-wordpress-desde-joomla-prod.md b/docs/actualizar-wordpress-desde-joomla-prod.md new file mode 100644 index 0000000..dc06e69 --- /dev/null +++ b/docs/actualizar-wordpress-desde-joomla-prod.md @@ -0,0 +1,269 @@ +# Actualizar WordPress local desde Joomla produccion + +Runbook para refrescar la copia local migrada a WordPress con los ultimos +articulos visibles en Joomla produccion. + +## Contexto + +- Repo local: `/home/rafa/joomla-migration` +- WordPress local: `https://farmer.taild3aaf6.ts.net/fea/` +- WordPress DB local: contenedor `wordpress-mysql`, base `wordpress_db` +- WordPress web local: contenedor `wordpress-web` +- Joomla produccion esta detras de Cloudflare. Para leer el origen se uso: + +```bash +curl --resolve www.feadulta.com:443:134.0.10.170 -k -L \ + -A "Mozilla/5.0 Codex Feadulta" https://www.feadulta.com/es/ +``` + +El acceso SSH/MySQL de produccion documentado en scripts antiguos puede estar +rotado. Si falla, no asumir que los scripts historicos funcionan contra DB real. + +## Flujo preferente + +Si hay credenciales actuales o dump de produccion, usar una sincronizacion desde +base de datos. Los scripts historicos que documentan el modelo son: + +- `scripts/import_new_k2_items.py` +- `scripts/import_new_cartas.py` +- `scripts/import_new_content.py` +- `scripts/fix_imported_k2_metas.py` +- `scripts/regenerar_clasificacion_csv.py` +- `scripts/aplicar_clasificacion_a_bd.py` + +Ojo con `scripts/import_new_k2_items.py`: la version historica lee +`LAST_INSERT_ID()` en otra conexion, asi que puede devolver `0`. En el delta de +2026 se corrigieron metadatos con el offset `wp_id = k2_id + 26040`. + +## Flujo de contingencia: HTML publico + +Cuando no hay acceso a la DB de Joomla produccion, usar: + +```bash +python3 scripts/import_public_joomla_delta.py +python3 scripts/import_public_joomla_delta.py --apply +``` + +El primer comando es dry-run. El segundo escribe en WordPress local. + +Este importador: + +- lee las cartas visibles configuradas en `CARTAS`; +- recorre los enlaces de esas cartas; +- importa solo IDs superiores al maximo ya presente en WordPress; +- conserva `_fgj2wp_old_k2_id` y `_fgj2wp_old_content_id`; +- asigna `Idioma=1`; +- asigna `_carta_id` a los K2 importados; +- clasifica por seccion de la carta: + `lecturas-biblicas`, `comentario-editorial`, + `comentarios-al-evangelio`, `eucaristia`, `multimedia`, `articulos`. + +Limitacion importante: este flujo solo ve contenido enlazado desde las cartas +publicas actuales. No detecta articulos ocultos, no publicados o no enlazados. + +## Preparar una semana nueva + +1. Localizar las cartas visibles en produccion: + +```bash +curl --resolve www.feadulta.com:443:134.0.10.170 -k -L \ + -A "Mozilla/5.0 Codex Feadulta" \ + https://www.feadulta.com/es/ayuda/esta-semana.html + +curl --resolve www.feadulta.com:443:134.0.10.170 -k -L \ + -A "Mozilla/5.0 Codex Feadulta" \ + https://www.feadulta.com/es/ayuda/semana-pasada.html + +curl --resolve www.feadulta.com:443:134.0.10.170 -k -L \ + -A "Mozilla/5.0 Codex Feadulta" \ + https://www.feadulta.com/es/ayuda/otras-semanas.html +``` + +2. Actualizar `CARTAS` en `scripts/import_public_joomla_delta.py` con: + +- `content_id` +- URL relativa +- fecha de publicacion +- categorias de carta: + - actual: `[TERM_CARTA_SEMANA, TERM_CARTAS_OTRAS, TERM_FEADULTA]` + - semana pasada: `[TERM_CARTAS_OTRAS, TERM_CARTA_PASADA, TERM_FEADULTA]` + - otras semanas: `[TERM_CARTAS_OTRAS, TERM_FEADULTA]` + +3. Ejecutar dry-run y comprobar conteos: + +```bash +python3 scripts/import_public_joomla_delta.py +``` + +4. Aplicar: + +```bash +python3 scripts/import_public_joomla_delta.py --apply +``` + +## Verificaciones + +Maximos importados: + +```bash +docker exec wordpress-mysql mysql \ + -u wordpress_user -pwordpress_pass wordpress_db \ + --default-character-set=utf8mb4 -B -e " +SELECT meta_key, + MIN(CAST(meta_value AS UNSIGNED)) min_id, + MAX(CAST(meta_value AS UNSIGNED)) max_id, + COUNT(*) n +FROM wp_postmeta +WHERE meta_key IN ('_fgj2wp_old_k2_id','_fgj2wp_old_content_id') +GROUP BY meta_key;" +``` + +Delta exacto para un corte dado: + +```bash +docker exec wordpress-mysql mysql \ + -u wordpress_user -pwordpress_pass wordpress_db \ + --default-character-set=utf8mb4 -B -e " +SELECT pm.meta_key, COUNT(DISTINCT pm.post_id) n +FROM wp_postmeta pm +WHERE (pm.meta_key='_fgj2wp_old_k2_id' + AND CAST(pm.meta_value AS UNSIGNED) > 18102) + OR (pm.meta_key='_fgj2wp_old_content_id' + AND CAST(pm.meta_value AS UNSIGNED) > 9133) +GROUP BY pm.meta_key;" +``` + +Portada: + +```bash +curl -k -L --max-time 20 -sS \ + https://farmer.taild3aaf6.ts.net/fea/ \ + | rg -n "La puerta pequeña|Carta de la semana" +``` + +## Carrusel de portada + +El carrusel no depende de imagen destacada de posts. Se sincroniza desde: + +```text +wordpress/wp-content/uploads/home/ +``` + +El plugin responsable es: + +```text +wordpress/wp-content/mu-plugins/fea-slider-sync.php +``` + +Ese plugin replica el modelo Joomla `images/home/` y sincroniza Smart Slider 3 +slider `2`. + +Detectar imagenes actuales en produccion: + +```bash +curl --resolve www.feadulta.com:443:134.0.10.170 -k -L \ + --max-time 20 -sS -A "Mozilla/5.0 Codex Feadulta" \ + https://www.feadulta.com/es/ \ + | rg -o 'images/home/[^"'"'"') ]+' +``` + +Procedimiento: + +1. Mover las imagenes antiguas fuera de `uploads/home/`. +2. Descargar las nuevas desde `https://www.feadulta.com/images/home/...`. +3. Copiarlas al contenedor si el host no tiene permisos de escritura: + +```bash +docker cp /tmp/pausa_999999000944.jpg \ + wordpress-web:/var/www/html/wp-content/uploads/home/pausa_999999000944.jpg +``` + +4. Ajustar propietario: + +```bash +docker exec wordpress-web chown www-data:www-data \ + /var/www/html/wp-content/uploads/home/pausa_999999000944.jpg +``` + +5. Forzar resync: + +```bash +docker exec wordpress-web \ + wp eval "var_export(fea_slider_home_sync_now(true));" --allow-root +``` + +6. Verificar Smart Slider: + +```bash +docker exec wordpress-mysql mysql \ + -u wordpress_user -pwordpress_pass wordpress_db \ + --default-character-set=utf8mb4 -B -e " +SELECT id,title,thumbnail,params +FROM wp_nextend2_smartslider3_slides +WHERE slider=2 +ORDER BY ordering;" +``` + +## Acceso wp-admin local + +URL: + +```text +https://farmer.taild3aaf6.ts.net/fea/wp-admin +``` + +Administradores vistos en la copia local: + +- `calvo` +- `eqpyk` +- `icalvotorre` +- `josek` +- `pabloarias` +- `andrey` + +Si no se conoce la clave, resetear una cuenta local: + +```bash +docker exec wordpress-web \ + wp user update calvo --user_pass='FeAdulta2024!' --allow-root +``` + +Comprobar rol: + +```bash +docker exec wordpress-web \ + wp user get calvo --fields=ID,user_login,user_email,roles,display_name \ + --allow-root +``` + +Si el login sigue fallando despues del reset, probar ventana privada o borrar +cookies de `farmer.taild3aaf6.ts.net`. + +## Estado del delta 2026-06-14 + +En la actualizacion del 14 de junio de 2026 se importaron: + +- `46` items K2, de `18103` a `18161`; +- `14` articulos `content`, de `9134` a `9150`; +- cartas: + - `9136` / `Uno y Trino` + - `9143` / `20 años de fe adulta` + - `9150` / `La puerta pequeña` + +Carrusel actualizado de: + +- `pausa_999999000941.jpg` +- `pausa_999999000942.jpg` +- `pausa_999999000943.jpg` + +a: + +- `pausa_999999000944.jpg` +- `pausa_999999000945.jpg` +- `pausa_999999000946.jpg` + +Las imagenes antiguas quedaron en: + +```text +wordpress/wp-content/uploads/home-old-20260614/ +``` diff --git a/docs/benchmarks/gemma4-12b-vs-4b.md b/docs/benchmarks/gemma4-12b-vs-4b.md new file mode 100644 index 0000000..3844fd0 --- /dev/null +++ b/docs/benchmarks/gemma4-12b-vs-4b.md @@ -0,0 +1,99 @@ +# Benchmark Gemma 4 12B vs Gemma 4 4B + +## Objetivo +Comparar si Gemma 4 12B mejora de forma material el flujo real de Feadulta frente a Gemma 4 4B sin ejecutar todavía una batería grande. + +Este benchmark propone una sola prueba pequeña pero representativa: traducción + preparación para TTS. + +## Condiciones fijas +Usar exactamente la misma configuración para ambos modelos: + +- mismo prompt +- misma temperatura (`0.2` recomendada) +- mismo max tokens +- misma context window +- sin herramientas externas +- misma máquina / mismo backend de LM Studio + +## Caso de uso elegido +Feadulta suele necesitar: + +- traducciones ES ↔ EN +- preservar tono espiritual/catequético +- mantener nombres propios y citas bíblicas correctas +- dejar el texto listo para TTS sin frases raras + +## Prueba +### Input sugerido +Tomar un fragmento real de Feadulta de 600 a 900 palabras en español con: + +- tono espiritual o doctrinal +- al menos 2 citas bíblicas +- nombres propios o referencias litúrgicas +- frases largas y algún matiz teológico + +### Prompt +```text +Actúa como editor bilingüe para Feadulta. + +Tarea: +1. Traduce al inglés el texto español adjunto. +2. Mantén el tono espiritual, cercano y claro; no lo vuelvas académico ni robótico. +3. Conserva correctamente citas bíblicas, nombres propios, referencias litúrgicas y términos doctrinales. +4. Después de la traducción, genera una segunda versión "TTS-ready" del mismo texto en inglés: + - frases algo más cortas + - puntuación natural para locución + - sin enumeraciones artificiales + - sin emojis + - sin notas del traductor +5. No inventes contenido ni expliques decisiones. + +Devuelve exactamente este formato: + +[EN_TRANSLATION] +... + +[TTS_READY_EN] +... +``` + +[PEGAR TEXTO FUENTE AQUÍ] +``` + +## Qué evaluar +Puntuar 1–5 en cada eje: + +1. Fidelidad al original + - no omite matices importantes + - no simplifica demasiado + - no añade ideas + +2. Naturalidad del inglés + - suena humano + - no parece traducción literal + - mantiene buen ritmo + +3. Precisión religiosa / terminológica + - citas bíblicas bien mantenidas + - términos doctrinales consistentes + - nombres y referencias correctos + +4. Calidad TTS-ready + - frases respirables + - puntuación ayuda a narración + - no hay giros torpes para voz + +5. Necesidad de edición manual + - 5 = casi publicar + - 1 = rehacer bastante + +## Señales de que gana el 12B +- conserva mejor el matiz del original +- hace menos traducción literal rara +- entrega una versión TTS más fluida +- requiere menos retoques antes de publicar + +## Regla práctica de decisión +Si el 12B gana claramente en 3 o más ejes y la latencia extra sigue siendo tolerable, merece ser el modelo por defecto para traducción/TTS de Feadulta. + +Si la mejora es pequeña, el 4B puede seguir siendo suficiente para borradores rápidos. diff --git a/docs/guia-publicacion-carta-inma.md b/docs/guia-publicacion-carta-inma.md new file mode 100644 index 0000000..3ebad8c --- /dev/null +++ b/docs/guia-publicacion-carta-inma.md @@ -0,0 +1,411 @@ +# Guía para publicar la carta semanal en Fe Adulta (WordPress) + +> **Para quién es este documento:** para el asistente (Claude Code / Cowork) que ayuda a +> **Inma** a publicar la carta semanal y los artículos en el WordPress nuevo de Fe Adulta. +> +> **Reparto de responsabilidades:** +> - **Inma** aporta el contenido editorial: cómo se redacta y compone la carta y los artículos. +> - **Este documento** aporta la infraestructura y la estructura del sitio: cómo conectarse, +> dónde va cada cosa, qué categorías usar, qué se publica automáticamente y qué hay que +> hacer a mano. +> - **Inma sube siempre en ESPAÑOL.** Las traducciones (EN/FR/IT/PT) y el audio (TTS) los +> genera un proceso automático en el servidor (ver §7). No hay que traducir a mano. + +--- + +## 1. El sitio y cómo conectarse + +### 1.1 Entornos + +| Entorno | URL | Estado | +|---------|-----|--------| +| **Producción** | `https://www.feadulta.com` | El sitio en vivo, WordPress. Cutover completado el 2026-07-08 | +| Legado Joomla | `https://antiguo.feadulta.com` | Solo contenido histórico no migrado. No se publica aquí | + +**El cutover de dominio ya se hizo (2026-07-08):** `www.feadulta.com` es WordPress y es donde +se publica siempre. `wp-nuevo.feadulta.com` (el subdominio de Beta) ha dejado de servir nada +(el directorio que usaba se vació al mover los ficheros a la raíz del hosting) — si algún +enlace o script antiguo todavía lo menciona, hay que cambiarlo a `www.feadulta.com`. + +### 1.2 Acceso recomendado: API REST de WordPress + contraseña de aplicación + +No hace falta SSH ni tocar la base de datos. WordPress trae una API REST y un sistema de +**"Contraseñas de aplicación"** (Application Passwords) pensado exactamente para esto. + +**Cómo obtener la credencial (lo hace Inma una sola vez):** +Inma **ya tiene usuario con rol Editor** en el sitio (suficiente para crear, editar, +publicar y programar entradas). Con ese usuario: +1. Entrar en `https://www.feadulta.com/wp-admin`. +2. Ir a **Usuarios → Perfil** → bajar hasta **"Contraseñas de aplicación"**. +3. Escribir un nombre (p. ej. `cowork-cartas`) y pulsar **Añadir**. +4. WordPress muestra una contraseña de 24 caracteres con espacios + (formato `xxxx xxxx xxxx xxxx xxxx xxxx`). **Se copia y se guarda**: solo se ve una vez. + +**Cómo la usa el asistente:** autenticación HTTP Basic sobre HTTPS, con +`usuario:contraseña_de_aplicación`. Ejemplo de prueba (debe responder con los datos del +usuario): + +```bash +curl -s -u "USUARIO:xxxx xxxx xxxx xxxx xxxx xxxx" \ + https://www.feadulta.com/wp-json/wp/v2/users/me +``` + +Base de la API para todo lo demás: `https://www.feadulta.com/wp-json/wp/v2/` + +> **⚠️ Pendiente de configurar en Cloudflare (bloqueante).** Comprobado el **27/06/2026**: el +> sitio está tras Cloudflare y devuelve **403 "Attention Required"** a **cualquier** petición +> que no sea un navegador real con JavaScript — afecta a la API REST, a la home y a +> `wp-login.php`, incluso enviando un User-Agent de navegador. Es el reto de navegador de +> Cloudflare, **no** un problema de la credencial. +> +> Para que el asistente pueda usar la API hay que crear una **excepción en el panel de +> Cloudflare** (lo hace Inma). Opciones, de más simple a más limpia: +> 1. **Allowlist por IP de origen:** WAF → Tools → permitir la IP pública desde la que se +> conecta el asistente. Simple, pero requiere IP fija/conocida. +> 2. **Regla WAF con token secreto:** una Custom Rule que haga *Skip / Bypass* cuando la +> petición vaya a `/wp-json/*` **y** lleve una cabecera secreta (p. ej. +> `X-FEA-Key: `). El asistente añade esa cabecera en cada llamada. +> 3. **Subdominio "solo DNS"** (sin proxy naranja), p. ej. `api.feadulta.com`, apuntando al +> mismo origen para servir la API sin pasar por el reto de Cloudflare. +> +> Hasta que se aplique una de estas, las llamadas a la API fallarán con 403. Coordinar con +> Rafa/Inma cuál se elige. + +--- + +## 2. La idea clave: **la carta de la semana ES la portada** + +Esto es lo más importante de entender. La portada de Fe Adulta **no** muestra "los últimos +artículos por categoría". Muestra **exactamente lo que enlaza la carta de la semana actual**, +agrupado por las secciones internas de esa carta. + +Es decir: la carta semanal es un artículo largo en HTML, con secciones marcadas por +encabezados, y dentro de cada sección hay enlaces a otros artículos. La portada lee la carta +vigente, detecta esas secciones y coloca cada grupo de enlaces en su bloque correspondiente. + +**Consecuencia práctica:** para que la portada se rellene bien, la carta tiene que estar +escrita con unos **encabezados de sección concretos** (§5) y enlazar a artículos que ya +existan en el sitio (§4). + +--- + +## 3. Categorías del sitio + +Las categorías se asignan por su **ID numérico** vía API (campo `categories: [..]`). + +### 3.1 Categorías de la carta (estado de cada carta) + +| ID | Nombre | Slug | Significado | +|----|--------|------|-------------| +| **6** | Carta de la semana | `cartasemana` | La carta **vigente**. Debe estar solo la actual. | +| **22** | La semana pasada | `carta-semana-pasada` | La carta de la semana anterior. Debe estar solo una. | +| **21** | Cartas de otras semanas | `cartas-de-otras-semanas` | Histórico **acumulativo**: TODAS las cartas publicadas alguna vez, incluidas la vigente (6) y la anterior (22). No se quita nunca, solo se añade. Así el listado de "Otras Semanas" sirve para navegar por cualquier carta pasada sin tener que ir carta por carta. | + +### 3.2 Categorías temáticas (para los artículos de dentro de la carta) + +| ID | Nombre | Uso | +|----|--------|-----| +| 1645 | Lecturas bíblicas | Lecturas del domingo | +| 1646 | Comentario editorial | Comentario editorial de la semana | +| 1647 | Comentarios al evangelio | Artículos que comentan el evangelio | +| 1648 | Eucaristía | Material para la eucaristía | +| 1649 | Multimedia | Vídeos / audios / material multimedia | +| 1650 | Artículos | Artículos generales seleccionados | + +> Cada artículo de la semana va en su categoría temática. **La carta** (el texto largo) va +> en la categoría **6**. + +--- + +## 4. Cómo se publica una carta nueva (flujo completo) + +Una "carta" no es un solo artículo: es **un artículo-carta que enlaza a varios artículos**. +El orden correcto es: primero los artículos, luego la carta que los enlaza. + +### Paso 1 — Crear los artículos de la semana + +Cada pieza de contenido (cada comentario al evangelio, cada artículo seleccionado, etc.) es +un **post** propio. Se crean vía API y conviene crearlos primero **como borrador** y, cuando +estén listos, publicarlos. + +```bash +curl -s -u "USUARIO:APP_PASSWORD" \ + -X POST https://www.feadulta.com/wp-json/wp/v2/posts \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Título del artículo", + "content": "

Cuerpo en HTML…

", + "status": "draft", + "categories": [1647] + }' +``` + +De la respuesta JSON interesan dos campos: +- `id` → identificador del post. +- `link` → la **URL pública** del artículo. **Guardar esta URL**: es la que se usa para + enlazar desde la carta. + +Repetir para cada artículo, usando la categoría temática que corresponda (§3.2). + +> ⚠️ **No adivinéis la URL a partir del título — usad siempre el `link` real de la API, +> y solo DESPUÉS de publicar.** En la carta 734 los artículos se quedaron en `draft` con el +> título prefijado `[PRUEBA]`, y la carta se compuso enlazando slugs *adivinados* a mano +> (`.../prueba-silencio/`, etc.) a partir de ese título provisional. Al publicar de verdad +> con el título limpio, WordPress genera el slug desde el título final y **le añade un +> sufijo (`-2`, `-4`…) si ya existe otro post con ese mismo slug** — algo frecuente en +> temas recurrentes ("Silencio", "La palabra", "Te encontré"…). Resultado: 21 de 22 +> enlaces de la carta apuntaban a una URL que nunca existió. Regla: **publicad cada +> artículo (quitando `[PRUEBA]` del título) en cuanto esté listo**, y componed los +> enlaces de la carta con el `link` que devuelve la API **tras ese publish**, no antes. + +### Paso 1.5 — Asignar `_carta_id` a cada artículo de la semana (IMPORTANTE) + +Cada artículo de la semana debe llevar el meta `_carta_id` = ID del post de la carta a la +que pertenece. **Sin esto, el sistema no puede localizar "todos los artículos de esta +carta" para publicarlos/traducirlos/locutarlos en bloque.** + +`_carta_id` es un meta interno y **no se puede asignar por el endpoint estándar** +`/wp/v2/posts/` (WordPress lo bloquea por empezar por `_`). Para esto hay un +**endpoint REST propio**, desplegado el 2026-07-07: + +```bash +# Leer el _carta_id actual de un artículo +curl -s -u "USUARIO:APP_PASSWORD" \ + https://www.feadulta.com/wp-json/fea/v1/carta-id/ID_DEL_ARTICULO + +# Asignar (o corregir) el _carta_id de un artículo +curl -s -u "USUARIO:APP_PASSWORD" \ + -X POST https://www.feadulta.com/wp-json/fea/v1/carta-id/ID_DEL_ARTICULO \ + -H "Content-Type: application/json" \ + -d '{"carta_id": ID_DE_LA_CARTA}' + +# Borrar el _carta_id (si os habéis equivocado de artículo) +curl -s -u "USUARIO:APP_PASSWORD" \ + -X DELETE https://www.feadulta.com/wp-json/fea/v1/carta-id/ID_DEL_ARTICULO +``` + +Respuesta en los tres casos: `{"post_id": ID, "carta_id": ID|null}`. + +Notas: +- Solo podéis asignar `_carta_id` a artículos **que ya podéis editar** (mismo criterio que + el resto de la API: vuestro usuario Editor). +- `carta_id` tiene que ser el ID de **un post que exista** (normalmente el de la carta, + aunque en el momento de llamar a este endpoint la carta puede que aún esté en borrador — + eso no importa, solo tiene que existir el post). +- Podéis llamarlo justo después de crear cada artículo (Paso 1) o al final, después de tener + el ID de la carta (Paso 3) — el orden entre pasos 1 y 1.5 y 3 no importa mientras al acabar + todos los artículos de la semana tengan el `_carta_id` correcto. +- Código fuente: `wp-content/mu-plugins/fea-carta-id-api.php` (mu-plugin, activo siempre). + +### Paso 2 — Componer el artículo-carta + +La carta es un post HTML con: +1. Texto editorial libre (introducción). +2. Una sección por cada bloque, **encabezada con una de las frases exactas de §5**. +3. Dentro de cada sección, **enlaces (``) a las URLs** de los artículos del Paso 1. + +Ver §5 para el formato exacto de los encabezados y un ejemplo completo. + +### Paso 3 — Publicar (o programar) la carta + +La carta va en la categoría **6** (`cartasemana`) **y también en la 21** (`cartas-de-otras-semanas`) +desde el primer momento — ver §3.1: la 21 es un histórico acumulativo que no se quita nunca, así +que toda carta la lleva ya desde que se crea, no solo cuando se "degrada" en el Paso 4. + +- **Publicar ya:** `"status": "publish"`. +- **Programar:** `"status": "future"` + `"date"` con la fecha/hora local del sitio en + formato ISO 8601, p. ej. `"2026-06-29T08:00:00"`. WordPress la publicará sola a esa hora. + +```bash +curl -s -u "USUARIO:APP_PASSWORD" \ + -X POST https://www.feadulta.com/wp-json/wp/v2/posts \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Carta de la semana — 29 de junio", + "content": "

…cuerpo de la carta con sus secciones y enlaces…

", + "status": "future", + "date": "2026-06-29T08:00:00", + "categories": [6, 21] + }' +``` + +### Paso 4 — Rotar la carta anterior (IMPORTANTE, manual) + +Al entrar una carta nueva en la categoría **6**, hay que **degradar la anterior** para que la +categoría "Carta de la semana" contenga **solo una** carta. Esto es solo mover el marcador de +**estado** (6 → 22 → ninguno): la categoría **21** ("Otras semanas") **no se toca en la +rotación**, porque es acumulativa y ya la lleva cada carta desde que se publicó (Paso 3). Nunca +se quita la 21 a una carta. + +1. A la carta que **dejaba** de ser actual: quitarle la categoría **6** y ponerle la **22** + ("La semana pasada"). Mantiene la **21** que ya tenía. +2. A la que estaba en **22**: quitarle la **22** sin más. Mantiene la **21** que ya tenía — + así queda solo en el histórico "Otras semanas", visible igual que todas las demás. + +Vía API se actualiza el array `categories` del post (sustituye al anterior, así que hay que +**incluir siempre la 21** en el array nuevo o se perdería): + +```bash +# 1. La carta que deja de ser actual: 6 → 22 (conserva 21) +curl -s -u "USUARIO:APP_PASSWORD" \ + -X POST https://www.feadulta.com/wp-json/wp/v2/posts/ID_DE_LA_CARTA_VIEJA \ + -H "Content-Type: application/json" \ + -d '{"categories": [22, 21]}' + +# 2. La que estaba en 22: se queda solo con 21 (y las que no sean de estado, p.ej. 71) +curl -s -u "USUARIO:APP_PASSWORD" \ + -X POST https://www.feadulta.com/wp-json/wp/v2/posts/ID_DE_LA_CARTA_MAS_VIEJA \ + -H "Content-Type: application/json" \ + -d '{"categories": [21]}' +``` + +> ⚠️ El endpoint `POST /wp/v2/posts/ID` con `categories` **sustituye** el array completo, no +> añade. Antes de rotar, comprobar con `GET /wp-json/wp/v2/posts/ID_DE_LA_CARTA?_fields=categories` +> qué categorías tiene ya el post (p. ej. si tiene además la 71 "Feadulta") e incluirlas todas +> en el array nuevo — si no, se pierden categorías sin querer (esto pasó en la carta 733/734, +> ver issue #159 y #161). +> +> Si se programa la carta nueva con `future`, esta rotación puede hacerse el mismo día en que +> se publique. Si surge duda sobre qué carta está en qué categoría, consultar: +> `GET /wp-json/wp/v2/posts?categories=6` (debe devolver solo una). + +--- + +## 5. Formato de la carta para que la portada la "entienda" + +La portada parsea el HTML de la carta buscando **encabezados con estas frases** (no distingue +mayúsculas/acentos, pero el texto debe coincidir). Cada encabezado abre una sección; todos los +enlaces que vayan **después** de ese encabezado (hasta el siguiente) se muestran en el bloque +de portada indicado: + +| Frase del encabezado en la carta | Bloque de portada | +|----------------------------------|-------------------| +| **Evangelio y comentarios al Evangelio** | Evangelio | +| **Artículos seleccionados para la semana** | Artículos de la semana | +| **Para unas eucaristías más participativas y actuales** | Eucaristía | +| **Material multimedia** | Multimedia | +| **Escuela EFFA** | EFFA | + +**Reglas:** +- Los encabezados se suelen marcar en rojo/negrita (es el estilo histórico), pero lo que + importa es que **el texto coincida** con la frase de la tabla. +- Los enlaces dentro de cada sección deben apuntar a **artículos que existan** en el sitio + (las URLs del Paso 1). Enlaces a páginas externas se ignoran. +- Si una sección no tiene enlaces, ese bloque de la portada queda vacío. +- **"Noticias de alcance" NO se enlaza a mano en el cuerpo de la carta.** Si la semana + incluye una noticia de este tipo, el artículo va categorizado en la categoría **41** + ("Noticias de alcance") y **eso basta**: la portada tiene un bloque de footer aparte que + se alimenta solo de esa categoría. Si además se enlaza esa noticia dentro de "Artículos + seleccionados para la semana", **sale duplicada** en la portada (pasó en la carta 734, + ver issue #159). + +**Ejemplo mínimo de cuerpo de carta:** + +```html +

Texto editorial de introducción de la semana…

+ +

Evangelio y comentarios al Evangelio

+
+ +

Artículos seleccionados para la semana

+ + +

Para unas eucaristías más participativas y actuales

+ + +

Material multimedia

+ +``` + +> La portada cachea el resultado **15 minutos**. Al guardar/editar la carta se refresca sola, +> pero si un cambio no se ve al momento, esperar unos minutos. + +--- + +## 6. Qué es automático y qué hay que tocar a mano + +### Automático — NO hay que hacer nada + +- **Portada (home):** se construye sola a partir de la carta vigente (la más reciente en la + categoría 6) y sus secciones (§5). +- **Página "Carta de la semana"** (`/carta-de-la-semana/`): redirige automáticamente a la + carta más reciente de la categoría 6. +- **Página "La semana pasada"** (`/la-semana-pasada/`): redirige a la carta más reciente de la + categoría 22. +- **Menús, avatares de autores, multiidioma, etc.:** gestionados por el tema/plugins. +- **Traducciones (EN/FR/IT/PT) y audio (TTS):** las genera el proceso del servidor (§7). + +### Manual — lo que hace Inma / su asistente + +- Crear los **artículos** de la semana (§4, Paso 1). +- Componer y **publicar/programar la carta** (§4, Pasos 2-3). +- **Rotar** la carta anterior de categoría (§4, Paso 4). +- Subir **imagen destacada** del artículo si se quiere (campo `featured_media` con el ID de + un adjunto previamente subido a la biblioteca de medios). + +--- + +## 7. Traducción y audio (lado servidor — ⚠️ hoy es MANUAL, no automático) + +**Corregido 2026-07-12: esto todavía NO es automático.** El cron que traduciría y generaría +audio solo al publicar (issue [#23](https://gitea.feadulta.com/rafa/feadulta/issues/23)) sigue +sin implementar — es una propuesta abierta, no algo que ya corra. Versiones anteriores de esta +guía decían "hay (o habrá) un proceso programado" dando a entender que ya estaba activo o a +punto; no lo está. **Inma NO tiene que traducir ni generar audio ella misma**, pero sí tiene que +**pedirlo** — no llega solo. + +**Cómo pedirlo hoy:** escribir al grupo de WhatsApp mencionando "Hermes" (o por Telegram), +indicando la carta/artículo. Hermes ejecuta los scripts correspondientes en el servidor de Rafa. +Ver el runbook completo (motores de traducción, voces TTS por autor, tiempos, qué hacer si +Hermes no responde) en `docs/guia-tts-traduccion-inma.md`. + +**Por eso sigue siendo importante subir siempre el contenido en español** — la traducción parte +siempre del ES, pero hay que pedirla, no asumir que "ya llegará". Si una carta urgente necesita +traducción inmediata, avisar a Rafa directamente además de pedírselo a Hermes. + +> *Este punto es responsabilidad de Rafa (infraestructura). Se incluye aquí para que el +> asistente de Inma sepa que el trabajo de traducir/locutar no lo tiene que hacer él mismo, +> pero sí que tiene que solicitarlo activamente.* + +--- + +## 8. Resumen rápido (checklist por carta) + +- [ ] Crear cada artículo de la semana y **publicarlo** (no dejarlo en `draft`/`[PRUEBA]`). +- [ ] Guardar la URL (`link`) de cada artículo **después** de publicarlo, no antes (el slug + puede cambiar por colisión con otro post del mismo título). +- [ ] Asignar `_carta_id` a cada artículo con el endpoint de §1.5 (`fea/v1/carta-id/{id}`). +- [ ] Componer la carta en HTML con los **encabezados exactos** de §5 y los enlaces a esos artículos. +- [ ] Publicar o programar la carta en las categorías **6 y 21** (§3.1, §4 Paso 3). +- [ ] Rotar la carta anterior: quitar 6, poner 22 (conservando su 21). A la que estaba en 22, + quitarle solo la 22 (conservando su 21 — nunca se quita la 21 a nadie, §4 Paso 4). +- [ ] Comprobar que la portada muestra las secciones (esperar hasta 15 min si hace falta). +- [ ] No traducir ni generar audio a mano — pero SÍ pedirlo a Hermes (WhatsApp/Telegram, ver §7 y `docs/guia-tts-traduccion-inma.md`). No es automático todavía. + +--- + +## 9. Referencia rápida de la API + +| Acción | Método y endpoint | +|--------|-------------------| +| Probar credencial | `GET /wp-json/wp/v2/users/me` | +| Listar categorías | `GET /wp-json/wp/v2/categories?per_page=100` | +| Crear artículo/carta | `POST /wp-json/wp/v2/posts` | +| Editar artículo/carta | `POST /wp-json/wp/v2/posts/{id}` | +| Ver carta vigente | `GET /wp-json/wp/v2/posts?categories=6` | +| Subir imagen | `POST /wp-json/wp/v2/media` (cabecera `Content-Disposition`) | +| Leer `_carta_id` de un artículo | `GET /wp-json/fea/v1/carta-id/{id}` | +| Asignar `_carta_id` a un artículo | `POST /wp-json/fea/v1/carta-id/{id}` con body `{"carta_id": N}` | +| Borrar `_carta_id` de un artículo | `DELETE /wp-json/fea/v1/carta-id/{id}` | + +Campos útiles del post: `title`, `content` (HTML), `status` (`draft`/`publish`/`future`), +`date` (ISO 8601 para programar), `categories` (array de IDs), `featured_media` (ID de adjunto). diff --git a/docs/guia-tts-traduccion-inma.md b/docs/guia-tts-traduccion-inma.md new file mode 100644 index 0000000..dfdaa18 --- /dev/null +++ b/docs/guia-tts-traduccion-inma.md @@ -0,0 +1,121 @@ +# Guía de traducción y audio (TTS) para Inma / Mixbot — feadulta.com + +> **Para quién es este documento:** para Inma y su asistente (Mixbot / Cowork), y como +> referencia para Hermes cuando se le pide que traduzca o locute un artículo/carta. +> +> **Estado real a 2026-07-12 (importante, corrige la guía de publicación §7 de versiones +> anteriores): esto NO es automático.** No hay ningún cron corriendo hoy que traduzca o genere +> audio solo al publicar — esa automatización es la propuesta abierta +> [issue #23](https://gitea.feadulta.com/rafa/feadulta/issues/23), sin implementar. Todo lo de +> abajo es un proceso que **hay que pedir**, hoy solo ejecutable en el servidor/PC de Rafa. + +--- + +## 1. Quién puede hacer qué, hoy + +| Tarea | ¿Quién puede hacerla sin Rafa presente? | +|---|---| +| Publicar carta/artículos en español | **Sí, Inma/Mixbot solos** — API REST ya funciona (ver `docs/guia-publicacion-carta-inma.md`). No depende del PC de Rafa. | +| Pedir traducción o TTS | **Solo indirectamente**: hay que pedírselo a **Hermes** (WhatsApp/Telegram). Los scripts que traducen y locutan viven únicamente en el PC/servidor de Rafa (Docker local + credenciales locales) — Mixbot no tiene acceso directo a ellos. | +| Traducir/locutar si Hermes tampoco está disponible | **Hoy, no.** Es la limitación real que hay que conocer: si el PC de Rafa está apagado o Hermes está caído, ni Inma ni Mixbot pueden disparar esto por su cuenta. Ver §5 (qué falta para que esto no dependa de Hermes). | + +## 2. Cómo pedir una traducción o un audio (mientras Hermes esté disponible) + +Escribir al grupo de WhatsApp `Feadulta_webmaster` mencionando "Hermes" (o por Telegram), +indicando qué carta/artículo (ID de WordPress o título+fecha si no se tiene el ID) y qué se +necesita: traducción, audio, o ambos. Ejemplos: + +> "Hermes, tradúceme la carta 54XXX a los 4 idiomas" +> "Hermes, genera el audio de los artículos de la carta de esta semana" + +Hermes ejecuta los scripts de abajo en el servidor de Rafa. No hace falta que Inma/Mixbot sepan +los nombres de los scripts ni los IDs internos — es información para cuando Hermes (o Rafa) +necesite el detalle técnico. + +## 3. Traducción — motores disponibles + +`scripts/translate_post.py` (repo `joomla-migration`) soporta tres motores via `FEA_ENGINE`: + +| Motor | Coste | Cuándo usarlo | +|---|---|---| +| **`gemma`** (por defecto) | Gratis (modelo local, LM Studio en el PC de Rafa) | Opción por defecto. Requiere que el PC/GPU de Rafa esté encendido. | +| **`minimax`** | De pago, acotado (misma cuenta que el TTS) | Alternativa cuando Gemma no está disponible o la calidad no basta. | +| **`haiku`** | ⚠️ **De pago vía API directa de Anthropic** (no es cuota de sesión) | **No usar por defecto ni de forma autónoma.** Choca con la política de no gastar API de pago sin que Rafa confirme cada vez. Reservado para cuando Rafa lo ejecuta él mismo o da autorización puntual. | + +Comando (lo ejecuta Hermes o Rafa, no Inma/Mixbot directamente): +```bash +cd /home/rafa/joomla-migration +python3 scripts/translate_post.py --carta --langs en,fr,it,pt --status draft +# o para un solo artículo: +python3 scripts/translate_post.py --post-id --langs en,fr,it,pt --status draft +``` +`--status draft` dejar en borrador para revisión; `--status publish` publica directo. Tras +traducir, hace falta el paso de enlaces internos (`scripts/fix_carta_joomla_links.php`) y, si se +publica, degradar la carta anterior (`scripts/demote_old_cartasemana.php`) — Hermes ya conoce +este flujo (ver skill `feadulta-webmaster`, `references/procedures.md`). + +## 4. Audio (TTS) — voces por autor + +`scripts/minimax_tts.py` + `scripts/tts_produce.py` (genera y escribe en WP local) + +`scripts/sync_audio_to_prod.py` (sube a prod, soporta `--rollback` para deshacer). Modelo MiniMax +`speech-2.8-hd`. + +**Voz por defecto:** `NicoFeadulta2026` (todos los autores sin voz clonada). + +**Voces clonadas por autor** (issue #152 — solo estos 4 autores usan su propia voz, el resto cae +a Nico): + +| Autor | WP user_id | voice_id | +|---|---|---| +| Fray Marcos | 382 | `FrayMarcosFeadulta2026` | +| José Antonio Pagola | 383 | `PagolaFeadulta2026` | +| José Luis Sicre | 774 | `SicreFeadulta2026` | +| José Arregi | 386 | `ArregiFeadulta2026` | + +Añadir un autor nuevo a esta lista requiere clonar su voz primero (grabación limpia 2-5 min, sin +música/ruido de fondo — verificar con espectrograma antes de clonar, ver memoria +`feadulta-tts-voz-fraymarcos-202607` para el procedimiento y los descartes por música colada) y +añadirlo a `AUTHOR_VOICES` en `scripts/minimax_tts.py`. Esto sí requiere que Rafa (o alguien con +acceso al repo y a MiniMax) lo haga — no es autoservicio para Inma/Mixbot hoy. + +**Generar audio de una carta concreta** (por defecto `tts_produce.py` procesa una cola larga de +cartas pendientes — para priorizar una carta concreta, sobreescribir la cola): +```bash +cd /home/rafa/joomla-migration +FEA_TTS_CARTAS="" python3 scripts/tts_produce.py +``` +Reanudable (no repite lo ya hecho, meta `fea_audio_done`) y con freno automático si la cuota de +MiniMax se agota (para tras fallos seguidos, no se queda colgado). + +**Publicar el audio en prod** (el paso anterior solo escribe en el WordPress local): +```bash +python3 scripts/sync_audio_to_prod.py --carta +# deshacer si algo suena mal: +python3 scripts/sync_audio_to_prod.py --rollback --carta +``` +Runbook de rollback ya documentado para que Hermes lo ejecute sin Rafa presente: issue #163. + +## 5. La API key de MiniMax — decisión pendiente (de Rafa, no resuelta en este documento) + +Hoy la key vive en un fichero local de Rafa, usada para TTS (y podría usarse para traducción +`FEA_ENGINE=minimax`). Para que Inma/Mixbot puedan disparar esto sin pasar por Hermes, harían +falta tanto acceso a esta key como acceso al entorno donde corren los scripts (Docker local del +PC de Rafa) — hoy ninguna de las dos cosas es cierta. Ver el issue maestro +[#172](https://gitea.feadulta.com/rafa/feadulta/issues/172) para las opciones que se están +valorando (key separada para Inma, gestor de secretos compartido, o mantener todo detrás de +Hermes). Mientras no se decida, la vía real es §2: pedírselo a Hermes. + +## 6. Qué falta para que esto sea de verdad independiente de Hermes/Rafa + +Siendo honestos: hoy, si el PC de Rafa está apagado (viaje, avería, lo que sea) y Hermes no +responde, **no hay forma de que Inma/Mixbot generen traducción o audio por su cuenta** — los +scripts y el WordPress local que usan como paso intermedio solo existen ahí. Para que esto +cambiara de verdad haría falta uno de: +- Mover el pipeline de traducción/TTS a un sitio alcanzable por Mixbot directamente (ej. correr + contra prod en vez de contra el WordPress local, y alojar los scripts en un servidor + accesible, no en el PC personal de Rafa). +- O implementar de una vez el cron automático (issue #23) para que ni siquiera haga falta + pedirlo — se dispara solo al publicar en español. + +Ninguna de las dos está hecha. Documentado aquí para que la decisión de priorizarlo (o no) sea +consciente, no un descuido. diff --git a/docs/handoff-carta-46956-2026-06-17.md b/docs/handoff-carta-46956-2026-06-17.md new file mode 100644 index 0000000..a70c77d --- /dev/null +++ b/docs/handoff-carta-46956-2026-06-17.md @@ -0,0 +1,86 @@ +# Handoff — Carta 46956 «Entre todos» (sesión 2026-06-17) + +Estado para que **Codex** continúe. Se hizo TODO en **local** (Docker `wordpress-web`/`wordpress-mysql`). **Nada está en prod todavía.** El despliegue a prod quedó **pendiente y SIN hacer** (Rafa quiere ir por partes). + +Repo: `/home/rafa/joomla-migration` (= remoto Gitea `rafa/feadulta`). Ver también wiki «Ciclo carta nueva» y memoria del ciclo. + +--- + +## 1. Qué se hizo en LOCAL (todo verificado) + +### Carta y artículos +- **Carta 46956 «Entre todos»** (ES). Estaba en estado `future` por desfase de zona horaria (server US detrás de Madrid) → **publicada** (`post_status=publish`, fecha 2026-06-18). **Issue #87 (cerrado).** +- **19 artículos** con `_carta_id=46956` (IDs 46937–46955). De ellos **15 son ES** (46937–46951) y 4 ya venían en otros idiomas desde Joomla. +- **Cluster multiidioma** 46951–46955 = el MISMO artículo en 5 idiomas; **enlazado** en Polylang: `{es:46951, fr:46952, en:46953, it:46954, pt:46955}` (no se tradujo, ya estaba). + +### Traducciones (motor Haiku) +- **Carta + 14 artículos ES → EN/FR/IT/PT = 60 posts** (IDs **46959–47018**), `publish`. +- Motor: **`FEA_ENGINE=haiku`** (nuevo flag en `scripts/translate_post.py`) → usa Claude Haiku 4.5 vía `translate_haiku.py`. API key en `/home/rafa/portfolio-tracker/.env`. Coste ~0.6–0.8 €/carta. + - Comando: `FEA_ENGINE=haiku python3 scripts/translate_post.py --carta 46956 --langs en,fr,it,pt --status draft` (luego publicado). + +### Evangelio / lecturas bíblicas (**issue #88**) +- El pasaje **ES `MATEO 10, 26-33` (post 2682)** solo existía en ES. Política: **DESCARGAR la Biblia oficial, NO traducir con LLM**. +- Descargado de **bolls.life** y creados 4 posts (IDs **47079–47082**), enlazados Polylang a 2682, cat "Lecturas bíblicas" mapeada, `publish`: + - EN=**Douay-Rheims** (católica), PT=**CNBB** (católica), IT=**Nuova Riveduta 2006** (única moderna IT), FR=**Bible du Semeur** (no hay católica libre en bolls). +- Scripts: `scripts/fetch_lectura_bolls.py` (descarga) + `scripts/create_lecturas.php` (crea posts). + +### Enlaces internos de las cartas +Las cartas traían enlaces Joomla legacy `es/buscadoravanzado/item/-.html` (rotos) y luego enlaces al pasaje ES. Arreglado en 2 pasos: +1. `scripts/fix_carta_joomla_links.php` — mapea `item/` por meta `_fgj2wp_old_k2_id` → permalink WP del artículo en el idioma de cada carta. +2. `scripts/repoint_carta_links.php` — repunta enlaces ya-permalink que apuntaban al ES → a la traducción del idioma de la carta (p.ej. el evangelio). + - `APPLY=1 CARTA=46956 docker exec ... php /tmp/