metis-desk/docs/mapa-visual-del-puesto.md
David 91f1d07e97 Add tests for current operating systems and workplace asset management
- 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.
2026-10-25 13:46:06 +01:00

44 KiB
Raw Blame History

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, handles, layout y contrato de ReactFlow.

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.

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:

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 Después
1440 px Antes Después
1280 px Antes Después
1024 px Antes Después
768 px Antes Después
390 px Antes Después

Evidencia adicional inspeccionada:

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.