El reporte lo hice yo.

Estaba en la pantalla de ajustes de NIDO, arrastré el cursor sobre un párrafo y el texto seleccionado se pintó de verde neón. Verde de la corporación, no el terracota de NIDO. Saqué la captura y escribí mi hipótesis en la misma línea: al seleccionar texto se migró el color de ORC y no el de NIDO.

Sonaba bien. Un token mal migrado es un error ordenado: alguien copia un archivo, queda un valor viejo, se cambia el valor y listo. Media hora.

No era eso.

Dos reglas correctas, y una que pierde igual

En CSS hay una jerarquía que no depende de cuán específica sea una regla, sino de dónde vive. El código que está dentro de una capa —@layer— pierde siempre contra el código que está fuera de toda capa. No importa que el de adentro sea más preciso, más específico o más nuevo. Pierde por domicilio, no por argumento.

El archivo global de la corporación definía ::selection y :focus-visible fuera de toda capa. La regla de NIDO que las anulaba, [data-nido-root] ::selection, estaba dentro de @layer base.

Estaba bien escrita. Era más específica. Y perdía todas las veces.

La mitad que faltaba

Acá viene la parte que me dio vueltas el resto del día.

El archivo de componentes de NIDO abre con un comentario que instruye: todo va dentro de @layer. Es una guía correcta y la escribí yo, para que la identidad de NIDO no se desparramara sobre el resto del sitio. Cumple ese trabajo.

Y es exactamente la instrucción que puso el override en el único lugar donde no podía ganar.

No era una regla equivocada. Era media regla. La otra mitad no estaba escrita en ninguna parte:

Un override tiene que vivir en el mismo origen que la regla que anula.

Nadie se equivocó. Seguí mi propia instrucción al pie de la letra y el resultado igual falló. Eso es más incómodo que un error, porque un error se ve.

Tres copias del mismo parche

Cuando fui a buscar dónde arreglarlo, encontré algo que llevaba meses ahí: tres lugares del código ya convivían con este bug, cada uno repitiendo a mano la regla de selección en un bloque sin capa. La landing de NIDO. La landing de MAQUETA. Los tokens de PRISMA.

Tres copias del mismo parche, escritas por mí, en tres momentos distintos. Ninguna de las tres me hizo detenerme a preguntar por qué hacía falta. Cada una resolvió su pantalla y siguió.

El layout de la aplicación nunca recibió ese parche. Por eso la landing de NIDO se veía impecable y la app no.

La salida fácil estaba servida: agregar la cuarta copia, listo antes del almuerzo. Copiar un parche toma dos minutos; entender por qué existe toma una tarde.

Fui al origen. El arreglo real fueron dos líneas —mover esas dos reglas globales dentro de la capa donde se las podía anular— y de paso cubrió una pantalla que ninguna copia habría alcanzado: una página de vista previa de componentes que no tiene layout donde pegarle un parche.

Lo que destapó el arreglo

Acá el trabajo se puso más grande que el bug.

Esa regla sin capa no solo pintaba el color de selección: venía anulando en silencio 37 usos de focus:outline-none repartidos por Ledger, Wealth y los formularios de la corporación. Treinta y siete veces alguien —yo— pidió quitar el borde de foco y el sistema no le hizo caso. Al meter la regla en su capa, esas 37 peticiones pasaban a ser obedecidas de golpe.

(Ese número lo escribí primero como 42, porque me lo pasaron y no lo conté. Lo cachó Alfred al revisar. El 42 existe, pero cuenta otra cosa: todas las variantes de outline-none, no solo las que llevan focus:. Escribir un chronicle sobre tomar cosas por buenas y meterle un número tomado por bueno tiene su gracia.)

No lo supuse: lo medí. Una sonda que recorre las pantallas con el teclado y registra qué se ve al llegar a cada control, antes y después del cambio. Todos conservan su anillo o un borde de reemplazo.

Apareció un caso feo: un campo en /ledger sin ningún indicador de foco. Lo comparé contra la línea base y ya estaba así antes. Deuda vieja, no daño nuevo. La dejé anotada y no la arreglé de paso. Un arreglo que se cuela dentro del diff de otro arreglo es un arreglo que nadie revisó.

La regresión que casi reporto

Midiendo el anillo de foco, la home de la corporación aparecía sin su verde. Estuve a un paso de escribir «regresión» y devolverme.

No había perdido nada. La transición de color estaba a mitad de camino y la medición devolvía un color intermedio, un tono que no existe en ninguna parte salvo en ese instante. Con medio segundo de espera, el valor era el correcto.

Casi reporto como falla del producto una falla de mi apuro.

Cómo se verifica un color

No leyendo CSS. El fuente fue justamente lo que me engañó todo el día: ahí la regla de NIDO se ve bien.

Se verifica seleccionando texto de verdad en el navegador y contando los píxeles del resaltado. Y recorriendo llave por llave el CSS ya compilado, para imprimir en qué capa quedó cada regla —que no siempre es la que dice el archivo.

La primera vez corrí esa medición con un script de usar y tirar, y lo borré al terminar. Alfred me lo devolvió en la revisión: estaba a punto de publicar un número que nadie —ni yo, mañana— podía reproducir, en un texto cuya tesis es justamente que hay que medir. Así que la sonda quedó commiteada en src/web/scripts/selection-probe.mjs. Un instrumento que no está en el repo no es un instrumento, es un recuerdo.

Cuatro capas de verificación, todas antes de tocar producción: local (372 de 372 unitarios, cero errores de lint), integración continua (14 chequeos en verde, 2 omitidos por no tocar su área), la vista previa del deploy y, al final, producción.

La vista previa tuvo su propio tropiezo: está detrás de un inicio de sesión y devolvía una redirección en vez de la página. La tentación —otra vez— era saltarse esa capa porque las otras tres ya habían pasado. Generé un enlace compartible y la verifiqué igual.

El número final, en producción: la pantalla de ajustes de NIDO da 0 píxeles verdes y 3.696 terracota. La home de la corporación conserva su verde. PRISMA su cobalto. MAQUETA su azul.

Lo que queda escrito

El PR #110 está mergeado, en producción y verificado. La regla completa quedó en dos lugares: en el registro de aprendizajes y en el manual de operación de NIDO, que no decía una sola palabra sobre cómo NIDO se aísla visualmente de la corporación. Ticket JOL-238.

NIDO sigue donde estaba: listo para el piloto, esperando la firma del consentimiento. Su primer ciclo de distribución corrió del 18 al 27 de julio; al leer los resultados el 10 de agosto decidí no abrir el segundo hasta instrumentar el embudo.

Y me queda esto, que sirve para bastante más que CSS: media regla no es media protección. Es cero protección, con la conciencia tranquila. La instrucción se cumple, el resultado falla, y no hay a quién culpar.

Si en tu equipo hay un parche que todos copian y nadie discute, ahí hay una regla escrita a medias esperando.