- Implement `current-operating-systems-ui.test.py` to validate the functionality of current operating systems in the UI, including tests for source and states, computer changes, responsive design, error handling, and version saving. - Create `workplace-add-asset.test.py` to test the addition of assets to workstations, covering scenarios such as conflict resolution, responsive design on mobile, error handling during asset creation, and HTTP guards for asset management.
345 lines
44 KiB
Markdown
345 lines
44 KiB
Markdown
# Mapa visual del puesto
|
||
|
||
Implementación del 25 de octubre de 2026 sobre la ficha existente de puesto de trabajo.
|
||
|
||
## Composición y decisiones
|
||
|
||
La cabecera funcional, edición, historial y gestión mantienen sus contratos. La parte superior contiene `WorkplaceVisualMapCard` y, cuando el contenedor dispone de 1040 px, una columna de 280 px con resumen, pendientes y soporte. En contenedores menores, esos paneles pasan debajo. Se conservan Inter, tokens, iconos semánticos, badges y `DetailPanel`; no hay cambios en vendor. El contexto remoto se despliega con `Collapsible` para mantener compacto el panel de soporte, conservando los enlaces y resúmenes existentes.
|
||
|
||
El equipo principal se valida con la composición del puesto y la pertenencia de compañía. El mapa distingue sus activos asociados y su software documental. Las líneas de equipamiento **no afirman conexión física**: el modelo actual documenta pertenencia al puesto, no cableado. Software conserva `configured`, `pending`, `removal_pending` y revisión; no convierte todos los registros en «Configurado». Se excluyen retirados y nunca configurados, como en la gestión previa. Estado técnico y asignación son independientes.
|
||
|
||
El canvas muestra hasta dos activos asociados y tres aplicaciones para mantener texto legible. Un aviso informa de las cantidades cuando hay más. «Ver todo el equipamiento» abre el componente existente con su preview ampliable; el panel de software conserva versiones, licencias, revisión, colección ampliable y diálogo de gestión. No se pierde acceso a elementos fuera del mapa.
|
||
|
||
La leyenda se sitúa fuera del canvas para conservar legibilidad y flujo de lectura. El resumen mantiene cero como valor real y muestra `—` cuando la fuente está pendiente o fallida. Las personas y perfiles utilizan `CompactList`/`CompactListItem`, avatares, estado, cuenta y resumen de configuraciones; conservan archivo, expansión contextual, foco y gestión.
|
||
|
||
## Componentes y responsabilidades
|
||
|
||
Todos los archivos específicos viven en `modules/companies/src/web/workstations/detail/`:
|
||
|
||
| Unidad | Responsabilidad |
|
||
| --- | --- |
|
||
| `ui/workplace-visual-map-card.tsx` | Card, acciones, estados parciales, preview y selección de presentación según capacidad |
|
||
| `controllers/use-workplace-map-model.ts` | Adaptación reactiva y resolución de nombres/enlaces con las lecturas existentes |
|
||
| `helpers/workplace-map-model.ts` | Modelo de presentación, aislamiento de compañía y selección del principal/software |
|
||
| `helpers/workplace-map-layout.ts` | Cálculo dagre independiente del fetch y de los componentes |
|
||
| `ui/workplace-map-canvas.tsx` | Canvas de React Flow cargado mediante `React.lazy` |
|
||
| `ui/workplace-equipment-node.tsx`, `ui/workplace-software-node.tsx` | Custom nodes con handles de solo lectura |
|
||
| `ui/workplace-relationship-edge.tsx` | Custom edge bezier, sin flechas, con patrón distinto por familia |
|
||
| `ui/workplace-equipment-card.tsx`, `ui/workplace-software-card.tsx` | Contenido común de canvas y presentación compacta; enlaces nativos que cubren la tarjeta |
|
||
| `ui/workplace-compact-map.tsx` | Presentación lineal cuando el ancho útil es inferior a 640 px |
|
||
| `ui/workplace-map-legend.tsx`, `ui/workplace-summary.tsx` | Leyenda y contadores |
|
||
| `controllers/use-workstation-software.ts` | Una lectura compartida entre mapa y gestión; claves e invalidación existentes |
|
||
| `ui/workstation-profile-summary.tsx` | Fila visual del perfil y acción independiente de configuraciones |
|
||
|
||
No se crean APIs, schemas, migraciones, persistencia, providers propios ni cambios de autorización. Los nodos reciben datos de presentación y enlaces; no realizan fetch. Las traducciones nuevas están en es/en del namespace `assets`.
|
||
|
||
## React Flow y layout
|
||
|
||
Dependencias directas de `@metis/companies`: `@xyflow/react` 12.12.0 y `@dagrejs/dagre` 3.1.2, fijadas en manifest y lockfile. React Flow y dagre quedan en el chunk diferido del canvas; los tipos compartidos no cargan la biblioteca en runtime.
|
||
|
||
Dos grupos dagre comparten el principal: LR para el equipamiento lateral y TB para el software inferior. Cada nodo recibe un objeto propio de dimensiones, porque dagre lo modifica al calcular posiciones. La franja de software queda debajo de toda la extensión vertical del equipamiento. Una prueba comprueba coordenadas finitas y ausencia de solapamientos.
|
||
|
||
La vista inicial usa `fitView`. El ancho real se mide con un único `ResizeObserver`, con desconexión al desmontar; los cambios de ancho reajustan el canvas. Están desactivados arrastre, selección, conexiones, reconexión, borrado, pan y zoom gestual. El scroll de página sigue funcionando. Los enlaces nativos tienen foco visible y son alcanzables por teclado; los wrappers y edges de React Flow no añaden pasos de tabulación. Se conserva la atribución de React Flow.
|
||
|
||
En espacios estrechos se muestran las mismas tarjetas a tamaño de lectura, ordenadas como principal, equipamiento y software. No se reduce un canvas hasta hacer ilegibles sus textos. Este cambio responde al ancho útil, incluyendo navegaciones y paneles, no solo al viewport.
|
||
|
||
Referencias consultadas: [custom nodes](https://reactflow.dev/learn/customization/custom-nodes), [handles](https://reactflow.dev/learn/customization/handles), [layout](https://reactflow.dev/learn/layouting/layouting) y [contrato de ReactFlow](https://reactflow.dev/api-reference/react-flow).
|
||
|
||
## Estados y accesibilidad
|
||
|
||
El esqueleto inicial de la ficha continúa vigente. Cada consulta conserva carga, refresco, datos retenidos, error y reintento mediante los componentes existentes. El fallo de software o referencias no retira el equipamiento. Un error de configuración mantiene disponible el mapa y los demás paneles. Se distinguen ausencia confirmada de software, ausencia de activos asociados, principal ausente, referencia no resuelta y principal fuera de composición.
|
||
|
||
Los badges contienen texto, los iconos decorativos se ocultan de tecnologías asistivas y las relaciones no dependen exclusivamente del color. Los enlaces cubren cada nodo y mantienen nombres completos accesibles aunque el título visual se limite a dos líneas. El listado de perfiles conserva `aria-busy`, estados y controles de expansión. La gestión utiliza los diálogos existentes, con sus restricciones y retorno de foco.
|
||
|
||
## Validación y evidencia
|
||
|
||
El recorrido reproducible está en `scripts/workplace-visual-map.test.py`; utiliza el cliente MCP y el servidor Docker fijado en `.codex/config.toml`, mediante el procedimiento de `docs/playwright-mcp.md`. Requiere mantener `visual-audit.py` abierto y ejecutar antes `visual-audit-volume.py` y `workstation-dashboard-fixtures.py` con su sesión privada. No imprime ni versiona cookies, claves ni `storageState`.
|
||
|
||
```bash
|
||
METIS_PLAYWRIGHT_STORAGE_STATE=.artifacts/playwright/authenticated-ID/session.json \
|
||
python3 scripts/workplace-visual-map.test.py
|
||
```
|
||
|
||
Datos reales de la ejecución sintética: 15 compañías, 59 personas, 135 activos y 73 recursos en la principal después de preparar las fixtures de puestos; escenario denso con 80 miembros y 60 perfiles. Toda persistencia pertenece al contenedor desechable `metis_desk_test`.
|
||
|
||
Los resultados técnicos y de navegador figuran en el apartado de resultados de esta entrega.
|
||
|
||
## Límites y evolución
|
||
|
||
- Vista de consulta: no hay edición libre, reordenación, relaciones físicas ni guardado de coordenadas.
|
||
- No se incluyen marcas/logos de aplicaciones porque los contratos existentes no ofrecen un icono validado. Se usa el icono semántico de software.
|
||
- La primera iteración limita nodos visibles y conserva el resto en las colecciones de gestión. No se implementan búsqueda, filtros ni virtualización del canvas.
|
||
- El layout está aislado para sustituir su cálculo por ELK si aparecen grafos más complejos; no se añade elkjs ni una interfaz anticipada de motores.
|
||
- No se certifica WCAG ni compatibilidad con lectores de pantalla concretos. Los auditores especializados `ux-*-audit` no están instalados; no se han instalado herramientas adicionales.
|
||
- Las suites visuales históricas de versiones anteriores contienen expectativas de seis paneles/tablas. El nuevo recorrido comprueba la composición del mapa; no se han reescrito auditorías históricas ajenas a esta tarea.
|
||
|
||
Skills consultadas: `frontend-design` para jerarquía y aproximación al mockup dentro de los tokens; `shadcn` para composición y APIs Base UI; `ux-dashboard` para resumen y prioridad; `ux-tables` para el listado compacto; `vercel-composition-patterns` para separar nodos, presentación y acciones; `vercel-react-best-practices` para datos derivados y carga diferida; `rdx-solid` para responsabilidades y pruebas observables. Se inspeccionaron `.agents/skills/`, `skills-lock.json`, DESIGN y la matriz del proyecto. No se aplicaron paletas ni dependencias alternativas propuestas genéricamente por skills.
|
||
|
||
## Resultados de esta entrega
|
||
|
||
| Comprobación ejecutada | Resultado |
|
||
| --- | --- |
|
||
| `pnpm --filter @metis/companies typecheck` | Correcto |
|
||
| `pnpm --filter @metis/web typecheck` | Correcto |
|
||
| Biome `check` sobre detalle de puestos, traducciones es/en de assets y manifest de companies | Correcto |
|
||
| ESLint sobre `modules/companies/src/web/workstations/detail` | Correcto |
|
||
| Suite dirigida de ficha (`node --import tsx` con tsconfig raíz) | 32 pruebas correctas en el estado final compartido, incluidas las incorporaciones concurrentes de contexto de persona |
|
||
| Suite dirigida de helpers y ficha con `node --test` | Correcta; se conservan las comprobaciones de relaciones, composición y expansión |
|
||
| `pnpm build` | Correcto para servidor y web; conserva el aviso de tamaño del chunk principal. El canvas se emite por separado, aproximadamente 226 kB / 74 kB gzip |
|
||
| Recorrido MCP completo del mapa | Cuatro pruebas correctas: seis tamaños, 80 activos, navegación/gestión y vacíos/errores parciales |
|
||
| `git diff --check` y sintaxis Python del nuevo recorrido | Correctos |
|
||
| `pnpm typecheck` | Falla fuera del cambio: `packages/shadcn-ui/src/components/calendar.tsx:165`, incompatibilidad de `locale` con `exactOptionalPropertyTypes`. No se modifica vendor |
|
||
| `pnpm lint` y `pnpm check` | Fallan en archivos ajenos al mapa: reglas de bloques en backend de personas/procesos/migraciones y formato previo, entre otros. Las comprobaciones dirigidas pasan |
|
||
| `pnpm test` | Falla en `modules/resources/src/api/application/resource-scope.test.ts`, ajeno a esta implementación |
|
||
| `pnpm --filter @metis/companies test` | La ficha y sus helpers pasan; permanecen dos aserciones de `companies-page.test.tsx` sobre textos de personas, fuera del cambio |
|
||
|
||
Las capturas e informes están excluidos de Git en `.artifacts/playwright/authenticated-ysqdk5ab/workstation-dashboard-wd043/visual-map/`: `map-1600.png`, `map-1440.png`, `map-1280.png`, `map-1024.png`, `map-768.png`, `map-390.png`, errores parciales y `measurements.json`. Se abrieron e inspeccionaron las seis vistas y las imágenes de error. El recorrido mide ausencia de overflow global y solapamientos, ancho legible de nodos, número de edges, foco, navegación y retorno desde diálogos. La prueba densa despliega los 80 miembros completos fuera del canvas.
|
||
|
||
Se verifica el enlace al principal mediante teclado y el retorno de foco desde equipamiento; la expansión del perfil, gestión de software e historial se comprueban con interacciones reales. Los escenarios de error simulan respuestas HTTP únicamente en el navegador, sin alterar datos. La validación no cubre contraste automatizado, lectores de pantalla, zoom de texto, modo oscuro ni carga masiva de software en navegador; las unidades cubren los tres estados de software y el layout con tres aplicaciones y dos elementos asociados. Estos límites no equivalen a una certificación de accesibilidad.
|
||
|
||
Se conservaron cambios previos y concurrentes en cabecera, contexto de personas y otros módulos; no forman parte de la implementación del mapa ni se revierten para validar esta tarea. Las comprobaciones globales reflejan ese workspace compartido.
|
||
|
||
Tras ajustar el breakpoint de la columna lateral a 1040 px útiles, se repitieron los recorridos de seis tamaños y navegación: dos pruebas correctas. Se verificaron Tab desde «Gestionar equipamiento» al principal, outline visible, Enter para navegar y Space para abrir/cerrar el contexto remoto. Se inspeccionó también `profiles-1440.png`.
|
||
|
||
## Refinamiento visual — Iteración 2
|
||
|
||
25 de octubre de 2026. Refinamiento de presentación sobre la implementación anterior; se conserva el informe histórico precedente.
|
||
|
||
### Estado anterior y referencia
|
||
|
||
Se inspeccionaron los componentes reales, DESIGN, el dashboard, el patrón de listados compactos y las capturas de la primera entrega. Se generaron otras seis capturas **antes de cambiar la composición**, en una nueva instalación desechable. El principal y el equipamiento compartían 260 × 166 px, el canvas reservaba 430 px con independencia del contenido y la leyenda repetía estados ya presentes en los nodos. El software no tenía un rótulo propio dentro del grafo.
|
||
|
||
La dirección visual procede del mockup aportado en la conversación y de la especificación de esta iteración: portátil protagonista, equipamiento secundario, software inferior y relaciones discretas. No se afirma una reproducción exacta de píxeles ni se incorporan fotografías o logos por coincidencia de nombre.
|
||
|
||
### Cambios de presentación
|
||
|
||
| Aspecto | Implementación de la iteración 2 |
|
||
| --- | --- |
|
||
| Principal | 264 × 192 px en el layout, centrado, icono semántico de 48 px, nombre semibold, tipo junto a «Principal» y estados agrupados abajo. Borde/fondo `secondary` y padding de 16 px |
|
||
| Secundarios | 212 × 120 px, icono de 24 px, identidad horizontal y estados agrupados; menor superficie y peso tipográfico que el principal |
|
||
| Software | 204 × 132 px, icono de 24 px, nombre de dos líneas y estado de instalación real. Revisión compacta, con explicación completa en el árbol accesible y tooltip |
|
||
| Títulos largos | Los enlaces nativos conservan el nombre completo; `Tooltip` de shadcn permite leerlo con foco y puntero sin añadir otro tab stop |
|
||
| Grupo de software | `WorkplaceSoftwareHeadingNode` muestra «Software del ordenador principal». No tiene Resource, handles, enlace, selección, arrastre ni foco |
|
||
| Aristas | `WorkplaceRelationshipEdge`: bezier de 1,5 px, asociación continua con opacidad 0,65 y software discontinuo con 0,55. Sin flechas ni animación. Handles transparentes, conservando sus dimensiones |
|
||
| Leyenda | Tres símbolos y textos: círculo lleno «Principal», círculo vacío «Asociado al puesto» y trazo discontinuo «Software del principal». Se retiran los badges de estado de la leyenda |
|
||
| Cobertura | `WorkplaceMapCoverage` separa «Mostrando 3 de 12 activos» y «Mostrando 3 de 8 instalaciones de software», solo cuando hay elementos fuera del mapa y la fuente es conocida |
|
||
| Acciones | Equipamiento conserva su ampliación existente. «Ver todo el software» lleva al panel existente y enfoca su botón «Gestionar software»; no crea otra pantalla ni otra implementación de gestión |
|
||
|
||
Las dimensiones de layout reservan espacio para dos líneas de identidad y los estados. Las tarjetas mantienen altura mínima y las pruebas de navegador verifican que su contenido no desborde; no se añade `overflow: hidden` para ocultar errores geométricos. Los detalles completos permanecen en las colecciones existentes.
|
||
|
||
### Dagre, geometría y estabilidad
|
||
|
||
Se conservan dos cálculos dagre. El primero distribuye equipamiento con separación horizontal de 36 px y vertical de 20 px. Con dos asociados y al menos 820 px útiles de canvas, los reparte a izquierda y derecha; con menos capacidad, los apila a la izquierda para preservar la lectura. El segundo distribuye software horizontalmente con 16 px entre nodos. La franja inferior se coloca bajo el límite inferior de **todo** el equipamiento, con espacio para el rótulo y las curvas.
|
||
|
||
Las coordenadas del rótulo y el tamaño del canvas se derivan de las dimensiones y bounds de esos resultados. No se recolocan nodos con CSS después de dagre. `WorkplaceLayoutNode` expresa dimensiones resueltas; no se debilitan los tipos de React Flow ni se añaden assertions o supresiones.
|
||
|
||
El canvas calcula su altura con los bounds y el ancho disponible, con 32 px de margen total; un principal sin periféricos/software ocupa aproximadamente 224 px de canvas. Con el preview completo la altura sigue acotada por los límites existentes. `fitView` tiene menos padding y zoom mínimo de 0,86. No se amplían nodos por encima de su tamaño de lectura.
|
||
|
||
La identidad del canvas ya no depende del objeto de datos ni se desmonta desde la card con cada render. React Flow reajusta la vista cuando cambian las dimensiones, posiciones, conjunto de IDs o ancho disponible. Un refetch con el mismo conjunto conserva el canvas y la cámara.
|
||
|
||
### Cobertura y estados parciales
|
||
|
||
`workplaceMapPreview` centraliza la presentación del límite que ya existía: un principal válido, hasta dos asociados y hasta tres instalaciones. `WorkplaceCompactMap` recibe ese mismo preview, evitando límites duplicados entre las dos presentaciones. No se modifica `workplaceMapModel`, la resolución de referencias ni ninguna regla de selección.
|
||
|
||
El total de equipamiento incluye el principal cuando forma parte de la composición. El de software corresponde a las instalaciones admitidas por el contrato previo, no al catálogo de productos ni a aplicaciones activas en general. No se muestran «3 de 3» ni cantidades de una fuente fallida. En un error con datos retenidos se conservan los nodos, se explica el error con el componente de consultas existente y se retira el total definitivo de esa fuente.
|
||
|
||
Se mantienen carga inicial, carga parcial, ausencia de software, ausencia de periféricos, principal ausente/inconsistente y errores recuperables. No se añaden nodos ficticios para rellenar vacíos ni se convierte «Pendiente» o «Retirada pendiente» en «Configurado».
|
||
|
||
### Responsive y accesibilidad
|
||
|
||
Se mantienen el umbral de 640 px útiles para la alternativa lineal y la estructura global del dashboard. En móvil aparecen principal, asociados y software, con las mismas tarjetas, estados, enlaces y cobertura. El nuevo rótulo no aparece en el orden de tabulación; las aristas tampoco reciben foco.
|
||
|
||
Se conservan controles de gestión fuera de los wrappers editables de React Flow, foco visible y devolución de foco desde los diálogos existentes. El mapa sigue sin arrastre, conexiones manuales, reconexión, borrado, selección múltiple, pan ni zoom gestual. No persiste coordenadas.
|
||
|
||
Los símbolos de la leyenda y los patrones de arista distinguen conceptos sin depender solo del color. Se usan tokens reales, Inter local y componentes instalados. Los tooltips respetan reducción de movimiento; no se añade animación a las aristas ni a la cámara.
|
||
|
||
### Archivos y alcance
|
||
|
||
Se ajustan las tarjetas/nodos, canvas, leyenda, compact map y `workplace-map-layout.ts`; se incorporan `workplace-map-preview.ts`, `WorkplaceSoftwareHeadingNode`, `WorkplaceMapCoverage` y pruebas geométricas específicas. La composición exterior solo aporta una referencia al panel de software para el acceso desde la cobertura, conservando sus columnas y componentes.
|
||
|
||
No se modifican APIs, DTO, MariaDB, Sequelize, permisos, compañías, estados, historial, configuraciones de perfiles, operaciones de equipamiento/software, sidebar ni cabecera. No se instalan dependencias o skills. El manifest de Companies únicamente registra la prueba nueva en su suite, además de los cambios previos de la primera iteración.
|
||
|
||
Skills utilizadas: `frontend-design` para protagonismo y proporción; `ux-dashboard` para jerarquía y reducción de redundancia; `shadcn` para badges, tooltip y acciones con las APIs Base UI reales; `vercel-composition-patterns` para separar cobertura, preview y nodos; `vercel-react-best-practices` para estabilidad del grafo y datos derivados; `ux-tables` y Compact List para cobertura y salida al conjunto completo; `ui-ux-pro-max` para foco, nombres largos, movimiento reducido y ausencia de overflow; `rdx-solid` para contratos y pruebas geométricas. No se instala un auditor WCAG dedicado ni se sustituye DESIGN por recomendaciones genéricas.
|
||
|
||
### Pruebas y resultados de la iteración 2
|
||
|
||
| Comprobación ejecutada | Resultado real |
|
||
| --- | --- |
|
||
| `pnpm --filter @metis/companies typecheck` | Correcto |
|
||
| `pnpm --filter @metis/web typecheck` | Correcto |
|
||
| `workplace-map-layout.test.ts`, con Node/tsx | 22 pruebas correctas: tamaños, bounds, coordenadas, no solapamiento, rótulo, software inferior, capacidad, determinismo, límites y cobertura parcial |
|
||
| `workstation-detail.test.tsx`, con Node/tsx | 34 pruebas correctas es/en. Se amplía cobertura/leyenda y se añade regresión de traducción de revisión sin referencia navegable |
|
||
| Suite dirigida con `node --test` | Seis ficheros correctos: mapa, ficha, dashboard, equipamiento, relaciones y expansión de perfiles |
|
||
| Biome `check` del detalle, traducciones es/en de assets y manifest de Companies | Correcto, sin supresiones ni advertencias del cambio |
|
||
| ESLint dirigido al detalle de puestos | Correcto |
|
||
| `pnpm build` | Correcto para servidor y web. Se mantiene el aviso general de chunk principal grande; el canvas diferido ocupa unos 228 kB / 74 kB gzip |
|
||
| Recorrido MCP completo | Siete recorridos correctos: seis anchos, denso, navegación/gestión, errores/vacíos, resize en la misma página, cobertura/estados/refetch y contraste/movimiento reducido |
|
||
| Verificación final de revisión | Tras corregir una clave de traducción, dos recorridos adicionales correctos para software/cobertura/refetch y contraste/movimiento reducido, con capturas renovadas |
|
||
| `git diff --check` y parseo AST del script Python | Correctos |
|
||
| Checks globales | Ejecutados; continúan fallando fuera del alcance, como se detalla a continuación |
|
||
|
||
`pnpm typecheck` vuelve a fallar en `packages/shadcn-ui/src/components/calendar.tsx:165`, por la opcionalidad de `locale`. `pnpm lint`/`pnpm check` señalan archivos ajenos al mapa, entre ellos código previo de fotos de personas, migraciones y ejemplos de skills. `pnpm test` se detiene en `modules/resources/src/api/application/resource-scope.test.ts`. La suite completa de Companies pasa el mapa/ficha y sigue fallando en `companies-page.test.tsx`, fuera del cambio. No se modifica vendor ni esos módulos para ocultar los fallos.
|
||
|
||
Las mediciones incluyen:
|
||
|
||
- Principal sin asociados ni software: **224 px** de canvas y un nodo, frente a los 430 px anteriores.
|
||
- Denso: tres nodos visibles de **80 activos**; la acción existente despliega los 80. Hay 60 perfiles sintéticos en ese escenario, 50 activos y 10 archivados.
|
||
- Estados de instalación: se conservan las etiquetas canónicas **Configurado**, **Pendiente de configurar** y **Pendiente de retirar**, más **Requiere revisión** cuando corresponde. No se sustituyen por el estado del producto.
|
||
- Nombres largos: tooltip abierto mediante Tab/Shift+Tab, nombre completo accesible y tarjetas de software sin overflow en ambos ejes.
|
||
- Refetch: **mismo elemento DOM de canvas y misma transformación de cámara**, con cinco nodos de datos retenidos. Al fallar software, esos nodos permanecen y desaparece el total definitivo.
|
||
- Contraste de 18 textos del mapa/leyenda en modo claro: mínimo medido **4,83:1**. La medición detectó inicialmente 4,39:1 en el subtipo del principal; se corrigió con `text-secondary`, sin crear otro token ni modificar el tema.
|
||
- Teclado: Tab al principal, outline visible, Enter para navegar, Tab/Shift+Tab para nombres largos, Space para contexto remoto y Escape para cerrar software. También se comprueban apertura/cancelación de gestión, expansión de perfiles y retorno de foco. Rótulo y aristas sin tab stop.
|
||
- Reducción de movimiento activada y comprobada en navegador; sin añadir animación al grafo.
|
||
|
||
### Evidencia comparativa e instalación utilizada
|
||
|
||
La auditoría usa el servidor Playwright MCP fijado en el proyecto y el procedimiento de `docs/playwright-mcp.md`, con usuario ordinario, API real e instalación aislada exclusivamente en `metis_desk_test`. Volumen final de ambas preparaciones: 15 compañías y, en la principal, 59 personas, 135 activos y 73 recursos. El escenario de ocho instalaciones, nombres largos y estados variados se simula en la **frontera HTTP del navegador** utilizando recursos sintéticos existentes; no se insertan esas instalaciones ni se modifican datos habituales. Los errores también se simulan en esa frontera.
|
||
|
||
Comandos reproducibles, con el preparador y las fixtures documentados abiertos:
|
||
|
||
```bash
|
||
METIS_PLAYWRIGHT_STORAGE_STATE=.artifacts/playwright/authenticated-ID/session.json \
|
||
METIS_MAP_CAPTURE_PHASE=before \
|
||
python3 scripts/workplace-visual-map.test.py WorkplaceVisualMapTests.test_map
|
||
|
||
METIS_PLAYWRIGHT_STORAGE_STATE=.artifacts/playwright/authenticated-ID/session.json \
|
||
METIS_MAP_CAPTURE_PHASE=after \
|
||
python3 scripts/workplace-visual-map.test.py
|
||
```
|
||
|
||
Capturas normales del mismo conjunto de datos, abiertas e inspeccionadas antes y después:
|
||
|
||
| Ancho | Antes | Después |
|
||
| --- | --- | --- |
|
||
| 1600 px | [Antes](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/before/map-1600.png) | [Después](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/after/map-1600.png) |
|
||
| 1440 px | [Antes](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/before/map-1440.png) | [Después](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/after/map-1440.png) |
|
||
| 1280 px | [Antes](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/before/map-1280.png) | [Después](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/after/map-1280.png) |
|
||
| 1024 px | [Antes](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/before/map-1024.png) | [Después](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/after/map-1024.png) |
|
||
| 768 px | [Antes](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/before/map-768.png) | [Después](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/after/map-768.png) |
|
||
| 390 px | [Antes](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/before/map-390.png) | [Después](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/after/map-390.png) |
|
||
|
||
Evidencia adicional inspeccionada:
|
||
|
||
- [Principal sin periféricos ni software](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/after/principal-only.png).
|
||
- [Sin software](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/after/without-software.png).
|
||
- [Sin principal](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/after/without-principal.png).
|
||
- [Denso y cobertura de equipamiento](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/after/dense.png).
|
||
- [Error parcial de software](../.artifacts/playwright/authenticated-9iin9foh/workstation-dashboard-wd043/visual-map-iteration-2/after/software-error.png).
|
||
- [Cobertura, estados y revisión traducida](../.artifacts/playwright/authenticated-rcm2kou7/workstation-dashboard-wd043/visual-map-iteration-2/after/software-coverage-states.png).
|
||
- [Datos retenidos sin total definitivo](../.artifacts/playwright/authenticated-rcm2kou7/workstation-dashboard-wd043/visual-map-iteration-2/after/software-stale-no-total.png).
|
||
|
||
Los dos últimos casos se repitieron en una segunda instalación desechable tras detectar y corregir la etiqueta de revisión sin traducir durante la inspección visual. Cada directorio conserva su propio `measurements.json`; no se mezclan sesiones ni se editan imágenes para aparentar un resultado. Ambos preparadores se cerraron y sus sesiones privadas se retiraron, conservando las imágenes excluidas de Git.
|
||
|
||
### Diferencias respecto al mockup, límites y mejoras futuras
|
||
|
||
La primera banda prioriza la misma jerarquía del mockup, pero mantiene los tokens navy/secondary del sistema, iconos semánticos y badges reales. No utiliza fotos de portátiles ni logos de software inventados. Los asociados representan pertenencia al puesto, no cableado; el software refleja documentación, no ejecución en tiempo real. No se añaden menús decorativos sin una acción existente.
|
||
|
||
La leyenda se sitúa debajo para no competir con el nodo principal ni reducir el ancho útil. El reparto bilateral depende de capacidad; las ventanas intermedias conservan una columna de asociados a la izquierda. Se mantienen los límites 2/3 y el resto se consulta en sus colecciones.
|
||
|
||
Quedan fuera de la validación lectores de pantalla concretos, ampliación exclusiva de texto al 200 %, alto contraste del sistema y modo oscuro. La medición de textos en claro y las pruebas de teclado no equivalen a certificar WCAG. Una mejora futura puede medir alturas a partir del contenido ampliado y ampliar esa matriz de accesibilidad; esta entrega no añade un motor de layout, virtualización, persistencia o edición de diagramas.
|
||
|
||
## WD04.4 — Asociación de activos desde el mapa visual
|
||
|
||
### Objetivo y equivalencia funcional
|
||
|
||
El mapa ofrece «Añadir activo» como otra entrada a la edición de la composición de Workstation. Utiliza `saveWorkstation`, el mismo `POST /api/assets/:companyId/workstations/:id/update`, el mapper de referencias y la transacción de inventario existentes. Conserva nombre, estado, sede, responsable, miembros anteriores y ordenador principal. La ausencia de principal se conserva como la ausencia opcional que devuelve actualmente el contrato; añadir un ordenador no lo convierte en principal.
|
||
|
||
No se añaden entidades, tablas, migraciones, endpoints del diagrama ni dependencias. Las asociaciones, restricciones e historial siguen perteneciendo a Assets. No se modifican las auditorías históricas ni Resources Global Scope.
|
||
|
||
### Componentes y composición
|
||
|
||
| Unidad | Responsabilidad |
|
||
| --- | --- |
|
||
| `WorkplaceAddAssetNode` | Botón semántico con Plus, borde discontinuo y tokens neutros. Nombre accesible «Añadir activo al puesto» y texto secundario «Seleccionar o crear». Reutilizado en canvas y presentación compacta. |
|
||
| `WorkplaceMapPresentation` | Selecciona React Flow o `WorkplaceCompactMap` según el ancho real; mantiene la carga diferida del canvas. |
|
||
| `workplaceMapLayout` | Incluye `presentation:add-asset` en Dagre junto al equipamiento. Los enlaces internos de distribución no se devuelven como aristas. El software queda debajo de todo el conjunto de equipamiento. |
|
||
| `WorkstationAssetAddDialog` | Diálogo Picker List de Assets, exportado mediante su API pública. Companies aporta puesto, compañía, lookups y destino del foco. |
|
||
| `useWorkstationAssetAdd` | Selección, snapshot de revisión, alta relacionada, guardado y recuperación de lectura. Los nodos no conocen HTTP ni consultas. |
|
||
| `WorkstationAssetAddActions` | Pie de creación, cancelación y confirmación; bloquea acciones durante las operaciones pendientes. |
|
||
| `workstationAssetUnavailable` | Regla de disponibilidad extraída del editor existente y compartida con el nuevo flujo. |
|
||
| `WorkstationSaveFeedback` | Error y recuperación explícita de conflictos, compartidos por el picker y el editor tradicional. |
|
||
|
||
El nodo de acción no tiene identidad de Asset, no entra en el adaptador de activos, en `assetIds`, en los contadores ni en el historial. No tiene estados técnicos ni enlace de ficha. El límite visual continúa siendo principal + dos asociados y tres elementos de software; la acción se añade aparte. «Ver todo el equipamiento» conserva acceso a los miembros ocultos.
|
||
|
||
Dagre conserva un layout determinista, sin coordenadas persistidas. La distribución admite dos lados desde 720 px efectivos, evitando alargar innecesariamente el mapa al incorporar la acción. El canvas conserva la política existente de cámara estable: se recalcula cuando cambian dimensiones o composición visible, sin ejecutar `fitView` por cada refetch o cambio de estado del diálogo. Continúan desactivados drag, conexiones, reconexiones y borrado.
|
||
|
||
### Picker, búsqueda y alta de activos
|
||
|
||
Se reutilizan `AssetPickerList`, `AssetList`, `useAssetPickerData` (consultas extraídas del controller existente y compartidas por ambas selecciones), `FormDialog layout="picker"`, `PickerHelp` y `AssetEditorDialog`/`AssetForm` con su asistente real. No se duplica el formulario ni se omite la plantilla. La lista utiliza las consultas y cachés existentes de inventario, catálogo e infraestructura. El selector actual de activos es local sobre la respuesta existente del inventario; mantiene sus filtros por plantilla, búsqueda, columnas y paginación. No se introduce una descarga alternativa ni se sustituye un selector remoto por otro local.
|
||
|
||
Solo se ofrecen activos de la compañía actual y se excluyen los miembros del snapshot abierto. Los activos no operativos u ocupados por otro puesto activo aparecen bloqueados conforme a las mismas reglas del editor. En puestos inactivos se mantiene la disponibilidad que ya admite el dominio. Se respeta el máximo de 200 miembros; el nodo deshabilitado informa del límite y la gestión existente permite retirar equipamiento.
|
||
|
||
El alta se abre anidada: cancelarla conserva búsqueda y selección y devuelve el foco a «Crear activo nuevo». Al guardar, el activo permanece en inventario y vuelve seleccionado al picker. La búsqueda utiliza su nombre real para hacerlo visible incluso cuando el inventario tiene varias páginas. La asociación exige después «Añadir al puesto». Cancelar esa asociación no elimina el activo creado.
|
||
|
||
### Revisión, persistencia e historial
|
||
|
||
La inspección detectó que el guardado bloqueaba la compañía y las reservas, pero no verificaba la revisión que el usuario había editado. Se incorpora una precondición opcional `If-Match: "<revisionId>"` al guardado existente, sin cambiar el cuerpo `{ values }`. Se compara con la última revisión **dentro de la transacción y después de adquirir los bloqueos existentes**. Si difiere, devuelve `409 WORKSTATION_CHANGED` antes de modificar la composición o generar historial.
|
||
|
||
El picker conserva la revisión de apertura aunque haya refetch en segundo plano. El editor tradicional captura también su revisión al abrir y el renombrado envía la revisión que acaba de leer. «Actualizar puesto y revisar selección» recupera el estado actual; el usuario revisa y confirma de nuevo. No hay rebase automático ni overwrite silencioso. Los clientes que no envían la cabecera conservan el contrato previo; no se ha convertido en una precondición obligatoria para integraciones existentes.
|
||
|
||
Una asociación válida sigue creando la revisión normal de Workstation. El servidor continúa validando pertenencia a compañía, duplicados, estado, reservas y máximo de miembros. No se escribe el nodo de acción ni se cambia el principal. La comprobación unitaria de concurrencia verifica también que un guardado semánticamente idéntico con la revisión actual no añade historial.
|
||
|
||
### Permisos y estados
|
||
|
||
Se comprobó `createAssetModule`, `registerWorkstationRoutes` y `requireAuthenticated`: la edición de Workstation utiliza actualmente la misma sesión autenticada que la consulta del inventario; no existe un permiso de composición distinto ni un rol de lectura de puestos. Se reutiliza ese límite real, sin inventar permisos ni excluir arbitrariamente al usuario ordinario. El consumidor aporta `canEdit` y el mapa oculta la acción cuando es falso o el inventario no está disponible. El servidor sigue siendo la autoridad. Las pruebas SSR cubren `canEdit=false` y el preparador/recorrido HTTP comprueban `401` sin sesión. **No se afirma una prueba con un rol de solo lectura que el contrato actual no ofrece.**
|
||
|
||
El puesto sin activos conserva su información de vacío y ofrece la acción tanto en desktop como en móvil. Sin principal se representan los miembros existentes, sin fabricar un ordenador central. Los errores parciales del dashboard mantienen su degradación anterior.
|
||
|
||
| Situación | Recuperación |
|
||
| --- | --- |
|
||
| Fallo de lectura del selector | Estado de error de la lista y Reintentar, manteniendo abierto el diálogo y la búsqueda. |
|
||
| Fallo de validación/asociación | Mensaje anunciable, selección conservada y posibilidad de reintentar; el mapa no se modifica de forma optimista. |
|
||
| Cambio concurrente | Rechazo del servidor y actualización explícita antes de confirmar otra selección. |
|
||
| Fallo del formulario de alta | El formulario real conserva su validación y su gestión de errores. |
|
||
| Alta correcta y asociación fallida/cancelada | El activo permanece en inventario; no se vuelve a crear ni se elimina como compensación. |
|
||
| Asociación confirmada y refetch fallido | Se informa de que se guardó, el diálogo permanece abierto y Reintentar ejecuta exclusivamente la lectura. |
|
||
|
||
El guardado correcto invalida únicamente `inventoryKeys.company(companyId)` y obtiene el inventario actual antes de cerrar. Se regeneran el modelo, los contadores y Dagre desde la respuesta leída. El retorno de foco utiliza el disparador original si sigue conectado o el botón actual del mapa si React Flow lo ha reemplazado.
|
||
|
||
### Responsive y accesibilidad
|
||
|
||
En menos de 640 px efectivos se utiliza la presentación compacta con la misma acción antes del software. Se comprobaron 1600, 1440, 1280, 1024, 768 y 390 px, incluyendo el ancho real con las navegaciones actuales. Los diálogos mantienen altura estable, búsqueda enfocada, selección en la primera columna y scroll interno; no aparece overflow horizontal global.
|
||
|
||
El botón es accesible mediante ratón, Enter y Espacio, con foco visible, estado disabled y texto de límite. Los errores usan `role="alert"`; la lectura posterior al guardado informa mediante `role="status"`. Base UI gestiona Escape, foco inicial y aislamiento/restauración entre diálogos; no se añaden listeners o focus traps manuales. La acción tiene `pointer-events-auto` para seguir recibiendo clics con los nodos de React Flow no editables.
|
||
|
||
Skills consultadas: `ux-inputs-and-forms` como principal para el flujo; `shadcn`, `frontend-design`, `vercel-composition-patterns` y `vercel-react-best-practices` para reutilización de primitivas, composición y estado. Se conservan los criterios de `ux-dashboard`/`ux-tables` aplicados en las iteraciones anteriores. No están disponibles los auditores especializados sugeridos por las skills; se utilizan las comprobaciones reales de teclado y navegador disponibles, sin instalar dependencias.
|
||
|
||
### Pruebas y evidencias WD04.4
|
||
|
||
Instalación desechable preparada con `scripts/visual-audit.py`, exclusivamente `metis_desk_test`, autenticada mediante el formulario real con un usuario ordinario. `visual-audit-volume.py` y `workstation-dashboard-fixtures.py`: 15 compañías; compañía principal con 59 personas, 135 activos y 73 recursos antes de las altas del recorrido, además de un puesto con 80 miembros y 60 perfiles. Las altas de WD04.4 amplían ese inventario; no se accede a bases habituales.
|
||
|
||
Las pruebas nuevas de revisión y disponibilidad se incluyen en `pnpm --filter @metis/assets test`. Se amplían las pruebas de layout y ficha para acción autorizada/oculta, vacío, separación de contadores y aristas, ausencia de solapamientos y determinismo. `scripts/workplace-add-asset.test.py` extiende el recorrido MCP existente y utiliza HTTP real para verificar composición, principal, historial, aislamiento y conflictos.
|
||
|
||
| Comprobación ejecutada | Resultado |
|
||
| --- | --- |
|
||
| `pnpm --filter @metis/assets typecheck` | Correcto. |
|
||
| `pnpm --filter @metis/companies typecheck` | Correcto. |
|
||
| `pnpm --filter @metis/web typecheck` | Correcto. |
|
||
| `pnpm --filter @metis/assets test` | 29 archivos de pruebas aprobados, incluidos selector, formulario, Workstation, revisión y disponibilidad. |
|
||
| `pnpm --filter @metis/companies test` | 49 archivos aprobados; persiste el fallo previo de `companies-page.test.tsx`, ajeno a WD04.4. Las pruebas de mapa, ficha, perfiles y software pasan. |
|
||
| Pruebas dirigidas nuevas de revisión, disponibilidad, layout y ficha | Correctas. Se cubren autorización visual es/en, vacío y separación de la acción respecto al equipamiento. |
|
||
| Biome sobre los 30 archivos TS/TSX/JSON de esta intervención | Correcto, sin avisos. |
|
||
| ESLint sobre sus archivos TS/TSX | Correcto. |
|
||
| `pnpm build` | Correcto; conserva el aviso de tamaño del chunk principal. |
|
||
| `pnpm typecheck` global | Bloqueado por el error previo de `locale` opcional en `packages/shadcn-ui/src/components/calendar.tsx:165`. No se modifica vendor. |
|
||
| `pnpm lint` / `pnpm check` globales | Fallan por deuda previa de código/formato/importaciones fuera de esta intervención. Las comprobaciones dirigidas quedan limpias. |
|
||
| `pnpm test` global | Se detiene por el fallo previo de `modules/resources/src/api/application/resource-scope.test.ts`. |
|
||
| Regresión `workplace-visual-map.test.py` | **7 recorridos aprobados**, 172,755 s: seis anchos, densidad, enlaces, gestión, soporte, parciales, principal único, cobertura/refetch, contraste y movimiento reducido. |
|
||
| `workplace-add-asset.test.py` | **4 recorridos aprobados**, 95,685 s: asociación/cancelación/conflicto; seis anchos y móvil; alta/cancelación/error/reintento/refetch; límite visual y restricciones HTTP/MariaDB. |
|
||
| `git diff --check` | Correcto. |
|
||
|
||
Los recorridos verifican Enter, Espacio, búsqueda enfocada, selección por teclado, Escape y retorno de foco. La asociación móvil conserva la ausencia de principal. Tras el fallo de lectura posterior a un guardado, el contador instrumentado verifica **una sola escritura**. En la comprobación HTTP se observan `INVALID_MEMBERS`, `ASSET_IN_WORKSTATION`, `ASSET_NOT_FOUND` y `401` sin sesión. Los cuatro recorridos WD04.4 terminan sin errores JavaScript de página no capturados.
|
||
|
||
Evidencias locales revisadas visualmente, bajo `.artifacts/playwright/authenticated-c1voaeu5/workstation-dashboard-wd043/`:
|
||
|
||
- `visual-map-iteration-2/after/map-{1600,1440,1280,1024,768,390}.png`: mapa con acción. Anchos reales 915, 755, 887, 872, 688 y 326 px; canvas de 424, 405, 424, 424 y 468 px, y presentación compacta en móvil. Sin overflow global.
|
||
- `wd044-add-asset/picker-{1600,1440,1280,1024,768,390}.png`: selector en los seis tamaños.
|
||
- `wd044-add-asset/S01-associated.png`, `S02-S10-mobile-associated.png`, `S04-create-form.png`, `S04-created-selected.png`, `S06-conflict.png`, `S07-save-error.png`, `S07-saved-refresh-error.png`, `S08-limit.png`.
|
||
- Ambos directorios conservan `measurements.json` con geometría, resultados y hashes de las capturas. Las capturas quedan fuera de Git; no se versiona ni se copia la sesión privada.
|
||
|
||
Para repetir: iniciar `python3 scripts/visual-audit.py`, mantener abierto su entorno, ejecutar `visual-audit-volume.py` y `workstation-dashboard-fixtures.py` con el `METIS_PLAYWRIGHT_STORAGE_STATE` anunciado por el preparador, y después `workplace-visual-map.test.py` y `workplace-add-asset.test.py`. Ejecutar la regresión antes de los recorridos de asociación, que modifican las composiciones sintéticas. Cerrar el preparador al terminar retira exclusivamente su instalación y borra la sesión.
|
||
|
||
Limitaciones: no existe un rol de solo lectura de Workstation con el que ejecutar S09 como usuario autenticado; se cubren la prop de autorización y el rechazo anónimo real. El formulario de alta conserva sus validaciones y errores existentes, pero esta auditoría no fuerza un fallo HTTP de creación. Las comprobaciones globales no están verdes por los fallos previos indicados. Se observaron avisos de claves duplicadas en flujos anteriores del inventario durante la preparación/regresión; ya aparecían al preparar la sesión, antes del flujo WD04.4, y no se amplía el alcance para corregirlos.
|
||
|
||
El preparador de esta ejecución se cerró al finalizar: instalación temporal retirada y ausencia de `session.json` verificada; las capturas se conservan.
|