tawasuyu · Hacer
Tu WordPress, sin WordPress: el mismo sitio al píxel, con su tienda, sin una sola línea de PHP
Un sitio hecho con WordPress es un programa que corre cada vez que alguien lo visita, con su base de datos, sus plugins y sus puertas de administración abiertas a internet. qhatu se lo lleva entero, igual al píxel —y con su tienda, si la tiene—, y lo deja sin nada que ejecutar: el mismo sitio, más rápido, y sin una sola línea de PHP.
12 min de lecturaPor Redacción Gioser
Cada vez que alguien abre una página de un sitio WordPress, en el servidor arranca un programa: lee la base de datos, carga el tema, despierta cada plugin, arma el HTML y lo manda. Lo mismo, una y otra vez, para un texto que cambia una vez por mes. Y mientras tanto, ese programa deja abiertas a todo internet las puertas por donde se administra. Este artículo cuenta cómo qhatu —el gestor de contenidos de tawasuyu— se lleva un sitio WordPress real entero, lo deja igual al píxel, se trae también su tienda de WooCommerce, y le quita todo lo que se podía atacar.
El sitio que se arma en cada visita
WordPress es la forma más común de tener un sitio, y hay buenas razones: se instala en una tarde, hay un tema para todo, y un constructor como Elementor deja diseñar sin escribir código. El precio se paga después, y en cuotas. Cada plugin es código ajeno que corre con todos los permisos del sitio; cada actualización que se posterga es una puerta; y la base de datos, el login y la API quedan escuchando las veinticuatro horas, aunque el sitio lo edite una persona dos veces por mes.
El sitio con el que se probó todo esto es el de un cliente real: un centro de retiros hecho con WordPress, el tema WoodMart y Elementor Pro, con 64 páginas, una galería, un blog y dos formularios de contacto. Un sitio común, del tipo que hay a millones. El pedido fue simple: que quede igualito, y que se pueda seguir editando fácil.
Un comando
Lo que el primer día se hizo a mano —bajar el sitio renderizado, limpiar lo que WordPress agrega a cada nombre de archivo, reapuntar los formularios— hoy es un verbo:
qhatu absorb https://el-sitio.com/ carpeta
Lee los mapas del sitio que publica WordPress, baja cada página y todo lo que necesita —hojas de estilo, scripts, imágenes en todos sus tamaños, fuentes, íconos—, y deja cada referencia apuntando a la copia local. No baja lo que no es contenido: el carrito, el panel de administración, los feeds, las búsquedas. Si una parte del sitio la arma JavaScript recién en el navegador, con --render cada página pasa antes por un Chrome sin pantalla y se guarda ya armada. Sobre el sitio del cliente:
| qué | medido |
|---|---|
| páginas | 64 |
| recursos (hojas, scripts, imágenes, fuentes) | 448, 45 MB |
| tiempo, con una pausa de cortesía entre pedido y pedido | 6 minutos |
| referencias rotas en la copia | 0 |
Y no hace falta que sea WordPress: el mismo verbo se llevó un Joomla, una demo de Squarespace que arma todo con JavaScript y el primer sitio web de la historia, el de CERN, con cero referencias rotas en los tres.
Igual al píxel, y con brazo de control
«Igualito» se puede medir. Se abrió la misma página del original y de la copia en Chrome, a 1366 por 2400 píxeles, y se compararon las dos capturas punto por punto. En la portada y en una entrada del blog, 0 píxeles distintos de 3.278.400. Para que ese cero signifique algo, la prueba lleva brazo de control: cada captura tiene 216.987 colores distintos, así que no se están comparando dos páginas en blanco.
Y la honestidad incluye lo que no dio cero. En una tercera página aparecieron 1.785 píxeles distintos en la franja del menú. Antes de culpar a la copia se comparó el original consigo mismo, dos veces: dio 1.642 y 0. Es la propia página, que no se pinta igual dos veces seguidas.
Ningún enlace viejo se rompe
Un sitio con años tiene enlaces con años: en Facebook, en correos, en otros blogs. WordPress contesta a direcciones que nadie escribió a mano —/?p=123, /2023/05/una-entrada/, las categorías, el feed— y una mudanza que las pierde deja un rastro de errores 404. qhatu arma una tabla con todas ellas y el servidor la consulta antes que los archivos. Contra el sitio en vivo: 143 direcciones viejas, ninguna en 404 —55 que siguen siendo una página y 88 que la tabla lleva a su lugar nuevo—. Y una inventada sigue dando 404, que es lo que tiene que pasar.
Nada que hackear
Un escáner de WordPress empieza siempre igual: pregunta por las direcciones que todo WordPress tiene. Se hizo esa misma pregunta a los dos sitios, en modo pasivo —sólo pedidos de lectura a direcciones públicas, sin probar una sola contraseña—:
| dirección | qué es | WordPress | copia |
|---|---|---|---|
/wp-login.php | la puerta del login | contesta | 404 |
/wp-admin/ | el panel | contesta | 404 |
/xmlrpc.php | la puerta clásica de la fuerza bruta en lote | contesta | 404 |
/wp-json/ | la API | contesta | 404 |
/wp-json/wp/v2/users | la lista de quienes pueden entrar | contesta, con 3 nombres | 404 |
/?author=1 | el nombre de usuario del primer autor | lo dice | no |
/readme.html | la versión exacta | contesta | 404 |
/wp-admin/install.php | el instalador | contesta | 404 |
/wp-cron.php | tareas que dispara cualquiera | contesta | 404 |
Nueve de nueve contra cero de nueve. Además, el original anunciaba en su propio código la versión exacta de WordPress y de dos de sus plugins: exactamente lo que un atacante busca para cruzar con una lista de fallas conocidas. No es que la copia tenga esas puertas mejor cerradas: no las tiene. No hay PHP que ejecutar, ni base de datos que consultar, ni usuarios que adivinar.
Midiendo esto apareció un detalle que vale contar. La copia todavía decía «WordPress» en una etiqueta invisible del HTML, heredada del original: un escáner la leía y anunciaba un WordPress que ya no existía. El absorbedor ahora la saca.
¿Y los formularios de contacto, que en WordPress son un plugin más? En la copia los atiende una función WebAssembly que corre en el servidor con capacidades declaradas: puede guardar lo que llega, leer el reloj y mandar el aviso por correo, y nada más. No puede leer el disco; lo que no pidió no existe para ella. Los mensajes se leen en el panel, y bajan como planilla. La lupa de búsqueda que la copia puede sumar a su menú es otra función así: lee un índice que se publica junto con el sitio, y no hay servidor de búsqueda que atacar.
Y si además vende
Muchos WordPress son también una tienda, con WooCommerce. Una tienda no es un conjunto de páginas: es un catálogo con precios, variantes y existencias, y un cobro. Así que no se copia como HTML: con --as-content, qhatu lee el catálogo por la misma API pública que usa el carrito de WooCommerce y cada producto entra a la tienda propia de qhatu con su nombre, su precio exacto, su moneda, sus imágenes y una variante por cada combinación —talle, color—, con la que está agotada marcada como agotada. El precio nunca pasa por un número con coma: se guarda en centavos, y al comprar el servidor lo recalcula contra el catálogo publicado, así nadie paga lo que se le ocurra mandando otro número.
Se probó contra un WooCommerce de verdad, con los productos de ejemplo que trae el propio WooCommerce: 16 productos y 7 variantes entraron, y un instrumento que compara la tienda original contra la copia, producto por producto y variante por variante, dio iguales. Su brazo de control: después de absorber se cambió un precio y se agotó una variante en el original, y el instrumento nombró exactamente esas dos diferencias. Las reseñas de cada producto vienen con él, al pie de su página. Y con una contraseña de aplicación de WordPress —la que el propio WordPress deja crear para estos casos— viene también cuántas unidades quedan de cada producto y de cada variante: el servidor descuenta lo que se vende. Después se compró: una sudadera, al carrito, a pagar con PayPal en su entorno de pruebas, y la tienda volvió diciendo que el pedido estaba pagado, con la captura de PayPal anotada del lado del servidor.
Esa tienda está en línea, para verla y comprar con plata de mentira: tienda.gioser.net.
Y no parecen dos sitios pegados. Lo que qhatu arma —los productos, la lista, el carrito, las entradas del blog— va adentro de la cabecera, el menú, el pie, los colores y las letras del sitio original. Medido: la cabecera de una página que vino del original y la de un producto armado por qhatu dan 0 píxeles distintos. El menú del celular abre en la copia aunque el WordPress original ya esté apagado, y ya no ofrece «Finalizar compra» ni «Mi cuenta», que en la copia no existen.
Más rápido, y medido sin trampa
Cinco páginas, cinco cargas en frío de cada lado —un navegador nuevo por carga, sin caché—, alternando original y copia para que una mala racha de la red no caiga entera de un lado. Las medianas:
| página | primer byte | lo más grande pintado | carga completa |
|---|---|---|---|
| portada | 2,5 s → 0,9 s | 6,1 s → 4,9 s | 9,9 s → 5,9 s |
| página de servicio | 1,9 s → 0,8 s | 6,5 s → 3,8 s | 14,9 s → 5,4 s |
| página larga con galería | 2,3 s → 0,9 s | 11,7 s → 6,3 s | 22,3 s → 7,9 s |
| listado del blog | 2,4 s → 0,7 s | 4,5 s → 4,1 s | 5,1 s → 5,3 s |
| una entrada | 2,3 s → 0,8 s | 5,9 s → 2,5 s | 7,0 s → 5,2 s |
Los bytes que viajan son prácticamente los mismos: es la misma página. Lo que cambia es el servidor. Y hay una salvedad que un artículo honesto tiene que hacer: los dos sitios están en servidores distintos, en lugares distintos, y la copia quedaba más cerca de donde se midió. Así que se separó la red del software. Abrir la conexión cuesta 392 milisegundos hasta el original y 179 hasta la copia; eso es distancia, no código. Descontando una ida y vuelta de cada lado, lo que queda es el trabajo de armar la página:
Y en un teléfono hay algo más. Al publicar, qhatu prepara cada imagen grande en varios anchos y el navegador elige la que le sirve: en la portada del cliente, vista a 390 píxeles de ancho, se bajan 72 % menos bytes de imagen, sin mover un píxel en una pantalla de escritorio.
Y se sigue editando, en el lugar
Una copia estática que no se puede tocar sería un museo. La de qhatu se edita desde un panel que vive en su propia dirección —nada del sitio público lo enlaza—, sobre la página misma, tal como se ve:
- Clic en un texto y se escribe. Una barra ofrece negrita, cursiva, enlaces, listas, títulos y citas, y los colores son los del sitio, no un selector cualquiera. Pegar desde Word o Google Docs deja el formato y tira la basura.
- Clic en una imagen y se reemplaza. Elegir varias de una vez agrega fotos a una galería: cada una de más duplica el ítem y le pone la suya.
- Duplicar, mover y borrar cualquier bloque: una tarjeta, una sección, un ítem de una lista. Y una página nueva es la copia de otra, en la dirección que se quiera.
- La paleta y las letras del sitio a un costado. Los colores que usa la página, contados; se cambia uno y se ve al instante, y un botón lo aplica a las ochenta y tantas hojas de estilo que dejó Elementor. Las tipografías se cambian igual. Si un texto deja de leerse sobre su fondo, el panel lo dice con la medida de contraste en la mano.
- Publicar es un acto firmado. Guardar cambia la carpeta; publicar convierte el sitio en una versión nueva, firmada, que el servidor sirve entera o no sirve. La historia dice qué cambió entre dos versiones cualesquiera, página por página. Volver a la anterior es un botón, y deja también la carpeta como estaba.
Y lo que el original armó con Elementor ya no queda como un HTML que sólo se toca en el lugar: con --as-content entra como bloques de qhatu —portadas, columnas, carruseles, preguntas frecuentes— que se editan con el editor de bloques y se pintan con el tema del sitio. En el del cliente, dos de sus páginas de Elementor entraron como 22 y 12 bloques, cinco portadas con su foto de fondo y tres carruseles de diez a doce fotos, sin un solo widget que quedara sin equivalente y sin una imagen sin bajar.
Todo eso corre en saywa, el servidor web de tawasuyu. En la máquina donde vive este sitio, saywa y la forja propia del proyecto ya reemplazaron a cuatro programas: el servidor web, la forja de git, el proxy y el intérprete de PHP. Alguien escribió las dos piezas desde cero, en Rust, con una regla que se repite en todo el proyecto: lo que no se midió no se afirma.
Lo que todavía no
- El cobro con plata de verdad no se probó. La tienda cobra con PayPal, y de punta a punta se probó en su entorno de pruebas; con una cuenta real, todavía no.
- De WooCommerce se trae el catálogo y sus reseñas, no la historia. Los pedidos viejos, los clientes, los cupones y las reglas de envío e impuestos se quedan en el WordPress.
- Renderizar es lento. Con
--renderel sitio del cliente tardó unos 30 minutos en lugar de 6: Chrome carga cada página entera. - Hay cuentas de lectores, páginas sólo para miembros y boletín, pero no membresía paga.
- Los números de velocidad son de un día y desde un lugar. Por eso va la separación de la red: es la parte que no depende de dónde se mida.
Cómo probarlo
La tienda de este artículo está en tienda.gioser.net, y su panel está abierto: en tienda.gioser.net/_/ cualquiera puede entrar con «Probar como autor» y editarla a gusto; lo suyo se borra a las 24 horas. El mismo qhatu arma esta bitácora y el manual de tawasuyu, y con una línea más en su configuración se vuelve una wiki: enlaces por título, la historia firmada de cada página, y dos personas que editan la misma página a la vez sin pisarse. Para tener lo mismo en una máquina propia, una VPS recién creada alcanza: qhatu server install --domain el-sitio.com deja el servidor, el panel y el certificado andando —medido sobre Debian 13 y Fedora 44, con una autoridad de certificados de prueba: la portada contesta por HTTPS a los siete segundos de tener acceso a la máquina— y qhatu absorb se trae el WordPress. qhatu vive en el repositorio de tawasuyu —git.tawasuyu.net, en un servidor propio— bajo 02_ruway/qhatu, y saywa bajo 03_ukupacha/saywa. Los instrumentos de este artículo —exposición, velocidad, píxeles y el careo de la tienda— están en scripts/qhatu-careo/: cualquiera puede correrlos contra su propio WordPress y contra su copia, y comparar.