- Introduced `ColumnBrowserFixture` component for testing column visibility and interactions. - Created `entity-column-configuration.ts` to define types and utility functions for managing entity columns. - Implemented Playwright tests in `columns-ui.test.py` to validate column behavior and preferences. - Added `visual-audit-volume.py` script to enhance the visual audit setup with synthetic data.
211 lines
21 KiB
Markdown
211 lines
21 KiB
Markdown
# L02.2 — Configuración avanzada de columnas (V08)
|
||
|
||
Fecha: 22 de octubre de 2026. Segunda entrega de [L01](auditoria-listados-l01.md), sobre la composición de [L02.1](selectores-accesibilidad-l02-1.md).
|
||
|
||
## 1. Resumen de implementación
|
||
|
||
**V08 resuelto en los escenarios verificados**, con ocho pruebas MCP específicas y cuatro regresiones conjuntas correctas.
|
||
|
||
EntityTable incorpora búsqueda local de columnas, agrupación semántica opcional, contador de visibilidad efectiva y restauración inmediata. Activos aporta cinco grupos; Recursos, Personas y los selectores sencillos conservan una presentación sin categorías artificiales. No cambia la ordenación real de columnas ni se añade un borrador Aplicar/Cancelar.
|
||
|
||
La corrección se limita a V08. V05, V07 y V12 permanecen pendientes; la normalización del selector de columnas no cambia las búsquedas de entidades. No se modifican vendor, rutas, autenticación, contratos HTTP, persistencia del negocio, tokens ni dependencias.
|
||
|
||
## 2. Arquitectura y API
|
||
|
||
[EntityColumn](../packages/rdx-ui/src/components/entity-list.tsx) conserva `id`, `label`, `render`, `value`, `defaultSort`, `hideable` y `defaultVisible`. Añade opcionalmente `groupId?: string` y `keywords?: readonly string[]`.
|
||
|
||
`EntityTable.columnGroups?: readonly EntityColumnGroup[]` recibe grupos `{ id, label, description? }`; su orden en el array es el orden de presentación. El consumidor aporta etiquetas y descripciones traducidas. No se implementa `align` en esta entrega.
|
||
|
||
```tsx
|
||
<EntityTable
|
||
columns={[
|
||
{ id: "name", label: t("name"), groupId: "general", hideable: false, render: renderName },
|
||
{ id: "notes", label: t("notes"), groupId: "general", keywords: [t("description")], defaultVisible: false, render: renderNotes },
|
||
]}
|
||
columnGroups={[{ id: "general", label: t("general") }]}
|
||
items={items}
|
||
title={t("title")}
|
||
/>
|
||
```
|
||
|
||
[EntityTableConfiguration](../packages/rdx-ui/src/components/entity-table-configuration.tsx) transmite metadatos y grupos al [diálogo](../packages/rdx-ui/src/components/entity-table-columns-dialog.tsx). Los nuevos parámetros del diálogo/configurador son opcionales. `columnMetadata` es una colección de `{ id, groupId?, keywords? }`, sin DTO ni conceptos del catálogo. El diálogo sigue admitiendo una tabla TanStack y, sin callback específico, restaura mediante `table.resetColumnVisibility()`.
|
||
|
||
La integración de EntityTable aporta `onResetColumns`, que vacía sus preferencias locales y permite que useEntityTable resuelva los defaults **actuales**, también cuando las columnas llegan después. No se duplica el estado de TanStack ni se usa un efecto para calcular resultados, grupos o contadores.
|
||
|
||
## 3. Búsqueda
|
||
|
||
[normalizeColumnSearch/groupColumnOptions](../packages/rdx-ui/src/lib/entity-column-configuration.ts) filtran por etiqueta o keywords, mediante coincidencia parcial inmediata. Recortan espacios externos, ignoran mayúsculas y tildes y conservan la diferencia entre ñ/n, también con Unicode descompuesto.
|
||
|
||
La consulta solo vive en el diálogo y se limpia al cerrar desde sus acciones. El botón Limpiar devuelve foco al buscador. Sin coincidencias, el estado explica cómo recuperarse; búsqueda, limpieza, restauración y cierre continúan disponibles.
|
||
|
||
Filtrar no cambia visibilidad, IDs, sort, filtros del inventario ni preferencias. El contador de coincidencias describe opciones encontradas; el resumen superior sigue describiendo columnas visibles del conjunto completo. Esta utilidad podría reutilizarse, tras definir el contrato de entidades en L03; **V07 no queda resuelto**.
|
||
|
||
## 4. Agrupación
|
||
|
||
Los grupos de Activos son:
|
||
|
||
| ID | Castellano | Inglés | Contenido |
|
||
| --- | --- | --- | --- |
|
||
| `general` | General | General | Tipo, nombre, plantilla, puesto, notas y acciones |
|
||
| `state-location` | Estado y ubicación | Status and location | Estado mostrado/técnico, sede y asignación |
|
||
| `audit` | Auditoría | Audit | Fecha de alta y fechas/autores de revisiones |
|
||
| `infrastructure` | Infraestructura | Infrastructure | Funciones, interfaces, direcciones IP/MAC y notas de interfaz |
|
||
| `characteristics` | Características del catálogo | Catalog characteristics | Campos activos de catálogo e instantáneas conservadas |
|
||
|
||
La primera declaración de un ID repetido de grupo o metadato prevalece. Las opciones se deduplican por ID; los grupos sin coincidencias desaparecen. Columnas sin grupo o con grupo desconocido siguen disponibles al final, bajo «Otras columnas» cuando se han proporcionado categorías. Sin categorías, hay una lista sencilla sin leyenda artificial.
|
||
|
||
Los metadatos cambian la organización del selector, nunca la posición de las columnas en la tabla.
|
||
|
||
## 5. Columnas dinámicas
|
||
|
||
[assetCharacteristicColumns](../modules/assets/src/web/assets/shared/helpers/asset-characteristic-columns.ts) conserva los IDs `characteristic-<UUID>`, la unión de campos activos con instantáneas y los ordinales para etiquetas iguales de campos diferentes.
|
||
|
||
Cada campo pertenece al grupo principal estable `characteristics`. No se elige arbitrariamente una plantilla o sección como propietario visual. Keywords reúnen los nombres de todas sus plantillas/secciones, etiquetas históricas, descripción y unidad del campo activo. Un campo compartido mantiene un único checkbox. Los contextos se ordenan y las instantáneas se recorren por ID de activo para mantener una etiqueta histórica determinista cuando no hay definición activa.
|
||
|
||
El render de celdas continúa utilizando el tipo y la unidad de la instantánea; estos metadatos no cambian valores, sincronización de plantillas ni conservación de campos retirados.
|
||
|
||
## 6. Visibilidad y obligatoriedad
|
||
|
||
`hideable:false` y las protecciones de useEntityTable siguen prevaleciendo sobre preferencias incompatibles. En Activos permanecen obligatorias Tipo de activo, Nombre, Puesto de trabajo y Acciones. Se muestran marcadas, deshabilitadas y con «Obligatoria»; una descripción accesible explica que siempre permanecen visibles.
|
||
|
||
El resumen incluye todas las columnas expuestas, también las obligatorias y Acciones. La columna interna `selection` de pickers se excluye del configurador y su contador: sigue obligatoria en la tabla. Tipo de activo se incluye porque representa información de datos, aunque su cabecera visual sea un icono.
|
||
|
||
Los totales del catálogo comprobado son **75 columnas: 20 base/adicionales/acciones y 55 características**. Ocho están visibles inicialmente. El contador no representa coincidencias de búsqueda ni filas del inventario.
|
||
|
||
## 7. Restauración
|
||
|
||
Restaurar columnas aplica inmediatamente el conjunto inicial definido por `defaultVisible`, la visibilidad predeterminada y las protecciones de cada consumidor. No equivale a mostrar todas las columnas. Se conservan búsqueda y orden del listado y la consulta local del diálogo.
|
||
|
||
El callback de EntityTable restablece las preferencias a `{}`: cada columna disponible recupera su default actual; las retiradas no reaparecen y las nuevas respetan su definición. Cerrar termina la interacción sin revertir cambios. No se añaden Aplicar/Cancelar ni escrituras de negocio.
|
||
|
||
## 8. Persistencia
|
||
|
||
Se mantiene `asset-inventory-columns`, su alcance existente entre compañías del navegador y el formato plano `{ "column-id": boolean }`. La restauración persiste `{}` mediante el escritor existente, sin borrar otras claves ni migrar globalmente preferencias.
|
||
|
||
Sin preferencia, con valores parciales o con IDs desconocidos, el hook mantiene los defaults y solo presenta columnas reales. Los valores protegidos incompatibles no afectan a la identidad. La prueba de restauración verifica recarga y conservación de una clave ajena. Datos inválidos y storage inaccesible siguen permitiendo operar localmente.
|
||
|
||
Los listados sin `columnVisibilityStorageKey`, incluidos Recursos y Personas, conservan su configuración local durante su montaje; esta entrega no les añade persistencia.
|
||
|
||
## 9. Accesibilidad
|
||
|
||
Se compone FormDialog con Dialog/Base UI, Checkbox, InputGroup y FieldSet/FieldLegend ya instalados. No hay listeners globales ni focus traps propios. El buscador tiene etiqueta accesible, foco inicial y limpieza con retorno de foco. Las categorías tienen leyendas semánticas y las descripciones opcionales están asociadas a su grupo.
|
||
|
||
Las etiquetas de checkbox excluyen el indicador visual redundante «Obligatoria» de su nombre y lo complementan con una descripción accesible. Tab, Shift+Tab, Enter, Espacio, Escape, cierre y retorno al activador se verifican mediante MCP; el último control conserva foco visible al desplazar las opciones. El diálogo de columnas también se comprueba dentro de un picker sin cerrar el formulario padre.
|
||
|
||
No se certifica WCAG ni se atribuye a estas pruebas una validación con lector de pantalla.
|
||
|
||
## 10. Responsive y reproducción inicial
|
||
|
||
Las medidas previas se tomaron en esta instalación, **antes de cambiar el código funcional**, y se conservan en `before/measurements.json`:
|
||
|
||
| Resolución | Antes: modal / área con scroll | Después: modal / área de opciones |
|
||
| --- | --- | --- |
|
||
| 320×640 | 288×608; 468 px de área, lista plana | 288×608; 336 px útiles para opciones, búsqueda y restauración fuera del scroll |
|
||
| 1440×900 | 576×868; 728 px de área, lista plana | 576×765; altura exterior estable según el patrón picker |
|
||
|
||
Antes había 75 checkbox, cero buscadores de texto, cero grupos y foco inicial en Plantilla. Se comprobó ocultar Sede y recargar: `{ "site": false }` se conserva. En móvil había entonces siete visibles por esa elección previa, no por pérdida de un default.
|
||
|
||
Mediciones de la ejecución final, en píxeles CSS:
|
||
|
||
| Resolución | Alto exterior | Alto útil de opciones | Filas completas iniciales |
|
||
| --- | --- | --- | --- |
|
||
| 320×640 | 608.0 | 336 | 6 |
|
||
| 390×844 | 812.0 | 540 | 10 |
|
||
| 768×1024 | 870.4 | 626 | 12 |
|
||
| 1024×768 | 652.8 | 409 | 8 |
|
||
| 1366×768 | 652.8 | 409 | 8 |
|
||
| 1440×900 | 765.0 | 521 | 10 |
|
||
| 1920×1080 | 900.0 | 656 | 13 |
|
||
|
||
Después se verifican 320×640, 390×844, 768×1024, 1024×768, 1366×768, 1440×900 y 1920×1080. FormBody conserva la cadena flex/min-height y desplazamiento interno. Buscar o vaciar resultados no cambia la altura; la lista no colapsa, ni aparece overflow horizontal global. No se cambian tamaños globales de Dialog.
|
||
|
||
## 11. Pruebas automatizadas
|
||
|
||
Se amplían las pruebas existentes de rdx-ui y columnas de activos. Cubren normalización, ñ/n, keywords, grupos opcionales/múltiples/vacíos/desconocidos/repetidos, unicidad y contextos compartidos, defaults, campos nuevos/retirados, storage inválido/bloqueado y aislamiento por clave.
|
||
|
||
[scripts/columns-ui.test.py](../scripts/columns-ui.test.py) utiliza unittest y el cliente MCP stdio existente. [La fixture React](../packages/rdx-ui/src/column-browser-fixture.tsx) se importa exclusivamente desde Vite durante la prueba, sin rutas ni importación de producción: tres columnas, keywords, campo nuevo y campo retirado.
|
||
|
||
La suite de navegador comprueba catálogo real, siete resoluciones, visibilidad inmediata, filtrado sin mutaciones, reapertura sin filtro, persistencia/recarga/restauración, inglés/español, teclado, scroll y consumidores sencillos/anidados. La fixture de storage bloqueado afecta solo a la clave de prueba.
|
||
|
||
## 12. Entorno, evidencias y reproducción Playwright
|
||
|
||
Instancia única de `scripts/visual-audit.py`, MariaDB desechable `metis_desk_test`, usuario ordinario y login real. [visual-audit-volume.py](../scripts/visual-audit-volume.py) amplía exclusivamente la compañía sintética del informe, mediante API HTTP real con sesión y CSRF válidos. Resultado: **15 compañías; 50 personas, 51 activos y 66 recursos en la principal**. Repetir conserva esos recuentos. Incluye nombres extensos/con tildes, activos de distintas plantillas/estados y recursos activos/inactivos; no simula permisos ni identidades de dominio.
|
||
|
||
```bash
|
||
python3 scripts/visual-audit.py
|
||
# En otra terminal, utilizar la ruta privada que imprime el preparador:
|
||
PYTHONDONTWRITEBYTECODE=1 METIS_PLAYWRIGHT_STORAGE_STATE=.artifacts/playwright/authenticated-ID/session.json \
|
||
python3 scripts/visual-audit-volume.py
|
||
PYTHONDONTWRITEBYTECODE=1 METIS_PLAYWRIGHT_STORAGE_STATE=.artifacts/playwright/authenticated-ID/session.json \
|
||
python3 scripts/columns-ui.test.py
|
||
```
|
||
|
||
Mantener abierto el preparador; cerrarlo al terminar para retirar solo sus servicios y sesión. El guion de tests no inicia entornos ni crea entidades y bloquea escrituras HTTP de negocio. Las preferencias solo afectan al navegador aislado. No copiar el contenido de `session.json` a Git, logs ni mensajes.
|
||
|
||
Evidencias ignoradas en [.artifacts/playwright/l02-2/](../.artifacts/playwright/l02-2/): capturas antes/después, snapshots, mediciones por ejecución, consola, comprobaciones técnicas e índice con dimensiones y SHA-256. El índice conserva 134 capturas, incluidas ejecuciones intermedias; `verification.json` identifica la ejecución completa final y las regresiones correctas. Se abren e inspeccionan capturas representativas de móvil/escritorio, inglés, búsqueda, campo compartido, foco y paneles antes de cerrar la entrega. MCP se ejecuta realmente por stdio dentro de la imagen Docker fijada del proyecto; no se instala Chromium en WSL.
|
||
|
||
Capturas representativas inspeccionadas:
|
||
|
||
- Antes: [escritorio](../.artifacts/playwright/l02-2/before/columns-1440.png) y [móvil](../.artifacts/playwright/l02-2/before/columns-320.png).
|
||
- Después: [escritorio](../.artifacts/playwright/l02-2/after/assets-1440.png) y [320×640](../.artifacts/playwright/l02-2/after/assets-320.png).
|
||
- [Inglés en móvil](../.artifacts/playwright/l02-2/after/assets-english-320.png), [campo compartido único](../.artifacts/playwright/l02-2/after/shared-field-one-option.png) y [foco al final del scroll](../.artifacts/playwright/l02-2/after/scroll-focus-last-320.png).
|
||
- Regresión: [selección/contraste de activos](../.artifacts/playwright/l02-2/regression-verified/contrast-selected-1920.png) y [panel de recursos en W=984](../.artifacts/playwright/l02-2/regression-verified/resource-capacity-984.png).
|
||
|
||
## 13. Resultados técnicos y regresiones
|
||
|
||
Resultados realmente ejecutados:
|
||
|
||
| Comprobación | Resultado |
|
||
| --- | --- |
|
||
| Suite MCP L02.2 | **8/8 correctas**, 160,367 s en la ejecución completa final. |
|
||
| Siete resoluciones | Correctas; entre 336 y 656 px útiles en el área de opciones. |
|
||
| Campo compartido real | Marca, usado por doce plantillas: una opción por ID; localizable por varios contextos. |
|
||
| Teclado y scroll | Último checkbox con foco visible, scrollTop 3064; recorrido de 85 pulsaciones Tab sin salir del diálogo tras asentarse Base UI. |
|
||
| Persistencia y restauración | Aplicación inmediata, recarga, defaults, claves ajenas intactas, campos nuevos/retirados y storage bloqueado: correctos en navegador. |
|
||
| `pnpm --filter @repo/rdx-ui test` | Correcto: 11 archivos. |
|
||
| `pnpm --filter @metis/assets test` | Correcto: 27 archivos; repetido tras ampliar el caso de instantáneas históricas. |
|
||
| `pnpm --filter @metis/resources test` | Correcto: 11 archivos. |
|
||
| Typecheck dirigido rdx-ui, activos, recursos, compañías y web | Correcto en los cinco paquetes. |
|
||
| Biome dirigido y ESLint de espaciado | Correctos sobre los 15 archivos propios TS/TSX/JSON modificados/nuevos (ESLint sobre los TS/TSX). |
|
||
| `pnpm typecheck` | Falla en vendor `calendar.tsx:165`, incompatibilidad previa de `locale`/exactOptionalPropertyTypes. |
|
||
| `pnpm lint` | Falla: 15 errores, un aviso y cuatro informaciones, ajenos al cambio y coincidentes con L02.1. |
|
||
| `pnpm check` | Falla: 168 errores, un aviso y cuatro informaciones previos; comprobación dirigida correcta. |
|
||
| `pnpm test`, MariaDB temporal independiente | Falla en dos expectativas antiguas de compañías; 7 tareas correctas de 8. No se presenta como suite global superada. |
|
||
| `pnpm build` | Correcto, dos tareas; conserva aviso de chunk web superior a 500 kB. |
|
||
| Preparación de volumen repetida | Correcta: 15 compañías, 50 personas, 51 activos y 66 recursos; sin duplicados. |
|
||
| Regresión MCP V01.3/V01.4, V02 y asistentes | **4/4 correctas**, 158,944 s; contraste, ambos presupuestos/paneles y geometría de asistentes. |
|
||
| Consola suite MCP final | Cero errores y advertencias; ninguna escritura HTTP de negocio intentada. |
|
||
|
||
Las primeras ejecuciones del guion detectaron errores de instrumentación: nombre «Campo» en la fixture de selector, comillas en un localizador y comprobación de foco durante una transferencia transitoria de Base UI. Se corrigieron los tests y se repitió la suite completa. No son defectos funcionales de Metis ni se cuentan como pruebas correctas esos intentos.
|
||
|
||
Las regresiones antiguas de contraste/capacidad buscaban AUDIT-PORT-01 en la primera página; al preparar 51 activos dejó de estar ahí. Se utiliza la búsqueda real antes de esos dos escenarios. La ejecución conjunta final obtiene **4/4 correctas**, 158,944 s (`browser-regression-verified.log`), con lecturas mediante Locator que esperan la fila/tabla real antes de medir. Las ejecuciones intermedias con errores quedan conservadas y no se presentan como suites correctas. La espera de elementos no modifica la aplicación ni resuelve V12. Las pruebas de asistente y recursos conservan sus operaciones de lectura/cancelación; no guardan cambios.
|
||
|
||
Durante la revisión se corrigió también la sustitución de parámetros del nuevo preparador de volumen: los placeholders ya no pueden alterar el prefijo de los nombres de compañía. Se normalizaron mediante HTTP las catorce compañías **creadas por esta fixture en la instalación desechable**, sin borrar registros, y se repitió la preparación para verificar los recuentos e idempotencia.
|
||
|
||
Los fallos globales se comparan con L02.1: `calendar.tsx:165` de vendor en typecheck, 15 errores de lint, 168 de check y las dos expectativas antiguas de compañías («Personas (1)» y aviso de persona sin acceso). No se corrige código ajeno para presentar una suite global verde. El test global usa otra MariaDB nueva y temporal, separada del entorno visual, con cleanup propio.
|
||
|
||
## 14. Limitaciones y skills
|
||
|
||
No se introducen reordenación, densidad, alineación semántica, cambios de búsqueda de entidades, restauración completa de contexto ni skeletons. La persistencia sigue compartida entre compañías por el contrato previo; no se transforma en preferencia de servidor ni por usuario autenticado. Los datos voluminosos no constituyen una prueba de carga o rendimiento de producción.
|
||
|
||
Skill principal: `ux-tables` para personalización y lectura de tablas. Complementarias leídas: `ux-search` para búsqueda secundaria y recuperación, `shadcn` para composición Base UI y componentes instalados, `ux-microcopy` para contadores/obligatoriedad/restauración, `vercel-composition-patterns` para metadatos genéricos sin flags de entidad y `vercel-react-best-practices` para estado derivado sin efectos. Se consultan sus referencias aplicables de estado derivado/eventos, composición y formularios; prevalecen las APIs locales y los patrones Metis.
|
||
|
||
No se necesita `frontend-design` ni `ui-ux-pro-max` para cambiar jerarquía o resolver otra duda. Los auditores especializados `*-audit` y `docs/orchestration-policy.md` referenciados por las skills no están instalados; no se instalan herramientas ni se presentan esos auditores como ejecutados. No se ejecuta el CLI de shadcn ni se descarga otra versión; se utilizan las primitivas locales sin regenerar vendor.
|
||
|
||
## 15. Preparación de L02.3
|
||
|
||
El selector dispone de metadatos opcionales y restauración por consumidor; los listados sin categorías no necesitan migración. L02.3 requiere un encargo propio. **V05, V07 y V12 permanecen pendientes**; no se adelanta reordenación, densidad ni otra entrega del programa.
|
||
|
||
## Archivos principales
|
||
|
||
| Área | Archivos modificados o creados |
|
||
| --- | --- |
|
||
| rdx-ui | `components/entity-list.tsx`, `entity-table-configuration.tsx`, `entity-table-columns-dialog.tsx`; nuevo `lib/entity-column-configuration.ts`; `lib/entity-list.ts`; traducciones `locales/es.json` y `en.json`. |
|
||
| Activos | `assets/shared/ui/asset-list.tsx`; helpers `asset-additional-columns.tsx`, `asset-characteristic-columns.ts`; traducciones comunes es/en. |
|
||
| Pruebas | `entity-column-visibility.test.tsx`, `asset-columns.test.tsx`; nueva fixture `column-browser-fixture.tsx`; nuevos `scripts/columns-ui.test.py`, `scripts/visual-audit-volume.py`; adaptación acotada de `scripts/visual-audit-ui.test.py` a volumen y lecturas de DOM mediante Locator. |
|
||
| Documentación | Este informe, DESIGN.md, auditorías L01/visual, patrón de listados, componentes y Playwright MCP. |
|
||
|
||
No se modifican `useEntityTable`, el contrato de almacenamiento ni EntityTableContent: sus protecciones, resolución de defaults, persistencia y render existentes permiten implementar la mejora desde la composición. No hay cambios en `packages/shadcn-ui`, package.json, lockfiles o AGENTS.md.
|
||
|
||
Cierre: se detuvo el preparador propio; retiró sus servicios temporales y eliminó `session.json`. Se verificó la ausencia del archivo de sesión. Las capturas e informes sin credenciales permanecen en `.artifacts/playwright/l02-2/`, excluidos de Git.
|