metis-desk/docs/inventario-responsive-v01-3.md
David f9384c9376 feat: Introduce contextual detail layout and budget management for assets and resources
- Added `INVENTORY_DETAIL_BUDGET` and `RESOURCE_DETAIL_BUDGET` to define layout parameters for inventory and resource detail views.
- Implemented `useContextualDetail` hook to manage visibility and focus transitions between inline and overlay modes.
- Created `contextual-detail-layout` module to handle budget calculations and state management for contextual details.
- Updated documentation to reflect changes in asset and resource handling, ensuring consistent UI behavior across different viewports.
- Conducted regression tests to validate functionality and ensure no existing features were broken during the implementation.
2026-10-22 01:24:37 +02:00

145 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Inventario de activos por capacidad — V01.3
## Arquitectura y alcance
Entrega sobre V01.1/V01.2, sin cambiar su shell. La selección sigue en `useAssetInventoryPageController`; `useInventoryDetail` coordina capacidad y visibilidad contextual. No se modifica la navegación a fichas completas, permisos, API, datos, vendor ni dependencias. Recursos queda expresamente fuera: su migración corresponde a V01.4 y V01 continúa parcialmente resuelto.
Una referencia al `div[data-slot=inventory-layout]` observa su content box mediante un único `ResizeObserver`. El observer se desconecta al desmontar, publica solamente cruces de modo y comienza en `overlay` hasta recibir una medida fiable. No observa la columna que encoge, no usa `innerWidth` ni guarda píxeles como estado React. El grid no cambia de anchura exterior al mostrar el panel: no hay realimentación. El reducer devuelve el mismo estado ante capacidad/visibilidad iguales.
`inventory-detail-layout.ts` es la fuente única del presupuesto, la decisión y las columnas. No se replica un umbral en CSS. `SelectionPanel` añade una opción explícita `layout="inline"`; su valor predeterminado `responsive` conserva exactamente el contrato CSS de recursos. `useCompactDetail` y el breakpoint `inventory` siguen disponibles para ese consumidor.
## Presupuesto y presentación
| Concepto | Valor efectivo |
| --- | --- |
| Columna de tabla de referencia | 800 px |
| Separación | 24 px |
| Panel | 320–400 px |
| Umbral content box exterior W | 1144 px inclusive |
| Panel cuando cabe | clamp(320 px, W − 824 px, 400 px) |
| Tabla interior en el límite | 798 px útiles; 2 px de borde exterior de EntityTable |
No se incrementa artificialmente el umbral por las columnas opcionales. Su desbordamiento horizontal permanece dentro de la tabla. Con W inferior al mínimo, el listado ocupa toda la columna y la selección explícita abre `AssetDetailDialog`, con `FormDialog`, título y detalle compacto existentes. No queda un panel oculto focusable ni una columna vacía.
El panel inline es un `aside` cuyo nombre accesible incluye el nombre del activo, con cierre y acciones existentes. No atrapa foco ni bloquea la tabla. Se conservan todos los campos, acordeones, relaciones y permisos del panel existente. El diálogo conserva su contenido e historial; esta entrega no homogeneiza ambos contenidos.
## Selección, visibilidad y transiciones
| Evento | Selección | Presentación y foco |
| --- | --- | --- |
| Pulsar zona no interactiva de fila | Activo pulsado | Abre en el modo disponible |
| Enlace del nombre o Ficha e historial | Contrato de ruta existente | Ficha completa, sin nueva ruta |
| Cerrar detalle | Se conserva | Sin panel ni modal; botón «Mostrar detalle seleccionado» |
| Inline → overlay | Se conserva | Cierra detalle; nunca abre modal; foco del panel al botón de reapertura, foco de tabla permanece |
| Overlay abierto → inline | Se conserva | Desmonta modal, libera modalidad y muestra panel; foco al cierre del panel |
| Overlay cerrado → inline | Se conserva | Permanece cerrado |
| Cambios dentro del mismo modo | Se conserva | Sin cambio de visibilidad ni reconstrucción de tabla |
| Desaparición externa del activo | Contrato anterior | Aviso existente `EntityNotFound`, sin inventar una selección alternativa |
| Borrado confirmado | Contrato anterior | Limpia selección y detalle solo tras éxito de la mutación |
No se ejecutan borrados en la auditoría. La desaparición externa se simula en la respuesta HTTP de la instalación sintética. Un filtro o cambio de página no elimina la selección si el activo sigue en el inventario cargado. Ante desaparición externa se conserva el nodo exterior observado y el aviso previo; si desaparece el elemento enfocado, el contenedor recibe foco para evitar BODY.
Base UI conserva Escape, modalidad y retorno ordinario mediante `finalFocus`. El destino alternativo local comprueba disparador conectado/habilitado, control de reapertura, búsqueda y contenedor. En el cambio de modal a panel se enfoca el cierre en el siguiente frame, una vez retiradas las guardas/modalidad; el frame se cancela al desmontar o cambiar de estado. No se añade un focus trap ni listeners globales. La edición conserva el contrato V06.
## Estabilidad y geometrías
`AssetList` y su única `EntityTable` permanecen en la misma posición de React y DOM; no hay claves o ramas de layout que las reconstruyan. Cambiar capacidad no cambia URL, consultas, búsqueda, plantilla, filtros, orden, página ni preferencias de columnas. El scroll vertical se conserva cuando sigue existiendo contenido suficiente; no se promete conservar un desplazamiento superior al nuevo máximo físico tras retirar un panel largo.
Mediciones reales con usuario ordinario, compañía/portátil sintéticos, global predeterminada y compañía fija cuando cabe. Anchos en px; tabla = viewport horizontal interior. Las medidas previas permanecen en `before/measurements.json`, las nuevas en `after/measurements.json`.
| Viewport | W inventario | Tabla con detalle antes | Panel antes | Tabla después | Presentación después |
| --- | ---: | ---: | ---: | ---: | --- |
| 1920×1080 | 1403 | 977 | 400 | 977 | Inline, panel 400 |
| 1660×900 | 1143 | 719 | 398,39 | 1141 | Overlay |
| 1440×900 | 1079 | 707 | 345,59 | 1077 | Overlay |
| 1366×768 | 1005 | 651 | 327,83 | 1003 | Overlay |
| 1160×800 | 799 | 453 | 320 | 797 | Overlay |
| 1100×800 | 980 | 634 | 320 | 978 | Overlay |
| 1024×768 | 904 | 902 | Oculto | 902 | Overlay |
| 920×768 | 800 | 798 | Oculto | 798 | Overlay |
| 768×1024 | 720 | 718 | Oculto | 718 | Overlay |
| 390×844 | 358 | 356 | Oculto | 356 | Overlay |
| 320×640 | 288 | 286 | Oculto | 286 | Overlay |
En 1660 se evita específicamente confundir viewport amplio con capacidad suficiente: W=1143 queda 1 px por debajo. Compactar manualmente global aumenta W y habilita inline; expandirla lo cierra sin abrir modal. Ocultar/fijar compañía también recalcula la medida. El panel no controla las políticas del shell.
## Pruebas, evidencias y límites
Las evidencias locales se guardan en `.artifacts/playwright/v01-3/`, ignorado por Git. Se utiliza una sola instalación de `scripts/visual-audit.py`, preparada por los endpoints reales, con login de usuario ordinario y 403 verificado en gestión de usuarios. Cada cliente de auditoría se conecta al servidor MCP Docker fijado en `.codex/config.toml`; Chromium headless no se instala en WSL. Se bloquean escrituras HTTP en las pruebas visuales.
- `before/` y `after/`: inventario cerrado y seleccionado en las once resoluciones, 22 estados por fase, geometrías y consola. Se abrieron e inspeccionaron capturas comparativas.
- Prueba de capacidad: W=1143, 1144, 1145, 1200 y 1224, en el mismo viewport; se mide la caja real y se comprueban mínimos, ausencia de doble detalle y nodo de tabla.
- Pruebas de foco: contracción con foco en panel y en acción de fila, ampliación con modal abierto, reapertura por teclado, Escape, edición/cancelación y selector anidado.
- Prueba de estado: fixture HTTP de 41 activos derivada del portátil sintético, sin insertarlos en BD. Se comprueban búsqueda, plantilla, orden descendente, segunda página, columnas, scroll horizontal/vertical y nodo DOM al cruzar cuatro veces el límite.
- Prueba de desaparición: fixture HTTP vacía y refetch por foco; no borra datos. El enlace nativo sigue abriendo la ficha completa.
- Regresión: suite existente de navegación V01.1/V01.2, selección V02, asistentes V03, retorno V06 y cabeceras V09. El caso de contraste se ejecuta ahora a 1920 porque el panel inline ya no corresponde a 1440; conserva los mismos estados y colores.
Capturas representativas abiertas e inspeccionadas: [1100 antes](../.artifacts/playwright/v01-3/before/selected-1100.png), [1100 con diálogo](../.artifacts/playwright/v01-3/after/selected-1100.png), [1920 inline](../.artifacts/playwright/v01-3/after/selected-1920.png), [320 con diálogo](../.artifacts/playwright/v01-3/after/selected-320.png), [límite W=1144](../.artifacts/playwright/v01-3/targeted/inventory-boundary-1144.png), [reapertura explícita tras resize rápido](../.artifacts/playwright/v01-3/rapid/rapid-transition-final.png). `manifest.json` registra dimensiones, tamaño y SHA-256 de los PNG, sin información de sesión.
### Resultados ejecutados
| Comprobación ejecutada | Resultado |
| --- | --- |
| Playwright MCP, cobertura completa reejecutada en tres grupos | 21/21: inventario/foco 6/6, navegación 8/8, componentes/formularios 7/7; sin casos duplicados ni omitidos |
| Resize durante apertura/animación | Tres ciclos rápidos correctos, sin modal residual ni foco en BODY |
| Resize con edición y selector abiertos | Borrador conservado, editor permanece, cancelación con foco alternativo V06; el desplegable de Base UI se cierra al recalcular geometría |
| Consola de los grupos finales y escenarios adicionales | Cero errores y warnings registrados por MCP |
| Typecheck dirigido assets, rdx-ui y web | Correcto |
| `pnpm --filter @metis/assets test` | 27 ficheros correctos |
| Inventario ejecutado directamente con Node/tsx | 24 pruebas correctas, incluidas capacidad y transiciones |
| Tests rdx-ui y resources | 11 ficheros correctos en cada paquete; contrato predeterminado de recursos conservado |
| Biome dirigido a los archivos afectados | Correcto, sin supresiones |
| `pnpm typecheck` | Falla en vendor `calendar.tsx:165`, incompatibilidad locale/exactOptionalPropertyTypes ya documentada |
| `pnpm lint` | Falla: 15 errores, 1 warning, 4 infos en archivos preexistentes ajenos al diff |
| `pnpm check` | Falla: 168 errores, 1 warning, 4 infos preexistentes; no se reformatean archivos ajenos |
| `pnpm test` con MariaDB temporal independiente | Falla: dos expectativas SSR antiguas de compañías («Personas (1)» y aviso de persona sin acceso); 7 tareas correctas de 8 ejecutadas; no se completa la suite global |
| `pnpm build` | Correcto; aviso existente de chunks mayores de 500 kB |
| Enlaces locales de los seis documentos afectados | 190 comprobados, ninguno ausente |
Los fallos globales se contrastaron con los ya documentados en V01.2 y con los archivos sin modificar. Sus logs quedan en `technical/`. La base de pruebas global se retiró al terminar cada ejecución. Las pasadas intermedias de UI que detectaron foco incorrecto o selectores/eventos inadecuados se conservan como diagnóstico, no se cuentan como aprobadas. La pasada secuencial de 21 casos terminó 20/21 al buscar el panel inicial: el guion no restauraba las anchuras de sus fixtures. Se restituyeron y se espera la medida inicial mediante frames; se añadió una comprobación explícita del modo antes del caso de foco. Se reejecutaron los 21 métodos del runner en tres grupos, incluyendo en el mismo cliente la secuencia de seis casos antes implicada. La pasada anterior no se presenta como aprobada. `verification.json` enumera los métodos y `technical/visual-*.log` conserva los resultados finales.
### Reproducción
Desde WSL con Node 24 y PNPM fijado en package.json:
```bash
python3 scripts/visual-audit.py
```
Conservar ese preparador abierto y utilizar la ruta privada que imprime, sin copiar el contenido de la sesión:
```bash
METIS_PLAYWRIGHT_STORAGE_STATE=.artifacts/playwright/authenticated-XXXX/session.json \
METIS_UI_AUDIT_DIRECTORY=.artifacts/playwright/v01-3/regression \
python3 scripts/visual-audit-ui.test.py
pnpm --filter @metis/assets typecheck
pnpm --filter @repo/rdx-ui typecheck
pnpm --filter @metis/assets test
pnpm --filter @repo/rdx-ui test
```
Al finalizar esta entrega se envió SIGINT únicamente al preparador identificado por sus logs; terminó correctamente, retiró su BD y eliminó `session.json` y `database.env`. Se conservaron los contenedores ajenos y las capturas; comprobación en `cleanup.json`. Ctrl+C reproduce esa retirada de servicios y sesión del preparador. La suite global `pnpm test` necesita su propia BD de pruebas desechable, separada de la instalación visual; no usar bases existentes ni sus fixtures para preparar auditorías.
No se afirma cumplimiento WCAG completo, prueba con lector de pantalla, zoom nativo validado ni validación de otros motores/dispositivos físicos. Se conserva el estado de los otros hallazgos. Falta V01.4 para migrar recursos y realizar su regresión; el contrato explícito de SelectionPanel permite hacerlo sin modificar vendor ni duplicar política CSS/React.
## Archivos principales
- `modules/assets/src/web/assets/list/helpers/inventory-detail-layout.ts`: presupuesto, política y reducer.
- `modules/assets/src/web/assets/list/hooks/use-inventory-detail.ts`: único observer, visibilidad y coordinación de foco.
- `use-inventory-page-controller.ts` e `inventory-page.tsx`: selección, composición y reapertura; tabla estable.
- `asset-selection-panel.tsx` y `asset-detail-dialog.tsx`: semántica y finalFocus del detalle existente.
- `packages/rdx-ui/src/components/selection-panel.tsx`: variante explícita, predeterminado compatible con recursos.
- Locales es/en: etiqueta contextual independiente de presentación lateral/modal.
- `inventory.test.tsx` y `scripts/visual-audit-ui.test.py`: pruebas de política y regresión visual.
- `DESIGN.md`, patrón de listados, biblioteca de componentes, propuesta V01 y auditoría: contrato actual y seguimiento sin borrar mediciones históricas.
## Skills y APIs consultadas
Se leyeron `ux-tables` (principal: proteger comparación y manipulación), `ux-navigation` (contexto y foco), `shadcn` y referencias de composición/Base UI (modalidad y APIs existentes), `vercel-composition-patterns` (datos frente a presentación) y `vercel-react-best-practices`, incluida `rerender-derived-state` (modo discreto, sin estado por píxel). No se impone estética nueva ni se aplica la recomendación genérica de ocultar acciones frente al patrón Metis.
La CLI de documentación shadcn falló por EAI_AGAIN. Se contrastaron la API instalada Base UI 1.8.0 y las documentaciones oficiales de [Dialog shadcn/Base UI](https://ui.shadcn.com/docs/components/base/dialog) y [Dialog Base UI](https://base-ui.com/react/components/dialog), sin instalar ni regenerar componentes. No se requirieron frontend-design/ProMax para decisiones estéticas. No están instaladas las skills de auditoría `ux-heuristics-audit`, `ux-accessibility-audit`, `ux-microcopy-audit`, `ux-aesthetics-audit`, `ux-mobile-responsiveness-audit` ni `ux-information-architecture-audit` citadas por las guías. No se instalaron; se aplicaron los criterios disponibles y las verificaciones MCP.
## Seguimiento V01.4
Este informe y sus mediciones se conservan como historia. [V01.4](recursos-responsive-v01-4.md) extrae el hook a `@repo/rdx-ui/hooks/use-contextual-detail` y la política a `lib/contextual-detail-layout`, con activos y recursos como consumidores reales. El fichero de activos conserva el presupuesto 800/24/320–400; su mínimo sigue en 1144. Las 22 geometrías de activos repetidas en V01.4 coinciden con esta entrega. No se conserva una copia del observer específico. El estado vigente y el cierre de V01 se consultan en V01.4.