metis-desk/docs/componentes-de-interfaz.md

817 lines
80 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# 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.
El [patrón de diseño de cuadros de diálogo](patron-de-dialogos.md) es la fuente normativa
para componer grupos, columnas responsive, filas compactas y selectores. Esta guía describe
los componentes que lo implementan y sus contratos.
## Iconografía de la aplicación
El código propio utiliza `@tabler/icons-react`, con versión exacta en `@repo/rdx-ui` y
licencia MIT. La dependencia sustituye a Lucide en la biblioteca propia y amplía el
catálogo disponible sin cargar iconos desde servicios externos. Las importaciones de
la aplicación y de sus componentes compartidos pasan por `@repo/rdx-ui/lib/icons`
(o `../lib/icons` dentro del paquete). No importar las bibliotecas de iconos directamente
en módulos ni duplicar catálogos locales para acciones comunes.
`lib/icons.ts` exporta nombres semánticos estables, asociados a los componentes de
contorno de Tabler. El tamaño se controla con las clases existentes y el color con
`currentColor`; el trazo predeterminado de Tabler es 2. Para ajustar su grosor se utiliza
la prop `stroke`. Los iconos decorativos mantienen `aria-hidden`; las acciones de icono
conservan su nombre accesible traducido. Importar únicamente los iconos necesarios.
Las claves persistidas de las plantillas de activos y recursos conservan su contrato:
`laptop`, `desktop`, `mail`, `file-key`, etc. La correspondencia visual cambia a Tabler,
sin migraciones de datos ni cambios en DTO, historial o preferencias del usuario.
Los selectores de plantillas usan la misma correspondencia que las fichas y listados.
`apps/web/components.json` declara Tabler para la generación desde la aplicación.
`packages/shadcn-ui` conserva su implementación vendor, configuración y dependencia de
Lucide para sus iconos internos; no modificar el código de terceros para migrar los
iconos propios. Consultar la [API oficial de Tabler para React](https://docs.tabler.io/icons/libraries/react).
## 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 y PhoneLink.
## 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 tres familias variables WOFF2 desde `apps/web/src/fonts`,
con `font-display: swap` y pesos reales continuos de 100 a 900 en estilo normal:
| Utilidad | Familia | Uso |
| --- | --- | --- |
| `font-sans` | Inter | Tipografía general. |
| `font-serif` | Roboto Serif | Contenido editorial puntual. |
| `font-mono` | Geist Mono | Código, identificadores y valores técnicos. |
Las tres cubren los nueve pesos estándar de Tailwind 4: `thin` (100), `extralight` (200),
`light` (300), `normal` (400), `medium` (500), `semibold` (600), `bold` (700),
`extrabold` (800) y `black` (900). No fijar el eje `wght` mediante
`font-variation-settings`: el peso se controla con las utilidades `font-*`.
Las cursivas no se incluyen como archivos propios.
Los tokens de familia se definen en `apps/web/src/global.css`, para `:root` y `.dark`,
fuera de `@theme`, porque el vendor declara esos mismos tokens en ambos temas y
sus utilidades los consumen. Las extensiones de la aplicación prevalecen sin modificar
`packages/shadcn-ui`.
Geist Mono y Roboto Serif proceden de Google Fonts, con subconjuntos `latin` y
`latin-ext` separados mediante `unicode-range`; incluyen los caracteres del castellano
y del inglés. Los archivos se sirven desde la aplicación, sin peticiones a Google Fonts
en ejecución. Las licencias OFL 1.1 acompañan las tres familias.
Fuentes de procedencia y rangos: [Inter](https://github.com/rsms/inter),
[Geist Mono](https://github.com/google/fonts/blob/main/ofl/geistmono/METADATA.pb) y
[Roboto Serif](https://github.com/google/fonts/blob/main/ofl/robotoserif/METADATA.pb).
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.
## 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.
`@tabler/icons-react` aporta los iconos del código propio y tw-animate-css sus transiciones.
El código vendor de shadcn/ui conserva sus dependencias e iconos internos del registro.
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={<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.
## 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 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.
## 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 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.
## 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 el formulario reutilizable ResourceEditor 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.
## Avatar de persona
`PersonAvatar` reutiliza `InitialsAvatar` con los mismos colores y ajuste de texto que
`CompanyAvatar`. Muestra la foto versionada con carga diferida y recurre a las iniciales
si falta la foto o falla su carga. Conserva el nombre accesible, admite atributos de
`Avatar` y permite indicadores de estado como `children`. `PersonAvatarGroup` compone
los avatares del grupo y vive en un fichero independiente.
## 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.
`FormDialog layout="stable"` reserva la altura exterior para formularios y asistentes
cuyo contenido cambia durante la interacción. Comparte con `layout="picker"` el tamaño
adaptado a la ventana: altura dinámica menos 2 rem en móvil y mínimo de 85dvh/900px desde
`sm`, limitado siempre por los márgenes. Conserva el espaciado normal del formulario,
con `FormBody` desplazable y `scrollbar-gutter: stable`; no distribuye los hijos como una
tabla. Cambiar de paso, añadir o retirar activos y desplegar ayuda no redimensiona el
modal. La cabecera y las acciones permanecen visibles. `layout="content"` queda para
contenido breve que no cambia de tamaño. Véase la [regla normativa de estabilidad](patron-de-dialogos.md#estabilidad-de-altura-del-modal).
La cabecera permanece visible. Un formulario modal utiliza una raíz flex con altura mínima cero, `FormBody` para campos y errores, 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 inmediatamente antes de las acciones; en diálogos de altura estable ocupa un bloque anclado abajo fuera del scroll de campos, con altura limitada y desplazamiento propio si se despliega contenido extenso. 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.
## Resumen de compañía
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.
## 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: 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.
`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.
### Confirmación de eliminación de registros
`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.
## IconPicker
`@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 Tabler, traducciones y validación de claves.
## Consulta compacta de plantillas
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.
## Formulario compacto de activos
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.
## Texto de ayuda de campos de activos y recursos
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.
## Ficha de puesto de trabajo
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.
## Gestión de usuarios
`/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.
## Alta de recursos por pasos
El diálogo «Nuevo recurso» reutiliza `FormDialog`, `FormBody`, `FormActions`, `FormSection` y
los controles actuales. Su indicador muestra los tres pasos, identifica el actual mediante
`aria-current="step"` y anuncia el avance con un texto traducido al que se lleva el foco.
Las acciones conservan Cancelar y añaden Anterior/Siguiente; Guardar aparece en el último
paso. La ayuda permanece al final del cuerpo, inmediatamente antes del pie.
`ResourceIdentityFields` y `ResourceFields` comparten identificación, integración y
características entre alta y edición. `ResourceFormActions` encapsula las acciones de ambos
modos. Estos componentes pertenecen a resources porque conocen sus contratos y textos; no
se añade una primitiva genérica de wizard sin otros consumidores. Véase
[el comportamiento del alta](recursos-campos-y-plantillas.md#alta-de-recursos-en-tres-pasos).
### Presentación compacta del alta por pasos
El alta de recursos y el alta de activos desde el inventario de compañía usan
`FormDialog size="medium"` para limitar el ancho a 768 px; la edición completa conserva
el tamaño grande. La cabecera aporta un icono decorativo de la entidad. El indicador numera los pasos, distingue el actual y marca los completados con
un símbolo y texto accesible; las etiquetas permanecen visibles también en móvil.
Cada paso incluye una instrucción breve y, tras elegir la plantilla, muestra su nombre.
Los formularios de recursos reutilizan el patrón compacto de activos:
`FormSection variant="plain"`, títulos pequeños en mayúsculas y color
`muted-foreground`, `FieldSections layout="form"` para las características y
`ContextHelp variant="plain"` al final. Se evitan tarjetas anidadas y títulos grandes
entre controles. Las columnas responden al ancho del contenedor; nombre y estado
comparten fila cuando hay espacio. Se conservan los márgenes del anillo de foco de
`FormBody`, la ayuda de campo, el desplazamiento del cuerpo y las acciones fijas.
Se emplean los tokens semánticos del tema, sin estilos ni cambios en vendor.
El alta de activos sigue tres pasos: plantilla, características e identificación/notas.
Conserva marca y secciones propias de la plantilla; sede, estado técnico y fecha de alta
se completan al final. Propone el primer campo de texto distinto de marca, o el nombre
de la plantilla si no hay ninguno; un nombre editado manualmente se conserva al volver.
Cambiar de plantilla reinicia características y marca; volver sin cambiarla conserva los valores.
La edición y el alta anidada desde un puesto mantienen el formulario completo y sus pestañas.
`AssetForm.creationLayout` activa los pasos únicamente al crear.
`FormStepActions` de `@repo/rdx-ui/components/form-step-actions` comparte el pie entre
recursos y activos: recibe paso/total, estado pendiente, disponibilidad de plantilla,
callback de retroceso y etiquetas traducidas. Solo muestra Guardar al final (o en modo
completo) y bloquea las acciones durante el envío; Cancelar lo aporta `FormDialog`.
No conoce entidades, formularios, consultas ni endpoints.
## Progreso de formularios y alta de puestos
`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 diálogo de alta de puesto usa tamaño medio y cabecera con icono decorativo; la edición
mantiene tamaño grande. Ambos utilizan `layout="stable"`. Asociaciones, activos, identificación y resumen utilizan
`FormSection variant="plain"`, títulos de grupo uniformes y separación de 20 px. Las
asociaciones y el resumen pasan a dos columnas según el ancho del contenedor; el nombre
del alta ocupa todo el ancho. Los activos seleccionados utilizan `AssetSummaryCard`, con nombres que pueden partirse
y botones de retirada con etiqueta accesible. El resumen
muestra `—` para valores ausentes. La ayuda del ordenador se vincula al selector mediante
`aria-describedby`; el nombre identifica su error junto al campo. La ayuda general queda
al final, antes del pie fijo, con navegación por flechas y cancelación deshabilitada durante
el guardado. El alta utiliza `layout="stable"`: el cuerpo se desplaza dentro de una altura
exterior estable, independiente del paso o del número de activos seleccionados.
La ayuda contextual del puesto es hermana de `FormBody`, inmediatamente antes de las
acciones: permanece alineada abajo aunque el paso tenga pocos campos. Su contenedor
elimina el margen superior de la variante plain, limita su altura al 40 % del formulario
y permite scroll propio para ejemplos extensos, manteniendo visible el footer.
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 inventario no selecciona ningún activo al entrar en el listado. Pulsar el nombre o una zona
sin controles de la fila abre su ficha lateral (en móvil, el diálogo existente), conservando
la pestaña y la búsqueda en la URL. El nombre mantiene su enlace para navegación por teclado;
los menús y demás controles conservan su acción propia. Una URL de activo concreto abre su ficha.
`EntityTable.onRowClick` permite esta interacción desde el consumidor sin conocer rutas o entidades.
Bajo el nombre, inventario y selector muestran un resumen automático: los cuatro primeros
campos con valor de la especificación capturada del activo, en el orden de su plantilla.
Incluye los valores y sus unidades, conserva cero y valores booleanos traducidos y omite vacíos y texto
largo. Cada tipo utiliza así sus propios campos, incluidos los personalizados, sin reglas
basadas en nombres ni cambios de persistencia. Si no hay características, se muestra solo el
nombre. El resumen ocupa como máximo dos líneas y su texto completo está disponible en `title`.
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.
## Panel lateral y resumen de recursos
Recursos aplica el mismo patrón de selección que activos: el listado comienza sin ficha,
el nombre y las zonas sin controles de cada fila abren el recurso y conservan la pestaña
y la búsqueda de la URL. La selección se resalta mientras el panel está abierto. El cierre
oculta el panel y ofrece recuperarlo desde la cabecera. Las URLs de un recurso concreto
siguen abriendo su ficha; en pantallas pequeñas se conserva el diálogo con historial.
`SelectionPanel` de `@repo/rdx-ui/components/selection-panel` comparte la estructura de
activos y recursos: título, cierre accesible, resumen, acciones y contenido por composición.
El consumidor aporta textos traducidos, datos y callbacks. `useCompactDetail` comparte el
umbral de 1100 px con la variante `inventory` de Tailwind. Las fichas completas conservan
su diálogo; el panel de recursos muestra características, integración, notas y contenido
relacionado, sin repetir el historial completo.
`characteristicsSummary` de `@repo/rdx-ui/lib/characteristics-summary` recibe campos,
valores y textos de Sí/No sin depender de DTOs. Activos y recursos muestran los primeros
cuatro valores no vacíos en el orden de su especificación, con unidades, cero y booleanos
traducidos. El resumen ocupa dos líneas bajo el nombre y ofrece el texto completo en
`title`. No se añaden reglas basadas en nombres de plantillas ni cambios de persistencia.
## Componentes archivados
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.
## Formulario de personas
El alta y la edición comparten `PersonForm` y `PersonEditorDialog`. Los switches de
persona activa y contacto de la compañía aparecen al comienzo, en una franja adaptable.
Los datos personales y de contacto se agrupan con `FormSection variant="plain"` y
rejillas de una o dos columnas. La ayuda contextual permanece al final del cuerpo.
`PersonPhotoField` encapsula el selector accesible de foto y su vista previa circular,
utilizando Avatar y Button de shadcn/ui. Sin imagen se muestra un placeholder de cámara;
al editar se presenta la foto vigente. La selección es provisional hasta guardar y las
URLs de vista previa se liberan al cambiar de archivo o desmontar el componente.
## Jerarquía del editor de plantillas
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.
### Orden mediante arrastre y teclado
`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 con contenido variable deben utilizar `layout="stable"`, también cuando se abren desde un picker; las confirmaciones breves pueden conservar `layout="content"`.
`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.
## Títulos de grupos en diálogos
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.
## Ajustes comunes tras la revisión de diálogos
`FormBody` establece su propio contenedor de consultas de tamaño: las cuadrículas de formularios
y subcontroles utilizan `@min-[560px]` para decidir sus dos columnas según el ancho real del
modal. `FieldSections` aplica la misma regla a los campos y estira los grupos de cada fila
a la altura de su vecino más alto. Los títulos de consulta, grupos de permisos y secciones
desplegables reutilizan `FormSectionTitle`, conservando el estilo vigente del componente.
`FormActions` admite `cancelDisabled` para conservar el bloqueo de cancelación durante una
confirmación pendiente, además de `leadingActions` para acciones opcionales a la izquierda.
Las confirmaciones de marcas y definiciones de catálogo usan `FormBody` y `FormActions` para
mantener su pie fuera del área desplazable. La selección de compañía del dashboard también
sitúa Continuar en ese pie y lo deshabilita mientras falta una selección o una consulta correcta.
`SoftwareInstallationEditorDialog`, `SoftwareResourceCreateDialog` y `EntityTableColumnsDialog`
encapsulan sus interacciones en ficheros independientes. Sus consumidores conservan apertura,
selección, datos y callbacks; los formularios y controles mantienen sus contratos anteriores.
## Etiquetas y campos obligatorios
`FormLabel`, en `@repo/rdx-ui/components/form-label`, compone la primitiva `Label` de
shadcn/ui y conserva sus props y la asociación `htmlFor`. La prop `required` añade al
final un único asterisco con color `destructive`, mediante `RequiredIndicator`. El indicador
es visual; el control mantiene sus atributos de validación y su semántica accesible.
La etiqueta de un campo optativo contiene solo su texto, sin sufijos de opcionalidad.
Los módulos aportan nombres traducidos sin asteriscos y la condición de obligatoriedad.
Las características de activos y recursos conservan su unidad; las referencias y fechas
condicionales utilizan la condición de su contrato. No se deduce obligatoriedad de que
un valor esté vacío, de su estado deshabilitado ni de que un booleano sea `false`.
Los grupos de campos, como permisos, reutilizan `RequiredIndicator` en su leyenda,
sin marcar cada checkbox como obligatorio. La configuración de obligatoriedad de las
plantillas conserva sus switches y su significado.
`TemplateFieldSummary` aplica el mismo criterio en la consulta de plantillas: recibe
`requiredLabel` con el texto traducido de obligatoriedad solo si el campo es obligatorio.
Muestra el asterisco junto al nombre y conserva ese texto para lectores de pantalla.
Los campos optativos no muestran indicación de opcionalidad; tipo, unidad y avisos se
mantienen como metadatos de consulta.
## Pestañas horizontales desplazables
`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.
## Archivo y eliminación de sedes
`SiteSection` conserva el listado común, añade el estado y un filtro para consultar las
archivadas. Su controller coordina la selección y el cambio de estado; `SiteForm`
reutiliza `SwitchField`. `SiteDeleteDialog` compone `ConfirmDeleteDialog`, presenta el
nombre de la sede y conserva el error de negocio dentro del modal. La caché de detalle
de compañía se actualiza tras completar cada operación. Los selectores conservan la
sede anterior archivada y excluyen las demás archivadas. Véase [Sedes](sedes.md).
## Fichas compactas de activos elegidos
`AssetSummaryCard`, en `assets/shared/ui`, presenta nombre, icono y nombre de plantilla,
resumen de características y estado. Reutiliza `Card` de shadcn/ui, `AssetTemplateIcon`,
`assetCharacteristicsSummary` y `EntityStatus`. La especificación capturada determina el
nombre de plantilla y el resumen; el catálogo aporta únicamente el icono actual, con
icono de caja como alternativa. Los valores ausentes muestran `—`; cero y booleanos se
conservan con las traducciones habituales.
El consumidor aporta el activo, la plantilla opcional, acciones mediante `actions` y un
aviso mediante `error`. La ficha no carga datos ni decide cómo seleccionar o retirar activos.
Permanece en el módulo porque conoce sus DTO; las primitivas visuales siguen siendo
independientes del dominio. La usan la selección de activos del puesto y su resumen final.
Las tarjetas se distribuyen en una columna o dos desde 560 px de contenedor, igualan altura
por fila y mantienen el orden del DOM. En selección, un contador anuncia los cambios y
la retirada conserva el nombre accesible del activo. Los errores se presentan junto a
la ficha y se distinguen también mediante texto.
El paso de activos del puesto sin selección reutiliza `EmptyState` de `@repo/rdx-ui`:
icono de ordenador, título, explicación traducida y dos botones independientes centrados:
«Seleccionar activos» y «Nuevo activo». `AssetSelect` ofrece
`presentation="action"` para un disparador compacto y primario; `field` conserva la
presentación habitual. Ambas variantes abren el mismo selector y respetan permisos,
estados deshabilitados. El alta se ofrece mediante su propio botón con texto y permisos.
Al añadir activos aparecen las fichas conservando ambas acciones; al retirar el último
vuelve el estado vacío. El formulario utiliza `FormBody contentLayout="fill"`: el propio
cuerpo desplazable distribuye mediante flex el espacio disponible, sin un contenedor
intermedio con altura porcentual. En el paso 2, el fieldset crece y distribuye ese alto
al estado vacío, que ocupa también todo el ancho. El contenido
permanece centrado, con acciones que se adaptan a espacios estrechos y scroll cuando
se necesita; no altera la altura exterior del modal ni la ayuda anclada al pie.
## Estado vacío con acciones
`EmptyState` se importa desde `@repo/rdx-ui/components/empty-state`. Encapsula las
primitivas `Empty` de shadcn/ui con borde discontinuo, icono decorativo, título,
descripción y acciones independientes, centradas y adaptables al ancho disponible.
Recibe `icon` y `actions` como `ReactNode`, y `title` y `description` como textos
ya traducidos por el consumidor. Vincula el título y la descripción a una región
accesible con identificadores únicos; no mueve el foco ni anuncia cambios por sí mismo.
Ocupa todo el ancho y crece en un padre flex para llenar el alto restante, sin fijar
alturas. El consumidor mantiene el scroll, la cabecera y el pie del diálogo.
No conoce activos, DTO, consultas, permisos ni callbacks de negocio: el consumidor
compone sus botones o selectores con sus propios estados deshabilitados y acciones.
El paso 2 de puestos aporta un ordenador como icono y los botones de selección y alta.
## Elección visual del ordenador principal
`WorkstationComputerField` reutiliza `AssetSummaryCard` para los activos ya incorporados
al puesto. Las fichas se componen con `FieldLabel`, `RadioGroup` y `RadioGroupItem` de
shadcn/ui/Base UI. Pulsar cualquier zona de la etiqueta selecciona su radio, sin handlers
de click duplicados ni otro modal. El grupo tiene leyenda, ayuda vinculada, nombres
accesibles traducidos y selección única; la primitiva gestiona foco, flechas y Espacio.
El borde y el radio marcado distinguen la selección sin depender solo del color.
La cuadrícula conserva una o dos columnas según el contenedor y la altura estable del
modal. No carga datos: recibe activos y catálogo del formulario. El activo único sigue
seleccionándose automáticamente y el guardado bloquea los controles. En edición se
conserva la acción explícita de quitar la selección cuando no es obligatoria.
## Foco inicial de los selectores PickerList
`EntityTable` identifica su input de búsqueda rápida mediante `data-quick-search`.
Al abrir un `FormDialog layout="picker"`, `initialFocus` de Base UI apunta a ese input
buscándolo dentro del popup, sin afectar a listados de página ni otros modales. La
búsqueda permanece montada durante la carga: `AssetPickerDialog` presenta la lista con
`loading`/`unavailable`, propagados por `AssetList` hasta `EntityTable`. Así se puede
escribir desde la apertura, sin anunciar falsos vacíos. El foco no se reaplica al filtrar
o recargar datos, y la primitiva conserva la restauración al cerrar diálogos anidados.
La [regla normativa](patron-de-dialogos.md#foco-inicial-en-la-búsqueda-rápida) se aplica
a todos los PickerList actuales y futuros, incluidos los de campos de ambos catálogos.
## Selección y revisión de perfiles de usuario
El alta de perfiles sigue cuatro pasos: puesto y ordenador derivado, persona elegida
expresamente, cuenta opcional y resumen con nombre, notas y estado. Reutiliza el progreso,
las acciones y el cuerpo de los puestos, con tamaño medio y altura estable; la edición
conserva el formulario completo y el tamaño grande, sin cambiar persona ni dispositivo.
El nombre propuesto es «Persona · Ordenador», limitado por el contrato del perfil; una
edición manual se conserva al retroceder y volver al resumen. Véase
[Perfiles de usuario](perfiles-de-trabajo.md).
Componentes genéricos de `@repo/rdx-ui`, independientes de DTO, consultas y traducciones:
- `FormHelp` encapsula `ContextHelp` y su área inferior con altura limitada y scroll propio.
Perfiles y puestos lo sitúan como hermano de `FormBody`, inmediatamente antes del pie.
- `FormStepActions` comparte Anterior, Siguiente y Guardar: `canContinue` bloquea el envío
en cualquier paso; durante el guardado bloquea también Cancelar y Anterior. El consumidor
aporta todos los textos y la navegación. Los puestos usan el mismo componente.
- `SelectionSummaryCard` recibe `icon`, `title`, `description` y acciones opcionales.
Presenta una ficha de consulta sin decidir cómo elegir ni consultar una entidad.
- `OptionSelectionField` recibe opciones `{ id, label, description, disabled? }`, valor,
icono, textos, idioma y callbacks. Sin valor muestra `EmptyState`; con selección muestra
una ficha en `ul`/`li`. Ofrece elegir/cambiar, alta opcional y retirada opcional como
acciones independientes. `locked` impide cambiar o retirar y oculta el alta; `disabled`
y `loading` bloquean las acciones que requieren disponibilidad.
- `OptionPickerDialog` conserva una elección provisional hasta confirmar; cancelar no
modifica el formulario. `OptionPickerList` reutiliza `EntityTable layout="fill"` y radios,
con selección al pulsar zonas no interactivas de la fila y opciones no disponibles
deshabilitadas. El diálogo usa `FormDialog layout="picker"`: búsqueda rápida con foco
inicial, altura estable y columna `selection` compacta al principio.
El módulo de perfiles aporta las opciones disponibles de esta compañía, mantiene el puesto
fijado cuando procede de su ficha y deriva el ordenador de su principal. No ofrece alta
de puesto desde este selector: un puesto incompleto debe completarse antes. Persona y
cuenta conservan sus altas rápidas, condicionadas por `allowQuickCreate`, sin un tercer
modal. `AssetSummaryCard`, exportada por `@metis/assets/client`, presenta el ordenador
en el paso del puesto y una sola vez en el resumen. Persona y cuenta se presentan en
listas sin duplicar el ordenador; los valores ausentes muestran `—`.
La autorización de cuenta sigue siendo una acción explícita y la cuenta es opcional.
Una autorización pendiente se explica tanto en su paso como en el resumen, con acción
para registrarla cuando está permitida. Guardar solo se ofrece al final del alta. La edición
conserva las excepciones de dominio para modificar nombre/notas o archivar perfiles con
referencias que requieren revisión; el servidor mantiene la validación definitiva.