Cómo Una Regla CSS Arregló 20 Juegos a la Vez
Una línea de CSS. Un commit. Veinte juegos arreglados. Esta es la historia de un arreglo de bug que tomó treinta segundos en escribirse — y la arquitectura que lo hizo posible.
El Bug
El domingo pasado, recibimos un reporte de que los botones en nuestros juegos se sentían rotos en tablets. Los jugadores tenían que tocar dos veces para empezar un juego, reiniciar tras un game-over, o abrir ajustes. Algunos taps simplemente no se registraban. Otros tenían un retraso notable — lo suficiente para hacerte preguntar si el botón funcionaba.
El bug no estaba en un juego. Estaba en todos. Cada juego en Vibe Arcade usa elementos HTML <button> para la UI — empezar, pausar, reiniciar, ajustes. Y cada uno de esos botones se comportaba mal en dispositivos táctiles.
Si has lanzado para móvil, puede que ya conozcas al culpable. Los navegadores móviles añaden un retraso de ~300ms a los eventos de click en elementos que podrían ser parte de un gesto de double-tap-to-zoom. El navegador espera para ver si viene un segundo tap antes de disparar el click. En una UI de juego, 300ms es una eternidad.
El Arreglo
Aquí está el parche entero:
button { touch-action: manipulation; }
Eso es todo. Una regla CSS. La declaración touch-action: manipulation le dice al navegador: "Este elemento soporta pan y pinch-to-zoom, pero no double-tap-to-zoom." Con el double-tap-to-zoom fuera de la mesa, el navegador ya no necesita esperar 300ms para distinguir un solo tap del inicio de un double-tap. Los taps se disparan inmediatamente.
Añadimos esta línea a nuestra hoja de estilos compartida, hicimos commit y desplegamos. Cada juego del sitio — veinte en ese momento — heredó el arreglo al instante. Sin parches por juego. Sin coordinación de release. Sin conflictos de merge. Una línea, un commit, veinte juegos arreglados.
El Sistema Que Lo Hizo Posible
La razón por la que una regla CSS pudo arreglar veinte juegos es que los veinte comparten una sola hoja de estilos. Cada juego carga el mismo main.css — layout, tipografía, estilos de botón, estructura de secciones. Pero nuestros juegos no se ven todos iguales. El CSS estructural es compartido; la variación de color y tema es por juego. Cada juego sobrescribe un pequeño conjunto de propiedades personalizadas de CSS:
body {
--game-accent: #ff00ff;
--game-secondary: #00ffcc;
--game-bg: #0a0012;
--game-surface: rgba(255, 0, 255, 0.08);
}
La hoja de estilos compartida hace referencia a estas variables para colores y degradados. Las reglas de layout — tamaño de botón, espaciado de secciones, estructura de contenedor — son globales e idénticas en cada juego.
Por eso button { touch-action: manipulation; } funcionó como arreglo global. El comportamiento de los botones es estructural, no temático. Pertenece a la capa compartida. Cada juego lo hereda. Y cuando se construya un juego nuevo mañana, heredará este arreglo automáticamente — el arreglo es parte de la plataforma ahora, no un parche por juego que alguien tenga que recordar aplicar.
Arreglar Una Vez Globalmente: El Principio Más Amplio
El arreglo CSS cristalizó un principio que habíamos estado aprendiendo a las malas: cuando un bug afecta a cada juego, el arreglo debería vivir en exactamente un lugar. Hemos identificado tres ubicaciones canónicas donde un solo cambio se propaga por todas partes:
- La hoja de estilos compartida — para layout, tipografía y comportamiento de UI. Añade una regla una vez, cada juego la hereda. El arreglo de
touch-actiones el ejemplo más claro. - Un script de lint de integración de juego — para reglas estructurales y de cableado. Una comprobación basada en grep se ejecuta contra cada juego en cada cambio, capturando clases de bugs antes de que salgan a producción.
- Las reglas de autoría del constructor de juegos — patrones que nuestra pipeline de construcción sigue al crear juegos nuevos. Los arreglos se vuelven el predeterminado, así que los juegos nuevos nacen correctos.
Cada capa captura un tipo diferente de problema. Las reglas CSS arreglan presentación y comportamiento. Las reglas de lint capturan errores estructurales de cableado. Las reglas de autoría previenen categorías enteras de bugs de ser introducidas en primer lugar.
La Historia del Script de Lint
La misma semana del arreglo CSS, descubrimos algo peor que un bug cosmético. Nuestro widget de leaderboard tiene un método llamado checkScore que determina si la puntuación de un jugador califica para el leaderboard. Varios juegos estaban cableados para llamar al widget pero nunca invocaban checkScore — renderizaban la UI del leaderboard pero silenciosamente saltaban la lógica de envío de puntuaciones. Los jugadores terminaban juegos, veían un leaderboard, y sus puntuaciones se descartaban silenciosamente.
Esto había estado en producción durante semanas. Nadie lo notó porque el leaderboard seguía apareciendo — solo nunca se actualizaba con puntuaciones nuevas para esos juegos.
El arreglo para cada juego afectado era directo: añadir la llamada faltante a checkScore. Pero el arreglo más importante fue una sola regla de grep añadida a nuestro script de lint:
# Cada juego que incluye LeaderboardWidget debe llamar a checkScore
grep -l "LeaderboardWidget" games/*/index.html | while read f; do
grep -q "checkScore" "$f" || echo "FAIL: $f incluye LeaderboardWidget pero nunca llama a checkScore"
done
Esta comprobación ahora se ejecuta contra cada juego, en cada cambio. Capturó los juegos rotos existentes retroactivamente, y capturará cualquier juego futuro que cometa el mismo error. Una regla de grep, protección permanente contra una clase entera de bug.
Hay una filosofía aquí: las reglas de lint basadas en grep son órdenes de magnitud más baratas que pasadas sofisticadas de análisis. Son rápidas, obvias, y capturan los bugs que realmente ocurren en la práctica. Las ejecutamos primero, en cada cambio. Cuando un patrón de bug sigue recurriendo, la primera pregunta es: "¿Podemos escribir un grep para esto?"
Cuándo Los Barridos Por Juego Aún Son Necesarios
Estaríamos mintiendo si dijéramos que todo es un arreglo global de una línea. La misma semana que añadimos touch-action: manipulation, ejecutamos una migración en cada juego para mover los bloques <style> en línea a propiedades personalizadas de CSS. Eso significó abrir cada archivo de juego, reemplazar valores de color hardcodeados con referencias a variables, y probar que cada juego siguiera viéndose bien. Veinte archivos. Tiempo real.
Pero fue un costo de migración único. El patrón nuevo vive ahora en la capa global. Los juegos futuros lo heredan automáticamente. Nunca volveremos a hacer ese barrido.
La versión honesta del principio de arreglar-una-vez-globalmente: a veces tienes que hacer primero el barrido por juego para extraer el patrón, luego capturarlo en la capa compartida para que nadie lo repita. Este principio no se planeó desde el día uno. Se aprendió. Hicimos el trabajo doloroso por juego, notamos que lo seguíamos haciendo, y construimos la infraestructura para parar.
La prueba para saber si un barrido valió la pena: ¿terminó con una nueva regla en la hoja de estilos compartida, una nueva comprobación en el script de lint, o un patrón nuevo en la guía de autoría? Si sí, se pagó solo. Si solo arreglaste veinte archivos y no capturaste el patrón en ninguna parte, estarás haciendo el mismo barrido en tres meses.
La Conclusión
Una regla CSS arreglando veinte juegos no es magia — es el resultado de una hoja de estilos compartida de la que cada juego hereda. Una regla de grep capturando un bug de cableado a través de cada juego no es astuto — es el resultado de decidir que las comprobaciones estructurales deberían vivir en un lugar, no en veinte revisiones de código.
La parte satisfactoria no es el arreglo individual. Es saber que cada juego que construyas la próxima semana nacerá con el arreglo ya aplicado. Ese es el interés compuesto de arreglar cosas en el lugar correcto.
Lectura relacionada: ¿Qué Es Vibe Coding? · Herramientas de Vibe Coding: De Chatbots a IDEs con IA · Construyendo un Leaderboard Universal