De WordPress a estático con Claude Code: cómo reconstruí este sitio en una tarde

Categoría
IA y LLM Local
Publicado
11 julio 2026
Por
Jacob Lloyd — escrito con ayuda de IA, después del proyecto
Tiempo de lectura
10 min de lectura

En palabras simples: La historia de cómo este mismo sitio web fue reconstruido en una tarde por un asistente de codificación con IA, pasando de WordPress a una carpeta de archivos simples. El nuevo sitio es más rápido, gratis de operar y no tiene página de inicio de sesión que los hackers puedan atacar. Termina con la receta paso a paso para hacer lo mismo con tu propio sitio.

Este sitio antes funcionaba con WordPress. Ahora es una carpeta de archivos Markdown y un script en Python de 183 líneas, construido a partir de una exportación de WordPress por un agente de codificación con IA en una sola tarde. Así fue como pasó, y por qué el mismo flujo de trabajo sigue llevando el sitio.

tl;dr
  • Qué es: un generador de sitio estático hecho desde cero (Python + Jinja2) que reemplazó a WordPress, más un importador de un solo uso para el contenido antiguo
  • Qué cuesta: nada. Sin plugins, sin licencia de tema, el mismo hosting compartido barato de siempre
  • Qué necesitas: tu exportación de WordPress (un archivo WXR), tu carpeta de uploads y un agente de codificación con IA — yo usé Claude Code
  • Qué obtienes: archivos HTML planos, sin base de datos, sin página de inicio de sesión que alguien pueda atacar por fuerza bruta

Lo que obtienes

Primero, las pruebas — la salida real de terminal del día en que la reconstrucción quedó lista:

$ python3 _build/build.py
Built 15 files (7 projects, 3 pages, 4 categories).

Quince páginas HTML eran todo el sitio en el momento de la migración: inicio, cuatro páginas de categoría, siete publicaciones de proyectos, tres páginas informativas. Todas prerrenderizadas, todas archivos planos, cero código del lado del servidor ejecutándose por solicitud.

La exportación de WordPress de la que salieron pesaba 572 KB y tenía 123 elementos — publicaciones, páginas, adjuntos de imagen y un montón de borradores abandonados. Siete publicaciones reales sobrevivieron el viaje. El resto era basura que el importador descartó a propósito.

Y la maquinaria que lo construye cabe en una tabla:

PiezaLíneas
build.py (el generador)183
migrate.py (el importador de un solo uso)166
Plantillas (5 diseños de página)139
Parciales (nav, pie de página, tarjeta)51
site.yaml (toda la configuración del sitio)36
serve.sh (vista previa local)12
.htaccess (reglas del servidor)57
Total644

Suma el CSS (322 líneas) y el único archivo JavaScript (31 líneas — un interruptor de navegación móvil, eso es todo) y el sitio entero queda por debajo de 1.000 líneas. Sin node_modules, sin framework de build, sin lockfile con el que pelear.

Parte 1: dejar WordPress atrás

No escribí nada de esto a mano. Le describí a Claude Code lo que quería y escribió el importador, luego el generador, luego las plantillas, mientras mi trabajo era revisar el resultado y decir "no, hazlo así en su lugar". Las marcas de tiempo de los archivos lo confirman: de tener la exportación en la mano a un sitio construido y navegable en menos de dos horas. Una tarde, no un fin de semana.

El flujo de trabajo en el que terminó:

La exportación y el espejo de medios

WordPress exporta todo mediante Herramientas → Exportar → Todo el contenido. Obtienes un archivo WXR — XML con tus publicaciones, páginas, categorías y registros de adjuntos metidos dentro. Esa es la única entrada del importador.

Lo único que la exportación no incluye son tus archivos de imagen reales, solo sus URLs. Así que la carpeta de uploads se trae aparte — por SFTP, un gestor de archivos o un simple espejo con wget — hacia assets/uploads/, manteniendo la misma estructura de año/mes que usaba WordPress para que las URLs reescritas resuelvan bien.

Lo que hace migrate.py en realidad

El importador es de un solo uso, pero se puede volver a ejecutar sin peligro. Ninguna librería XML — solo expresiones regulares contra el texto del WXR, lo cual suena frágil hasta que te das cuenta de que el XML de exportación de WordPress es extremadamente regular, y aplicarle grep es muchísimo menos código que montar un parser completo. Para cada publicación extrae el título, el slug, la fecha, el cuerpo, la categoría y la imagen destacada, y luego:

  • Descarta la basura — las publicaciones vacías y cualquier cosa titulada "(borrador)…" nunca llegan a content/
  • Mapea las categorías a un conjunto corto y limpio de claves en lugar de los nicenames desordenados de WordPress
  • Resuelve las imágenes destacadas mediante el id del adjunto, recurriendo a la primera imagen subida en el cuerpo si no la encuentra
  • Reescribe las URLs de medios de wp-content/uploads/… a /assets/uploads/…
  • Reescribe los enlaces internos — los permalinks antiguos se convierten en las nuevas URLs limpias, usando un mapa de slugs construido a partir de todas las publicaciones y páginas
  • Elimina los comentarios de bloque de Gutenberg y borra directamente los bloques de listados dinámicos — ahora el paso de build genera los listados de categoría, así que los antiguos, ya fijos en el HTML, solo se quedarían ahí quedando obsoletos
  • Genera extractos automáticamente — quita las etiquetas, trunca a 160 caracteres y los usa como texto de tarjeta y meta description

La parte interesante: las publicaciones salen del otro lado como HTML de WordPress limpio metido dentro de un archivo Markdown, no convertido a sintaxis Markdown. El renderizador deja pasar el HTML crudo tal cual, así que al contenido antiguo no le pasó nada con pérdida — y las publicaciones nuevas se escriben en Markdown normal. Ambos se construyen sin problema desde la misma carpeta. La primera vez que abrí un archivo ".md" que era 90 % etiquetas HTML pensé que algo se había roto. No era así.

Lo que hace build.py

Esta es la parte que se ejecuta para siempre. Lee site.yaml para la navegación, las categorías y los enlaces, lee todo lo que hay en content/, y renderiza cada archivo a través de una plantilla Jinja2 hacia una carpeta real con un index.html dentro — URLs limpias sin necesitar reglas de reescritura en el servidor. Más allá de las páginas en sí, produce:

  • Una barra de navegación y un pie de página construidos a partir de datos — agregas una categoría o una página y aparece por sí sola
  • Título, meta description, URL canónica y etiquetas Open Graph y Twitter por página
  • sitemap.xml, robots.txt y un manifest web, regenerados en cada ejecución
  • Un paso de limpieza que borra primero la salida obsoleta, para que una publicación renombrada no deje atrás una página muerta
  • Cero dependencias en tiempo de ejecución: fuentes autoalojadas, iconos SVG en línea, ese único archivo JS pequeño, nada proveniente de un CDN

Agregar una publicación es: soltar un archivo Markdown en content/projects/ y ejecutar el build. Ese es el flujo de trabajo completo.

Vista previa y despliegue

La vista previa local es un solo script — construye y luego sirve. Recorre todas las páginas con clics antes de que nada se acerque al hosting real.

./serve.sh
# builds, then serves the whole site at http://localhost:8099

El despliegue es un push por rsync guiado por un manifest que enumera exactamente qué se envía (HTML, CSS, JS, imágenes, fuentes, .htaccess) y qué queda denegado (los scripts de Python, el código fuente en Markdown, la exportación de WordPress, las plantillas). La configuración del servidor agrega compresión, cabeceras de caché, cabeceras de seguridad y una denegación total sobre acceder directamente a cualquier archivo fuente — así que aunque alguien sepa que el generador existe, no hay nada que descargar.

Parte 2: cómo se mantiene este sitio ahora

Esto es lo que hizo que valiera la pena hacer bien la reconstrucción: el sitio tampoco se mantiene más a mano. El mismo flujo de trabajo de agentes que lo construyó lo mantiene al día — incluida la actualización que estás leyendo ahora mismo.

Esta revisión empezó con un enjambre de auditoría. Un equipo verificó los datos de cada página publicada y le dio clic a cada enlace saliente; otro reunió capturas de pantalla y datos técnicos concretos sobre cómo funciona el sitio. Todo alimentó una única especificación escrita: construye esto, no inventes números, nunca publiques un secreto.

A partir de ahí el trabajo se repartió — un agente de plataforma (estructura de categorías, un segundo idioma, la fontanería de motores de búsqueda), un diseñador y un agente de redacción por artículo, este incluido. Un modelo rápido redacta el borrador de cada página a partir de su archivo de datos. Un modelo más potente edita para cuidar el tono y la precisión. Luego una pasada adversarial intenta romperlo — revisando de nuevo los números contra los datos de origen, volviendo a hacer clic en los enlaces, cazando cualquier cosa que suene alucinada. O, siendo honestos, cualquier cosa que suene a que la escribió una IA. Que, en este caso, así fue.

Dale este trabajo a tu propio agente

¿Tienes un viejo sitio de WordPress al que no quieres seguirle poniendo parches para siempre? Esta es la receta que le di al mío. Descríbele cada paso a tu agente y deja que escriba el código.

  1. Exporta. Panel de administración de WordPress → Herramientas → Exportar → Todo el contenido. Deja el archivo WXR donde el agente pueda leerlo, por ejemplo _build/source.xml.
  2. Espeja los medios. Copia wp-content/uploads/ desde el hosting antiguo hacia assets/uploads/, conservando la estructura de año/mes.
  3. Pide un script de migración. Que analice los elementos del WXR, descarte borradores y vacíos, mapee las categorías a una lista corta y limpia, resuelva las imágenes destacadas (recurriendo a la primera imagen en línea si falta), reescriba las URLs de subida y los enlaces internos, elimine los comentarios de bloque de Gutenberg, genere extractos automáticamente y escriba archivos Markdown con frontmatter.
  4. Pide un generador. Que lea una configuración de sitio más la carpeta de contenido, renderice a través de plantillas (base, inicio, categoría, publicación, página, parciales compartidos de nav/pie/tarjeta), genere carpetas con URLs limpias, y produzca sitemap.xml, robots.txt y un manifest web.
  5. Reconstruye el diseño desde cero. No migres el CSS del tema antiguo — una única hoja de estilos limpia, fuentes abiertas autoalojadas, iconos SVG en línea, cero solicitudes externas.
  6. Previsualiza todo. Un script de una línea que construye y luego sirve localmente. Haz clic en cada página.
  7. Escribe la configuración del servidor. Compresión, cabeceras de caché, cabeceras de seguridad, reglas de denegación para archivos fuente, un fallback para URLs sin extensión.
  8. Despliega con un manifest. Sincroniza la salida con rsync, excluyendo explícitamente las carpetas fuente y los scripts, y luego comprueba que la URL en vivo devuelva 200.
  9. Conserva la exportación. El archivo WXR se queda en el proyecto — volver a ejecutar el script de migración regenera la carpeta de contenido de forma limpia si alguna vez necesitas una importación nueva.

La lista completa de dependencias en tiempo de ejecución, entera:

pip install jinja2 pyyaml markdown

Eso es todo.

Trampas habituales

  • Olvidar espejar la carpeta de uploads y nada te avisa. El build se completa con éxito mientras cada miniatura da 404. Lo descubrí por las malas.
  • La salida y la fuente comparten carpeta. El HTML construido cae en la misma raíz del proyecto que el código fuente en Python y Markdown — la separación del despliegue depende por completo de la lista de allow/deny. Al principio se sentía al revés; es justo lo que mantiene el despliegue como un simple rsync.
  • Renombrar una categoría puede dejar varada una carpeta muerta. El paso de limpieza tiene los nombres de categoría escritos directamente en él. Renombras una en la configuración, olvidas esa lista, y la carpeta vieja se queda ahí publicada y huérfana.
  • Las fechas se ordenan como cadenas de texto, no como fechas. Está bien mientras cada fecha se escriba YYYY-MM-DD. Escribe una mal y tu publicación más reciente se hunde silenciosamente hasta el fondo.

← Más de IA y LLM Local