|
|
# PLAN — Subir el listón de los blocks (análisis comparativo + plan de mejora)
|
|
|
|
|
|
> Encargo del usuario (2026-07-24): _«todos los bloques hechos hasta ahora son
|
|
|
> simplones… no hay animaciones, no hay disposiciones o diseños que los hagan
|
|
|
> sobresalir… la peor versión de todos los que se analizaron»_. Este documento
|
|
|
> audita los 7 blocks hechos contra las referencias del dossier
|
|
|
> (`RESEARCH-blocks-references.md`) **y contra el propio framework**, y fija el
|
|
|
> plan para que cada block gane la comparación en vez de empatarla.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 1. Diagnóstico: qué compone realmente cada block (dato, no opinión)
|
|
|
|
|
|
Auditoría de imports reales en `src/uix/blocks/*/*.svelte`:
|
|
|
|
|
|
| Block | Componentes que compone |
|
|
|
| ------------- | --------------------------------------------------------------------------------------------------- |
|
|
|
| site-header | box · container · drawer · group · sticky |
|
|
|
| hero | box · container · grid · group · **heading** · section · stack · text |
|
|
|
| feature-grid | auto-grid · box · container · heading · section · stack · surface · text |
|
|
|
| feature-split | box · container · grid · group · heading · icon · section · stack · text |
|
|
|
| pricing | auto-grid · box · card · container · group · heading · icon · section · stack · text · toggle-group |
|
|
|
| testimonials | auto-grid · box · card · container · group · section · stack · text |
|
|
|
| faq | accordion · box · container · section · stack |
|
|
|
|
|
|
**Los 7 componen EXCLUSIVAMENTE la capa de layout** (`Box`/`Stack`/`Grid`/
|
|
|
`Group`/`Section`/`Container`) + tipografía base (`Text`/`Heading`) + `Card`/
|
|
|
`Surface`, más el componente de comportamiento que cada uno necesita
|
|
|
(`Drawer`, `Sticky`, `Accordion`, `ToggleGroup`).
|
|
|
|
|
|
**Uso de la capa expresiva del framework: CERO.** Ni un `Motion`, ni un
|
|
|
`Cascade`, ni un `Display`, ni un `TextGradient`, ni un `CountUp`, ni un `Aura`.
|
|
|
|
|
|
### El framework SÍ tiene esa capa — y no la usamos
|
|
|
|
|
|
| Componente del canon | Qué hace | Dónde debería estar |
|
|
|
| ---------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------ |
|
|
|
| **`Display`** | _«eidos single-component **hero typography** primitive»_ | **el hero** (usé `Heading`) |
|
|
|
| **`Cascade`** | escalona (stagger) la entrada/salida de sus hijos | feature-grid, testimonials, pricing, stats |
|
|
|
| **`Motion`** | animador de un elemento con presets (`scale-fade`, `slide-fade`, `spring-pop`…) | entradas de cualquier pieza |
|
|
|
| **`TextGradient`** | degradado animado sobre texto real; acepta `colors="aurora"` | titulares de hero/CTA |
|
|
|
| **`CountUp`** | cuenta con spring **al entrar en viewport**, locale-aware, respeta reduced-motion | stats-band, pricing, métricas |
|
|
|
| **`Mockup`** | cromo de navegador/teléfono (lo construí en F2.3b) | hero, feature-split, testimonials |
|
|
|
| **`Aura`** | escena ambiental | fondos de sección |
|
|
|
| `TextBlur` · `TextFocus` · `TextScramble` · `TextCircular` | familia de efectos de texto | acentos puntuales |
|
|
|
| `Carousel` · `Timeline` · `Metrics` · `Image` | — | testimonials, stats, content |
|
|
|
|
|
|
### Por qué salieron planos: la causa es ESTRUCTURAL, no de gusto
|
|
|
|
|
|
El contrato B prohíbe que un block traiga `.css` propio (D-BLK.2). Es una regla
|
|
|
buena — mantiene el tier componiendo en vez de pintando. Pero implica una cosa
|
|
|
que no vi a tiempo:
|
|
|
|
|
|
> **Un block solo puede ser tan expresivo como los componentes del canon que
|
|
|
> compone.**
|
|
|
|
|
|
Con solo primitivas de layout disponibles y **sin primitiva de scroll-reveal ni
|
|
|
de fondo decorativo**, el resultado plano era el único posible. Mi error no fue
|
|
|
"no decorar los blocks": fue **no detectar que faltaban primitivas en el canon y
|
|
|
construirlas** (como sí hice con `Mockup`), y dar por hecho un block que compone
|
|
|
bien pero no gana la comparación.
|
|
|
|
|
|
**Corolario que dirige el plan**: la mejora NO es maquillar 14 blocks uno a uno.
|
|
|
Es **construir 2–3 primitivas que faltan y aplicarlas sistemáticamente** — sube
|
|
|
todo el tier a la vez y queda arquitectónicamente limpio.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 2. Los dos huecos del canon que bloquean el acabado
|
|
|
|
|
|
### Hueco 1 — No hay scroll-reveal
|
|
|
|
|
|
`Cascade` escalona, pero se dispara por `open` (disclosure), no por viewport.
|
|
|
Toda la maquinaria existe ya a nivel de art:
|
|
|
|
|
|
- `eidos.dom.observeIntersection(el, cb)` (adom)
|
|
|
- `src/arts/adom/is-in-viewport.svelte.ts` (primitiva reactiva)
|
|
|
- `eidos.dom.prefersReducedMotion`
|
|
|
|
|
|
`CountUp` la usa **en privado**. Nada la expone como composable. Sin esto, cada
|
|
|
sección aparece de golpe: el rasgo nº1 que separa a Linear/Vercel/Stripe de un
|
|
|
dump estático.
|
|
|
|
|
|
→ **Resuelto**: `Motion` gana `trigger="viewport"` (NO un componente nuevo — `Reveal` se creó y se retiró por ser un fork) y `Cascade` gana el mismo disparo.
|
|
|
|
|
|
### Hueco 2 — No hay fondo decorativo de sección
|
|
|
|
|
|
No existe `backdrop`/`pattern`/`glow`/`mesh`/`noise`/`spotlight` en el canon.
|
|
|
Solo hay **un** gradiente nombrado (`--gradient-aurora`) y los acabados de
|
|
|
`Surface`. Las referencias viven de esto (glow radial tras el hero, rejilla de
|
|
|
puntos, mesh, viñeta).
|
|
|
|
|
|
→ **Candidato a canon: `<Backdrop>`** (patrones + glow + mesh sobre tokens).
|
|
|
|
|
|
---
|
|
|
|
|
|
## 3. Comparativa por block: anatomía (dossier) + acabado (nuevo)
|
|
|
|
|
|
**A** = brecha de anatomía ya registrada en el dossier §P1.
|
|
|
**B** = brecha de acabado (nueva, la que motivó este plan).
|
|
|
**C** = superación que podemos meter y ninguna referencia puede.
|
|
|
|
|
|
| Block | A · anatomía que falta | B · acabado que falta | C · superación disponible |
|
|
|
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
|
| **hero** | presets de media ✔(Mockup) · **form-in-hero** · logo-strip como slot · video | `Display` en vez de `Heading` · `TextGradient` en el titular · `Backdrop` (glow/mesh) · reveal escalonado · parallax suave del mockup | tipografía fluida por tokens + dark/RTL/density gratis |
|
|
|
| **feature-grid** | variante en `Card` · numerada | `Cascade` al entrar · hover con elevación/acento en el item · iconos con más presencia | stagger con reduced-motion correcto |
|
|
|
| **feature-split** | ~~banda de fondo por fila~~ → **por SECCIÓN** (enmienda firmada 2026-08-18, ver nota abajo) · media pegajosa | reveal por fila (media y copy con desfase) · ~~`Backdrop` alterno~~ → `Background` en la sección | `Mockup` real (ninguna ref lo compone, lo dibujan a mano) |
|
|
|
| **pricing** | **tabla de comparación** · single-price | transición del precio al cambiar periodo (`Motion`) · destaque del featured (glow/escala) · `CountUp` en el importe | precio locale-aware (`FormatNumber`) + toggle con estado real |
|
|
|
| **testimonials** | **cita en spotlight** (la variante modal de TODAS las refs) | `Cascade` · logos de empresa · comillas decorativas · avatar con anillo | comillas locale-aware (territorio sin reclamar) |
|
|
|
| **faq** | **lista estática 2/3 col** (6 de 7 en TW) · cola de soporte como slot | transición de apertura con carácter · numeración/iconografía | teclado+ARIA del `Accordion` del canon |
|
|
|
| **site-header** | flyout ✔ · banner hermano | fondo con blur al pegarse (`[data-stuck]` ya lo da) · transición de la sombra | `data-stuck` = polyfill del `scroll-state()` futuro |
|
|
|
| **stats-band** _(pendiente)_ | split-with-image · timeline | reveal + `Backdrop` | **`CountUp`: ninguna ref puede shippearlo** (markup estático) — la superación literal |
|
|
|
| **cta** _(pendiente)_ | arreglo **justified** · split-with-media | `Backdrop` + gradiente de acabado | — |
|
|
|
| **newsletter** _(pendiente)_ | nota de privacidad | estados (enviando/éxito) con `Motion` | validación real del `Form` del canon |
|
|
|
| **site-footer** _(pendiente)_ | newsletter en footer · **selector de idioma** | — | el selector de idioma encaja con `langs` (ninguna ref lo tiene funcional) |
|
|
|
| **banner · team · contact · content-section** _(pendientes)_ | — | al nivel nuevo desde el día 1 | — |
|
|
|
|
|
|
> **Enmienda 2026-08-18 · la banda de `feature-split` va por SECCIÓN.** La columna
|
|
|
> B pedía `Backdrop` alterno POR FILA. Ejecutado en F5 del eje Background quedó en
|
|
|
> la sección, y la enmienda confirma esa forma: una banda por fila obliga a cada
|
|
|
> `Row` a poseer el estado de alternancia —saber su índice y el de sus hermanas—, y
|
|
|
> eso es coordinación entre partes. Un block que coordina deja de ser un block que
|
|
|
> no posee nada (doctrina de `blocks.md`). Si la alternancia se quisiera de todos
|
|
|
> modos, el lugar correcto no es el block: es un selector de la receta
|
|
|
> (`:nth-child`), y entonces es una decisión de eidos, no de composición.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 4. Plan
|
|
|
|
|
|
### F-Q0 · Primitivas que faltan en el canon _(la palanca)_
|
|
|
|
|
|
| # | Pieza | Por qué |
|
|
|
| ---- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
|
| Q0.1 | ~~`Reveal`~~ → **`Motion trigger="viewport"`** (+ `rootMargin`, `threshold`, `once`, `delay`) | HECHO. Se creó un `Reveal` aparte y se consolidó: era un fork de `Motion`, que ya era el animador de un elemento — solo le faltaba el CUÁNDO |
|
|
|
| Q0.2 | **`Cascade` por viewport** — `trigger="viewport"` además de `open` | HECHO |
|
|
|
| Q0.3 | **`Backdrop`** — fondo de sección: `glow` · `mesh` · `grid` · `dots`, todo sobre tokens | HECHO — y **superado el 2026-08-18** por [`Background`](../../src/uix/eidos/components/background/README.md), que lo absorbe: las cuatro tramas viajan valor por valor a `Background.Pattern` (más `lines` · `noise` · `rings` · `vignette`), y donde `Backdrop` envolvía a su contenido y pintaba UNA capa en un `::before`, la pila se cuelga del anfitrión como hija y apila cuantas capas haga falta — imagen, vídeo, gradiente, velo. El plan y las decisiones firmadas: [`PLAN-background.md`](./PLAN-background.md) |
|
|
|
|
|
|
Los tres siguen el patrón `Mockup`: componente eidos + morfo mínimo, recipe
|
|
|
sobre tokens, cero color a mano.
|
|
|
|
|
|
### F-Q1 · Pasada de acabado a los 7 hechos
|
|
|
|
|
|
Por block: aplicar A (anatomía) + B (acabado) de la tabla §3. Orden sugerido por
|
|
|
impacto: **hero → pricing → feature-split → testimonials → feature-grid → faq →
|
|
|
site-header**. El hero primero como **prueba del nuevo listón**: si no gana
|
|
|
al lado de la referencia, se itera antes de replicar.
|
|
|
|
|
|
### F-Q2 · Los 7 pendientes, ya al nivel nuevo
|
|
|
|
|
|
stats-band (con `CountUp` — la superación literal) → cta → newsletter →
|
|
|
site-footer → banner → team → contact → content-section.
|
|
|
|
|
|
### F-Q3 · Página compuesta como prueba de aceptación
|
|
|
|
|
|
Todas las refs shippean páginas compuestas (TW 10 · Untitled 105). La nuestra
|
|
|
—los 14 blocks en una landing real— es el test de que el conjunto respira.
|
|
|
|
|
|
### Cambio en la definición de "hecho"
|
|
|
|
|
|
Un block **no** está hecho cuando compone bien y pasa `blocks:check`. Está hecho
|
|
|
cuando, **puesto al lado del mejor ejemplo de las referencias, gana**. Añadir al
|
|
|
checklist del contrato B: ¿tiene movimiento? ¿profundidad/fondo que lo
|
|
|
distinga? ¿una disposición que no sea la rejilla obvia? ¿tipografía con
|
|
|
presencia?
|
|
|
|
|
|
---
|
|
|
|
|
|
## 5. Riesgos
|
|
|
|
|
|
- **Sobre-animar**: el movimiento debe respetar `prefers-reduced-motion` (la
|
|
|
maquinaria ya lo hace) y no retrasar la lectura. Presets sobrios, no circo.
|
|
|
- **Romper el contrato B**: nada de `.css` en blocks — todo el acabado entra por
|
|
|
primitivas del canon. Si algo no se puede, es que falta una primitiva.
|
|
|
- **Regresiones**: `Reveal`/`Backdrop` tocan el canon → suite eidos + barrido de
|
|
|
demos, como en los fixes de `Grid` y `Accordion`.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 6. Auditoría posterior (2026-07-29) — deuda que dejó esta pasada
|
|
|
|
|
|
Una auditoría multi-agente (7 dimensiones doctrinales + refutación adversarial)
|
|
|
confirmó **32 hallazgos** sobre el trabajo de la pasada. Lo corregido dentro del
|
|
|
tier queda en los README de cada block; lo que cae **fuera de `blocks/`** queda
|
|
|
registrado aquí como deuda, sin tocar:
|
|
|
|
|
|
| # | Dónde | Qué |
|
|
|
| --- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
|
| A1 | `eidos/components/motion/motion.svelte` | con `trigger='viewport'` no estampa `data-state` hasta que dispara el observador, y su morfo lo declara `always`+`required`. `Cascade` —construido en la misma pasada— sí lo hace bien: estampa el estado y deja la espera en un attr aparte |
|
|
|
| A2 | `motion.css` · `cascade.css` | el `opacity: 0` de espera solo se levanta con la media query del SO; la preferencia de app proyectada como `data-motion='reduce'` NO lo levanta |
|
|
|
| A4 | `motion.svelte` (`leave`) | la salida no pasa por el motor: un preset JS no anima al salir y el nodo se retiene 200 ms |
|
|
|
| C7 | `motion.css` + su README | crear el recipe activó la regla de auditoría R-1.1 (sin selector raíz `[data-motion]`) y el README declara la excepción CONTRARIA («ships no CSS») y sigue diciendo «no JS engine» |
|
|
|
| C8 | `eidos/components/{mockup,backdrop}` | sin README (162 de 165 componentes lo tienen) |
|
|
|
| C10 | `docs/theming/motion.md` | no documenta el `trigger` nuevo ni la llamada al motor |
|
|
|
| D12 | `morfo/components/backdrop.ts` · `cascade.css` | citan `reveal.ts`, borrado; y `Cascade` emite `data-reveal-pending` — nombre de un componente que ya no existe — para el mismo concepto que `Motion` llama `data-animation-pending` |
|
|
|
| E14 | `motion.css:42` | `--motion-stagger-each-default` no existe en ninguna parte: el knob es ficción y siempre vale el literal 70ms. Ya existe el canónico `--motion-stagger` — **✅ la MITAD del diagnóstico, ejecutada 2026-08-26** (firma opción 1 del autor; commit del coordinador): tenía razón, el nombre no se declaraba en ningún sitio. El knob deja de ser ficción — `primitives.motion.staggerViewport: '70ms'` → `--motion-stagger-viewport`, y la regla emigra de `motion.css` a `renderMotionBlocks`. **La otra mitad —colapsarlo en `--motion-stagger`— queda APLAZADA como firma de DISEÑO propia**: el cableado conserva 70 ms verbatim porque 70 → 20 ms **cambia el píxel en 8 blocks** (team · stats-band · feature-grid · hero · article-grid · faq · pricing · testimonials — la entrada se comprime a menos de un tercio; deltas de ~67 ms medidos en `AUDIT-blocks-ledger.md`) y colapsa dos trabajos perceptuales en un knob: un menú ondea a 20 ms, una sección respira a 70. Espera firma del autor. Acta: `docs/theming/changelog.md` §57 |
|
|
|
|
|
|
### Añadidos al construir `cta` (2026-07-30) — medidos en navegador
|
|
|
|
|
|
Tres cosas que salieron al componer F2.8, todas verificadas con sonda de
|
|
|
luminancia / `getComputedStyle`, no a ojo. Caen fuera de `blocks/`, así que
|
|
|
tampoco se han tocado:
|
|
|
|
|
|
| # | Dónde | Qué |
|
|
|
| --- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
|
| F15 | `eidos/components/surface` | `variant='soft'` no puede acotar un panel: el track de `primary` mide `oklch(0.9932 0.0034 325.6)` contra un `--color-surface-default` de `oklch(0.9911 0 0)` — **0.002 L** — y `Surface` no tiene prop de borde. `Card outline` sí acota pero no acepta `gradient`, así que «panel sosegado con borde» no tiene primitivo. Por eso `cta` **eliminó** su prop `variant` |
|
|
|
| F16 | paleta (`contrast` slot) | la ranura de contraste es `#ffffff` en **todo** escalón sólido, así que un lienzo de luminancia media deja el cuerpo de texto por debajo de AA. Medido sobre el panel: `primary` 5.18 · `indigo` 5.21 · `plum` 4.75 (pasan en ambos modos) vs `neutral` 3.32 · `secondary` 3.30 · `slate` 3.30 · `teal` 3.07 (fallan en claro). La garantía de emparejamiento solo se cumple en los lienzos oscuros |
|
|
|
| F17 | `eidos/components/text` | `align` es **inerte** por defecto: el componente renderiza un `span` y `text-align` no hace nada sobre una caja inline. `align="center"` dejó la copia alineada a la izquierda dentro de un layout centrado sin avisar de nada. Se rodea con `as="p"`, pero el prop anuncia un efecto que no tiene hasta que el consumidor cambia el elemento |
|
|
|
|
|
|
### Añadidos al construir `newsletter` (2026-07-30) — el tier de formulario
|
|
|
|
|
|
| # | Dónde | Qué |
|
|
|
| --- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
|
| F18 | fundación de `eidos` (`index.css`) | **No hay reset de modelo de caja.** Recetas como `[data-field-control]` declaran `inline-size: 100%` + `padding-inline`, así que bajo `content-box` el control mide **30px más que su contenedor**: en la fila `1fr auto` del newsletter el campo se metía por debajo del botón de envío. Medido: campo 480 / control 510 en la galería de blocks frente a 502 / 502 en los docs de componentes, cuyo `uix.css` resetea `box-sizing` bajo `[data-uix-docs]` (y `web/routes/active/styles.css` hace lo mismo). O sea: el framework **asume** que el app pone `border-box` y esa asunción solo está documentada porque app-land la cumple dos veces. Arreglado en mi lado con `web/routes/blocks/_lib/reset.css` (A/B sobre los 10 previews y 5 páginas de shell: cambia el newsletter y NADA más). La pregunta de canon es si la fundación debería poseerlo en vez de asumirlo |
|
|
|
| F19 | `soma/components/form` (`form.svelte` + `types.ts`) | **`onValidSubmit` / `onInvalidSubmit` son no-op silenciosos cuando se pasa un `form` ya construido.** El componente solo los reenvía al `createForm` que hace él mismo (la rama `defaults`); con un handle externo se ignoran sin aviso, aunque el tipo los documenta como «Called when validation passes». El envío validaba, limpiaba el error y no anunciaba nada. El block los quitó de su API: el handler va en `createForm` |
|
|
|
| F20 | `libs/forms` + `web/routes/uix/components/form` | Los mensajes de SIUM llegan como **idlangref** (`#?sium.errors.email | Must be a valid email address`). La vía soportada es `uix.langs.t(issue.message, issue.params)`— verificado, resuelve el catálogo («Debe ser una dirección de correo válida»), no el fallback inglés. Pero la demo de docs del propio`Form`**parte la cadena a mano** tras el` | `con un helper local`fallbackMessage`, así que el único ejemplo del repo enseña el patrón equivocado y siempre muestra inglés |
|
|
|
|
|
|
### Añadidos al construir `site-footer` (2026-07-30)
|
|
|
|
|
|
| # | Dónde | Qué |
|
|
|
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
|
| ~~F21~~ **CERRADO 2026-08-19** — `Box` gana `as` (lista cerrada de elementos contenedores, default `div`), así que `Stack`/`Flex`/`Grid`/`Group`/`Wrap`/`Container`/`Section` heredan el polimorfismo; `Section` lo usa para renderizar `<section>`. La columna de enlaces del pie ya puede ser `ul`/`li`. | `eidos/components/box` (y todo lo construido sobre él) | **Los primitivos de layout no pueden cambiar de elemento.** `Text` y `Heading` aceptan `as`; `Box` —y por tanto `Stack`, `Flex`, `Grid`, `Group`, `Wrap`, `Container`, `Section`— renderiza un `<div>` fijo. Consecuencia concreta: una columna de enlaces de pie no puede ser `<ul>/<li>`, que es como la marcan las referencias. Un block solo puede elegir entre divs o escribir markup que luego no puede estilar (contrato B). La asimetría (tipográficos sí, layout no) ES el hallazgo |
|
|
|
| F22 | `soma/components/select` | **Un `Select` controlado muestra el VALOR crudo hasta que se abre una vez.** `getDisplayText()` resuelve contra un registro de etiquetas que llenan los `Select.Item` AL MONTARSE; con el `Content` dentro de `Select.Portal` y cerrado no hay ninguno montado, así que `value=['es']` pinta «es» en vez de «Español». Se rodea con el `child` de `Select.Value`, pero el caso «select controlado que aún no se ha abierto» es el más común de todos |
|