# El taller de UI

Aquí se **hacen** las maquetas. En [`../output/`](../output/) se **entregan**, y solo las que
Guille ha dado por cerradas: si dev abre la entrega, todo lo que encuentre está revisado.

```
ui/
├── output/          ENTREGA · lo que dev coge
│   ├── pages/       solo páginas revisadas
│   ├── new/         peticiones de componente, con su contrato
│   ├── registro-dev.md  el parte: qué hace la maqueta por encima del theme y por qué
│   ├── handoff.md   mapeo UI → bloques sek_c_* del theme
│   └── index.html   índice: qué está entregado y qué sigue en el taller
└── guille/          TALLER · esto
    ├── dev-*/       un build.mjs y un contenido.mjs por página
    ├── _staging/    lib.mjs (criterios de las 11) · etapa.mjs (módulos de las páginas de
    │                etapa) · lint-sistema.mjs (las reglas) · bloques de staging
    ├── pages/       las once maquetas
    ├── ux-irene/    los wireframes de UX, que son la fuente del contenido
    ├── publicar.mjs promueve a la entrega lo que consta revisado
    └── indice.mjs   genera la portada del taller desde el estado real
```

## Cómo se trabaja

**Un solo comando**, que hace el ciclo entero y no deja saltarse ningún paso:

```bash
npm i                    # una vez (cheerio)
node compilar.mjs        # compila las 7 → verifica → reglas del sistema → publica lo revisado
node compilar.mjs residencias porque-sek     # solo algunas (igual verifica todas)
```

**Si el linter de sistema falla, no se publica.** Es deliberado: la entrega no puede recibir una
página que incumpla algo que ya habíamos cerrado.

### Por qué existe ese linter

Cada regla que cerrábamos —enlaces en negro, divider del overtitle, tinta según el fondo, ritmo
40/16— se arreglaba **en la página que se estaba tocando**, y la siguiente nacía con el mismo
fallo. `verify-page.mjs` no lo veía: comprueba clases contra el CSS y anclas, no criterios de
diseño. Ahora cada acuerdo es un chequeo en `_staging/lint-sistema.mjs`.

**Cuando se acuerde una regla nueva, se añade ahí.** Es el sitio donde vive el criterio: si no
está en el linter, se volverá a romper.

### El trinquete

Una regla nueva sobre siete páginas ya hechas nace en rojo, y un linter que nace en rojo se
acaba desactivando. Por eso una regla puede marcarse `trinquete: true`: congela los casos que ya
había —los de `_staging/deuda.json`— y **solo salta si aparecen más**. La deuda vieja se limpia
cuando toque; la nueva no entra.

```bash
node _staging/lint-sistema.mjs           comprueba
node _staging/lint-sistema.mjs --fijar   recalcula deuda.json con los casos de hoy
```

Cuando limpias deuda vieja, el linter avisa («deuda a la baja») y con `--fijar` cierras el
trinquete un punto más abajo, para que no pueda volver a subir.

### Cotejar la composición

`lint-sistema.mjs` comprueba el **resultado**. `cotejo-helpers.mjs` comprueba el **método**: qué
funciones de `lib.mjs` importa cada `build.mjs`. Una página que no usa un helper que sus
hermanas sí usan tiene ese módulo montado a mano, y montado a mano es como sale distinto en cada
página.

```bash
node _staging/cotejo-helpers.mjs            la matriz de las siete
node _staging/cotejo-helpers.mjs infantil   solo esa, con lo que le falta
```

### Los dos comandos

- `/cotejar [página]` — el repaso completo antes de enseñar nada: compila, reglas, composición.
- `/feedback "el ítem"` — convierte un punto de la revisión en regla. La regla primero, el
  arreglo después. Es el que evita que un acuerdo se quede en la página que estabas mirando.

Para lo que solo se ve en pantalla —el contraste real de un texto sobre su fondo— está
`auditar-contraste.js`, que se pega en la consola del navegador.

Tras tocar `_staging/lib.mjs` hay que **recompilar las siete**: la librería es común y un
cambio ahí las afecta a todas. `compilar.mjs` ya lo hace.

## Qué es "revisado"

Lo que diga [`../context/revisadas.json`](../context/revisadas.json) — una línea por página, con
fecha y quién la cerró. Es la misma lista que pinta los checks del índice y la que usa
`publicar.mjs`, así que no hay dos verdades. Al cerrar una página se añade su línea y se
publica; si una se reabre, se quita y `publicar.mjs` la retira de la entrega.

## De dónde sale cada cosa

- **El montaje es 1:1 con staging**: `_staging/blocks/` guarda los bloques ya renderizados por
  el theme y las maquetas se arman recortando y rellenando ese HTML con cheerio. Por eso salen
  con las clases reales y no con marcado inventado. Se refrescan con `_staging/fetch-blocks.mjs`.
- **El contenido lo pone UX**: `ux-irene/` son sus wireframes, de donde salen copys y estructura.
- **Los assets son los del theme**: `assets` es un enlace a `../output/assets`, que a su vez
  guarda el CSS y el JS descargados de staging. `verify-page.mjs` comprueba **todas** las clases
  de la página contra ese CSS, porque está precompilado: una utilidad que no esté emitida no
  existe y el elemento sale a su tamaño intrínseco sin avisar.

## Dónde va cada cosa

Tres sitios, y la diferencia importa: es lo que evita que un arreglo se quede en una página.

- **`_staging/lib.mjs`** — los criterios que valen para **las once**: tinta, ritmo, trazo de
  icono, migas, espaciado. Si un acuerdo es global, va aquí y lo heredan todas.
- **`_staging/etapa.mjs`** — los módulos de las páginas de **etapa educativa** (Infantil,
  Primaria, Secundaria, Bachillerato). Salieron del build de Infantil el 24/08 justo para que
  Primaria no tuviera que copiarlos. Reciben todo por parámetro y no leen nada de quien llama.
- **`dev-<página>/`** — lo que es de **esa página y solo de esa**: su copy (`contenido.mjs`), sus
  fotos y las tres o cuatro piezas que leen ese contenido.

> **Una función copiada en un build es deuda, aunque funcione.** UCJC, Infantil y Espacios
> deportivos tenían copias de la librería, y por eso ningún arreglo global les llegaba: el mismo
> feedback volvía una y otra vez. `node _staging/cotejo-helpers.mjs` dice qué helpers no usa una
> página; cada uno es un módulo montado a mano que hay que justificar.

### El espaciado sale de un token

`esp(n)` devuelve el token si el valor está en la escala (`esp(24)` →
`var(--sek-primitive-spacing-space-24)`) y píxeles si no lo está, **a propósito**: inventar un
token para un número suelto es peor que dejarlo visible. Si no está en la escala, o falta el
token o el valor está mal elegido, y la regla `sin-valores-crudos` lo caza para que alguien lo
decida. Nomenclatura value-based (`space-24`), no t-shirt.

### Comprobar lo que el linter no puede ver

Hay fallos que solo existen **con un ratón de verdad**. Las anclas estuvieron rotas días con el
linter en verde: el arrastre de la barra capturaba el puntero al pulsar y el `click` no llegaba
nunca al enlace. Cada verificación daba bien porque un `.click()` desde la consola **no pasa por
los eventos de puntero**.

Cuando el ítem es de comportamiento —un enlace, un arrastre, un acordeón—, la comprobación es un
**clic real** (`browser_click` de Playwright, que mueve el ratón), no un `.click()` sintético. Y
se mira lo que el usuario notaría: que la URL cambie, que la página se mueva, que se pinte el
estado activo.

## Cuidado con

- **`closest()` de cheerio incluye el propio nodo.** Un `closest('div[class*="relative"]')` sobre
  un elemento que ya tiene esa clase se lo aplica a sí mismo y machaca su `style`. Ha pasado dos
  veces con el mapa.
- **Cortar `lib.mjs` por índices de texto.** Se llevó por delante cuatro funciones y un
  `return $.html()`, y las páginas salieron vacías o en texto plano.
- **Rutas absolutas.** Atan la maqueta a un Mac concreto; todo va relativo a `import.meta.url`.
- **Acentos graves dentro del CSS o el JS inyectado.** `CSS_REGLA`, `JS_BARRIDO` y compañía son
  template strings: un acento grave en un comentario parte la plantilla y el build revienta con
  un error que señala al comentario, no a la causa. Ha pasado **tres veces en un mismo día**.
- **Medir con selectores frágiles.** Ha dado dos falsos negativos seguidos: La Colmena «no estaba
  en 3×2» —lo estaba, con 1 px de diferencia— y el Cross «sin cards» —el texto estaba en otro
  nodo—. Si el dato y la captura no coinciden, **manda la captura**.
