# Los 500 de las cartas traducidas (`url_to_postid()`) ## ✅ RESUELTO — 4-ago-2026, 02:00 UTC **36 URLs devolvían 500**: las mismas 9 «cartas de otras semanas» en los cuatro idiomas traducidos. En español las 9 iban bien. ``` /fr/lumiere-et-phare/ /it/luce-e-faro/ /en/light-and-beacon/ /pt/luz-e-farol/ … ×9 ``` Era **una regresión del cutover, no algo heredado**: las mismas URLs contra CDMON respondían 200. ## La causa `fea-homepage.php` reescribe los enlaces internos al idioma activo, y para eso llamaba a **`url_to_postid()`** una vez por enlace. Estas cartas traen unos 40. `url_to_postid()` acaba construyendo una `WP_Query` a partir de las reglas de reescritura. Cuando la URL no encaja en ninguna, la query se queda **sin cláusula que la acote** y sale esto: ```sql SELECT wp_posts.* FROM wp_posts WHERE 1=1 AND wp_posts.post_type = 'post' ORDER BY post_date DESC ``` **Las 32.311 entradas con su contenido entero.** Dos veces por petición. Los 256 MB de PHP se agotaban en `class-wpdb.php:2322` y Apache devolvía el 500. Encaja con el patrón observado: el filtro se salta el español (`if (!$lang || $lang === 'es') return`), que es justo el idioma que no fallaba, y solo actúa en `is_singular()`. ## El arreglo Se sustituye `url_to_postid()` por `fea_href_a_post_id()`: la estructura de enlaces es `/%postname%/`, así que basta el último segmento del path y **una consulta con `LIMIT 1`**, que no puede degenerar por rara que sea la URL. El resultado se cachea por petición. El orden del `ORDER BY` no es cosmético, reproduce a quién sirve WordPress esa misma URL: | caso | cuántos | criterio | |---|---|---| | página de primer nivel con el mismo slug que una entrada | 4 | gana la **página** (reglas verbosas de reescritura) | | dos entradas con el mismo slug (duplicados del import de Joomla) | 58 | gana la de **`post_date` más reciente** | Ordenar por `ID ASC`, que fue el primer intento, devolvía la entrada vieja y habría traducido el enlace equivocado en esos 58 casos. ## Verificación | | | |---|---| | Las 116 cartas traducidas (29 × 4 idiomas) | **116/116 en 200** | | Muestra de 200 entradas al azar (40 por idioma) | todas en 200 | | `fea_href_a_post_id()` vs `url_to_postid()`, 61 slugs repetidos + 400 al azar | **461/461 idénticos** | | La consulta gorda | desaparecida: la que más filas devuelve ahora son 640 de `wp_options` | | Suite E2E | **13/13 en 200** | | Errores PHP y 5xx tras el despliegue | 0 y 0 | Los enlaces se siguen reescribiendo, y **algunos más que antes**: `url_to_postid()` fallaba en URLs que sí resuelven bien por slug. Comprobado contra CDMON que los nuevos pares son de verdad la misma entrada (mismo grupo de traducción de Polylang): `como-bendecir-la-mesa` → `comment-benir-la-table`, `felices-6` → `heureux`, `3-temario` → `programme`… ## Centinelas `tools/e2e/sites/www.json` incorpora `carta-trad-it` y `carta-trad-fr`, dos de las que reventaban. Si esto vuelve, lo dice la suite. ## ⚠️ Para la próxima **`url_to_postid()` no es seguro con URLs arbitrarias en un sitio grande.** El comentario que ya había en el fichero decía que en los listados agotaba memoria, y por eso se había acotado a `is_singular()`. La cura se quedó corta: el problema no era el listado, era la función. Y una trampa de método: esto **no se reproduce con wp-cli**. Polylang no instancia su clase de frontend en CLI, así que el filtro sale antes de tiempo y no pasa nada. Se cazó mirando `performance_schema.events_statements_history_long` en MySQL mientras se pedía la página por HTTP, que muestra la secuencia real de sentencias de la petición.