- Introduced PickerHelp component for contextual help in pickers. - Created PickerBrowserFixture for testing picker components in a browser-like environment. - Implemented a comprehensive regression test suite for picker UI using Playwright. - Added support for localization in picker components and tests.
163 lines
19 KiB
Markdown
163 lines
19 KiB
Markdown
# L02.1 — Selectores tabulares, capacidad móvil y accesibilidad
|
||
|
||
Fecha: 22 de octubre de 2026. Implementación derivada de [L01](auditoria-listados-l01.md), limitada a selectores, identidad mínima y microcopy. No implementa L02.2 ni V05/V07/V08/V12.
|
||
|
||
## 1. Resumen de la implementación
|
||
|
||
Los selectores locales siguen utilizando FormDialog, FormBody y EntityTable. Se corrigió su cadena flex y se compuso una barra de herramientas y paginación apropiadas para el espacio de selección. Los listados generales conservan su presentación. La altura exterior, selección provisional, filtros, ordenación por cabecera y operaciones de confirmar/cancelar se mantienen.
|
||
|
||
La ayuda de campos utiliza `PickerHelp`, compuesto con Popover de Base UI/shadcn: un botón junto al pie permite consultar sección de destino e instrucciones completas, también con teclado. No hay focus trap ni listener Escape nuevos.
|
||
|
||
## 2. Hallazgos corregidos y alcance
|
||
|
||
| Hallazgo | Resultado y límites |
|
||
| --- | --- |
|
||
| L01-01 | Corregido el colapso de los selectores tabulares comprobados: campos de activos/recursos, opciones de perfil y activos del puesto. Los selectores remotos se verifican por separado. No se declara corregida la densidad inicial de **listados generales** señalada bajo el mismo ID en L01. |
|
||
| L01-02 | Selección e identidad protegidas en OptionPickerList; nombre protegido también en DashboardDetailsDialog y AssetRelatedInformation. No se hacen obligatorias todas las columnas. |
|
||
| L01-04 | AssetSelect en presentación de acción conserva como nombre accesible su contenido visible; la presentación de campo incluye etiqueta y valor. Rangos de selectores usan nombres de entidades y pluralización es/en según el número filtrado. |
|
||
|
||
Las medidas y reproducciones originales de L01 se conservan. V05, V07, V08 y V12 permanecen pendientes; el selector de columnas sigue sin búsqueda/grupos/restauración. No se modifica el estado de V01/V02/V03/V06/V09 ni otros hallazgos.
|
||
|
||
## 3. Causa técnica del colapso
|
||
|
||
La reproducción previa mediante MCP confirmó en 320×640:
|
||
|
||
| Nivel | Distribución efectiva antes del cambio |
|
||
| --- | --- |
|
||
| DialogContent | Flex columna; altura exterior 608 px, máximo 608; overflow hidden. |
|
||
| Contenido del diálogo | Flex columna, grow/shrink 1, min-height 0; alto 509 px y overflow hidden. |
|
||
| FormBody | Flex columna, grow/shrink 1, min-height 0, overflow auto; clientHeight 332 y scrollHeight 604. |
|
||
| EntityTable fill | Flex columna, grow 1, **shrink 0 impuesto por el padre**, mínimo 384 px; clientHeight 382, scrollHeight 387. |
|
||
| TableContainer | Grow/shrink 1, min-height 0, overflow auto; **clientHeight 0**, scrollHeight 950. |
|
||
| Pie de acciones | Alto aproximado 157 px. |
|
||
|
||
El mínimo de tabla no garantizaba filas: sus bandas no reducibles lo consumían. La ayuda y destino añadían altura al cuerpo, pero no eran la única causa. Desplazar FormBody no recuperaba el contenedor colapsado. `before-results.json` conserva display, dirección, grow/shrink, mínimos/máximos, overflow y geometría de cada ancestro.
|
||
|
||
## 4. Cambios de composición y presupuesto
|
||
|
||
- FormBody en contexto picker deja de imponer shrink 0 a todos sus hijos; conserva min-height 0, margen para foco y scroll exterior de contingencia. No cambia los modos content/stable ni su `contentLayout="fill"`.
|
||
- OptionPickerList, AssetPickerList y el wrapper Tabs de AssetList propagan min-height 0. La tabla se encarga de su presupuesto, en lugar de sumar mínimos independientes en cada wrapper.
|
||
- EntityTable `layout="fill"` agrupa búsqueda y configuración en una barra. En contenedores inferiores a 32 rem, el selector de orden redundante se omite; las cabeceras continúan permitiendo ordenar. Columnas permanece accesible. El listado general `layout="content"` conserva las bandas previas.
|
||
- El picker muestra un único rango en el pie, sin duplicarlo arriba. La paginación incluye tamaño de página etiquetado, anterior/siguiente y página actual/total. No se elimina la navegación de páginas ni se repagina una respuesta remota.
|
||
- Mínimo de tabla local: 18 rem, presupuesto para herramientas compactas, cabecera y opciones. No es altura fija de filas ni reducción de tipografía/controles. Las filas siguen creciendo por su contenido.
|
||
- FormActions en contexto picker distribuye Cancelar/Confirmar en la misma fila cuando caben; el alta relacionada conserva una fila propia en móvil. Cabecera y pie siguen fuera del scroll de resultados.
|
||
- PickerHelp conserva instrucciones completas y destino fuera del flujo que reparte las filas. Abrirlo no altera la altura exterior; su contenido extenso tiene desplazamiento limitado al espacio disponible.
|
||
|
||
En las ventanas ordinarias probadas solo los resultados necesitan scroll vertical. En ventanas de altura insuficiente o texto ampliado, el mínimo operativo permite scroll adicional de FormBody para alcanzar herramientas/resultados; `overscroll-contain` evita encadenar el gesto hacia el otro scroll. Este fallback es deliberado, no una promesa de dos filas en toda altura o longitud de texto.
|
||
|
||
## 5. Política de identidad mínima
|
||
|
||
`hideable:false` expresa la política existente. En OptionPickerList se protegen selection y name; description sigue siendo configurable. FieldPickerList y AssetPickerList ya protegían selección/nombre. La revisión de consumidores detectó además nombre ocultable en el resumen de compañías del dashboard y en los perfiles asociados a un activo: ambos se corrigen con la misma propiedad.
|
||
|
||
El hook de tabla ya impone las columnas protegidas sobre una preferencia `false`; no se cambia el formato de almacenamiento. IDs desconocidos no crean columnas ni rompen el render. Las preferencias de otros listados no se borran y los selectores sin clave de persistencia siguen sin ella. Las pruebas existentes de persistencia se amplían con un ID desconocido y las pruebas MCP comprueban que el checkbox de identidad está deshabilitado.
|
||
|
||
## 6. Microcopy y traducciones
|
||
|
||
- AssetSelect de acción deja de sobrescribir el texto visible con `aria-label={label}`. «Seleccionar activos» se reconoce por su propio contenido; el signo + es decorativo. No se sustituye globalmente «Añadir» por «Seleccionar».
|
||
- La presentación de campo mantiene contexto y valor en el nombre accesible, por ejemplo «Ordenador principal: Equipo 2».
|
||
- `EntityTable.entityLabel` admite la cadena anterior o una función del número filtrado. Es una capacidad presentacional, sin DTO ni conocimiento de entidades.
|
||
- `OptionSelectionField`/OptionPickerDialog/OptionPickerList reciben `labels.entity(count)`, independiente del título de acción. El módulo de perfiles aporta puestos/personas/cuentas y sus formas es/en.
|
||
- Campos y activos aportan también singular/plural mediante i18n. El rango compacto es «1–1 de 1 puesto» / «1–1 of 1 workstation»; las pruebas distinguen entidad y acción. El formato general «Mostrando…» no cambia.
|
||
- La ayuda añade «Sección y ayuda» / «Section and help». No se introducen textos de negocio literales en los componentes genéricos.
|
||
|
||
## 7. Componentes y consumidores afectados
|
||
|
||
| Área | Archivos/responsabilidad |
|
||
| --- | --- |
|
||
| Composición propia compartida | FormDialog/FormBody/FormActions, EntityTable, EntityTableConfiguration y EntityListPagination. Defaults de listados generales conservados; presentación picker explícita en controles internos. |
|
||
| Selección y ayuda | FieldPickerDialog/List, OptionSelectionField, OptionPickerDialog/List y nuevo PickerHelp. |
|
||
| Activos | AssetSelect, AssetPickerList, AssetList, selectores de campos de plantilla y traducciones. |
|
||
| Recursos | Adaptadores de FieldPickerDialog/List y traducciones de campos. |
|
||
| Compañías | Etiquetas de selección de perfiles, identidad de dashboard y sublistado de perfiles del activo. |
|
||
| Pruebas | Tests SSR de selección/visibilidad/AssetSelect, `scripts/picker-ui.test.py` y `picker-browser-fixture.tsx`, importada únicamente por Vite desde el guion de navegador. |
|
||
|
||
No se cambian vendor, rutas, consultas HTTP, Sequelize, autenticación, permisos, paleta, fuentes ni arquitectura V01. La fixture React no es una ruta ni un consumidor de producción; solo monta componentes reales con datos temporales para medir geometría.
|
||
|
||
## 8. Mediciones antes y después
|
||
|
||
| Escenario 320×640 | Antes | Después |
|
||
| --- | --- | --- |
|
||
| Campos de plantilla de activo | Modal 608 px; filas clientHeight 0. | Misma altura exterior; filas clientHeight 184 px con el catálogo real sintético y tres acciones. |
|
||
| Campos de plantilla de recurso | Misma composición susceptible de colapso. | Filas clientHeight 184 px en el recorrido real. No se atribuye una medida anterior no tomada a este consumidor. |
|
||
| Fixture de campos, 0/1/2 opciones cortas | No forma parte de L01. | Filas clientHeight 256 px; se ven 0/1/2 opciones completas respectivamente. Con doce opciones cortas, la muestra de campos ofrece 228 px y tres filas completas; la de opciones, 324 px y cinco. |
|
||
| Activos desde puesto | No se atribuye aquí una medida anterior no tomada. | Contenedor 212 px con doce activos y dos opciones completas; la primera deshabilitada conserva su explicación. Se verifican seis resoluciones, cancelación y confirmación. La separación entre pestañas y tabla se ajusta solo en selectores estrechos. |
|
||
| Nombres/descripciones extensos | L01 mostraba filas en DOM sin superficie útil. | Contenido íntegro y scroll utilizable; una opción puede superar por sí sola la ventana de resultados. No se ocultan descripciones para alcanzar una cifra. |
|
||
|
||
Objetivo de dos filas: se verifica con opciones cortas en los seis tamaños solicitados. En el catálogo real, las dos primeras opciones necesitan aproximadamente 206 px más 40 px de cabecera; el contenedor de 184 px deja una completa y parte de la segunda. Se adopta ese presupuesto como alternativa fundamentada: conserva tres acciones, contador, búsqueda, selección y ayuda, y permite recorrer íntegramente las opciones. No se declara cumplido un mínimo universal de dos opciones de longitud arbitraria.
|
||
|
||
Los JSON diferencian altura del modal, clientHeight/scrollHeight del scroller efectivo, clipping por ancestros, filas completas, foco, pie y overflow global. El selector remoto utiliza su contenedor real, no el wrapper horizontal de Table como sustituto de la superficie visible.
|
||
|
||
## 9. Pruebas funcionales
|
||
|
||
La suite MCP usa unittest y el cliente stdio existente de `scripts/visual-audit.py`, sin framework nuevo. Valida 0/1/2/12 opciones, resize con diálogo abierto, cero coincidencias, identidad, selección por radio/checkbox y fila, opciones deshabilitadas, paginación, confirmación y cancelación provisional.
|
||
|
||
Los flujos reales recorren plantilla de activo y de recurso, activos desde nuevo puesto, puesto desde nuevo perfil y selector de dominio. Cuando faltan sedes/puestos, el guion sustituye únicamente GET con relaciones sintéticas coherentes: una sede, doce activos y un puesto con ordenador y miembros consistentes. No se guardan formularios ni se realizan altas de negocio durante estos recorridos.
|
||
|
||
Los datos del catálogo y las relaciones de dominio proceden de la instancia desechable del preparador existente. La fixture de RemoteOptionPicker prueba su contrato con páginas aportadas por el consumidor; no demuestra por sí sola paginación de servidor ni publicaciones SMB.
|
||
|
||
## 10. Pruebas de accesibilidad
|
||
|
||
Se comprueban foco en búsqueda al abrir, Space en radio, nombres accesibles, columnas protegidas, contador de entidad, Escape, cancelación y retorno a disparador. En plantillas se abre y cancela el alta rápida anidada sin cerrar picker ni formulario padre. La ayuda es un Popover operable por teclado; PageDown desplaza las instrucciones extensas y Escape cierra esa capa conservando el picker.
|
||
|
||
Las comprobaciones de geometría no se sustituyen por snapshots. Se prueba altura reducida 320×480 y ampliación de texto CSS al 200 %; esta última no equivale al zoom nativo del navegador ni certifica WCAG. Lector de pantalla, contraste exhaustivo y zoom nativo 400 % no se acreditan en esta entrega.
|
||
|
||
## 11. Evidencias y reproducción Playwright
|
||
|
||
Directorio ignorado: [.artifacts/playwright/l02-1](../.artifacts/playwright/l02-1/). Incluye capturas antes/después, snapshots, medidas por ejecución (`measurements*.json`), consola y resultados técnicos. `evidence-index.json` relaciona archivos, dimensiones y SHA-256. Las capturas relevantes se abren para inspección visual antes del cierre.
|
||
|
||
La sesión de L01 se reutilizó para las reproducciones previas, pero caducó durante la instrumentación. Se comprobó la redirección al acceso y se preparó una instancia nueva mediante `scripts/visual-audit.py`; no se recuperaron credenciales ni se cambió seguridad. Las pruebas permanentes validan que la sesión proporcionada siga autorizada antes de navegar.
|
||
|
||
En otro entorno, seguir [Playwright MCP](playwright-mcp.md), mantener el preparador abierto y utilizar su ruta privada de sesión:
|
||
|
||
```bash
|
||
PYTHONDONTWRITEBYTECODE=1 METIS_PLAYWRIGHT_STORAGE_STATE=.artifacts/playwright/authenticated-ID/session.json \
|
||
python3 scripts/picker-ui.test.py
|
||
```
|
||
|
||
No copiar sesiones ni credenciales a Git. El script no inicia servicios ni escribe entidades. Al cierre de L02.1 se retira únicamente la instancia visual nueva que se preparó para estas pruebas; la sesión caducada y las instalaciones previas no se modifican. Requiere Vite de desarrollo para importar la fixture de componentes. Las regresiones de asistentes/foco/editor de recursos utilizan `scripts/visual-audit-ui.test.py` con una carpeta de evidencia separada.
|
||
|
||
## 12. Resultados técnicos y regresiones
|
||
|
||
Los logs distinguen comprobaciones dirigidas, globales y guiones de navegador. Los intentos iniciales con importación incorrecta, sesión caducada o localizador que confundía el Popover con el diálogo se conservan y no se presentan como defectos de Metis ni pruebas correctas.
|
||
|
||
Los fallos globales anteriores se contrastan con [V01.2](navegacion-responsive-v01-2.md) y [V01.4](recursos-responsive-v01-4.md): calendar del vendor en typecheck, reglas/formato ajenos en lint/check y dos expectativas antiguas de compañías en test. No se modifica ese código para conseguir un resultado global verde. La ejecución global de tests utiliza MariaDB nueva y desechable, exclusivamente `metis_desk_test`; no utiliza ni limpia la instancia visual o bases existentes.
|
||
|
||
Los resultados finales por comando y la regresión de navegador se registran en la tabla de cierre siguiente; un comando global fallido no se sustituye por el resultado de una prueba dirigida.
|
||
|
||
| Comprobación ejecutada | Resultado final |
|
||
| --- | --- |
|
||
| `pnpm --filter @repo/rdx-ui test` | Correcto: 11 archivos de prueba. |
|
||
| `pnpm --filter @metis/assets test` | Correcto: 27 archivos. Test de AssetSelect repetido tras completar su fixture tipada. |
|
||
| `pnpm --filter @metis/resources test` | Correcto: 11 archivos. |
|
||
| Tests dirigidos de perfiles y ficha de puesto | Correctos: dos archivos, incluida pluralización real de puestos/personas/cuentas es/en. |
|
||
| Typecheck dirigido de rdx-ui, activos, recursos y compañías | Correcto en los cuatro paquetes. |
|
||
| Typecheck de web | Correcto. |
|
||
| Biome dirigido | Correcto: 34 archivos propios TS/TSX/JSON, sin errores ni avisos. |
|
||
| ESLint de espaciado dirigido | Correcto sobre los archivos propios TS/TSX; se corrigieron tres separaciones entre sentencias sin cambios de comportamiento. |
|
||
| `pnpm typecheck` | Falla en vendor `calendar.tsx:165`, `locale` y exactOptionalPropertyTypes, ya documentado. Vendor intacto. |
|
||
| `pnpm lint` | Falla: 15 errores, un aviso y cuatro informaciones ajenos, coincidentes con el estado anterior. |
|
||
| `pnpm check` | Falla: 168 errores, un aviso y cuatro informaciones globales previos. Los archivos propios se comprueban separadamente. |
|
||
| `pnpm test`, MariaDB aislada | Falla en compañías: expectativas antiguas «Personas (1)» y «Esta persona no tiene acceso a Metis Desk»; 7 tareas correctas de 8. No acredita las suites que Turbo interrumpe. El contenedor exclusivo se retira en finally. |
|
||
| `pnpm build` | Correcto, dos tareas. Persiste el aviso de chunk web superior a 500 kB. |
|
||
| `python3 scripts/picker-ui.test.py` | **Ocho pruebas correctas**, 183,081 s en la ejecución integrada. Incluye seis tamaños, casos 0/1/2/12, catálogos, acciones, selección/foco y remoto. |
|
||
| Repetición ayuda/texto ampliado | Dos pruebas correctas; prueba de foco ampliado repetida con medición conservada por ejecución. Checkbox visible entre y=378–410 dentro de cuerpo y=250–538. |
|
||
| Aceptación adicional de activos/ayuda | Dos pruebas correctas, 54,319 s: dos opciones completas a 320×640 y PageDown de ayuda (scroll 336 px con foco dentro). |
|
||
| Regresión `visual-audit-ui.test.py` | **Tres pruebas correctas**, 162,850 s: geometría de asistentes de activo/recurso, retorno de foco y editor anidado de recursos. |
|
||
| Consola de las ejecuciones finales | Cero errores y cero advertencias. Ninguna escritura HTTP de negocio intentada en los recorridos. |
|
||
|
||
Se corrigieron errores del propio test (fixture múltiple sin maxSelection, inferencia de tuplas, localizador de output inerte y recuento de padres ocultos para accesibilidad). No eran fallos globales previos ni se dejaron supresiones. Las reproducciones finales con tipos estrictos y localizadores adecuados pasan.
|
||
|
||
## 13. Limitaciones y alcance pendiente
|
||
|
||
- Dos filas completas son un objetivo para opciones ordinarias, no para texto arbitrariamente largo. Se conserva todo el contenido con scroll, incluidos los catálogos reales de la muestra.
|
||
- La compactación de herramientas se limita a selectores. La altura inicial de datos en listados generales permanece pendiente bajo L01-01.
|
||
- Los cambios de identidad de dashboard y perfiles del activo se verifican en código y por el contrato común de visibilidad; no implican una auditoría completa de esas fichas.
|
||
- No se prueba escritura de altas rápidas ni gestión de publicaciones SMB; sí apertura/cancelación y las páginas aportadas al selector remoto.
|
||
- No hay certificación WCAG, prueba de lector de pantalla ni rendimiento de producción. Tampoco se declara resuelto V04 como proyecto independiente.
|
||
|
||
Skills: principal `ux-inputs-and-forms`; complementarias `ux-tables`, `shadcn`, `ux-navigation` y `ux-microcopy`. Se aplican a reservar superficie operativa, preservar borrador/confirmación y foco, reutilizar Popover/Dialog de Base UI y separar entidad de acción. `vercel-composition-patterns` orienta variantes genéricas sin flags por entidad; `vercel-react-best-practices` se consulta para estado derivado y fixtures sin efectos de sincronización. Las guías genéricas no autorizan cambiar tokens/vendor. Los auditores especializados ausentes no se presentan como ejecutados.
|
||
|
||
## 14. Preparación de L02.2
|
||
|
||
Quedan disponibles la superficie de resultados operativa, identidad protegida y `entityLabel(count)` traducible. L02.2 deberá tener encargo propio: V08 sigue pendiente de búsqueda/grupos/restauración de columnas y V12 de reserva de estructura durante carga inicial. V05/V07 mantienen su entrega independiente de contexto/búsqueda. No se adelantan esas implementaciones en L02.1.
|