Claude
Cuánto cupo queda. Mira el peor de los dos: ventana de 5 h y semanal.
- Vía libre — 50% o más libre
- Precaución — menos del 50%
- Alto — menos del 25%
Al abrirla también ves cuántas sesiones corren y cuántas más entran con la RAM libre.
El tablero es una página estática. No tiene backend, no tiene login y no consulta nada
en vivo: lee un solo archivo, data.json, que alguien más dejó escrito. Todo lo interesante
pasa antes de que abras el navegador.
~/panel-data.py, disparado por un timer de systemd cada 2 minutos.
Es el único que trabaja de verdad.
/var/www/kostik/: commits, rama, últimos 8 mensajes, archivos sin commitear.kostik-dev): CPU, RAM, disco, steal, uptime y si nginx / ssh / zabbix / los snapshots están vivos.Todo eso se escribe junto en /var/www/kostik/panel/data.json, de forma atómica
(se escribe un .tmp y se renombra: nunca vas a leer medio archivo).
app.js pide data.json al abrir y vuelve a pedirlo cada 60 s.
Dibuja las señales, el tablero de repos y los badges. Nada más.
data.json~/pages-deploy/run-tests.sh <sitio>, que corre la auditoría
(~/validar.sh) y Lighthouse, y guarda el resultado en test-results.json.
No corre solo: se dispara después de un deploy. Por eso los badges dicen “última corrida” y
guardan el commit contra el que se midieron — pueden estar atrasados respecto de la punta del repo.git archive y te muestra el sitio como estaba en ese momento. Lo único del panel
que no es un archivo estático.El panel es público y sin auth. Cualquiera que sepa la URL ve los mensajes de commit y puede navegar los snapshots de cualquier commit. Nada de secretos en los mensajes de commit (guía §8.1).
La barra fina que late abajo del encabezado es el latido del dato, no un adorno: si el colector se muere, el dato envejece y el encabezado cambia de estado — fresco hasta 5 minutos, sospechoso hasta 15, viejo pasado eso. El reloj es tu reloj local, sirve para comparar de un vistazo.
Están copiadas de las señales de vía del ferrocarril: cada una muestra el aspecto escrito además de encendido, así el estado nunca depende solo del color. Se clickean para ver el detalle.
Cuánto cupo queda. Mira el peor de los dos: ventana de 5 h y semanal.
Al abrirla también ves cuántas sesiones corren y cuántas más entran con la RAM libre.
Salud de la VM, vía Zabbix.
Steal = el porcentaje de CPU que el host le roba a la VM. Esta máquina idlea entre 6 y 14% (vCPU compartida): eso es lo normal, recién arriba de 18 hay algo que mirar.
Si el taller se está moviendo.
Adentro hay barras por repo y un selector hora / día / semana / mes. Si no la tocás, la ventana baja sola hasta la primera que tenga commits.
Una fila por repo, ordenadas por frescura. La franja de color de la izquierda es el calor: ámbar pleno = commiteado hoy, apagándose hasta gris a los 21 días.
El badge Guía ✓ de cada fila resume la corrida de
~/validar.sh: la checklist de la guía de sitios. ✗ es una falla (algo está mal
y hay que arreglarlo), ⚠ es un aviso (se puede vivir con eso, pero cuesta plata en bytes
o en posiciones). Acá está cada ítem que puede aparecer, aunque hoy esté en verde.
lang, title, viewport, meta-description, favicon, og-tagsRevisa el <head> del index.html y lista lo que no encontró.
Por qué importa: sin viewport el sitio no es usable en celular;
sin description y og-tags, Google y cualquier red social inventan el resumen y la
miniatura; sin lang los lectores de pantalla leen con el acento equivocado.
Cómo se arregla: copiar el <head> estándar de la guía (§3) desde
un sitio que ya esté en verde y adaptar los textos.
robots.txtQue exista y que su primera línea sea una directiva de verdad
(User-agent, Allow, Disallow, Sitemap o un comentario).
Por qué importa: es la trampa clásica de Cloudflare Pages. Si el archivo no existe,
Pages no devuelve 404: sirve el index.html como fallback. El crawler recibe una página HTML
donde esperaba reglas, la marca como malformada, y Lighthouse te baja SEO. Además es donde vive la política de
bots de IA (guía §13).
Cómo se arregla: poner un robots.txt real en la raíz del sitio, con
su Sitemap: apuntando al dominio de producción.
Pide http://127.0.0.1/<sitio>/ y espera un 200.
Por qué importa: es el chequeo más tonto y el que más veces salvó el día. Si esto
falla, el sitio está roto en la VM — permisos, nginx, un index.html que no está donde debería.
Cómo se arregla: mirar el error de nginx antes de tocar cualquier otra cosa.
<img> sin altCuenta las imágenes del index.html sin atributo alt.
Por qué importa: quien usa lector de pantalla no ve nada ahí. Google tampoco. Es la mitad de la nota de accesibilidad de Lighthouse.
Cómo se arregla: alt que describa la imagen. Si es puro adorno,
alt="" (vacío a propósito) también cuenta como resuelto.
Cualquier imagen publicada que pase el umbral de 200 KB. Muestra las 5 más pesadas.
Por qué importa: es lo primero que se come el tiempo de carga en celular. El puntaje de Velocidad casi siempre se explica acá.
Cómo se arregla: pasarlas a AVIF y reducirlas al tamaño en que se muestran (ver el pipeline de assets, §5). El original grande se guarda afuera de la carpeta publicada.
De las imágenes realmente referenciadas por el sitio, cuántas siguen siendo jpg / png / gif.
Por qué importa: AVIF ahorra entre 50 y 70% sobre el mismo jpg/png, con la misma pinta. Es el ahorro más grande y más barato que hay.
Cómo se arregla: convertir y actualizar la referencia. Ojo con la diferencia: este ítem mira lo que se sirve; el de arriba mira lo que pesa.
Compara los píxeles reales del archivo contra el ancho declarado en el
<img>. Avisa si el archivo tiene más de 2,2× lo necesario.
Por qué importa: estás mandando por la red píxeles que el navegador tira a la basura. Un 2× está bien (pantallas retina); 4× es puro desperdicio.
Cómo se arregla: reducir el archivo al doble del ancho declarado y guardar el original afuera de la carpeta publicada.
Archivos .css / .js que pasan los 100 KB.
Por qué importa: el JS bloquea el dibujado de la página mientras se descarga y se parsea, así que pesa más de lo que dice la balanza.
Cómo se arregla: minificar. Con una excepción honesta: los catálogos
(films_*.js, cartoons_*.js) son datos, no código — ahí la respuesta no es
minificar sino partirlos o servirlos como JSON. Está anotado como pendiente, no como olvido.
Archivos que están en la carpeta publicada pero que nadie referencia desde el
HTML, el CSS ni el JS. Excluye translations/ y los .json, que se piden dinámicamente.
Por qué importa: no le hacen más lenta la página al visitante (nunca los descarga), pero inflan el deploy, el repo y el backup. En kino y arjlover son decenas de miles de archivos y cientos de megas: son las capturas del catálogo espejado, y ahí el número es esperable.
Cómo se arregla: borrar si es basura de verdad. Si es material del catálogo, dejarlo y saber que ese ⚠ es ruido conocido, no una tarea.
Pregunta por HTTP/3 al dominio propio del sitio. Si el sitio no tiene subdominio (vive bajo el hub), el ítem no aplica y no cuenta.
Por qué importa: HTTP/3 arranca la conexión en menos vueltas; se nota en móvil y en redes malas. En Cloudflare es un interruptor, no un trabajo.
Cómo se arregla: activarlo en el panel de Cloudflare de esa zona.
El total en KB de la carpeta (sin _antes/ ni .git/) y cuántos
jpg/png hay contra cuántos avif/webp.
Por qué importa: no genera ni ✗ ni ⚠ — es el contexto para leer todo lo demás. Un sitio de 900 MB con 40 avisos y uno de 2 MB con 40 avisos no son el mismo problema.
La fuente de verdad es ~/GUIA_SITIOS.md. Esto es el índice comentado: qué dice
cada §, para saber a cuál ir. Los ítems de la auditoría de arriba son solo la parte que una máquina puede
chequear sola — la mayoría de la guía no la mide ningún validador.
Un sitio = una carpeta en /var/www/kostik/<sitio>/, que nginx sirve al instante y sin caché
en kostik.loadsavedelete.com/<sitio>/. Eso es dev. El subdominio propio lo cablea Nikolai
en Cloudflare. El hub linkea a cada sitio (§7).
Siempre lo mismo: index.html de una sola página con secciones ancladas,
style.css y script.js linkeados con ?v=N para bustear caché,
video/, og.jpg de 1200×630, logo en avif + png, y su .gitignore.
<head> estándarSe copia de un sitio que ya esté bien y se adapta. En orden: charset y viewport; title y
description reales, no placeholder; favicon SVG inline; Open Graph completo; Twitter card
summary_large_image; las fuentes auto-hosteadas — nunca Google Fonts, su CSS bloquea el
render 2–3 s en mobile; y el style.css?v=N.
Es lo que audita el ítem HTML falta…
El look es único de cada sitio (paleta de 4–6 colores, display + body + mono, un elemento firma
memorable; inspiración en httpster.net antes de definir nada). Lo que se reutiliza es el script.js,
siempre vanilla y sin build: initReveal(), el modal de video, la nav sticky, el i18n por
localStorage. Piso de accesibilidad innegociable: prefers-reduced-motion,
:focus-visible, alt, HTML semántico.
La trampa grande: las secciones data-driven (renderizadas desde un array en el JS) son invisibles para los crawlers de IA, que no ejecutan JavaScript. Ya no se acepta sin más: se mitiga con el resumen estático de §13.
§4.1 — Temas (lo que ves acá arriba, el chip “lente”): un sitio con varios temas no se hace agregando
temas, se hace escribiendo bien el CSS la primera vez. Las variables van por rol: --ink es
la superficie y --paper la tinta — nunca “oscuro” y “claro” —, y así el tema claro se da
vuelta sin tocar una sola regla. En el CSS no se escribe un color: se pide un rol, y un color crudo suelto
(sobre todo en el JS) se queda con el tema viejo puesto. Cada paleta se valida a WCAG
(~/panel-contraste.py, que las lee del propio style.css): corrió sobre el tema que ya
estaba en prod y lo reprobó. Lo que no se tematiza es el estado: los tres aspectos de señal siguen
siendo distinguibles en las 6 lentes y con la palabra al lado — un monocromo lindo que te esconda un servicio
caído no sirve para nada.
Principio “moderno primero”: solo tecnología moderna; la compatibilidad con lo viejo se sacrifica cuando cuesta performance — y al decidir, se estima cuánto cuesta (KB, ms, PSI) en vez de suponerlo. El peso en disco no es la restricción; las métricas y el SEO sí.
La sección más larga, y donde se gana la velocidad.
avifenc -q 55-72 --speed 5. Nada de jpg/png sueltos; la
única excepción es og.jpg, porque algunos scrapers viejos de redes no leen avif.codecs declarado en ambos <source> o el fallback no dispara.
Audio AAC, nunca Opus (Safari no lo lee en MP4 y se quedaría mudo).<source> está cableado.
Y el que se sale del modal de video del toolkit se sale del AV1: los sitios que lo sirven son
exactamente los que usan el modal; el que se arma un player propio queda afuera del patrón y del
test-av1.js a la vez (mccat, 2026-07-13). Un .av1.mp4 que pesa casi lo mismo
que el H.264 no es un encode fallado: el CRF adaptativo gasta el ahorro en calidad hasta llenar
el budget de 25 MiB.Lo auditan imágenes pesadas, solo-avif y resolución sobrada. Los aprendizajes medidos están en el Apéndice A.
El orden del control: curl por el 200 → ~/sanity.sh (todas las refs internas
responden) → ~/validar.sh (los ítems de arriba) → Playwright → Lighthouse mobile,
apuntando a 90+ en las cuatro categorías.
El chromium del sistema está roto (crashea con SIGTRAP en headless): se usa siempre el chromium
pineado de Playwright, en ~/browser-tools/. El gate previo al deploy es
preflight-sitio.js <url> — cero 404, favicon, robots, description, title, viewport, lang, sin
errores JS — y se corre dos veces: contra dev antes de subir, y contra el *.pages.dev antes
del switch de DNS.
404.html es obligatorio, y es el caso general de lo del robots.txt: sin él Pages contesta
200 con el index entero a cualquier URL inexistente. Eso genera soft-404 y, peor,
envenena el caché: si la URL cae bajo una regla immutable, el edge cachea la página como si
fuera el archivo, por un año. Le pasó a filmiki: tres hojas de sprites devolvían text/html y esas
fichas quedaban vacías en prod, idénticas en dev. Y al verificarlo, ojo: sondear una URL inexistente la
envenena — para probar el 404 usá siempre una ruta nueva.
Agregar la fila del sitio en /var/www/kostik/index.html siguiendo el patrón split-flap
existente, y actualizar el contador del pie.
Un repo por sitio, rama main. nginx bloquea /.git/, así que el repo no se puede
clonar por HTTP.
§8.1 es la regla que más caro sale olvidar. Este panel no tiene autenticación y su DNS no pasa por el
proxy de Cloudflare: cualquiera que abra la URL ve los mensajes de commit, el autor, la rama, y puede navegar
el árbol completo de archivos de cualquier commit de la historia vía snapshots. El
noindex pide no indexar, no impide leer. Consecuencias: nada de secretos en los mensajes ni en los
archivos —y no alcanza con borrarlos después, porque el snapshot sirve cualquier commit viejo: si pasó, hay que
rotar la credencial. Los secretos viven fuera de /var/www, en
~/.cloudflare.env y ~/.zabbix.env con permisos 600.
/tmp es un tmpfs chico (~987 MB): descargas y transcodes grandes van bajo
/home o /var. Si se llena, fallan hasta los echo./tmp, no en la carpeta del sitio.pip: python por python3 -m o binarios en ~/.local/bin.
node y npm sí están.El TLS no lo termina esta VM: hay un nginx reverse proxy en el router (192.168.3.1) que termina HTTPS
y nos manda HTTP plano con X-Forwarded-For. Por eso existe realip.conf: sin él el
access.log registraría al proxy en vez del visitante. Las visitas se leen con ~/visitas.sh
(“visita” = IP única por día; el grueso del tráfico crudo son bots, nunca contar líneas del log a lo bruto).
Desde la LAN el dominio público no responde: no hay NAT loopback.
Dev es esta VM; prod es Pages, bajo el dominio propio del sitio. Nada se toca directo en prod: se
edita en dev, se commitea, y cuando gusta se puja. El deploy siempre sale de
git archive HEAD — commit = deploy.
La caché es política fija, no se sube y baja el TTL: el HTML siempre revalida y las carpetas de assets
van immutable por un año, con ?v= para bustear. La trampa verificada: Pages
concatena los headers de todas las reglas que matchean en vez de pisarlos, así que en la regla
específica hay que des-setear primero con ! Cache-Control.
Hoy está en simulacro: nginx registra quién caería, pero no bloquea a nadie. Motivo del plan: el origen está expuesto (el dominio no pasa por el proxy de Cloudflare) y hay bots crawleándolo.
La traba para activarlo: el ~95% del tráfico entra por el proxy 172.16.0.1, que pisa el
X-Forwarded-For con su propia IP privada — la IP real del visitante se pierde y no se puede
geo-filtrar. Antes de encender el bloqueo hay que arreglar eso, o decidir qué pasa con ese tráfico.
El principio que ordena todo lo demás: estos sitios promocionan productos y trabajo humano. El diseño lindo se amerita por el esfuerzo que hay detrás, pero la clave es ser encontrable — por buscadores clásicos y por asistentes de IA, que hoy son vía principal de descubrimiento: si no estás en esa respuesta, no existís. Doble audiencia sobre el mismo HTML: lindo para el humano, estructurado para la máquina que lo trae.
Regla de oro: los crawlers de IA no ejecutan JavaScript. Todo lo que solo existe en
script.js es invisible para ellos. De ahí sale el resto:
<head>, con el tipo que corresponda. Estándar
consolidado: es lo que buscadores e IAs consumen directo.La trampa de robots.txt que nos mordió (RFC 9309): un grupo con User-agent nombrado
reemplaza al grupo *, no lo hereda. Si le escribís User-agent: GPTBot +
Allow: /, GPTBot ignora todos los Disallow que pusiste en *. Bien usado
es una herramienta: en kino y arjlover el catálogo está cerrado para todos y abierto para los bots de IA,
que es justo el material que queremos que citen.
Estado: §13 completa y verificada en prod en los 16 sitios (2026-07-11).
argentinafilma es el sitio modelo — el único con el set completo, incluido el feed Atom: copiar de ahí.
Los 3 catálogos ya son indexables (2026-07-13): filmiki, arjlover y kino tienen su catálogo
estático por letra + sitemap.xml. Un bot pasó de ver 44 palabras a 173.921 en kino y
71.308 en arjlover. Lo que enseñó: el display:flex en las filas cuesta 620 ms de
layout (perf 88) — sin flex y con content-visibility, kino sirve 659 filas con perf 100;
y el rojo de marca de los dos sitios no pasa contraste como texto (hay que usar la variante
oscura). Y no alcanza con generarlo: hay que linkearlo desde el pie (si el bot ve algo que el
visitante no ve, es cloaking) y corregir el llms.txt, que decía que la lista completa
no estaba en HTML.
<head> completo (§3) con canonical.og.jpg 1200×630.sanity.sh ✓ y validar.sh ✓ · PageSpeed mobile 90+ en las 4 categorías.git init + commit + entrada en la bitácora.Regla meta: medí, no supongas. Nada de esto era predecible por teoría; todo cambió al probarlo.
?v= debe incluir lo que cambia los píxeles (resolución, cuantizador), no solo la
lista de archivos — si no, el browser sigue sirviendo el sheet viejo immutable.setTimeout, nunca
requestAnimationFrame (se throttlea en tabs no visibles y en headless). Medido en kino:
Perf 66→78, TBT 680→310 ms.Los mide Google sobre la copia de la VM, en modo mobile. Escala 0–100: verde ≥90, ámbar ≥50, rojo <50, gris = sin dato.
Títulos, metas, robots, enlaces rastreables. Es el más fácil de dejar en 100 y el que más rinde: si esto no está bien, el sitio no existe para nadie.
Qué tan rápido carga y responde. Es el único que suele estar en ámbar, y casi siempre por imágenes pesadas. En catálogos gigantes, un 46 no es un bug: es el precio de servir miles de fichas.
Lector de pantalla, teclado, contraste,
etiquetas. Lo que más lo baja son los alt faltantes.
Seguridad, errores en consola, APIs obsoletas. Si baja, algo se rompió: mirá la consola del navegador.
run-tests.sh <sitio>, típicamente después de un
deploy. El badge guarda contra qué commit se midió: si el repo siguió avanzando, el badge habla del pasado.~/validar.sh <sitio>.data.json: el colector no corrió. Se revisa con
systemctl --user status panel-data.timer.