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

25 KiB
Raw Blame History

Componentes de interfaz

Metis Desk usa shadcn/ui, con Base UI y el estilo base-vega, siguiendo la organización de factuges-erp. packages/shadcn-ui contiene únicamente el código del registro oficial y no se adapta ni se extiende desde este proyecto. Se consume mediante @repo/shadcn-ui.

Los componentes genéricos compuestos o reutilizables propios viven en packages/rdx-ui y se consumen mediante @repo/rdx-ui. Los componentes que conocen contratos, textos, consultas o reglas de Metis viven en el módulo o aplicación que los utiliza; nunca en ninguno de los dos paquetes genéricos.

Componentes actuales

  • Button: volver a comprobar la conexión.
  • Card: bloques de información y estado.
  • Select: idioma y selección de sistemas, entornos, cuentas, personas y demás relaciones, con navegación por teclado.
  • Input y Label: credenciales y datos de usuario.
  • Checkbox: filtros de listado, selección múltiple y configuración de columnas.
  • SwitchField: campos binarios de formularios, como estado activo, VPN, predeterminado o requerido.
  • Table: listados de usuarios, compañías, personas y departamentos.
  • Textarea: dirección opcional de las sedes, con varias líneas.
  • Dialog: primitiva de shadcn/ui, con foco y cierre por teclado.
  • RDX UI: FormDialog, FormActions, ContextHelp, SelectWithAction, SwitchField, EntityTable y StatisticCard.

Convención de navegación

Los destinos de secciones y fichas existentes siguen Rutas del cliente. 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:

import { Button } from "@repo/shadcn-ui/components/button";

El tema base se define en packages/shadcn-ui/src/styles/globals.css; las extensiones propias se declaran fuera del vendor. La aplicación importa ese CSS y declara las fuentes de Tailwind de los módulos y de packages/rdx-ui. Usar tokens semánticos como bg-background, text-foreground y border-border en vez de repetir colores por pantalla.

Tipografía

La aplicación carga localmente Inter y JetBrains Mono desde apps/web/src/fonts, con font-display: swap. Georgia usa la fuente disponible en el sistema, seguida de Times New Roman. Tailwind 4 expone las tres familias mediante font-sans, font-serif y font-mono: Inter es la tipografía general; Georgia se reserva para contenido editorial puntual; JetBrains Mono, para código, identificadores y valores técnicos. Las licencias OFL 1.1 de las dos fuentes descargadas acompañan sus respectivos archivos.

Añadir componentes

SectionNavigation, en modules/companies/src/web/shared/ui, compone Card de la biblioteca y BrowserLink de core para índices de secciones. Recibe una etiqueta de navegación y elementos con href, título, descripción y valor numérico opcionales. No conoce DTO, consultas ni endpoints; sus consumidores aportan los textos traducidos y únicamente valores conocidos. Se reutiliza en el resumen de compañía y en los índices de catálogos globales y de compañía, con foco visible y enlaces que pueden abrirse en otra pestaña. No se modifica el paquete vendor.

Para componentes propios de un módulo, consultar primero la estructura web por entidades y operaciones 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:

pnpm --filter @repo/shadcn-ui ui:add input

El CLI está fijado en package.json. Revisar los archivos y dependencias añadidos antes de validar. No usar --overwrite sin revisar las adaptaciones locales. apps/web/components.json también permite al CLI localizar el paquete compartido desde la aplicación.

Después de generar código, separar los imports de tipos con import type y ejecutar las comprobaciones del proyecto. No añadir todo el catálogo ni crear envoltorios sin una necesidad concreta.

Dependencias

Base UI proporciona interacción accesible y gestión del foco. class-variance-authority define variantes del botón; cn combina clases de Tailwind. lucide-react aporta los iconos del selector y tw-animate-css sus transiciones. shadcn es una herramienta de desarrollo; su hoja de estilos común se incorpora al compilar.

Guías oficiales: monorepos e instalación 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 SelectWithAction para añadir campos a una plantilla y crearlos mediante un modal adicional. Los tipos de dato constituyen una lista cerrada. Las definiciones de serie se consultan en modales de lectura; su personalización se realiza mediante copias.

El inventario añade altas rápidas de plantilla, sede y persona en los activos. La plantilla creada desde un activo solo ofrece campos existentes para mantener un único nivel adicional de modal. Las características utilizan controles según su tipo; los valores sí/no conservan un estado vacío distinto de No.

Listados de entidades

El estándar se define en Patrón de listados de entidades, con inventario de pantallas y excepciones. EntityListHeader, EntityTable, EntityRowActions, EntityAction y EntityStatus viven en @repo/rdx-ui/components/entity-list.

EntityTable recibe datos y columnas tipadas; utiliza TanStack Table para búsqueda, ordenación, visibilidad y paginación local. Admite búsqueda y filtros controlados por el módulo, conservando su semántica y ámbito. Las columnas de identificación y acciones quedan visibles. Los menús utilizan Base UI y conservan los callbacks de las acciones originales. No realiza peticiones ni conoce DTO de negocio.

La paginación es local sobre los conjuntos que las API actuales ya devuelven. No simula paginación de servidor. La selección del activo para el panel sigue perteneciendo a Inventario, fuera de la tabla compartida.

EntityTable separa internamente la configuración de columnas/ordenación, el contenido semántico y la paginación; useEntityTable concentra el estado de representación. entityLabel permite contar «plantillas», «campos» o «tickets» aunque el título del espacio sea más amplio. Los tamaños disponibles son 10, 25, 50 y 100; una opción vacía nunca cambia el tamaño a cero.

Se declara @tanstack/react-table en el paquete compartido porque es quien consume ahora la librería; se reutiliza la versión 8.21.3 que ya utilizaba Compañías. No se introduce un segundo sistema de tablas.

Catálogo de marcas y selector funcional

BrandCatalogPage reutiliza EntityListHeader/EntityTable con búsqueda, filtro de estado, columnas, ordenación y paginación. BrandEditorDialog compone FormDialog y BrandForm tanto para la gestión global como para las altas relacionadas; el formulario conserva ContextHelp y FormActions.

BrandLogo y BrandLogoField muestran y permiten seleccionar SVG estáticos locales, con vista previa, errores de campo y acción de quitar. La columna de logotipo reutiliza esa presentación. Los límites, validación y fuentes se describen en Catálogo global de marcas.

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.

Componentes modales de los módulos

La ubicación de componentes, formularios y clientes HTTP sigue la estructura web por entidades y operaciones.

El inventario de modales localiza las interacciones de alta, edición, copia, confirmación, detalle, historial, selección, ayuda y configuración, incluyendo las altas rápidas dentro de otros formularios. La regla normativa de organización reside en AGENTS.md.

Las páginas coordinan apertura, selección y callbacks. El componente modal posee su estructura y la lógica que le pertenece; el formulario mantiene validación, borrador, errores y guardado. Se reutilizan FormDialog y FormActions sin modificar su implementación vendor. Los diálogos específicos de entidades viven junto a sus consumidores, no en la biblioteca visual genérica.

CompanyEditorDialog, PersonEditorDialog, DepartmentEditorDialog, SiteEditorDialog, BrandEditorDialog y SupportToolEditorDialog se reutilizan donde ya existen operaciones equivalentes. Los callbacks de guardado actualizan la selección del consumidor; no se cierra un formulario por el mero inicio de una mutación. Los historiales de consulta se derivan de los datos remotos actuales, mientras que los borradores de edición permanecen locales.

Acciones de fila ancladas

EntityRowActions muestra un botón icon-sm con EllipsisIcon, etiqueta accesible y título traducidos, sin texto visible repetido. EntityTable reserva la identidad de columna actions para operaciones: siempre visible, sin búsqueda/ordenación y fijada a la derecha mediante columnPinning de TanStack Table. EntityTableContent renderiza primero las columnas de datos y después la columna anclada, con cabecera/celdas sticky right-0, alineación derecha, anchura compacta y fondo opaco para evitar solapamientos al desplazar la tabla. Todos los listados consumidores heredan este comportamiento.

Tarjetas de indicadores

StatisticCard (@repo/rdx-ui/components/statistic-card) representa un indicador con etiqueta, valor, icono y tinte discreto. Admite carga/error, evita sustituir datos desconocidos por cero y utiliza un botón con foco visible cuando hay navegación. No depende de DTO ni endpoints. Inicio la reutiliza para sus cinco KPI; la organización de bloques y sus estados permanece local al dashboard.

Shell global

App conserva el acceso y el selector de idioma. CompaniesWorkspace compone sidebar, header global y contenido con los destinos y estado de navegación existentes. El header solo aparece en sesiones autenticadas. Los catálogos se seleccionan en la sidebar; la navegación interna de cada compañía permanece en CompanyDetail.

Los tokens Tailwind 4 --color-shell-* de apps/web/src/global.css aíslan la paleta del shell de los colores semánticos del resto del producto: superficie negra exterior, contenido #F7F9FB, sidebar #0E282C, acento turquesa, texto y borde azul, radios 8/10/20px, sidebar 228px, header 88px, búsqueda 41px, fila de navegación 48px y avatar 35px. En móvil se conserva la navegación horizontal y envuelta, sin introducir drawer.

El buscador usa Input de shadcn/ui y filtra compañías cargadas y destinos globales; Escape limpia y los resultados se recorren mediante Tab. No existe un servicio de búsqueda transversal de entidades. La campana no muestra avisos ficticios: no existe servicio de notificaciones. El perfil usa disclosure nativo con foco visible y conserva las acciones de identidad y selección de idioma. No hay primitives Avatar ni DropdownMenu instalados; el avatar muestra iniciales del usuario real, sin inventar foto ni cargo. Configuración abre un FormDialog con las acciones existentes.

La sidebar global permite contraerse en escritorio de 228px a 72px mediante «Contraer menú» / «Expandir menú». Conserva iconos, nombres accesibles y títulos de ayuda también para compañías y subcatálogos. La navegación se desplaza por separado, manteniendo Configuración y el control de anchura en el pie visible. En móvil se conserva la navegación horizontal existente.

El plegado se conserva entre recargas y sesiones mediante la clave metis-desk:sidebar-collapsed de localStorage, compartida por las sesiones de ese navegador y origen, no por cuenta ni mediante el servidor. Solo true inicia la barra plegada; valores ausentes o inválidos mantienen el estado desplegado. Se guarda desde la acción de plegar/desplegar. Si el navegador bloquea la lectura o la escritura, la barra sigue funcionando en memoria. No cambia el layout móvil.

El menú expandido y su pie comparten márgenes horizontales de 20px mediante --spacing-shell-sidebar-inset y la utilidad px-shell-sidebar-inset. El header global usa bg-shell-header (#FFFFFF), con un borde inferior border-shell-border, sobre el contenido #F7F9FB.

Estilos con Tailwind 4

La aplicación y la biblioteca base utilizan Tailwind 4.2.2 y el plugin oficial de Vite. El CSS de la aplicación contiene únicamente imports, fuentes de detección y tokens @theme; los layouts, estados y reglas responsive se expresan mediante utilidades en sus componentes. No hay clases globales de componente desk-*, company-space-*, dashboard-* ni de inventario. La implementación vendor de @repo/shadcn-ui no se modifica para personalizar el shell.

Los colores propios utilizan --color-shell-*; las dimensiones, --spacing-shell-*; los radios, --radius-shell-* y --radius-panel; la sombra del desplegable, --shadow-shell-popover. El resto de la interfaz conserva los tokens semánticos de shadcn/ui. Los valores puntuales que no se repiten se declaran como utilidades arbitrarias, no como tokens o abstracciones anticipadas. Los degradados conservan su interpolación CSS original.

Los breakpoints de la adaptación se declaran en píxeles para conservar los cortes anteriores: shell (768px), inventory (1100px) y dashboard (1200px). Se usan consistentemente dentro de cada layout para evitar conflictos de orden entre variantes con distintas unidades.

WorkspaceNavigationButton compone Button y comparte los niveles principal y de catálogo, selección, foco y contracción. DashboardNavigationButton, DashboardStateBadge y DashboardTable encapsulan patrones repetidos del dashboard; este último reutiliza Table. Cada componente vive en su propio fichero y recibe acciones, textos o contenido del consumidor. LanguageSwitcher permite ajustar su margen mediante className, sin selectores globales dependientes de la cabecera que lo contiene.

Avatar de iniciales

InitialsAvatar (modules/companies/src/web/shared/ui/initials-avatar.tsx) es un componente visual sin dependencias de compañías, DTO ni consultas. Recibe name y atributos de span; admite className para tamaño y aria-hidden cuando el control contenedor ya tiene nombre accesible. Se reutiliza en las compañías de la sidebar y en el perfil del header.

Muestra la primera inicial de hasta dos palabras; conserva siglas de una palabra como IBM (hasta tres caracteres), y usa «?» para nombres vacíos. El fondo pastel se elige de una paleta de doce colores mediante un hash del nombre normalizado: mayúsculas, espacios y acentos no cambian el color. El color no depende del orden ni cambia al recargar; distintos nombres pueden compartir color. El texto oscuro mantiene contraste legible. Los avatares de compañías siguen visibles cuando se contrae el menú y conservan la selección y las ayudas del botón de navegación.

Avatar y logotipo de compañía

CompanyAvatar, CompanyIdentity y CompanyLogoField reúnen la presentación y selección del logotipo. Su contrato, límites y persistencia se documentan en Logotipos de compañías. Se usan tamaños de 24–28 px en navegación/listas y 40–56 px en detalle/formulario.

FieldSections y TemplateSectionsEditor

Componentes de @repo/rdx-ui independientes de los DTO, consultas y endpoints de Metis Desk. FieldSections recibe secciones, elementos con sectionId opcional y renderItem; presenta una cuadrícula plana cuando no hay secciones, o bloques desplegables accesibles con descripción y «Sin sección». Admite presentación de valores como listas de descripción y apertura ante errores externos.

TemplateSectionsEditor recibe secciones, referencias de campos, nombres, textos traducidos, generador de identidad y onChange. Permite ordenar secciones/campos, editar nombre/descripción, asignar campos y mantener su obligatoriedad mediante SwitchField. El módulo consumidor conserva el estado en React Hook Form y genera UUID v7. Los helpers removeTemplateSection y copyTemplateLayout conservan referencias y remapean identidades respectivamente. No se modifican los componentes vendor.

Formularios modales adaptativos

FormDialog admite size="small" | "medium" | "large". Los formularios sencillos mantienen el tamaño pequeño; personas y usuarios emplean el intermedio. wide sigue siendo un alias del tamaño grande: 90 % del ancho disponible, con máximo de 1.200 px y márgenes en móvil. No se ensanchan automáticamente las altas sencillas.

La cabecera permanece visible. Un formulario modal utiliza una raíz flex con altura mínima cero, FormBody para campos, errores y ayuda, y FormActions como hermano final fuera del cuerpo desplazable. Los botones permanecen dentro del formulario HTML original: se conservan envío por teclado, validación nativa, estados pendientes y cancelación. La ayuda queda al final del cuerpo, inmediatamente antes de las acciones. Los diálogos de consulta conservan el cuerpo desplazable y su pie externo.

FormSection agrupa datos relacionados mediante título accesible y un borde discreto. Activos, recursos y plantillas lo utilizan para sus datos principales. Las características y las secciones del editor se distribuyen en dos bloques por fila cuando el contenedor dispone de al menos 900 px; los controles dentro de cada bloque mantienen una o dos columnas según el espacio. Una plantilla sin secciones puede aprovechar hasta tres columnas de características. Observaciones y ayuda ocupan toda la fila. La disposición responde al espacio del modal, no solo al ancho del navegador.

El desplazamiento vertical se conserva cuando el contenido supera la altura disponible; no se ocultan campos ni se reduce artificialmente su tamaño para eliminarlo. No se introducen pestañas ni páginas nuevas sin una necesidad específica del formulario.

Resumen de compañía

La pantalla CompanyOverview tiene composición independiente de DashboardPage (Inicio). Comparte tarjetas, bloques de trabajo/avisos/actividad y consultas existentes, siempre fijando el ámbito al UUID de la compañía. El encabezado «De un vistazo» precede las acciones rápidas (ticket, intervención, activo y recurso), cuatro indicadores operativos y una franja navegable de personas, departamentos y sedes. No muestra compañías activas ni el resumen por compañías. La variante compacta de StatisticCard mejora la jerarquía del valor y la etiqueta sin modificar la presentación de Inicio.

La navegación de compañía empieza por Resumen, Tickets e Intervenciones; después agrupa Personas, Puestos de trabajo y Perfiles de usuario y finalmente las demás secciones. Los grupos tienen separación visual. Las operaciones de usuario permanecen en el menú superior y dejan de repetirse al pie del menú de compañía.