36 URLs en 500 desde el cutover: las mismas 9 cartas de otras semanas en
fr/it/en/pt. En espanol iban bien, y contra CDMON las mismas URLs daban 200, o
sea que era regresion nuestra.
La reescritura de enlaces internos al idioma activo llamaba a url_to_postid()
una vez por enlace, y estas cartas traen unos 40. Cuando la URL no encaja en
ninguna regla de reescritura, la WP_Query que monta esa funcion se queda sin
clausula que la acote y se trae las 32.311 entradas CON su contenido, dos veces
por peticion. 256 MB agotados en class-wpdb.php y 500.
Se sustituye por fea_href_a_post_id(): ultimo segmento del path y una consulta
con LIMIT 1, que no puede degenerar, cacheada por peticion.
El ORDER BY reproduce a quien sirve WordPress esa misma URL, y no es cosmetico:
hay 4 slugs compartidos por una pagina de primer nivel y una entrada, donde gana
la pagina, y 58 compartidos por dos entradas -duplicados del import de Joomla-
donde gana la de post_date mas reciente. Mi primer intento ordenaba por ID ASC y
en esos 58 habria traducido el enlace equivocado.
Verificado: 116/116 cartas traducidas en 200, 200 entradas al azar en 200,
E2E 13/13, y el resolutor nuevo coincide con url_to_postid() en 461 de 461
slugs de los casos donde url_to_postid() no revienta. La consulta gorda ha
desaparecido del performance_schema.
Van dos centinelas a la suite E2E, una carta italiana y una francesa de las que
fallaban.
Nota de metodo para el futuro: esto no se reproduce con wp-cli, porque Polylang
no instancia su frontend en CLI y el filtro sale antes de tiempo. Se cazo
mirando events_statements_history_long en MySQL mientras se pedia la pagina por
HTTP.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Despues del cutover, la suite no servia para verificar www.feadulta.com: el WAF
de Cloudflare devuelve 403 a las IPs desde las que corremos (la de Rafa y la del
propio Hetzner), asi que por el dominio real no se comprueba nada.
run.js acepta ahora hostResolverRules en el JSON del sitio y se lo pasa a
Chromium como --host-resolver-rules. El navegador va directo al origen con el
Host correcto, que es justo lo que queremos comprobar: lo que hemos cambiado es
el origen, no Cloudflare.
Se anade sites/www.json con las 11 URLs de produccion. Resultado del 3-ago tras
el cutover: 11 de 11 en 200. Los WARN que salen son dos falsos positivos, ambos
comprobados:
- la baliza de GA4 aborta al cerrar la pagina en headless, y de paso confirma
que GA4 dispara desde el servidor nuevo con el G-6RT9ZRS4LW correcto.
- la "imagen rota" es un img con src vacio que crea el lazy-loader en tiempo de
ejecucion. En el HTML no existe en ninguno de los dos servidores, y el numero
de imagenes es identico entre Hetzner y CDMON (4 y 4 en las entradas, 1.253 y
1.253 en el listado de autores).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El runner necesita el array 'urls'; el fichero solo tenia baseUrl. Se anaden 11
targets: portadas de los 5 idiomas, carta de la semana, un articulo, evangelio
del dia, buscador, wp-admin y wp-login.
Resultado de la corrida contra nuevo.feadulta.com: los 6 puntos en verde
(5 idiomas 200, carta de la semana con sus enlaces, permalink de articulo,
buscador con 7.344 resultados para "evangelio", wp-admin/wp-login, y CERO
errores PHP en el HTML). Los WARN son imagenes que aun no habian llegado por el
rsync en curso, no fallos de la migracion.
Nota para no volver a asustarse: 6.195 guid de adjuntos apuntan al WordPress
local (IP de Tailscale). Es cosmetico: el guid no sirve imagenes. Lo que las
sirve es _wp_attached_file, y las 7.755 son rutas relativas, 0 absolutas, con
upload_path y upload_url_path vacias.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Con los tiempos REALES medidos en el ensayo del 31-jul, no estimados:
dump 25s + subida 74s + saneo 2s + import 107s = camino critico ~3min30s.
- docs/cutover/RUNBOOK-cutover-wordpress.md: los pasos en orden, copiables, con
el rollback y los tres verdes obligatorios previos (freeze, CDMON vivo, modo
SSL de la zona).
- scripts/cutover/post-import-seguridad.sh: rota la contrasena de los 6
administradores (las lee del .env del perfil, nunca van en el repo), retira el
rol a pabloarias/josek/andrey y limpia los cron huerfanos de Yoast y WP
Profile Builder. Es un script y no una tarea manual porque el dump revive esas
cuentas en CADA import.
- tools/e2e/sites/nuevo.json: la suite toma el host de un JSON, asi que apuntarla
al staging es anadir un fichero.
Hallazgo del ensayo: el origen es MariaDB 11.8.6 y el destino MySQL 8.4.10; el
dump trae 20 tablas con collations uca1400_* que MySQL rechaza. El saneo las
mapea antes de importar. Sin ese paso, el import aborta.
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.
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>
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>
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>
- scripts/fix_image_paths.php: reescribe src/href "images/..." en wp_posts
a "/fea/wp-content/uploads/..." cuando el fichero existe en uploads.
Cubre comillas dobles/simples, urldecode antes de chequear filesystem.
Resuelto contra issue #34 (458 posts, 465 refs) y completado con #36
(124 posts, 128 refs tras recuperar 127 assets del backup producción).
- tools/e2e/: pipeline 3-tier para validar WP local con coste mínimo en
tokens de Claude — Playwright (Tier 1, deterministas) + Gemma 4 vision
en LM Studio (Tier 2, bajo demanda) + Claude solo lee report.md.
Issue de diseño #37, suite inicial en sites/feadulta.json (13 URLs).
- .gitignore: excluir tools/e2e/node_modules y out/.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>