metis-desk/docs/patron-de-listados-compactos.md
David fa9d95fa9e feat: add workstation equipment and organizational assignment functionalities
- Implemented `workstationEquipment` to manage workstation asset assignments, including principal and secondary assets.
- Created tests for `workstationEquipment` to ensure correct behavior with various scenarios.
- Developed `workstationOrganizationalAssignments` to retrieve current assignments for a responsible person in a company.
- Added tests for `workstationOrganizationalAssignments` to validate assignment retrieval logic.
- Introduced UI components for displaying workstation profile configurations and responsible person details.
- Created compact UI components for displaying lists and tables, enhancing the user interface for asset management.
- Added utility functions for handling cell content in tables, ensuring proper rendering of data.
2026-10-24 04:13:30 +02:00

26 KiB
Raw Blame History

Patrón de listados compactos — v2

Fecha: 22 de octubre de 2026. Contrato de diseño vigente; primera implementación compartida adoptada en el dashboard de puesto. Complementa el patrón de listados de entidades, que continúa siendo normativo para exploración y gestión. DESIGN.md gobierna tokens, tipografía e identidad; el patrón de diálogos gobierna las interacciones modales. Este documento no crea componentes, rutas, permisos ni APIs.

1. Objetivo y selección del patrón

Un Listado compacto (Compact List) permite reconocer, consultar y actuar sobre elementos relacionados en una ficha, dashboard, tarjeta o panel contextual. Selecciona los atributos útiles para esa relación y permite profundizar sin reproducir las herramientas de una pantalla de exploración. No sustituye un formulario ni un picker.

Un Listado de entidades (Entity Listing) permite encontrar y gestionar registros de una colección completa mediante las bandas y capacidades del estándar existente. Activos, Personas, Recursos y Compañías en sus pantallas principales conservan ese patrón.

Criterio Listado de entidades Listado compacto
Tarea principal Encontrar, filtrar, comparar muchos atributos y gestionar Reconocer relaciones y ejecutar acciones contextuales
Ámbito Colección completa dentro de su ámbito funcional Relación o resumen de una entidad/contexto explícito
Encabezado Cabecera de pantalla con título, subtítulo y acciones Título de sección normalmente aportado por el contenedor
Composición Cuatro bandas normativas Cabecera opcional, filas y salida al conjunto completo cuando proceda
Búsqueda/filtros Herramientas del patrón completo Ausentes por defecto; búsqueda local solo con justificación
Columnas Configurables, con identidad y acciones protegidas Seleccionadas por el consumidor, sin configurador
Ordenación Contrato actual de cabeceras y selector Orden estable del contexto; ordenación local opcional
Rango y paginación Rango superior/inferior, tamaño y paginación Sin esos controles; ampliación o acceso completo según necesidad
Estado navegable URL/History en consumidores habilitados por L03.1 Estado local del resumen; conserva retorno al abrir destinos reales
Volumen Preparado para exploración sostenida Resumen o colección legible con estrategia explícita de acceso completo
Presentación Tabla de entidades Compact Table o Compact Item List
Estado técnico EntityTable implementada CompactTable, CompactList y CompactListItem implementados; adopción inicial en puestos

Decidir en este orden:

  1. Identificar la pregunta del usuario y la relación que necesita consultar. Si debe explorar repetidamente el conjunto, aplicar listado de entidades aunque haya pocas filas.
  2. Elegir atributos prioritarios y tareas: comparar columnas favorece Compact Table; reconocer títulos con metadatos favorece Compact Item List.
  3. Evaluar crecimiento esperado, frecuencia de uso, acciones y ancho del contenedor. Definir cómo alcanzar todo el conjunto antes de limitar filas.
  4. Si necesita filtros combinados, configuración de columnas, tamaño de página y paginación recurrente, ofrecer una vista completa con el patrón de entidades. La tarjeta puede conservar un resumen; no acumular esas capacidades en él.

Estar en una ficha no obliga a compactar una colección que requiere exploración compleja. Tener 60 perfiles no obliga a cambiar de patrón: puede mantenerse un resumen compacto, siempre que el conjunto completo resulte accesible y el alcance visible esté explicado. Un selector continúa sujeto al patrón de diálogos/PickerList; no pierde búsqueda, selección o paginación por parecerse visualmente a una lista corta.

2. Estructura y variantes

El contenedor aporta el ámbito y normalmente el encabezado: título accesible, contador conocido opcional y acción contextual opcional. No repetir dentro de la colección el título de DetailPanel. El cuerpo contiene filas, estados y acciones. Al final puede aparecer «Mostrar más» o «Ver todos» con alcance explícito.

Compact Table: comparación de atributos homogéneos. Tabla HTML, nombre accesible, cabeceras de columna y filas con identidad estable. Las cabeceras aportan significado; no omitirlas en tablas comparativas ni usar una tabla para maquetar tarjetas.

Perfil Persona Cuenta Estado
Administración Ángela Muñoz angela.pruebas Activo
Consultas Íñigo Fernández — Activo

Compact Item List: lectura lineal de elementos con título, metadatos y acciones. Usar ul/li; ol solo si la secuencia tiene significado. Por ejemplo, portátil principal con estado técnico y uso, monitor asociado, o aplicación con versión y estado. Puede componer dl dentro de un elemento para pares etiqueta/valor. Un único elemento conserva la misma semántica; no requiere una tarjeta nueva para cada fila.

Ambas variantes comparten reglas de datos, densidad y estados, pero mantienen semánticas y estructuras independientes. No convertir automáticamente una tabla en lista al crecer el número de filas.

3. Contrato visual

Valores orientativos derivados de las primitivas y tokens instalados. La primera receta compartida se concreta en el contrato implementado al final de este documento. Los equivalentes en px usan la escala actual; el contenido y la ampliación de texto pueden aumentar las alturas. Consultar las fuentes inspeccionadas.

Elemento Receta orientativa Criterio
Contenido principal Inter, text-sm (14 px), título font-medium No reducir todo a 12 o 13 px para ganar densidad
Metadatos text-xs (12 px), text-muted-foreground Mantener legibilidad y contraste efectivo
Cabecera tabular text-sm font-medium, altura orientativa h-10 (40 px) Etiquetas breves; no nuevo estilo de títulos globales
Celdas px-2 py-2 (8 px) Menor padding vertical que EntityTable (py-3); sin alterar vendor
Fila simple Altura natural, alrededor de 40 px de texto; 48 px con acción de 32 px y py-2 No fijar altura máxima; aceptar filas multilínea y zoom
Elemento de lista py-2, separación título/metadatos gap-1; entre grupos gap-2/gap-3 Ritmo de 4/8/12 px según contenido
Acciones Button size="sm" o size="icon-sm" (32 px) Conservar foco y separación; ampliar superficie si el contexto táctil lo exige
Separadores border-border, divisores horizontales discretos Evitar cuadrícula completa, sombras y tarjeta anidada innecesaria
Superficie Heredar bg-card/text-card-foreground o fondo del contenedor Sin paleta alternativa ni tokens nuevos de éxito/advertencia

Texto a la izquierda; números comparables a la derecha con tabular-nums; acciones al final y alineadas a la derecha. La columna de identidad recibe el espacio flexible (min-w-0); estado y acciones tienen ancho controlado por su contenido. Evitar anchos mínimos heredados de inventario que hagan inutilizable una tarjeta estrecha. No añadir un configurador para resolver un exceso de atributos: elegir los esenciales y ofrecer detalle.

Permitir saltos de línea en nombres largos y valores técnicos. Si se trunca, conservar el valor completo en el árbol accesible y ofrecer lectura visual completa mediante expansión, detalle o tooltip accesible también por foco. title o hover aislados no bastan. No recortar cuentas diferentes hasta hacerlas indistinguibles.

Ausencias en datos: exclusivamente —, preservando cero y estados reales. Un error de resolución de referencias debe explicarse al nivel de la fuente, sin convertirlo en «sin relaciones». Estado textual además de color; conservar estados técnicos, uso y revisión por separado. EntityStatus.active es binario: no representa por sí solo todos los estados de configuración o disponibilidad.

4. Capacidades obligatorias y opcionales

Obligatorio: ámbito reconocible, identidad estable, información prioritaria, semántica correcta, estados honestos, acciones accesibles y acceso completo a los elementos que no se muestran. El contador es opcional; si existe, debe indicar si cuenta el conjunto, los elementos visibles o las coincidencias. Omitir el total cuando se desconoce.

No componer por defecto barra de búsqueda, barra de filtros, configurador de columnas, selector de tamaño, paginación, rangos duplicados ni menús de configuración. Omitirlos en la composición, sin controles ocultos por CSS ni elementos invisibles enfocables.

Capacidad opcional Cuándo y condiciones
Ordenación local Comparación útil y conjunto local completo; estable para empates, indicador aria-sort y botones de cabecera. No confundir ordenación con búsqueda.
Búsqueda local Localización frecuente dentro de la relación; campos y alcance explícitos, limpieza y estado sin coincidencias. No filtrar una página como si fuera el conjunto entero.
Expansión de fila Metadatos secundarios vinculados al elemento; botón con aria-expanded/aria-controls, contenido asociado y foco estable.
Mostrar más Revelar una porción adicional de la misma colección; explicar cantidad visible si se conoce. Tras activar, no mover foco inesperadamente; al contraer evitar retirar el elemento enfocado.
Ver todos Ruta real con ámbito conservado, o diálogo/panel existente que permita recorrer todo el conjunto. Distinguir navegar de revelar contenido.
Scroll interno Proteger el presupuesto de una tarjeta de consulta repetida; límite de altura responsive, indicador de continuidad, acceso por teclado y foco sin recortes. Evitar scroll anidado innecesario.
Límite inicial Solo con orden y criterio explícitos y una salida que alcance todos los registros. Nunca un slice silencioso.
Acciones por elemento Operaciones reales y permitidas; principal visible, secundarias en menú accesible, sin duplicar la principal. No ocultarlas exclusivamente al hover.
Selección simple Alimenta una acción o detalle concreto; radio o control equivalente semántico, nombre y estado visibles. Enlaces y botones internos mantienen su comportamiento.
Selección múltiple Existe una operación colectiva real y se define su alcance y confirmación. Casillas primero, restricciones y selección parcial explícitas; considerar PickerList si se está eligiendo en un formulario.

Cada consumidor documentará qué capacidad necesita y por qué. Un nuevo requisito de exploración compleja debe resolverse en la vista completa antes de ampliar el resumen con otra barra. Búsqueda y ordenación opcionales no implican persistencia global nueva.

5. Volumen y acceso completo

Escenarios de diseño, no umbrales automáticos:

Escenario Estrategia
0 Vacío real tras respuesta válida, explicación contextual y alta/vinculación si existe y está permitida. No mostrar tabla de controles sin propósito.
1 Mantener fila o elemento del mismo patrón; tabla si las etiquetas comparables siguen aportando significado.
2–5 Mostrar normalmente todo; sin herramientas de exploración añadidas por defecto.
6–15 Evaluar altura, anchura y frecuencia; mostrar todo si resulta legible o ampliar progresivamente con alcance explícito.
Decenas/cientos Resumen inicial determinista y acceso completo mediante expansión, región desplazable o destino completo real. Para investigación sostenida, utilizar listado de entidades en ese destino.

Si no existe una ruta que conserve la relación, no inventar un enlace ni enlazar «Ver todos» a un listado sin ese filtro. Puede mostrarse todo con desplazamiento contenido o proponerse una interacción local de ampliación para implementación posterior. La ausencia de destino completo bloquea un recorte que haría inaccesibles registros, no obliga a inventar contratos HTTP.

En datos remotos paginados indicar «elementos cargados» o «hay más» según el contrato. «Cargar más» obtiene páginas reales y conserva orden/identidades; no es paginación local oculta. Si se necesita cambiar página/tamaño o filtrar todo el servidor, llevar esa tarea a la vista completa. No derivar total global de items.length de una página.

6. Estados y fuentes

Situación Presentación y recuperación
Carga inicial Skeleton de geometría plausible, decorativo y sin nombres, acciones o totales inventados; mensaje de carga de la colección.
Actualización con datos previos Conservar filas, selección, expansión y foco; aviso discreto. Si falla, explicar que se muestran últimos datos disponibles y ofrecer reintento dirigido.
Vacío real Cero únicamente confirmado por respuesta válida del ámbito completo; CTA pertinente con permisos.
Sin coincidencias Mantener criterio visible y acción de limpiar búsqueda; no afirmar ausencia de relaciones.
Error parcial Mantener fuentes válidas; identificar la fallida, las limitaciones del resumen y su reintento. No mostrar un total definitivo incompleto.
Fuente no disponible Mensaje contextual y recuperación si procede; no reutilizar vacío. Si no existe caché autorizada, no mostrar filas inventadas.
Información incompleta Conservar registros válidos y — en datos ausentes; explicar referencia no resuelta o metadatos pendientes en su contexto. No equiparar ausencia y error.
Datos paginados Identificar alcance cargado, total solo si es conocido y acceso al resto.
Ausencia de permisos Respetar denegación real, retirar datos no autorizados y explicar acceso según contrato del módulo. No usar caché anterior como sustituto de autorización.

EntityListState existente cubre loading/ready/refreshing/unavailable/stale. No cubre por sí solo total desconocido, permisos, error de otra fuente o parcialidad de datos: esos hechos pertenecen al adaptador consumidor. Reutilizar la unión cuando encaje y componer la información adicional; este estándar no añade variantes a su API.

Ubicar feedback y reintento fuera de la región horizontal desplazable. Conservar un destino de foco estable cuando desaparezca el botón de reintento. Usar anuncios moderados por colección, sin una región live por celda ni toasts repetidos en refetch. Los datos remotos siguen en TanStack Query; no duplicarlos en estado React.

7. Responsive y accesibilidad

Adaptar por capacidad real del contenedor, sin copiar presupuestos de Activos o Recursos ni definir un breakpoint global. Mantener identidad, estado y acciones; los atributos secundarios pueden ir al detalle o a una fila expandida con acceso explícito. En lista, apilar título, metadatos y acciones conservando orden DOM. En tabla, preferir salto de línea y columnas prioritarias; usar scroll horizontal contenido cuando la comparación lo requiera. Si se diseña una alternativa móvil como lista, conservar etiquetas y relaciones, una sola presentación accesible e identidad/foco estables.

  • Tabla nativa con nombre mediante caption, aria-label o aria-labelledby, th con scope="col" y cabecera de fila cuando resulte pertinente. Etiquetas accesibles para columnas visuales de iconos/acciones, aunque usen sr-only.
  • Lista nativa con nombre de sección; no simular tabla con div ni añadir role="grid" sin implementar su modelo de interacción. No usar la semántica de tabla para composición.
  • Enlace para destino real, botón para acciones, expansión y diálogos. Navegación del cliente mediante BrowserLink desde el consumidor; la biblioteca genérica no conoce rutas.
  • Acciones por teclado, etiquetas traducidas para iconos funcionales y decorativos aria-hidden. Una fila clicable conserva controles internos y alternativa de teclado.
  • Foco visible no recortado, sin dependencia de hover; retorno al origen tras modal y conservación del registro al ampliar/cargar más. No introducir saltos de foco en refetch.
  • Contraste efectivo según DESIGN.md: texto normal 4,5:1 y texto grande 3:1; comprobar también controles, selección y foco según los criterios aplicables del proyecto.
  • Contenido completo legible con teclado y tacto; zoom 200/400 %, nombres largos, espacios y valores técnicos sin desbordamiento global. La compactación no justifica reducir controles ni fijar alturas que recorten texto.

La futura implementación debe verificarse con el procedimiento Playwright MCP, datos sintéticos aislados y volumen de AGENTS.md, además de escenarios específicos vacíos y pequeños. Las reglas aquí escritas no equivalen a una validación en navegador ni a cumplimiento WCAG demostrado.

8. Arquitectura de componentes

Compartir primitivas y comportamientos transversales; diferenciar composición y capacidades. No añadir un modo universal a EntityTable con flags para quitar barras.

Pieza existente Reutilización y límite
Table, TableHeader, TableBody, TableRow, TableHead, TableCell Base semántica de Compact Table. Componer desde código propio; no editar vendor.
DetailPanel Contenedor opcional con título/acciones/contador; density="compact" reduce espaciado, sin gestionar filas ni estados. Evitar doble tarjeta.
EntityStatus / Badge Estado textual y visual cuando la semántica encaja; el booleano de EntityStatus no modela un estado complejo.
EntityRowActions / EntityAction Acciones existentes, menú Base UI y restricciones. La navegación conserva un enlace real, no se transforma en callback de botón por usar primaryAction.
EntityListState / EntityListStatus Disponibilidad, refetch, error y reintento cuando el contrato encaja; el consumidor conserva permisos y parcialidad.
EmptyState, Skeleton Composición de estados; EmptyState tiene presentación de incorporación y no es obligatorio para todo vacío mínimo.
BrowserLink y contratos de retorno El módulo aporta enlaces y contexto real; no acoplar rdx-ui a core o DTO.
EntityTableContent / useEntityTable Acoplados a TanStack y al patrón completo; no reutilizarlos obligando a inicializar paginación/visibilidad que el compacto no necesita.
DashboardTable Precedente local sobre Table; no es todavía API compartida ni receta normativa del compacto.

Responsabilidades del patrón; la API mínima implementada se describe en §11. Las capacidades opcionales anteriores no se consideran implementadas por aparecer aquí:

Componente Responsabilidad y entradas conceptuales
CompactTable Tabla comparable: nombre accesible, colección readonly con identidad estable y definición mínima de columnas (ID, etiqueta y render). Alineación y prioridad explícitas cuando proceda.
CompactList Lista semántica de elementos con identidad estable, nombre accesible y contenido compuesto por el consumidor. Sin motor de tabla.
CompactListItem Título, metadatos y acciones mediante composición; independiente de entidades, consultas y traducciones. Extraer solo ante repetición real.

Los nombres de ficheros siguen kebab-case y componentes PascalCase. La implementación genérica pertenece a @repo/rdx-ui; las adaptaciones de entidades viven en su operación conforme a la estructura web vigente. No crear barrels vacíos, factories, contexto React o un motor de columnas universal de antemano.

Los renderizadores reciben datos presentacionales, textos traducidos y acciones del módulo, sin endpoints, ORM ni DTO específicos. No exigir EntityColumn con capacidades de exploración para describir una fila compacta. Selección, búsqueda o ampliación se añaden como composición explícita cuando haya consumidores; no mediante una colección de flags hide*/show*.

La consulta se comparte por query keys existentes. Calcular filtrados, contadores y filas durante render; solo criterio, selección y expansión necesitan estado local. Sin efectos para copiar datos remotos, consultas por fila, memoización preventiva ni nuevas dependencias. No persistir preferencias nuevas ni introducir paginación/virtualización sin necesidad medida.

9. Casos de referencia y ejemplos

La ficha de Puesto de trabajo es referencia funcional, no la única especificación visual. La tabla conserva el diagnóstico anterior y la transformación adoptada en la primera implementación, descrita en el informe de refinamiento:

Colección Actual inspeccionado Propuesta
Personas y perfiles WorkstationProfiles usa EntityTable completa con búsqueda, contador, columnas, ordenación y paginación dentro de DetailPanel Compact Table: perfil, persona, cuenta, estado y acción contextual; resumen de configuraciones solo si aporta a la tarea y cabe. Conservar alta, archivados, revisión, enlaces y estados independientes.
Equipamiento WorkstationAssets usa EntityTable completa Compact Item List: nombre y enlace, plantilla, condición de principal, estado técnico y uso separados; editar composición sigue siendo una acción explícita.
Software del principal WorkstationSoftware ya presenta ul/li con scroll y gestión en diálogo Normalizar como Compact Item List preservando versión, licencia, estado, revisión, errores por fuente y gestión existente. No sustituir su diálogo de gestión completa por resumen.
Configuraciones por perfil WorkstationConfigurationFamily presenta listas por familia, selector de perfil y destinos de gestión Colecciones contextuales con selección de perfil fuera de las filas; conservar familias, estados de configuración, cobertura documental y revisión, sin inferir permisos efectivos.

Con 60 perfiles: indicar alcance (por ejemplo, activos y archivados separados), mantener orden estable y permitir recorrerlos todos. Hasta disponer de una ampliación o destino real que conserve la relación con el ordenador principal, no limitar el resumen sin salida. Una ruta de perfiles de compañía sin ese filtro no equivale a «todos los perfiles del puesto».

Correcto Incorrecto
Tres activos con título, estado técnico, uso y enlaces en lista EntityTable completa dentro de tarjeta solo para mostrar tres nombres
Resumen de perfiles con Mostrar más que permite alcanzar los 60 Recortar a cinco sin explicar que faltan registros
Ver todos abre un destino real con el mismo ámbito Enlace inventado o listado general presentado como si conservara la relación
Herramientas omitidas de la composición Ocultar buscador y paginación de EntityTable con selectores CSS
Reintento de la familia fallida y datos válidos conservados Fallo de metadatos convertido en «0 configuraciones»
Acción visible y menú operable con teclado Acción disponible únicamente al pasar el ratón
Vista completa para filtrar muchas relaciones Añadir sucesivamente todas las barras del inventario al resumen

10. Compatibilidad y decisiones de implementación pendientes

Este patrón no altera los listados de entidades existentes, la excepción de pickers layout="fill", L03.1, contratos HTTP, i18n es/en, permisos ni políticas de actualización de consultas. Mantener contexto de compañía y retorno al navegar; el resumen no hereda sort/página del listado padre ni añade parámetros URL por defecto. Una interacción contextual con contrato URL propio lo conserva. Modal de alta/edición sigue el patrón de diálogos.

Para futuras adopciones debe concretarse por consumidor:

  • Prioridad y anchura efectiva de columnas, altura disponible y forma de ampliación. La receta orientativa de densidad debe validarse con texto largo, tacto y zoom.
  • Destino o interacción para consultar todos los perfiles del principal y tratamiento de archivados; no asumir que existe un filtro de ruta aún no implementado.
  • Evoluciones de la API mínima y extracción real de renderizadores/estados comunes. Los nombres propuestos no comprometen una API universal.
  • Búsqueda local si se necesita: V07 sigue pendiente. La normalización del selector de columnas no aprueba automáticamente la de entidades; conservar la semántica vigente hasta una entrega específica y documentar los campos buscables.

La auditoría v2 contiene candidatos adicionales, evidencias estáticas, discrepancias y verificaciones realizadas en esta fase documental.

11. Primera implementación compartida

Adoptada en el refinamiento del dashboard de puesto. Primitivas de @repo/rdx-ui/components/<nombre-en-kebab-case>:

API disponible Contrato implementado
CompactTable<T extends { id: string }> title, items readonly, columns readonly y emptyMessage; opcionales className sobre la tabla, state, onRetry y retryLabel.
CompactColumn<T> id, label, render(item) y align opcional: start o end. Sin metadatos de búsqueda, visibilidad o paginación.
CompactList title accesible y children; lista nativa con divisores y tipografía estándar.
CompactListItem title, metadata?, icon?, actions?, children?; identidad flexible y acciones independientes.
CollectionPreview<T> items, limit, expandLabel, collapseLabel y función hija que recibe los elementos visibles; amplía al conjunto completo cargado.

CompactTable conserva mensajes de EntityListState mediante EntityListStatus, reintento con foco estable fuera del scroll y valores ausentes mediante el mismo helper que EntityTable. El consumidor aporta textos y no debe proporcionar un callback de reintento sin su etiqueta. El padding implementado de celda es px-2 py-1.5 (8/6 px), ajustado para filas de texto y acciones legibles; la altura continúa siendo natural. No hay herramientas ocultas, motor TanStack, consultas ni dependencia de Workstation.

CollectionPreview muestra inicialmente el límite elegido por la tarea (cinco en el resumen de puesto) y ofrece Mostrar todos con cantidad y Mostrar menos, con aria-expanded/aria-controls. No pagina, no filtra ni presume que los elementos cargados sean un total remoto. Mantiene el botón y su foco al ampliar o contraer. Límite no entero positivo implica mostrar todo, evitando recortes inaccesibles.

Ordenación local, búsqueda, selección y carga de páginas remotas siguen siendo capacidades de diseño opcionales no implementadas por estos componentes. No se añadieron flags a EntityTable. Las adopciones restantes de la auditoría v2 continúan pendientes.