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.
Los nuevos WOFF2 se obtuvieron de la [API CSS de Google Fonts](https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&family=Roboto+Serif:wght@100..900&display=swap)
con un navegador compatible con fuentes variables.
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={<UsersIcon />}`. 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.
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.
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 un diálogo de selección múltiple basado en EntityTable para añadir campos a una plantilla y conserva el alta rápida mediante un modal independiente. Los tipos de dato constituyen una lista cerrada. Las plantillas de serie admiten edición modal desde el catálogo global; para personalizar exclusivamente una compañía se crean copias. Los campos de serie admiten edición modal desde el catálogo global.
El inventario añade altas rápidas de plantilla y sede 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.
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 primera columna con `value`, excluida `actions`, se ordena inicialmente en ascendente. Una columna puede declarar `defaultSort: "asc" | "desc"` para elegir otro criterio; declarar como máximo una por tabla. La ordenación se aplica antes de paginar, conserva empates y se refleja en la cabecera y el selector, incluso sin filas. «Orden inicial» restaura este criterio y las cabeceras alternan dirección sin eliminar la ordenación. Las tablas sin columnas comparables conservan el orden recibido.
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.
Los inventarios de activos y recursos componen las primitivas Tabs de shadcn/ui con EntityTable para filtrar por plantilla. La barra admite desplazamiento horizontal y navegación por teclado; incluye «Todo» y las plantillas del catálogo de la compañía. Un único panel conserva la tabla, su ordenación y sus columnas al cambiar de pestaña. `filterKey` reinicia la página; `filtersApplied` y `onClearFilters` integran el filtro con los estados vacíos y la limpieza común del listado.
EntityTable muestra `—` en las celdas cuyo renderizador devuelve ausencia, texto vacío, espacios, arrays vacíos o fragmentos sin contenido. Los consumidores devuelven ausencia directamente, sin textos como «Sin indicar»; véase [celdas sin valor](patron-de-listados-de-entidades.md#celdas-sin-valor).
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 «recursos» 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.
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).
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.
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.
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.
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.
`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; la variante `card` añade un borde discreto y `plain` conserva el bloque sin marco. Ambas utilizan `FormSectionTitle`. Activos, recursos y plantillas lo utilizan para sus datos principales. Las características 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.
La pantalla `CompanyOverview` tiene composición independiente de `DashboardPage` (Inicio). Comparte tarjetas, bloques de 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 (activo y recurso), dos 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; 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.
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: 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.
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.
`PhoneLink` centraliza la visualización no editable de teléfonos: utiliza un enlace `tel:`, conserva extensiones para la llamada y presenta los números españoles de nueve dígitos en grupos de tres. Los números internacionales que no se pueden agrupar con seguridad conservan su estructura, sin inventar un formato nacional. Los formularios no lo usan: mantienen el valor editable original.
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.
`ConfirmDeleteDialog` de `@repo/rdx-ui` presenta una confirmación modal con descripción de consecuencias, Cancelar y Eliminar. El consumidor aporta los textos traducidos y una operación asíncrona. Mientras se ejecuta, evita envíos repetidos y el cierre; si falla, mantiene el diálogo abierto y muestra un error. Solo se cierra tras completar la operación.
Los módulos conectan este componente mediante `DeleteRecordDialog`, separado de los menús de fila para que la confirmación permanezca montada cuando se cierre el menú. La operación invalida la consulta de la entidad y las revisiones de procesos de la compañía.
`@repo/rdx-ui/components/icon-picker` presenta un desplegable compacto mediante `Select`, `SelectTrigger`, `SelectValue` y `SelectItem` de shadcn/ui (Base UI). Muestra el icono seleccionado y su etiqueta traducida; cada opción combina icono y texto, incluida «Sin icono». Recibe opciones (valor, etiqueta e icono), valor actual, callback, estado deshabilitado y descripción opcional enlazada mediante `aria-describedby`. Su anchura máxima en escritorio es 16 rem y se adapta al espacio disponible en móvil. La etiqueta se asocia al control y los iconos se ocultan del nombre accesible. Mantiene teclado, foco y desplazamiento del desplegable mediante la primitiva existente. No conoce entidades ni endpoints. Las plantillas de activos y recursos reutilizan el mismo componente y aportan sus iconos Lucide, traducciones y validación de claves.
Las fichas de plantillas de activos y recursos usan `FieldSections layout="rows"` y `TemplateFieldSummary`. Secciones plegables abiertas inicialmente, con títulos en mayúsculas de tamaño pequeño y color `muted-foreground`, sin tarjetas ni rellenos anidados. El indicador ocupa una anchura fija y los campos se alinean con el inicio del texto del título; filas compactas con nombre, tipo/unidad y obligatoriedad. Los campos desactivados conservan su aviso y los campos sin sección siguen visibles. En pantallas amplias se muestran dos columnas de secciones; en anchos menores, una. Cada fila adapta sus metadatos al ancho real de su sección.
La ayuda de consulta usa `ContextHelp variant="plain"`: separación superior discreta, sin caja exterior ni fondo en el desplegable. Los formularios mantienen la presentación existente por defecto. `FormDialog.title` admite contenido React para colocar un icono decorativo junto al título sin una segunda cabecera repetida.
El alta y la edición de activos usan `FormSection variant="plain"` para separar identificación y características mediante títulos pequeños en mayúsculas y color `muted-foreground`, sin tarjetas anidadas. Nombre, plantilla, estado y sede comparten una rejilla adaptable.
Para formularios que muestran todos los grupos juntos, `FieldSections layout="form"` presenta cada grupo plegable de ancho completo, abierto inicialmente, con separador tenue bajo el título. Los controles se alinean con el texto del título y pasan de una a dos, tres o cuatro columnas según el ancho real del contenedor, conservando el orden de los campos. No se emparejan grupos de distinta altura, evitando huecos entre secciones. Los campos sin grupo permanecen visibles; los grupos con controles inválidos se abren para permitir su enfoque.
La ayuda inferior utiliza `ContextHelp variant="plain"`. Guardar y Cancelar permanecen en el pie fijo del diálogo; el cuerpo conserva desplazamiento cuando sea necesario. Las variantes anteriores continúan disponibles para los demás formularios. Esta distribución no modifica validación, instantáneas, valores ni operaciones de alta relacionada.
### Navegación entre grupos de características
El formulario de activos mantiene nombre, plantilla, estado y sede siempre visibles en su cabecera; notas y ayuda permanecen después de las características. `TabbedFieldSections` de `@repo/rdx-ui` presenta una pestaña por grupo configurado en la plantilla, omitiendo grupos vacíos y conservando los campos sin grupo. No introduce categorías fijas de Metis Desk.
En escritorio, los grupos se navegan en una columna vertical a la izquierda de los controles. En móvil, la lista pasa a horizontal con desplazamiento; la orientación de teclado también se adapta mediante las primitivas Tabs de shadcn/ui. Los paneles se mantienen montados para conservar los valores; al cambiar de plantilla, se inicia la navegación en su primer grupo.
Antes del envío, el formulario localiza el primer control inválido y solicita la validación nativa. El panel correspondiente se activa de forma síncrona antes de que el navegador enfoque el control. Se mantienen la validación de esquema, los errores, la ayuda inferior y el pie fijo de operaciones.
## Edición de plantillas globales
Los catálogos de activos y recursos ofrecen Editar para las plantillas y campos globales de serie desde el ámbito global, reutilizando sus formularios modales. En una compañía, las plantillas globales se consultan, se copian o se activa/desactiva su disponibilidad; la edición del global se realiza desde su catálogo. Los campos de serie admiten edición con tipo fijo y opciones ampliables; los tipos de relación de serie siguen protegidos.
La edición conserva UUID, clave de serie y las identidades de asociaciones que continúan. La carga inicial no sobrescribe plantillas existentes, incluidos grupos vacíos e iconos retirados. Los activos y recursos creados conservan sus instantáneas: la modificación de una plantilla se aplica a futuras altas. El uso especializado de una plantilla de recurso permanece fijo para conservar sus integraciones.
El atributo `description` de las definiciones de campo se presenta como «Texto de ayuda», con una explicación de dónde aparecerá. En los formularios de alta y edición se muestra bajo el control, con tipografía pequeña, color `muted-foreground` y saltos de línea conservados. Se reutiliza `FieldDescription` de shadcn/ui y se vincula mediante `aria-describedby`, incluidos desplegables y el selector de marca.
La ayuda se consulta en el catálogo de cada ámbito por el identificador del campo, manteniendo separados activos y recursos. En edición se utiliza la ayuda vigente del catálogo sin modificar los tipos, etiquetas ni valores de la especificación capturada. Actualizar un texto de ayuda no requiere alterar datos ni historial de los activos o recursos existentes. Si el texto está vacío o falta la definición, no se muestra un bloque vacío.
Las fichas de activos usan también `FieldSections layout="rows" values`. Sus grupos y los de las plantillas se componen con `DetailSection`: título plegable en mayúsculas, pequeño y `muted-foreground`, indicador y contenido con sangría simétrica. `DetailField` destaca el valor y atenúa la etiqueta; `TemplateFieldSummary` destaca el nombre del campo y atenúa los metadatos. La ficha de valores mantiene una columna de grupos, adaptándose al ancho del panel lateral.
El panel lateral de activos muestra plantilla, estado y puestos activos inmediatamente después del título y antes de las acciones, mediante `AssetPanelSummary`. Los mismos datos no se repiten en los grupos inferiores; la ficha completa conserva sus secciones. Los enlaces mantienen su estilo común y los iconos decorativos tienen etiquetas textuales accesibles.
La ruta `/companies/:companyId/workstations/:id` muestra una ficha completa al pulsar el nombre en el listado. La cabecera concentra nombre, estado, sede, persona asignada y edición modal. El contenido adapta sus dos columnas a una en pantallas pequeñas: activos del puesto, perfiles de usuario y personas relacionadas con avatar. Los enlaces abren fichas reales de activos, personas y perfiles. El historial sigue disponible desde las acciones del listado.
Los perfiles se deducen por el ordenador principal del puesto; no se introduce una relación de propiedad entre perfil y puesto. Los inactivos se identifican explícitamente. Las personas se reúnen sin duplicados a partir de la asignación al puesto y sus perfiles. Los fallos parciales al cargar perfiles no impiden consultar los activos; se ofrece reintento. No se confunde un error o carga pendiente con ausencia de perfiles.
`/settings/users` muestra el listado de usuarios como pantalla dentro del espacio global, con acceso directo desde la navegación para el administrador inicial. Identity aporta el contenido administrativo al shell mediante composición; Companies no importa el módulo Identity. Se reutiliza `UserManagement`, su API y los formularios modales existentes para crear, editar y restablecer contraseñas. La activación/desactivación se mantiene en la edición; las personas de las compañías son independientes de estas cuentas. El administrador inicial sigue protegido y la autorización del backend permanece obligatoria.
`FormProgress` de `@repo/rdx-ui/components/form-progress` presenta pasos numerados, marca el actual con `aria-current="step"`, identifica los completados y lleva el foco al anuncio traducido de progreso. Recibe etiquetas, descripción y textos accesibles del consumidor; no conoce entidades ni consultas. Lo usan los asistentes de recursos, activos y puestos.
El nuevo puesto se organiza en sede/responsable, activos, ordenador principal y resumen. Los controles de asociaciones, composición, ordenador y resumen tienen responsabilidades propias y se reutilizan en la edición cuando corresponde. Las acciones Anterior/Siguiente conservan valores; Guardar aparece en el resumen. La sede única y el activo único tienen selección automática. Véase [Puestos de trabajo](puestos-de-trabajo.md#alta-en-cuatro-pasos).
## Selección de activos en diálogo
`AssetSelect`, contrato público de `@metis/assets/client`, sustituye los desplegables de activos de formularios. Su disparador muestra el valor y conserva la acción de alta rápida. El modo único recibe un UUID y devuelve un UUID; el múltiple recibe una colección, devuelve una colección confirmada y declara `maxSelection`. Los consumidores aportan opciones permitidas, bloqueos, textos y sedes. Las reglas de negocio siguen en cada formulario.
`AssetPickerDialog` carga inventario y catálogo mediante sus queries existentes. Conserva una selección provisional hasta confirmar; Cancelar, Escape y cerrar no modifican el formulario padre. No invoca endpoints de escritura. El diálogo muestra estados de carga/error y reintento, contador de seleccionados, controles accesibles y confirmación deshabilitada si hay selecciones inválidas o se supera el límite. Las selecciones sobreviven a búsqueda, pestañas y paginación. No navega ni cambia filtros URL de la pantalla subyacente.
`AssetList`, en assets/shared/ui, concentra las pestañas por UUID de plantilla (incluidas vacías/inactivas y especificaciones históricas), búsqueda rápida por nombre/plantilla/estado/sede y las columnas Nombre, Plantilla, Estado y Sede. Tanto inventario como selector lo componen con `EntityTable`. El inventario aporta enlaces, acciones y resaltado; el selector aporta checkboxes o radios de shadcn/ui en la primera columna de selección. Se reutilizan ordenación, columnas configurables, rangos y paginación existentes. Este componente conoce activos y permanece en su módulo; `EntityTable`, `FormDialog`, `FormBody`, `FormActions` y `ButtonGroup` siguen siendo los patrones genéricos independientes del dominio.
El selector se aplica a activos del puesto (adición múltiple), ordenador principal (único entre miembros), extremo de relaciones, impresora por perfil, origen/destino de conexiones remotas. Cada caso conserva exclusiones, referencias fijas y altas rápidas. Añadir varios activos a un puesto es una sola actualización de composición; solo se propone automáticamente el ordenador cuando la composición resultante contiene un único activo.
Las referencias históricas a formularios y listados de tickets, intervenciones y sus plantillas pertenecen al [archivo de funcionalidades](funcionalidades-archivadas.md). No forman parte de la aplicación activa.
El alta y la edición de plantillas de activos y recursos agrupan los datos generales con `FormSection` e incorporan campos desde las cabeceras de sus grupos. `TemplateSectionsEditor` permite secciones apiladas o una cuadrícula responsive mediante `sectionLayout="stack" | "responsive"`, con una cabecera numerada sobre fondo diferenciado que contiene el input del nombre, el asa de arrastre y las acciones de orden y eliminación. El cuerpo conserva la descripción y un área de campos separada por un borde. `TemplateSectionEditor` encapsula cada sección con `Collapsible`: todas empiezan abiertas y un botón de cabecera permite plegarlas con ratón o teclado. El nombre, el asa y las acciones siguen visibles al plegar. El contenido permanece montado para conservar los controles; si la validación nativa detecta un control inválido oculto, la sección se abre antes de que el navegador lo enfoque. La apertura se conserva por identidad al reordenar y no se guarda como parte de la plantilla. Los editores de activos y recursos usan la cuadrícula responsive: una columna hasta 1.000 px de ancho disponible y dos a partir de ese umbral, con separación de 16 px y tarjetas de igual altura en cada fila, determinada por la sección más alta. El orden recorre cada fila de izquierda a derecha y se conserva en el DOM, la numeración, el teclado y el arrastre; la altura de cada fila se recalcula al plegar o desplegar sus secciones, manteniendo ambas tarjetas con la misma altura. El bloque «Sin sección» permanece debajo, a ancho completo.
`TemplateSectionFields` muestra un título traducido y el número de campos. Cada campo tiene fondo propio. En la presentación `cards`, separa su nombre y acciones de la configuración de obligatoriedad y asignación, cuyo selector conserva una etiqueta visible; los editores de plantillas de activos y recursos utilizan la presentación `compact` descrita más abajo. Los campos se distribuyen en dos columnas a partir de 700 px de su propio grupo y en una para anchos menores; cada grupo establece su contenedor para evitar columnas demasiado estrechas cuando las secciones se muestran en paralelo. El bloque «Sin sección» se diferencia con borde discontinuo; las secciones vacías conservan un mensaje explícito. Se reutilizan los colores semánticos del tema para mantener el contraste en modo claro y oscuro.
`TemplateSectionsEditor` y `TemplateSectionFields` utilizan `useDragReorder` y `ReorderHandle`, independientes del dominio. Cada sección y campo incorpora un asa etiquetada, con ayuda traducida y posición actual en su nombre accesible. Se puede arrastrar desde el asa y soltar en la mitad superior o inferior de otro elemento para colocarlo antes o después; una línea de color primario marca el destino sin modificar la altura del contenido. El elemento de origen se atenúa durante el arrastre. El orden se confirma únicamente al soltar y se conserva hasta guardar el formulario; cancelar el arrastre no cambia los datos.
El arrastre utiliza la API HTML nativa, sin dependencias adicionales. Cada lista tiene un ámbito propio: las secciones se reordenan entre sí y los campos dentro de su sección, incluido el bloque «Sin sección». En la presentación de tarjetas, el selector permite trasladar un campo a otro grupo. No se aceptan arrastres externos ni de otro diálogo. Las teclas arriba y abajo sobre el asa permiten mover el elemento conservando el foco; los botones explícitos de orden siguen disponibles, también para dispositivos sin arrastre nativo. Las asas quedan deshabilitadas cuando se guarda o no hay otro elemento en su lista.
### Selección múltiple de campos de plantillas de activos y recursos
El alta y la edición de plantillas de activos y recursos abren el `CatalogFieldPickerDialog` de su módulo desde «Añadir campo» en las cabeceras de los grupos de campos, siguiendo el patrón de `AssetPickerDialog` y `AssetPickerList`: `FormDialog`, `FormBody`, `FormActions`, `EntityTable` y `Checkbox`. La tabla reutiliza búsqueda, ordenación, columnas y paginación, con nombre, descripción, tipo y unidad como contexto. Solo recibe campos activos del catálogo del formulario que todavía no están en la plantilla; no añade consultas ni dependencias.
La selección permanece al buscar o paginar y se confirma como un lote mediante «Añadir campos». Cancelar descarta la selección provisional. Los campos nuevos se añaden al final, como opcionales en la sección de destino («Sin sección» desde el grupo sin sección), conservando el orden, agrupación y obligatoriedad de los existentes. `appendTemplateFields` deduplica y valida el conjunto con el schema existente; las casillas impiden superar su límite. El alta rápida «Nuevo campo» se ofrece en el pie de `CatalogFieldPickerDialog`, a la izquierda, mediante el slot `leadingActions` de `FormActions`. La prop opcional `quickCreate` aporta el ámbito y permite deshabilitar el botón con `disabled`; omitirla oculta la acción e impide abrir el alta. El editor de plantillas traduce su opción existente `allowQuickCreate` a este contrato. El diálogo de selección permanece montado durante el alta para conservar la búsqueda, página y selección provisional. Guardar el nuevo campo actualiza el catálogo y lo marca en esa selección; solo «Añadir campos» modifica el borrador de la plantilla. Cancelar el alta conserva la selección; cancelar el selector no elimina un campo ya creado del catálogo. Los cambios se persisten al guardar la plantilla.
### Interacción de filas en PickerList
En todos los diálogos de tipo `PickerList`, la columna del checkbox o radio ocupa la primera posición, antes del nombre y los demás datos. Usa el identificador `selection`, no se ordena ni se oculta y no se ancla a la derecha como las acciones. `EntityTable` oculta visualmente su cabecera mediante `sr-only`, conservando el nombre accesible; ajusta su anchura al control y reduce el relleno derecho para acercarlo al nombre. Se conserva el ancho mínimo de la columna del nombre. Los avisos de indisponibilidad de activos se muestran junto al nombre, para no ensanchar la columna de selección. `AssetList` permite anteponerla mediante `leadingColumns`, manteniendo las acciones adicionales al final. Pulsar una zona no interactiva de una fila disponible cambia su selección: alterna el checkbox en modo múltiple y selecciona el radio en modo único. `AssetPickerList` y `CatalogFieldPickerList` reutilizan `onRowClick` de `EntityTable` (a través de `AssetList` cuando corresponde), que ya excluye clics en controles internos. Así, pulsar directamente una casilla o un radio produce un único cambio y mantiene el uso por teclado. Las filas deshabilitadas respetan el mismo bloqueo que su control y muestran el cursor de acción no disponible. Esta regla se aplica también a los nuevos pickers.
### Altura estable de los diálogos PickerList
Todos los diálogos `PickerList` usan `FormDialog layout="picker"`. Esta variante fija la altura a la ventana disponible en móvil, con un margen total de 2 rem; en escritorio utiliza el menor valor entre el 85 % de la altura dinámica de la ventana y 900 px, respetando siempre el margen. La altura no depende de carga, errores, búsqueda, número de resultados o tamaño de página. Los formularios de alta y edición conservan la altura por contenido, también cuando se abren desde un picker.
`FormBody` hereda esta disposición del diálogo y distribuye sus hijos en columna. `EntityTable layout="fill"` ocupa el espacio restante; búsqueda, filtros, información y paginación no se desplazan con los resultados. El contenedor de tabla tiene scroll interno y `scrollbar-gutter: stable`; las cabeceras permanecen visibles al recorrer las filas. El área mantiene su espacio con pocos resultados o ninguno. En ventanas pequeñas, el cuerpo también puede desplazarse para evitar recortar controles. `AssetList` propaga `layout="fill"` por las pestañas y `AssetPickerList` conserva ese comportamiento en selección única y múltiple. `AssetPickerDialog` y `CatalogFieldPickerDialog` aplican esta variante común.
### Incorporar campos desde una sección de plantilla
En los editores de plantillas de activos y recursos, la cabecera «Campos» de cada sección ofrece «Añadir campo». Reutiliza `CatalogFieldPickerDialog` y muestra la sección de destino; permite seleccionar campos existentes o usar el alta rápida del pie cuando está habilitada. Al confirmar, solo las referencias nuevas se añaden como opcionales a ese grupo; no mueve ni cambia la obligatoriedad de los campos existentes. El bloque independiente «Añadir campos a la plantilla» se ha retirado. Si todavía no existen secciones, se muestra el grupo «Campos» con su botón de incorporación y ayuda de estado vacío, cuyo destino es «Sin sección». Cancelar no cambia el borrador.
`TemplateSectionsEditor` recibe la acción opcional `addFieldAction`, que traslada el destino al módulo consumidor. `TemplateSectionFields` presenta esa acción en su cabecera, con el contexto accesible del grupo y bloqueo durante el guardado; también se puede reutilizar en «Sin sección» cuando está visible. Los componentes genéricos no conocen el selector ni sus DTO. Cada módulo conserva el destino por identidad mientras el picker está abierto, comprueba su pertenencia a la plantilla y reutiliza `appendTemplateFields` para deduplicar y validar el lote.
### Selector de campos compartido
`FieldPickerDialog`, `FieldPickerList` y `useFieldPickerController`, en `@repo/rdx-ui`, comparten la selección múltiple, confirmación, alta opcional y bloqueo por capacidad entre activos y recursos. La tabla recibe datos presentacionales (identidad, nombre, descripción, tipo traducido y unidad) y etiquetas. El diálogo recibe la tabla y el alta mediante composición, sin importar DTO, consultas, schemas ni endpoints de los módulos. Los adaptadores `CatalogFieldPickerDialog` y `CatalogFieldPickerList` de cada módulo aportan esos datos, sus traducciones y su formulario de alta.
`appendTemplateBindings` concentra la incorporación idempotente de referencias, conservando las existentes y asignando solo las nuevas al destino. Cada `appendTemplateFields` valida el resultado con el schema normativo de su módulo. Las plantillas de recursos mantienen su configuración de uso y las restricciones de alta rápida del consumidor.
### Datos de las plantillas de activos y recursos
El alta y la edición de plantillas de activos y recursos reutilizan `TemplateMetadataSection`, que compone `FormSection variant="plain"` para «Datos de la plantilla», sin borde ni relleno interior de tarjeta. Los controles se distribuyen en dos columnas iguales a partir de 560 px del contenedor: nombre e icono debajo en la primera, descripción en la segunda. Ambas columnas tienen la misma altura; el área de descripción crece para alinearse con el borde inferior del selector de icono. En espacios estrechos se apilan en el orden nombre, icono y descripción. El icono conserva una anchura máxima de 16 rem. El componente recibe título traducido, `primaryFields`, `descriptionField` y contenido adicional opcional mediante `children`, sin conocer datos ni formularios de los módulos. Añade 16 px a la separación base de 20 px entre grupos. Recursos conserva su selector de uso y su ayuda bajo las dos columnas.
Todos los diálogos con grupos utilizan el estilo de «Datos de la plantilla»: texto pequeño, seminegrita, mayúsculas, espaciado entre letras y color atenuado (`text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-4`). `FormSectionTitle` centraliza esta presentación y recibe `children`, `id` opcional y `as="h3" | "h4" | "h5" | "legend" | "span"` para conservar la semántica del encabezado, leyenda o título dentro de un `summary`. El consumidor aporta el texto traducido; las mayúsculas se aplican mediante CSS. La variante `span` conserva la tipografía y deja el espaciado al `summary` que la contiene, sin margen inferior propio.
`FormSection`, `TemplateSectionsEditor` y `TemplateSectionFields` comparten este título, con independencia de sus bordes, acciones o contenido. La regla también se aplica a grupos de consulta, como los puestos del historial de perfiles. Los títulos principales del diálogo y los nombres editables de secciones mantienen su presentación propia.
### Campos compactos en plantillas de activos y recursos
`TemplateSectionsEditor fieldLayout="compact"` presenta cada campo en una fila: asa de arrastre, switch de obligatoriedad, nombre flexible, flechas arriba/abajo y papelera. El nombre puede ocupar varias líneas en pantallas estrechas. El switch conserva una etiqueta accesible con «Obligatorio» y el nombre del campo; su texto visual y el selector de sección se omiten. Las notas de campos inactivos siguen disponibles para lectores de pantalla. Se mantienen el arrastre, las flechas por teclado, los límites de orden y los estados deshabilitados. Ambos editores utilizan esta presentación compacta.
`ScrollableTabsList` de `@repo/rdx-ui/components/scrollable-tabs-list` se compone dentro de `Tabs` de shadcn/ui. Recibe `items` (identidad `value` y etiqueta `label`), `value`, una etiqueta accesible `label` y `locale` para los controles es/en. El consumidor mantiene la selección, los paneles y sus efectos sobre URL, filtros o consultas.
Los listados de activos y recursos lo utilizan para sus familias; el picker de activos lo hereda mediante `AssetList`. La barra ocupa únicamente el ancho disponible, mantiene una sola fila y no comprime los nombres. Permite desplazamiento horizontal nativo con tacto o trackpad y muestra botones laterales solo cuando hay desbordamiento. Los botones se deshabilitan al llegar a cada extremo. La barra de desplazamiento nativa permanece oculta, evitando controles verticales accidentales; los botones conservan nombre accesible y tooltip.
Conserva la semántica y el teclado de Base UI. Al cambiar de familia, enfocar una pestaña o redimensionar el contenedor, revela la pestaña correspondiente moviendo solo el eje horizontal. Un `ResizeObserver` actualiza los extremos al abrir o cerrar paneles, redimensionar la ventana o cambiar las etiquetas; se retiran el observer y los listeners al desmontar. La raíz `Tabs` y los paneles consumidores mantienen `min-w-0` para permitir que la barra se reduzca dentro de flex y grid.