De WordPress a Astro: caso de estudio técnico de una migración real
Documentación completa de una migración de un portfolio profesional con más de una decena de herramientas interactivas, de WordPress/Astra a Astro — decisiones técnicas, alternativas descartadas, y los problemas reales que aparecieron en el camino. No es una guía genérica: es el registro de lo que ocurrió de verdad.
Por motivos de privacidad, se han omitido u generalizado detalles de infraestructura sensibles (nombres de servidor, direcciones IP, rutas de cuentas de hosting). Todo lo demás — comandos, configuración, arquitectura, y las decisiones tomadas — está documentado tal cual ocurrió.
El problema de partida
El sitio original llevaba años construido sobre WordPress con el tema Astra: CSS personalizado mezclado con HTML insertado a mano dentro de páginas y entradas, y un ecosistema de más de una decena de plugins a medida, cada uno con su propia lógica y sus propias dependencias. Con el tiempo aparecieron dos problemas de raíz que ninguna optimización superficial podía resolver.
El primero era de CSS: al no existir un sistema de diseño centralizado, no había un criterio claro de qué colores usar sobre fondos oscuros frente a fondos claros, lo que producía fallos de contraste recurrentes cada vez que se añadía contenido nuevo. El segundo era de datos estructurados: el JSON-LD (schema.org) estaba disperso entre un bloque global y bloques distintos por página o entrada, sin ninguna fuente de verdad única, lo que hacía prácticamente imposible mantenerlo coherente a medida que crecía el sitio.
Ninguno de los dos problemas es exclusivo de WordPress — son síntomas de no tener una arquitectura de contenido y estilos centralizada. Pero en un CMS orientado a edición visual, con HTML insertado directamente en el editor, corregirlos de raíz habría significado básicamente reescribir el sitio entero desde dentro de WordPress. Se decidió aprovechar ese punto de inflexión para migrar a un framework donde el CSS y el schema pudieran vivir en un solo lugar, versionado, en vez de repartidos entre la base de datos y el editor visual.
Por qué Astro y no otra alternativa
Se evaluó Astro frente a alternativas como Eleventy. La decisión se inclinó hacia Astro por su modelo de islas (permite mezclar contenido estático con componentes interactivos solo donde hacen falta, sin cargar un framework de cliente completo en páginas que no lo necesitan), su soporte nativo para colecciones de contenido tipadas, y un ecosistema más activo para integraciones como sitemaps y generación de imágenes. Eleventy sigue siendo una opción perfectamente válida y en algunos casos más simple si el sitio no necesita ningún componente interactivo — la elección aquí respondió a que el portfolio en cuestión incluye más de una decena de herramientas interactivas (calculadoras, generadores de informes, un juego), no solo contenido estático.
Auditoría completa del sitio existente
Un error común en las migraciones es empezar a construir el sitio nuevo antes de tener un mapa completo del antiguo.
Antes de crear el proyecto Astro se hizo un rastreo completo del sitio en producción con Screaming Frog, documentando el número total de URLs rastreadas, cuántas eran HTML indexable frente a recursos CSS/JS, y cuántas redirecciones 301 y errores 404 existían ya en el sitio (algunos 404 resultaron ser páginas de plantilla sin rellenar, con marcadores de posición literales en el título — un tipo de error que solo se detecta con un rastreo real, no revisando el código a mano).
En paralelo se exportó la configuración del plugin de redirecciones activo en el sitio (todas las reglas 301 configuradas manualmente a lo largo de los años) y la configuración del plugin de SEO, para poder cruzar ambas fuentes contra el resultado del rastreo antes de decidir qué URLs debían conservarse exactamente igual y cuáles podían reestructurarse. Esta auditoría previa es la diferencia entre una migración que preserva el posicionamiento SEO existente y una que lo destruye por accidente al cambiar URLs sin darse cuenta de que tenían enlaces entrantes o redirecciones dependientes.
Dónde alojar el sitio nuevo: las opciones reales y sus soluciones de despliegue
Con el sitio nuevo ya en marcha en local, había que decidir dónde desplegarlo, y esta es una decisión que conviene documentar de forma general porque las opciones — y sobre todo la solución técnica de despliegue asociada a cada una — cambian según el tipo de alojamiento, y esto se repite en prácticamente cualquier migración a un framework estático, sea cual sea el hosting de partida.
- Hosting compartido tradicional
- El más habitual en migraciones de WordPress, porque es donde ya vivía el sitio de origen. Este tipo de hosting casi nunca da acceso SSH al usuario por política de seguridad del proveedor — como mucho ofrece una terminal web de cPanel que corre en un entorno aislado y no sirve para automatizar nada externo — así que cualquier estrategia de despliegue basada en SSH (rsync, git pull remoto en el propio servidor, hooks post-receive) queda descartada de raíz. La única vía de subida de archivos que suele quedar disponible es FTP o FTPS, y la solución práctica es un workflow de integración continua (en este caso GitHub Actions, pero el mismo patrón vale con GitLab CI o cualquier otro) que hace el build del sitio estático y sube el resultado por FTP al hosting en cada despliegue. Es la opción más barata y la que menos fricción de migración de dominio o DNS supone si ya se tenía ese hosting, pero tiene una contrapartida importante: el pipeline depende por completo de que el disparador del workflow esté bien configurado, porque no hay ningún mecanismo nativo del hosting que confirme o rechace el despliegue — si el workflow no se dispara, el hosting simplemente no se entera de que hay nada nuevo que servir, y no da ningún aviso de ello.
- Servidor propio (VPS o dedicado)
- Aquí sí hay acceso SSH real, lo que abre soluciones mucho más directas: un
rsyncpor SSH tras cada build, un hookpost-receivede git en el propio servidor que reconstruye el sitio automáticamente al recibir un push, o incluso correr el propio proceso de build en el servidor si tiene Node.js instalado. Es más trabajo de configuración inicial (hay que gestionar el servidor, sus actualizaciones de seguridad, y normalmente un proxy inverso como Nginx delante de los archivos estáticos), pero da control total y evita depender de un tercero para el propio despliegue. - Plataforma para sitios estáticos (Netlify, Vercel, Cloudflare Pages)
- Todas ofrecen integración nativa con GitHub o GitLab: basta con conectar el repositorio y cada push a la rama principal dispara un build y despliegue automáticos, sin necesidad de escribir ni mantener ningún workflow de CI propio. Es, con diferencia, la opción con menos piezas que pueden fallar en silencio, porque el propio panel de la plataforma muestra el estado de cada despliegue de forma explícita. La contrapartida es que implica depender de un proveedor externo adicional (con su propio dominio de plataforma, límites del plan gratuito, y en algunos casos una migración de DNS si se quiere usar como origen principal), y para un sitio que no tiene backend propio a veces es un salto mayor del necesario.
Ninguna de las tres es objetivamente mejor que las otras — cada una traslada el riesgo a un sitio distinto (a la configuración del pipeline, a la gestión del servidor, o a la dependencia de un tercero), y la elección correcta depende de qué riesgo se prefiere asumir en cada proyecto concreto.
El pipeline de despliegue: el error que costó una tarde entera
Sea cual sea la vía de subida de archivos elegida entre las anteriores, automatizar el despliegue mediante un workflow de CI (GitHub Actions u otro) introduce un punto de fallo concreto que conviene conocer, porque es fácil de pasar por alto y muy fácil de reproducir por accidente.
En este proyecto, el workflow que hacía el build de Astro y publicaba el resultado funcionó bien al principio — hasta que, después de varias sesiones de trabajo pusheando cambios a main, ninguno de esos cambios aparecía en producción. La sospecha inicial fue que el sitio en realidad se servía desde otra plataforma (se llegó a sospechar de Netlify, por una confusión). El diagnóstico correcto se hizo así, y es un método reutilizable para cualquier "por qué no se actualiza mi sitio" similar, con independencia de qué opción de hosting esté detrás:
Primero, verificar por DNS y cabeceras HTTP dónde se sirve realmente el sitio en producción — una respuesta de Netlify, por ejemplo, siempre incluye una cabecera x-nf-request-id, y su ausencia descarta esa hipótesis de forma objetiva, sin necesidad de acceso a ningún panel. Segundo, revisar el repositorio en busca de configuración específica de la plataforma sospechosa (un archivo netlify.toml, un adapter configurado) — su ausencia total refuerza la conclusión. Y tercero, y el paso que finalmente reveló el problema real: leer directamente el archivo YAML del workflow de GitHub Actions.
Resultó que el workflow de despliegue solo tenía configurado el disparador workflow_dispatch (manual) y no push — es decir, ningún git push a main iba a desplegar nada nunca, sin importar cuántas veces se repitiera, hasta que alguien entrara a la pestaña Actions del repositorio y pulsara "Run workflow" a mano.
La lección aquí no es solo la causa concreta, sino el método: cuando un despliegue "no se actualiza", antes de sospechar del código hay que confirmar objetivamente dónde y cómo se sirve el sitio en producción, y leer la configuración del pipeline de despliegue línea por línea en vez de asumir que hace lo que su nombre sugiere.
Centralizar schema.org: una fuente de verdad en vez de fragmentos dispersos
Uno de los dos problemas de raíz que motivaron la migración era el JSON-LD disperso.
La solución en Astro fue centralizar toda la lógica de generación de schema.org en un único archivo (schema.ts), del que cada página importa y compone los nodos que necesita, en vez de que cada página o componente construya su propio bloque de JSON-LD de forma independiente. Esto convierte cualquier corrección de schema (una URL desincronizada, un tipo mal aplicado, una propiedad obsoleta) en un cambio en un solo sitio, no en una búsqueda manual página por página.
Como complemento, se construyó un script de auditoría independiente que compara el menú de navegación real del sitio (la fuente de verdad de qué herramientas existen de verdad) contra las entradas correspondientes en el JSON-LD, detectando automáticamente herramientas sin schema, URLs desincronizadas entre el menú y el schema, y entradas huérfanas en el schema que ya no corresponden a nada del menú actual. Este tipo de script de auditoría estructural (que analiza el árbol HTML del menú de forma agnóstica a clases CSS o al tema visual) sobrevive a rediseños posteriores del sitio, a diferencia de una comprobación manual que hay que rehacer cada vez.
El patrón para portar cada herramienta interactiva
El sitio incluía más de una decena de herramientas construidas originalmente como plugins de WordPress con shortcodes — calculadoras, generadores de informes, formularios de autodiagnóstico. Varias de ellas tenían shortcodes rotos visibles en producción (el propio texto del shortcode sin renderizar, visible para cualquier visitante), lo cual convertía su migración en un bug urgente, no solo en una tarea de mantenimiento.
Se estableció un patrón repetible para cada una: eliminar cualquier integración con formularios externos o herramientas de captura de datos de terceros; decidir la arquitectura (client-side puro frente a un backend), siguiendo el principio arquitectónico ya establecido en el resto del sitio de no persistir ningún dato del usuario ni depender de APIs externas de pago; servir el JavaScript y CSS de la herramienta desde la carpeta de assets públicos del proyecto; insertar la aplicación real dentro de la página de contenido correspondiente; y corregir la URL propia y las referencias de schema (isPartOf y similares) de esa herramienta.
Caso real: un generador de documentos Word/Excel
El caso más ilustrativo de una decisión arquitectónica real fue un generador de informes que necesitaba producir documentos Word y Excel reales, no solo texto plano. Se plantearon tres opciones:
- Opción 1 — Backend en PHP en el servidor
- Descartada. Reintroducía exactamente el procesamiento de datos en servidor que el resto del sitio evita deliberadamente, y dependía de una capacidad del hosting (ejecución de PHP en la carpeta de assets públicos de un sitio pensado como estático) que no estaba garantizada.
- Opción 2 — Generación en el navegador con JavaScript
- Elegida. Generación de
.docxmediante manipulación directa del formato OOXML comprimido con una librería de compresión ZIP en cliente. Más trabajo de reescritura, pero coherente con el patrón ya establecido en el resto del sitio y sin ninguna dependencia del soporte del hosting. Se portó la lógica del PHP original a JavaScript de forma fiel, verificando el resultado. - Opción 3 — Parche mínimo, ocultar el shortcode roto
- Descartada. Dejaba la herramienta inservible, solo menos visible.
Caso real: una limitación estructural de CORS
Otro caso — una herramienta que necesitaba analizar el HTML de una URL externa cualquiera — chocó con una limitación estructural: sin backend, esa operación está bloqueada por la política CORS del navegador, así que "analizar cualquier URL" simplemente no es viable en un sitio estático puro. La solución fue rediseñar el flujo para que el usuario pegue directamente el código fuente de la página a analizar en vez de indicar una URL.
Aquí apareció una lección aparte: después de implementar el cambio de mecanismo, el texto de marketing de la propia página seguía anunciando "analiza cualquier URL" — el código se había corregido pero la copia visible no. Cambiar cómo funciona una herramienta obliga a auditar también todo el texto que describe su funcionamiento, no solo la lógica.
No confiar en que compila
Varias de las correcciones más importantes de esta migración no se detectaron revisando el código, sino probando la herramienta de verdad.
Al portar una calculadora compleja de PHP a JavaScript, en vez de asumir que la traducción era correcta, se generaron casos de prueba sintéticos y se ejecutaron en paralelo por el cálculo original y por el port, comparando los resultados campo a campo hasta confirmar una coincidencia exacta. Esta técnica de validación cruzada (correr los dos sistemas con la misma entrada y diferenciar la salida) es aplicable a cualquier port de lógica de negocio entre lenguajes, no solo a este caso.
Igual de importante fue probar en producción después de cada despliegue, no solo en local: una herramienta que en local generaba una puntuación aparentemente correcta necesitaba confirmarse funcionando de verdad en la URL real, con datos frescos que produjeran un resultado distinto en cada ejecución (confirmando que el cálculo era dinámico y no un valor fijo devuelto por error), con el canvas de una librería de gráficos pintando contenido real (verificable comprobando el porcentaje de píxeles no vacíos del canvas, no solo que no lanzara un error), y con la consola del navegador limpia de errores en el entorno real.
También apareció un patrón de diagnóstico útil para separar bugs reales de artefactos de caché: ante una queja de "el diseño no respeta los márgenes en todo el sitio", la comprobación objetiva (comparar el ancho de scroll de cada elemento contra su ancho visible, en varias páginas y resoluciones) no encontró ningún desbordamiento real, lo que llevó a pedir capturas concretas en vez de asumir un problema estructural de CSS. Una de esas capturas — un texto cortado en móvil — resultó ser, tras investigar, un problema de caché del teléfono del usuario y no un bug de código en absoluto. La lección: una queja de "algo se ve mal" sin una localización objetiva y reproducible no es procesable como bug hasta que se aísla con evidencia concreta.
Un límite real de las herramientas de asistencia con IA
Durante la limpieza de archivos huérfanos que ya no cumplían ninguna función tras la migración de una herramienta a client-side, el asistente de código intentó borrar un archivo PHP obsoleto y sin ninguna referencia activa, y la operación fue bloqueada por un clasificador de permisos del propio entorno — al no estar el archivo bajo control de versiones, borrarlo no habría sido recuperable desde el historial de git. La solución fue que el propio asistente entregara el comando exacto para que la persona lo ejecutara manualmente.
Vale la pena tenerlo en cuenta al planificar limpiezas de este tipo: un asistente de código puede identificar con precisión qué es seguro borrar, pero las salvaguardas de permisos pueden (con razón) exigir que el borrado final de algo no versionado lo confirme una persona.
El barrido final: lo que "funciona" no es lo mismo que "está terminado"
Una vez portadas y verificadas funcionalmente todas las herramientas, apareció una segunda ronda de trabajo centrada en experiencia real de usuario, no en funcionalidad: un menú desplegable de navegación que se abría desplazado fuera del viewport en escritorio, tapando parte del contenido y cortando columnas enteras; la ausencia de un botón o ancla de "volver arriba" en páginas largas; inconsistencias de espaciado entre el bloque de cabecera de cada página y el resto de su contenido; y una auditoría de accesibilidad real (contraste, navegación por teclado, atributos ARIA, texto alternativo, estructura de encabezados) en vez de asumir que una insignia de cumplimiento visible en el sitio seguía siendo cierta después de todos los cambios.
Este barrido final es fácil de saltarse porque nada de él aparece como "roto" en una prueba funcional básica — el sitio carga, las herramientas calculan bien, no hay errores en consola. Pero es precisamente el tipo de deuda que, si no se cierra antes de dar el proyecto por terminado, se acumula silenciosamente.
Lo reutilizable de todo esto
Más allá del caso concreto, hay una serie de principios que se pueden llevar a cualquier migración de CMS a un framework estático.
Antes de tocar nada, no solo revisando el código fuente a ojo.
CSS, schema, configuración — la dispersión es casi siempre la causa raíz cuando algo "es imposible de mantener coherente".
Con backend o sin él — documentada, no por inercia del sistema anterior.
No solo revisando que el código compile.
No solo en local.
Antes de sospechar del código.
Separada de la fase de "hacer que funcione" — son dos criterios de terminado distintos.
Yel Martínez, Digital Strategist & Technologist — desarrolladora de software open source y estratega digital. Para proyectos de migración web, arquitectura Astro o auditoría técnica: contacto.