- sync_translations_to_prod.py / sync_audio_to_prod.py / sync_carta_from_prod.py:
migran de FEA_PROD_HOST/PASS (CDMON, password) a FEA_PROD_SSH_HOST/PASS +
FEA_PROD_DOCKER_CONTAINER (Hetzner/Coolify, auth por clave), con wrapping
docker exec y el fix del bug de redirecciones (wc -c/cat) resuelto en el host
en vez de dentro del contenedor.
- fea_translate_helper.php: subcomando clone_new para clonar en ID local nuevo
cuando el ID de prod ya está ocupado localmente.
- translate_post.py: --dry-run.
- tts_produce.py: --allow-default-voice (voz Nico solo si se permite
explícitamente para autores sin voz clonada) + fix voice_for_author.
- minimax_tts.py: parámetro speed en t2a/_synth_chunk.
- mirror-antiguo/deploy/nginx-mirror.conf: redirects de URLs históricas de
catálogo y vista imprimible EFFA.
- Importaciones humanas de Pagola (carta 738) + scripts/manifest de la
fase A Enrique, y release_issue181_prod.sh / cdmon-retire-fewp1.sh.
- .gitignore: excluye docs/backups/ (dumps SQL, mismo motivo que backups/*).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
El informe avisaba de "ninguna ventana dejo rastro en 24 h" los miercoles y
jueves, que son dias de carta y no tienen cron por diseno. Ahora lee el crontab,
cuenta cuantas ventanas TENIAN que correr y solo avisa si de verdad tocaba.
Si Rafa cambia los dias del cron, el informe le sigue solo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hermes bloquea los scripts cuyo path resuelve fuera de ~/.hermes/scripts y
resuelve los symlinks antes de comprobarlo, asi que el enlace que habia puesto
al script del repo hacia fallar el job diario. Sustituido por un wrapper fino
que lo llama por subproceso, igual que feadulta_ga4_daily.py.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un tamano fijo de tanda no maximiza: deja la ventana de 5h a medias cuando esta
libre y no cabe cuando esta medio usada. Ahora cada corrida mide la cuota y
calcula cuantos audios caben.
Coste medido (2 tandas de 10 el 2-ago): 4,4 puntos de la ventana de 5h y 0,4 de
la semanal por audio. Con la ventana limpia salen 20, no 10.
Pero la semanal NO se gasta a tope en cada ventana: se REPARTE entre las que
quedan hasta su reset. Llenar cada ventana de 5h al 90% son ~8 puntos de
semanal, y hay 20 ventanas activas por semana = 160 puntos para un presupuesto
de 85. Sin reparto, domingo y lunes se lo comen y el fin de semana se queda a
cero (simulado: se seca en la ventana 12 de 20). Con reparto salen ~10 por
ventana, 204 audios/semana, y la semanal acaba justo en el 85% al llegar el
reset. En la ultima ventana el reparto vale todo lo que sobre, asi que tampoco
queda cuota sin usar: eso sustituye al apano anterior de "apurar en las ultimas
12h", que se retira.
El gate binario tambien desaparece: si otro esta usando MiniMax, en vez de
saltarse la ventana entera se hace una tanda pequena con lo que quede.
Validado en vivo: "reset semanal en 166h, 20 ventanas por delante · Caben: 10
por la de 5h, 10 por el reparto semanal".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gate: `int(v or 100)` trataba `five_h_pct = 0.0` como ausencia de dato y
devolvia 100, o sea que la tanda abortaba justo cuando la ventana de 5h estaba
entera libre. Cazado en la primera ventana real. Ahora se distingue 0.0 de None.
Reporte diario (F2): scripts/fea_tts_backlog_report.py, solo lectura. Cuenta
los audios de las ultimas 24h por autor (atribuidos por fea_audio_voice), lo que
queda por autor, la cuota de MiniMax y las ventanas ejecutadas/saltadas. Avisa
aparte si en 24h no hubo NI ventana ejecutada NI saltada, que es el sintoma de
que el cron no llego a correr (fue lo que paso el 2-ago con el bit +x).
Vive en el repo y ~/.hermes/scripts/ lo ve por symlink: sin copia duplicada que
se desincronice. Job: `hermes cron feadulta-tts-backlog-daily`, 07:30, no-agent,
deliver origin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Se subio sin el bit +x y cron fallaba en silencio (permission denied, sin log
y sin MTA que avise): 4 ventanas perdidas el 2-ago. La entrada de crontab pasa
a invocarlo con 'bash ...' para que no vuelva a depender del modo del fichero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aprovechar la cuota ociosa de MiniMax para locutar el backlog historico,
empezando por Fray Marcos (2026 pendientes + 2025 = 86 articulos).
- fea_post_io.php: accion `listpending <autor> <desde> <hasta> <limite> [voz]`.
Devuelve los posts ES publicados de un autor que no tienen audio, mas
recientes primero. Si se pasa la voz clonada del autor, tambien salen los
locutados con otra voz, para rehacerlos.
Esa consulta ES la idempotencia: no hay fichero de estado que mantener, lo ya
locutado deja de salir solo. Relanzar o solapar no duplica ni gasta cuota.
- tts_produce.py: flags --autor/--desde/--hasta/--max/--dry-run. Sin flags, el
modo cartas de la carta semanal no cambia (retrocompatible).
--max acota la tanda a la ventana de 5h.
- tts_backlog_cron.sh: wrapper de cron con flock y gate de cuota MiniMax
(semanal >=85% o ventana de 5h >=55% -> se salta la ventana, para no comerle
la cuota a la carta semanal). Cola configurable por entorno.
Crontab: 0 */5 * * 1,5,6,0 (lunes, viernes, sabado y domingo).
SOLO LOCAL: no toca produccion. Publicar el backlog en prod
(sync_audio_to_prod.py) queda bloqueado hasta despues del cutover a
Hetzner (#180).
De paso entran al historial los cambios de tts_produce.py que estaban sin
commitear desde el 24-jul (argparse con --ids/--cartas y QUOTA_OR_RATE_ERRORS
para no reintentar ante rc 2056/1039).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ga4_report.py vivia solo en el checkout separado feadulta-git, que apunta al Gitea
archivado y cuyo historial no tiene relacion con este repo. La copia canonica pasa a
estar aqui. El entorno de ejecucion (.venv/ y .secrets/ con el cliente OAuth y el token)
se queda en feadulta-git y nunca ha estado versionado -- por eso la doc sigue usando
rutas absolutas alli.
Ademas, el filtro que faltaba: la propiedad G-6RT9ZRS4LW recoge varios hostnames a la vez
-- el WordPress vivo (www.feadulta.com) y el archivo estatico del Joomla
(antiguo.feadulta.com, que lleva el mismo tag dentro del HTML capturado), mas restos
(wp-nuevo, bar). Cualquier informe sin filtro los sumaba en una cifra sin significado.
- preset "hosts": desglose de trafico por hostName.
- --host: filtro exacto, lista separada por comas, se combina con --page-path-regex en un
andGroup.
- --host-not: negacion.
Refs #180, #187
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Los scripts del mirror (00-90) vivian solo en el disco. Van al repo; los datos que
generan no (16 GB entre crawl, snapshot del origen y Joomla restaurado) -> .gitignore.
Nuevo 91-repone-assets404.sh: repone los ficheros que el crawl no capturo porque se
referencian SOLO desde CSS y el crawler seguia enlaces HTML (system.css, los fondos de
fe_adulta_1, ratingstars.gif de K2). Salian como 404 en los logs de nginx del Hetzner.
Descarga por HTTP desde el Joomla local aislado, nunca del filesystem -- mismo principio
que el crawl, para no arrastrar los .php comprometidos del #183 -- y escanea PHP embebido
antes de copiar a site/.
Resultado sobre las 286 rutas unicas con 404 del log: 196 repuestas y verificadas en
produccion (196/196 en 200 tras el rsync), 82 que dan 301->404 tambien en el origen (ya
estaban rotas en la web original) y 8 rutas basura /%22/... de HTML mal formado.
Refs #180
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Este repo local tenia origin apuntando al Gitea local (localhost:3000), que Rafa
declaro archivado el 2026-06-28 (commit 962f33a, desarrollo movido a
gitea.feadulta.com). Ese memo nunca llego a este checkout: main quedo congelado
y todo el trabajo real de las ultimas 3+ semanas se fue commiteando solo en la
rama fix/multiidioma-portada-132 (ya fusionada a main sin perdida, commit
2504666), mientras que ademas se acumulaban 78 cambios sin commitear en el
working tree que nunca llegaron a NINGUN historial de git.
Este commit consolida esos cambios sueltos: TTS multi-voz (tts_*.py), scripts
de traduccion (translate_haiku.py, pretranslate_en_haiku.py, sync_translations_to_prod.py),
mu-plugins nuevos desplegados a prod (fea-beta-feedback, fea-cloudflare-realip,
fea-legacy-redirect, fea-gsc-verification, fea-support-campaign, fea-ui, etc.),
scripts de mantenimiento de enlaces/cartas, capturas E2E (tools/e2e/shot_*.cjs)
y documentacion de sesiones recientes.
Excluido deliberadamente (no es codigo versionable): tts-voices/ (3.3GB de
muestras de audio para clonacion de voz, anadido a .gitignore), logs/ (logs de
ejecucion, anadido a .gitignore), y 2 ficheros vacios accidentales + 2 copias
duplicadas sueltas en la raiz que ya existen en su ubicacion correcta.
Tres bugs corregidos (issue gitea.feadulta.com#178):
- OR implicito en Boolean Mode devolvia casi todo el sitio (~24.700/24.780 posts)
para cualquier busqueda de varias palabras; ahora cada palabra es AND obligatorio (+).
- Paginas-indice residuales K2 (categorias 37/38, listados por autor) puntuaban
altisimo en relevancia por su tamano y tapaban el articulo real; excluidas de
resultados de busqueda.
- Palabras <3 caracteres forzadas como termino obligatorio rompian el calculo de
relevancia (score 0 en filas que si coincidian); se descartan antes de construir
la query.
- Anadido soporte real de frase exacta: termino entre comillas dobles se envia a
MySQL Boolean Mode como frase, en vez de descartarse silenciosamente.
Con Mixbot publicando la carta ES directamente en prod, el WP local dejaba de
recibir el contenido nuevo y se desalineaba (visto con la carta 735, ID 54495
apuntando a otro post en local). El nuevo script descubre el cluster de la
carta parseando sus propios enlaces (fea_parse_carta_sections, sin depender de
_carta_id) y clona cada post a local preservando ID, reutilizando
fea_translate_helper.php en la dirección contraria a sync_translations_to_prod.py.
Añade el subcomando 'carta_sections' al helper (aditivo, no toca el resto).
POST /wp-json/fea/v1/crear-autor — permite a Mixbot/Inma dar de alta
autores nuevos sin depender de intervención manual, sin necesitar el
permiso sensible create_users de WP. Rol 'author' fijo en el código
(no lo decide quien llama). Auth: is_user_logged_in() + edit_others_posts
(nivel Editor+, ya lo cumple su cuenta actual). Idempotente por slug:
si ya existe, devuelve el usuario existente en vez de duplicar/errorar.
Ver gitea.feadulta.com#166 (protocolo entre Claudes, opción B elegida
sobre A porque WP no permite nativamente restringir create_users a un
solo rol, y sobre C porque evita el salto humano/agente en el bucle).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fea-audio-player.php: el <audio> lleva data-post-id/data-voice; listener
en wp_footer dispara gtag('event','audio_play', {post_id, voice}) en el
primer play (no en cada resume).
fea-share.php: se sube también al repo (nunca se había comiteado). El
botón de Facebook lleva data-fea-share-track="facebook" + listener que
dispara gtag('event','share_click', {method, post_id}).
Ambos reusan el dataLayer/gtag que ya inicializa fea-analytics.php, sin
añadir JS de terceros. Verificado en local con Playwright (evento se
dispara correctamente en ambos casos).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Voz clonada por autor (Fray Marcos, Pagola, Sicre, Arregi) en vez de una
única voz genérica para todo el TTS. minimax_tts.py resuelve la voz por
post_author (AUTHOR_VOICES) y cae a Nico si no hay clon; fea_post_io.php
propaga autor/voz; tts_produce.py y sync_audio_to_prod.py (local→prod,
workaround glibc rota del servidor) usan la voz correcta en vez de forzar
Nico.
Incluye fix de trim_after_author_signature: la heurística de "firma final
del autor" confundía encabezados litúrgicos iniciales (p.ej. "CORPUS (A)")
con la firma, truncando el texto a sintetizar a un par de palabras. Ahora
solo cuenta como firma un párrafo con >200 caracteres de contenido real
por delante.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reports internal links whose language prefix/slug doesn't match the target
post's actual language (SIN_TRADUCIR / MISMATCH), so link bugs like the
silence/silence-2 cross-language mixup on carta 734 get caught before they
reach prod.
Replica el «Buscador avanzado» del Joomla viejo sin servicios externos:
- fea-search-fulltext.php: motor MySQL FULLTEXT (MATCH AGAINST, Boolean Mode,
orden por relevancia); comprueba que existe el índice fea_ft y degrada al
buscador nativo si no, sin romper.
- fea-search-advanced.php: formulario con filtros por autor, categoría
(lista curada de secciones), cita bíblica (meta _cita_evangelio por prefijo)
y rango de fechas; pre_get_posts, chips de filtros, byline en tarjetas,
i18n es/en/fr/it/pt. Excluye pseudo-autores «Testamento».
- create_buscar_page.php: crea la página /buscar + traducciones (idempotente).
- tools/e2e: suite Playwright de verificación.
Desplegado y verificado en prod (índice fea_ft + página /buscar + traducciones).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plan ejecutado por Sonnet: replica el «Buscador avanzado» K2 con WP nativo
+ MySQL FULLTEXT (palabra, autor, categoría, cita bíblica, fecha).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plan ejecutable (para Sonnet) de la fase 2: arquitectura, schema, indexador,
UI InstantSearch con faceta de autor, seguridad de claves y verificación.
La decisión de dónde corre Typesense en producción queda pendiente; todo lo
demás se puede construir y validar en local.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cuando created_by de Joomla no está en user_map, post_author cae a 1
('Fe Adulta'). Se añade un aviso por consola apuntando a fix_k2_authors.php
para corregirlo tras el delta. (El resto de cambios del fichero, de sesiones
previas, quedan sin commitear.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Guía para el asistente de Inma: conexión por REST API, modelo
carta->portada, categorías, flujo de publicación y qué es automático.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- fea-search.php: barra de búsqueda visible en móvil (donde el menú
colapsa y oculta el buscador del menú); multiidioma por Polylang.
- set_search_template.php: template FSE 'search' con resultados en
rejilla de tarjetas (reutiliza el layout de archive #63) en vez del
patrón del tema que los mostraba 'todos seguidos'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Artículos K2 cuyo created_by de Joomla no tenía usuario WP caían a
post_author=1 ('Fe Adulta'). Herramientas:
- fix_k2_authors.php: crea el usuario (nombre real de ew4r_users) y
reasigna post_author (idempotente, dry-run/APPLY).
- gen_avatars_initials.py: recrea el generador de avatares de iniciales
del #62 (paleta determinista + DejaVuSans-Bold).
- import_avatars_143.php: alta de attachment + foto_perfil con backup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El reproductor se inyecta en el grupo flex del autor (wp-block-group FSE).
El flex-wrap apuntaba a .fea-byline (solo existe en Astra), por lo que en
móvil vertical el <audio> se comprimía y no mostraba controles. Se dirige
el wrap también al grupo FSE del byline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mapa de 73 libros bíblicos ES->{en,fr,it,pt} y reemplazo determinista del
token de libro en cada cita biblica del post_title (inicio, compuestos '/',
ordinales '2 Timoteo', prefijos de fiesta). Idempotente, title-only,
cotejo insensible a acentos. Aplicado: local 2103, prod wp-nuevo 1605.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pipeline de los 23 posts de Mardones (anterior/*.htm → posts), repunte de
presentaciones de Galarreta, y scripts de sync a prod de las 5 issues del día.
Incluye retoques de fecha (2012-10-10) y eliminación de target=_blank en el
homenaje 17990.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fea_avatar_url: para posts no-ES resuelve el título del original ES (Polylang),
donde sí se reconoce el libro/evangelista. Así Matteo/Geremia/Romani heredan el
mismo icono que Mateo(ángel)/Jeremías/Romanos(biblia) en vez de identicon random.
También incluye el guard de Multimedia solo-ES (#135#1).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- autores_biblicos.php: 2ª pasada directa de reasignación de autor.
- quitar_multimedia.php: #4 como fichero reutilizable.
- trad_cats.php: quitado require_once con ruta local (wp eval-file ya carga WP).
Sync a prod ejecutado y verificado: código + 440 lecturas + grupo D + 8 usuarios
bíblicos + reasignación de autores y categorías. 0 fatales en las 5 portadas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- trad_cats.php: traduce categorías estructurales (Nuevo/Antiguo Testamento, etc.)
- fix_catnames.php: corrige nombres de términos traducidos
- autores_biblicos.php: crea usuarios NT/AT por idioma y reasigna autor de lecturas
- reasign_cats.php: reasigna a cada post no-ES las categorías del ES traducidas a
su idioma (crea término espejo si falta). Idempotente/resumible (_cats_reasignadas).
7977 posts procesados, 0 con categoría en idioma incorrecto.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
build_lectionary_index.py construye un índice por referencia bíblica
descargando un ciclo litúrgico de evangelizo.ws (combina idiomas por
reading_code, robusto a fiestas trasladadas por país). lecturas_apply.py
casa las lecturas ES por referencia y apply_lecturas_wp.php crea+asocia
las traducciones en Polylang. 440/501 lecturas cubiertas y publicadas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Descarga el texto litúrgico católico oficial en es/en/fr/it/pt por fecha
litúrgica (leccionario), para traducir las lecturas bíblicas de las cartas
sin LLM. Parsea las secciones (1ª lectura, salmo, 2ª lectura, evangelio).
Grupo C de #132.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug 2: fea_carta_section_posts parsea SIEMPRE la carta ES (el parser solo
reconoce cabeceras en español) y mapea cada artículo a su traducción
Polylang del idioma de la portada. Antes EN/FR/IT/PT caían al fallback
'últimos por categoría' y mostraban artículos incorrectos.
Bug 1: el enlace 'evangelio del día' resuelve la traducción del idioma
actual con pll_get_post (get_posts usa suppress_filters=true y devolvía
siempre la página ES).
Desplegado y verificado en prod (wp-nuevo) 2026-06-22.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El enumerador de URLs no se había commiteado y se perdió. Recuperado
reproduciendo la composición de la corrida previa (menús×5 idiomas +
categorías con contenido + contenido no-ES). Se añade también
classify_gaps.py (clasifica huecos: traducir vs descargar) y run_all.sh.
Outputs y .venv quedan en .gitignore.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Unificar backup/ + backups/ + Akeeba sueltos en backups/ etiquetados por fecha
(pre-incidente Enero, Marzo, INFECTED 25-mayo, clean 25-mayo, wp-local-dumps)
- Mover scripts de setup-inicial (Feb 2026) y logs de migración a archive/
- Mover kickstart.* a tools/akeeba-kickstart/
- Borrar joomla-php83-old-local-20260525/ (6.3 GB) y google-chrome.deb
- Conservar solo 2 de 8 dumps WP del 25-mayo (los hotfixes ya están consolidados)
- README de inventario con MD5 en backups/README.md y archive/README.md
- .gitignore: trackear los README dentro de carpetas gitignored
Toma el output de regenerar_clasificacion_csv.py y añade en BD las cats
1645-1650 que el CSV indique y no estén ya. Modo 'add' (default, conservador)
solo añade; modo 'replace' (opt-in) también borra cats sobrantes.
Recalcula wp_term_taxonomy.count tras los cambios.
Aplicado en local 2026-05-25 modo 'add': +183 atribuciones (1645 +1,
1646 +17, 1647 +29, 1648 +11, 1649 +93, 1650 +32). Cubre delta marzo-mayo
2026 + sub-clasificación posicional en evangelio.
Refs #42.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Regla del editor confirmada contra CSV histórico: dentro de "Evangelio y
comentarios al Evangelio" el orden es siempre:
pos 1 → lectura del evangelio
pos 2 → comentario editorial
pos 3+ → comentarios
Mejora diff contra CSV marzo 2026: 8.655 → 9.286 mismas cats
(de 1.599 a 968 divergencias, -631 resueltas).
Residual mayoritario son posts que ya no aparecen en las cartas
actuales (links eliminados, k2 ids sin mapping) o granularidad fina
no implementada (lectura dentro de eucaristía, secciones especiales).
Refs #42.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reproduce el CSV histórico (marzo 2026) recorriendo todas las cartas
(cat 6+21+22) y extrayendo los links agrupados por encabezado. Paridad
con mu-plugins/fea-carta-portada.php (#38).
Cobertura: 5 secciones core (comentario, articulo, eucaristia,
multimedia, effa). NO cubre granularidad fina del CSV histórico
(lectura, comentario_editorial, otro, noticia) ni encabezados de
fiestas especiales — esos quedan agrupados en la cat más cercana.
Usa pymysql para no depender del CLI mysql (que rompe el parsing
cuando post_content tiene tabs/newlines). Modo --diff compara contra
un CSV de referencia y reporta divergencias.
Resuelve #42.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Re-procesa los 724 avatares col_*.png (imágenes horizontales con foto+texto)
recortándolos cuadrados centrados en la cara antes de mostrarlos como avatar
circular. Resuelve la queja del #39 sobre las fotos "stretcheadas".
- face_crop_avatar.py: Haar cascade frontal+profile, padding 0.6×, fallback
inteligente para imágenes horizontales (recorta lado izquierdo), reduce
side cuando padding no cabe (no invade lado del texto)
- regen_avatars.php: borra thumbnails viejos + regenera con la nueva imagen
para todos los attachments de uploads/autores/joomla/
Resultado: 100 caras detectadas + 624 fallbacks (mayoría placeholders sin
foto real) + 0 fail. Attachment IDs preservados.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Usado para resolver issue #39 — 736 avatares de autor importados desde
joomla-php83/images/quienes_somos/col_*.png. Crea attachments WP con
wp_insert_attachment + wp_generate_attachment_metadata y asigna foto_perfil
a cada user_id según matching exacto (login==slug) o strong (login es token).
Preserva foto_perfil ya asignado. TSV de entrada esperado en
/tmp/avatar_assignments.tsv con columnas: aid, login, posts, source, filename.
Ficheros pre-copiados a uploads/autores/joomla/.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- fea-carta-portada.php (nuevo, 1.0): parser de la carta semanal.
Extrae secciones (Evangelio, Artículos, Eucaristía, Multimedia, EFFA)
por encabezados y resuelve los href (WP slug o K2 legacy id) a
wp_posts.ID. Cache en transient 15 min, invalidación en save_post.
Cierra issue #38.
- fea-homepage.php: 4 shortcodes reescritos para usar
fea_carta_section_posts() como fuente primaria, manteniendo el
fallback existente (ACF + últimos por categoría).
Afecta: [fea_articulos_semana], [fea_evangelio], [fea_eucaristia],
[fea_multimedia]. El hero y noticia_centro sin cambios.
- fea-slider-sync.php (nuevo, 1.0): sincroniza Smart Slider 3 id 2
con uploads/home/. Editor sube/borra imágenes en esa carpeta y el
slider se actualiza automáticamente al primer pageview (modelo
paridad con Joomla mod_ariimageslider). Cierra issue #43.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>