# Componentes de interfaz Metis Desk usa shadcn/ui, con Base UI y el estilo base-vega, siguiendo la organización de factuges-erp. `packages/shadcn-ui` contiene únicamente el código del registro oficial y no se adapta ni se extiende desde este proyecto. Se consume mediante `@repo/shadcn-ui`. Los componentes genéricos compuestos o reutilizables propios viven en `packages/rdx-ui` y se consumen mediante `@repo/rdx-ui`. Los componentes que conocen contratos, textos, consultas o reglas de Metis viven en el módulo o aplicación que los utiliza; nunca en ninguno de los dos paquetes genéricos. ## Componentes actuales - Button: volver a comprobar la conexión. - Card: bloques de información y estado. - Select: idioma y selección de sistemas, entornos, cuentas, personas y demás relaciones, con navegación por teclado. - Input y Label: credenciales y datos de usuario. - Checkbox: filtros de listado, selección múltiple y configuración de columnas. - SwitchField: campos binarios de formularios, como estado activo, VPN, predeterminado o requerido. - Table: listados de usuarios, compañías, personas y departamentos. - Textarea: dirección opcional de las sedes, con varias líneas. - Dialog: primitiva de shadcn/ui, con foco y cierre por teclado. - RDX UI: FormDialog, FormActions, ContextHelp, SelectWithAction, SwitchField, EntityTable y StatisticCard. ## Convención de navegación Los destinos de secciones y fichas existentes siguen [Rutas del cliente](rutas-del-cliente.md). Usar enlaces reales para esos destinos y botones para acciones o diálogos locales. Los listados presentan datos, búsqueda y acciones. El botón de alta se coloca en la cabecera, a la derecha. Los formularios se abren en modal como norma general, con anchura adaptable y desplazamiento si el contenido lo requiere. No mostrar formularios junto al listado. Las ediciones se abren mediante una acción explícita. Compañías, personas, departamentos, asignaciones, sedes y usuarios siguen este criterio. Las personas, los departamentos y las asignaciones se crean y editan en modales. El historial se consulta en un diálogo. Catálogos, cuentas, grupos, recursos, permisos y necesidades siguen la misma convención. Los permisos de una necesidad se eligen mediante casillas shadcn/ui. TanStack Table filtra el listado de compañías por nombre; React Hook Form gestiona los formularios con los contratos Zod compartidos. El paquete exporta componentes individuales. Importar solo el componente necesario: ```tsx import { Button } from "@repo/shadcn-ui/components/button"; ``` El tema base se define en `packages/shadcn-ui/src/styles/globals.css`; las extensiones propias se declaran fuera del vendor. La aplicación importa ese CSS y declara las fuentes de Tailwind de los módulos y de `packages/rdx-ui`. Usar tokens semánticos como bg-background, text-foreground y border-border en vez de repetir colores por pantalla. ## Tipografía La aplicación carga localmente Inter y JetBrains Mono desde `apps/web/src/fonts`, con `font-display: swap`. Georgia usa la fuente disponible en el sistema, seguida de `Times New Roman`. Tailwind 4 expone las tres familias mediante `font-sans`, `font-serif` y `font-mono`: Inter es la tipografía general; Georgia se reserva para contenido editorial puntual; JetBrains Mono, para código, identificadores y valores técnicos. Las licencias OFL 1.1 de las dos fuentes descargadas acompañan sus respectivos archivos. ## Añadir componentes `SectionNavigation`, en `modules/companies/src/web/shared/ui`, compone Card de la biblioteca y BrowserLink de core para índices de secciones. Recibe una etiqueta de navegación y elementos con `href`, título, descripción y valor numérico opcionales. No conoce DTO, consultas ni endpoints; sus consumidores aportan los textos traducidos y únicamente valores conocidos. Se reutiliza en el resumen de compañía y en los índices de catálogos globales y de compañía, con foco visible y enlaces que pueden abrirse en otra pestaña. No se modifica el paquete vendor. Para componentes propios de un módulo, consultar primero la [estructura web por entidades y operaciones](estructura-web-por-entidades-y-operaciones.md) y los componentes equivalentes existentes. Ubicar cada componente en la entidad y operación responsables, o en su ámbito compartido cuando tenga varios consumidores reales. Los componentes específicos de Metis Desk no pertenecen al paquete visual genérico. La incorporación o actualización de primitivas en el paquete vendor requiere una instrucción explícita del usuario según `AGENTS.md`. En ese caso, utilizar el procedimiento siguiente. Desde la raíz del proyecto, dentro de WSL: ```bash pnpm --filter @repo/shadcn-ui ui:add input ``` El CLI está fijado en package.json. Revisar los archivos y dependencias añadidos antes de validar. No usar --overwrite sin revisar las adaptaciones locales. apps/web/components.json también permite al CLI localizar el paquete compartido desde la aplicación. Después de generar código, separar los imports de tipos con import type y ejecutar las comprobaciones del proyecto. No añadir todo el catálogo ni crear envoltorios sin una necesidad concreta. ## Dependencias Base UI proporciona interacción accesible y gestión del foco. class-variance-authority define variantes del botón; cn combina clases de Tailwind. lucide-react aporta los iconos del selector y tw-animate-css sus transiciones. shadcn es una herramienta de desarrollo; su hoja de estilos común se incorpora al compilar. Guías oficiales: [monorepos](https://ui.shadcn.com/docs/monorepo) e [instalación manual](https://ui.shadcn.com/docs/installation/manual). ## Ayuda contextual ContextHelp muestra una explicación breve y permite desplegar ejemplos y recomendaciones mediante teclado. EntityHelp aporta contenido específico en español e inglés para compañías, personas, departamentos, sedes, asignaciones y todas las secciones de cuentas y accesos. Los catálogos explican si la definición es global o exclusiva de la compañía. Los ejemplos son orientativos: nunca se convierten en valores del formulario. La ayuda se presenta en una tarjeta con icono circular, título y explicación; debajo, una banda desplegable contiene los ejemplos y recomendaciones. ContextHelp admite `icon?: ReactNode` para sustituir el icono informativo predeterminado, por ejemplo `icon={}`. El icono es decorativo y hereda el tamaño y color del contenedor. Se conservan los tokens del tema, los textos aportados por cada módulo y el despliegue nativo por teclado. El botón Ayuda de Cuentas y accesos y Catálogos informáticos permite consultar la explicación de la sección sin abrir un alta. La misma ayuda aparece dentro de los modales de alta y edición. FormDialog utiliza una variante amplia para personas y formularios con más campos; el número de campos no cambia la navegación a una página propia. La ayuda de los formularios se coloca en su parte inferior, inmediatamente antes de FormActions. Este pie compartido reúne Guardar y Cancelar dentro del formulario, conservando el envío y el cierre accesible del diálogo. Los diálogos de consulta mantienen únicamente Cerrar. ## SelectWithAction: desplegable con acción integrada El componente compartido vive en `@repo/rdx-ui/components/select-with-action`. Compone Select, Button y ButtonGroup de shadcn/ui (Base UI, Vega). Presenta selección y botón «+» como un único grupo visual, manteniendo controles separados para teclado y lectores de pantalla. ButtonGroup utiliza fieldset para expresar semánticamente la agrupación. Su contrato recibe id, etiqueta, opciones, placeholder, valor controlado y callback de selección. La acción es opcional y contiene etiqueta accesible, callback y estado deshabilitado. Admite opciones deshabilitadas, estado inválido, descripción accesible y obligatoriedad. Deshabilitar el selector también deshabilita la acción. El botón usa type=button y no envía el formulario. No conoce compañías, HTTP, formularios ni modales: esas responsabilidades pertenecen al consumidor. No contiene textos propios que deban traducirse. ## SwitchField: activación y desactivación `SwitchField` vive en `@repo/rdx-ui/components/switch-field`. Compone el Switch de shadcn/ui con su etiqueta accesible y recibe un valor controlado, `onCheckedChange`, `id`, texto y estado deshabilitado. No conoce React Hook Form ni ningún DTO: el formulario aporta el valor y actualiza su fuente de verdad. Se usa para cambios binarios que expresan una propiedad o un estado de la entidad, incluidos activo, requerido, contacto, VPN, predeterminado y relación simétrica. No sustituye Checkbox en filtros ni en elecciones de varias opciones, donde la casilla comunica correctamente inclusión, exclusión o selección múltiple. ### Formularios que permiten alta rápida Sistema permite crear un sistema propio de la compañía mediante un modal adicional. Entorno permite crear un entorno para el sistema activo seleccionado; su acción no aparece antes de elegir un sistema. Los campos fijos en edición no ofrecen alta rápida. Uso y otros valores cerrados tampoco ofrecen altas. QuickCreateDialog conserva los valores del formulario principal, actualiza el catálogo y selecciona el nuevo UUID después de guardar. Usa las rutas, protección CSRF y reglas de negocio existentes. Un nombre activo ya existente permite usar ese registro sin duplicarlo. Cancelar el modal adicional conserva la cuenta sin cambios. El elemento creado permanece en el catálogo aunque se cancele después la cuenta. La ayuda aparece antes del pie con Guardar y seleccionar / Cancelar. Solo hay un nivel de alta adicional; el nuevo formulario no vuelve a ofrecer altas rápidas. La revisión de los formularios extiende este patrón a personas, departamentos, cuentas, grupos, recursos, tipos de recurso, permisos y plantillas. Las asignaciones de departamento permiten añadir el primero sin abandonar el formulario. Se reutilizan los formularios y rutas existentes, manteniendo su validación y ayuda inferior. Los entornos necesitan un sistema activo seleccionado. Los permisos nuevos heredan el tipo del recurso elegido. Al crear un grupo desde una pertenencia, sistema y entorno quedan fijados a los de la cuenta. Las plantillas siguen siendo globales; el modal lo explica y carga únicamente sus opciones globales. Los elementos creados desde un selector están activos para poder seleccionarlos. Uso de cuenta, estado de configuración e idioma son listas cerradas. Las referencias históricas de cobertura (necesidad, autorización, titularidad y pertenencia) conservan sus operaciones propias. Los campos inmutables en edición no ofrecen altas. Los permisos múltiples de una necesidad continúan como casillas. El catálogo de activos utiliza SelectWithAction para añadir campos a una plantilla y crearlos mediante un modal adicional. Los tipos de dato constituyen una lista cerrada. Las definiciones de serie se consultan en modales de lectura; su personalización se realiza mediante copias. El inventario añade altas rápidas de plantilla, sede y persona en los activos. La plantilla creada desde un activo solo ofrece campos existentes para mantener un único nivel adicional de modal. Las características utilizan controles según su tipo; los valores sí/no conservan un estado vacío distinto de No. ## Listados de entidades El estándar se define en [Patrón de listados de entidades](patron-de-listados-de-entidades.md), con inventario de pantallas y excepciones. EntityListHeader, EntityTable, EntityRowActions, EntityAction y EntityStatus viven en @repo/rdx-ui/components/entity-list. EntityTable recibe datos y columnas tipadas; utiliza TanStack Table para búsqueda, ordenación, visibilidad y paginación local. Admite búsqueda y filtros controlados por el módulo, conservando su semántica y ámbito. Las columnas de identificación y acciones quedan visibles. Los menús utilizan Base UI y conservan los callbacks de las acciones originales. No realiza peticiones ni conoce DTO de negocio. La paginación es local sobre los conjuntos que las API actuales ya devuelven. No simula paginación de servidor. La selección del activo para el panel sigue perteneciendo a Inventario, fuera de la tabla compartida. EntityTable separa internamente la configuración de columnas/ordenación, el contenido semántico y la paginación; useEntityTable concentra el estado de representación. entityLabel permite contar «plantillas», «campos» o «tickets» aunque el título del espacio sea más amplio. Los tamaños disponibles son 10, 25, 50 y 100; una opción vacía nunca cambia el tamaño a cero. Se declara @tanstack/react-table en el paquete compartido porque es quien consume ahora la librería; se reutiliza la versión 8.21.3 que ya utilizaba Compañías. No se introduce un segundo sistema de tablas. ## Catálogo de marcas y selector funcional BrandCatalogPage reutiliza EntityListHeader/EntityTable con búsqueda, filtro de estado, columnas, ordenación y paginación. BrandEditorDialog compone FormDialog y BrandForm tanto para la gestión global como para las altas relacionadas; el formulario conserva ContextHelp y FormActions. BrandLogo y BrandLogoField muestran y permiten seleccionar SVG estáticos locales, con vista previa, errores de campo y acción de quitar. La columna de logotipo reutiliza esa presentación. Los límites, validación y fuentes se describen en [Catálogo global de marcas](catalogo-global-de-marcas.md). DeleteBrandDialog compone FormDialog y DialogFooter para confirmar la eliminación de una marca personalizada. El listado solo coordina selección y cierre; el controller de `brands/delete` gestiona mutación y caché. Conserva cancelación y Escape, bloquea cierre y botones durante la petición y mantiene errores en el modal. Las referencias actuales o históricas se protegen en el servidor. BrandSelect, dentro del módulo assets, adapta SelectWithAction al catálogo remoto: filtra marcas activas, conserva la selección inactiva actual y textos anteriores, ofrece alta rápida y respeta una sola anidación. El borrador padre se conserva y la nueva marca se selecciona por UUID. El componente genérico no conoce BrandDTO, consultas ni endpoints. Véase [Catálogo global de marcas](catalogo-global-de-marcas.md). ## Componentes modales de los módulos La ubicación de componentes, formularios y clientes HTTP sigue la [estructura web por entidades y operaciones](estructura-web-por-entidades-y-operaciones.md). El [inventario de modales](inventario-de-modales.md) localiza las interacciones de alta, edición, copia, confirmación, detalle, historial, selección, ayuda y configuración, incluyendo las altas rápidas dentro de otros formularios. La regla normativa de organización reside en AGENTS.md. Las páginas coordinan apertura, selección y callbacks. El componente modal posee su estructura y la lógica que le pertenece; el formulario mantiene validación, borrador, errores y guardado. Se reutilizan FormDialog y FormActions sin modificar su implementación vendor. Los diálogos específicos de entidades viven junto a sus consumidores, no en la biblioteca visual genérica. CompanyEditorDialog, PersonEditorDialog, DepartmentEditorDialog, SiteEditorDialog, BrandEditorDialog y SupportToolEditorDialog se reutilizan donde ya existen operaciones equivalentes. Los callbacks de guardado actualizan la selección del consumidor; no se cierra un formulario por el mero inicio de una mutación. Los historiales de consulta se derivan de los datos remotos actuales, mientras que los borradores de edición permanecen locales. ## Acciones de fila ancladas EntityRowActions muestra un botón icon-sm con EllipsisIcon, etiqueta accesible y título traducidos, sin texto visible repetido. EntityTable reserva la identidad de columna actions para operaciones: siempre visible, sin búsqueda/ordenación y fijada a la derecha mediante columnPinning de TanStack Table. EntityTableContent renderiza primero las columnas de datos y después la columna anclada, con cabecera/celdas sticky right-0, alineación derecha, anchura compacta y fondo opaco para evitar solapamientos al desplazar la tabla. Todos los listados consumidores heredan este comportamiento. ## Tarjetas de indicadores StatisticCard (`@repo/rdx-ui/components/statistic-card`) representa un indicador con etiqueta, valor, icono y tinte discreto. Admite carga/error, evita sustituir datos desconocidos por cero y utiliza un botón con foco visible cuando hay navegación. No depende de DTO ni endpoints. Inicio la reutiliza para sus cinco KPI; la organización de bloques y sus estados permanece local al dashboard. ## Shell global `App` conserva el acceso y el selector de idioma. `CompaniesWorkspace` compone sidebar, header global y contenido con los destinos y estado de navegación existentes. El header solo aparece en sesiones autenticadas. Los catálogos se seleccionan en la sidebar; la navegación interna de cada compañía permanece en `CompanyDetail`. Los tokens Tailwind 4 `--color-shell-*` de `apps/web/src/global.css` aíslan la paleta del shell de los colores semánticos del resto del producto: superficie negra exterior, contenido #F7F9FB, sidebar #0E282C, acento turquesa, texto y borde azul, radios 8/10/20px, sidebar 228px, header 88px, búsqueda 41px, fila de navegación 48px y avatar 35px. En móvil se conserva la navegación horizontal y envuelta, sin introducir drawer. El buscador usa Input de shadcn/ui y filtra compañías cargadas y destinos globales; Escape limpia y los resultados se recorren mediante Tab. No existe un servicio de búsqueda transversal de entidades. La campana no muestra avisos ficticios: no existe servicio de notificaciones. El perfil usa disclosure nativo con foco visible y conserva las acciones de identidad y selección de idioma. No hay primitives Avatar ni DropdownMenu instalados; el avatar muestra iniciales del usuario real, sin inventar foto ni cargo. Configuración abre un FormDialog con las acciones existentes. La sidebar global permite contraerse en escritorio de 228px a 72px mediante «Contraer menú» / «Expandir menú». Conserva iconos, nombres accesibles y títulos de ayuda también para compañías y subcatálogos. La navegación se desplaza por separado, manteniendo Configuración y el control de anchura en el pie visible. En móvil se conserva la navegación horizontal existente. El plegado se conserva entre recargas y sesiones mediante la clave `metis-desk:sidebar-collapsed` de `localStorage`, compartida por las sesiones de ese navegador y origen, no por cuenta ni mediante el servidor. Solo `true` inicia la barra plegada; valores ausentes o inválidos mantienen el estado desplegado. Se guarda desde la acción de plegar/desplegar. Si el navegador bloquea la lectura o la escritura, la barra sigue funcionando en memoria. No cambia el layout móvil. El menú expandido y su pie comparten márgenes horizontales de 20px mediante `--spacing-shell-sidebar-inset` y la utilidad `px-shell-sidebar-inset`. El header global usa `bg-shell-header` (#FFFFFF), con un borde inferior `border-shell-border`, sobre el contenido #F7F9FB. ### Estilos con Tailwind 4 La aplicación y la biblioteca base utilizan Tailwind 4.2.2 y el plugin oficial de Vite. El CSS de la aplicación contiene únicamente imports, fuentes de detección y tokens `@theme`; los layouts, estados y reglas responsive se expresan mediante utilidades en sus componentes. No hay clases globales de componente `desk-*`, `company-space-*`, `dashboard-*` ni de inventario. La implementación vendor de `@repo/shadcn-ui` no se modifica para personalizar el shell. Los colores propios utilizan `--color-shell-*`; las dimensiones, `--spacing-shell-*`; los radios, `--radius-shell-*` y `--radius-panel`; la sombra del desplegable, `--shadow-shell-popover`. El resto de la interfaz conserva los tokens semánticos de shadcn/ui. Los valores puntuales que no se repiten se declaran como utilidades arbitrarias, no como tokens o abstracciones anticipadas. Los degradados conservan su interpolación CSS original. Los breakpoints de la adaptación se declaran en píxeles para conservar los cortes anteriores: `shell` (768px), `inventory` (1100px) y `dashboard` (1200px). Se usan consistentemente dentro de cada layout para evitar conflictos de orden entre variantes con distintas unidades. `WorkspaceNavigationButton` compone Button y comparte los niveles principal y de catálogo, selección, foco y contracción. `DashboardNavigationButton`, `DashboardStateBadge` y `DashboardTable` encapsulan patrones repetidos del dashboard; este último reutiliza Table. Cada componente vive en su propio fichero y recibe acciones, textos o contenido del consumidor. `LanguageSwitcher` permite ajustar su margen mediante `className`, sin selectores globales dependientes de la cabecera que lo contiene. ## Avatar de iniciales `InitialsAvatar` (`modules/companies/src/web/shared/ui/initials-avatar.tsx`) es un componente visual sin dependencias de compañías, DTO ni consultas. Recibe `name` y atributos de span; admite `className` para tamaño y `aria-hidden` cuando el control contenedor ya tiene nombre accesible. Se reutiliza en las compañías de la sidebar y en el perfil del header. Muestra la primera inicial de hasta dos palabras; conserva siglas de una palabra como IBM (hasta tres caracteres), y usa «?» para nombres vacíos. El fondo pastel se elige de una paleta de doce colores mediante un hash del nombre normalizado: mayúsculas, espacios y acentos no cambian el color. El color no depende del orden ni cambia al recargar; distintos nombres pueden compartir color. El texto oscuro mantiene contraste legible. Los avatares de compañías siguen visibles cuando se contrae el menú y conservan la selección y las ayudas del botón de navegación. ## Avatar y logotipo de compañía `CompanyAvatar`, `CompanyIdentity` y `CompanyLogoField` reúnen la presentación y selección del logotipo. Su contrato, límites y persistencia se documentan en [Logotipos de compañías](logotipos-de-companias.md). Se usan tamaños de 24–28 px en navegación/listas y 40–56 px en detalle/formulario. ## FieldSections y TemplateSectionsEditor Componentes de @repo/rdx-ui independientes de los DTO, consultas y endpoints de Metis Desk. FieldSections recibe secciones, elementos con sectionId opcional y renderItem; presenta una cuadrícula plana cuando no hay secciones, o bloques desplegables accesibles con descripción y «Sin sección». Admite presentación de valores como listas de descripción y apertura ante errores externos. TemplateSectionsEditor recibe secciones, referencias de campos, nombres, textos traducidos, generador de identidad y onChange. Permite ordenar secciones/campos, editar nombre/descripción, asignar campos y mantener su obligatoriedad mediante SwitchField. El módulo consumidor conserva el estado en React Hook Form y genera UUID v7. Los helpers removeTemplateSection y copyTemplateLayout conservan referencias y remapean identidades respectivamente. No se modifican los componentes vendor. ## Formularios modales adaptativos `FormDialog` admite `size="small" | "medium" | "large"`. Los formularios sencillos mantienen el tamaño pequeño; personas y usuarios emplean el intermedio. `wide` sigue siendo un alias del tamaño grande: 90 % del ancho disponible, con máximo de 1.200 px y márgenes en móvil. No se ensanchan automáticamente las altas sencillas. La cabecera permanece visible. Un formulario modal utiliza una raíz flex con altura mínima cero, `FormBody` para campos, errores y ayuda, y `FormActions` como hermano final fuera del cuerpo desplazable. Los botones permanecen dentro del formulario HTML original: se conservan envío por teclado, validación nativa, estados pendientes y cancelación. La ayuda queda al final del cuerpo, inmediatamente antes de las acciones. Los diálogos de consulta conservan el cuerpo desplazable y su pie externo. `FormSection` agrupa datos relacionados mediante título accesible y un borde discreto. Activos, recursos y plantillas lo utilizan para sus datos principales. Las características y las secciones del editor se distribuyen en dos bloques por fila cuando el contenedor dispone de al menos 900 px; los controles dentro de cada bloque mantienen una o dos columnas según el espacio. Una plantilla sin secciones puede aprovechar hasta tres columnas de características. Observaciones y ayuda ocupan toda la fila. La disposición responde al espacio del modal, no solo al ancho del navegador. El desplazamiento vertical se conserva cuando el contenido supera la altura disponible; no se ocultan campos ni se reduce artificialmente su tamaño para eliminarlo. No se introducen pestañas ni páginas nuevas sin una necesidad específica del formulario. ## Resumen de compañía La pantalla `CompanyOverview` tiene composición independiente de `DashboardPage` (Inicio). Comparte tarjetas, bloques de trabajo/avisos/actividad y consultas existentes, siempre fijando el ámbito al UUID de la compañía. El encabezado «De un vistazo» precede las acciones rápidas (ticket, intervención, activo y recurso), cuatro indicadores operativos y una franja navegable de personas, departamentos y sedes. No muestra compañías activas ni el resumen por compañías. La variante compacta de `StatisticCard` mejora la jerarquía del valor y la etiqueta sin modificar la presentación de Inicio. La navegación de compañía empieza por Resumen, Tickets e Intervenciones; después agrupa Personas, Puestos de trabajo y Perfiles de usuario y finalmente las demás secciones. Los grupos tienen separación visual. Las operaciones de usuario permanecen en el menú superior y dejan de repetirse al pie del menú de compañía. ## Fichas de consulta y resumen lateral Las secciones de consulta evitan recuadros anidados y rellenos laterales: usan títulos compactos y un separador tenue entre grupos. Las agrupaciones de características siguen este mismo criterio en lectura; los formularios conservan su organización propia. `DetailSection` ofrece agrupación plegable nativa (`details/summary`), abierta inicialmente, accesible por teclado. `DetailField` devuelve una fila semántica `dt/dd` para incluir en un `dl`: etiqueta y valor se alinean horizontalmente cuando el contenedor dispone de al menos 240 px y se apilan en espacios menores. El consumidor aporta los enlaces; el componente común les aplica cursor de enlace, color semántico y subrayado al pasar el puntero. Los valores ausentes permanecen como texto. La ficha lateral de activos utiliza un ancho adaptable entre 320 y 400 px, acciones debajo del título y bloques de identificación, ubicación/asignación, características, observaciones, relaciones y asistencia. Conserva las secciones capturadas de la plantilla, incluyendo la compatibilidad con especificaciones antiguas sin agrupación; no modifica valores ni instantáneas. Las relaciones llevan a destinos existentes: persona y activo relacionado a su ficha; sede, puesto y plantilla a sus listados/catálogos actuales. La vista completa e historial mantienen sus funciones. Las fichas de recursos reutilizan las filas de características. No se crean nuevas fichas laterales ni nuevas rutas. ## Enlaces y controles de navegación Los enlaces de contenido usan `app-link`: color semántico `primary`, cursor de mano, subrayado al pasar el puntero y foco visible por teclado. `BrowserLink` lo incorpora de forma predeterminada, conservando la navegación interna, los clics modificados, destinos externos y descargas. Los botones que presentan nombres navegables o «Ver todos» comparten este estilo sin alterar sus callbacks. Los controles deshabilitados no muestran cursor de enlace ni subrayado. La navegación lateral y los enlaces presentados como botones conservan los colores de su contexto para mantener contraste; las acciones de guardar, editar o cancelar mantienen su presentación de botón. En el menú de secciones de una compañía, los elementos utilizan el color normal de texto y solo destacan su texto al pasar el puntero. Se mantienen el cursor de mano, el foco por teclado y el indicador de la sección actual. Esta excepción no afecta a enlaces dentro de listados, fichas o bloques del dashboard. El cuerpo desplazable `FormBody` reserva 4 px interiores en ambos laterales y al inicio para el anillo exterior de foco (3 px). No debe eliminarse ese espacio: `overflow-y-auto` también recorta horizontalmente y produciría un foco asimétrico en campos pegados al borde. Se conserva el scroll y las acciones fijas. Los listados comparten la preferencia entity-page-size en localStorage: el número de elementos por página elegido en un listado se restaura en todos los demás. Solo se aceptan los tamaños disponibles en el selector; si el almacenamiento no está disponible o contiene otro valor, se usa el tamaño inicial.