FactuGES_ERP/docs/frontend/ui-patterns.md

204 lines
8.9 KiB
Markdown
Raw Normal View History

# Patrones de UI
> **Propósito:** explicar cómo implementar pantallas concretas. Este documento, junto con [Arquitectura frontend](./frontend-architecture.md) y [UI system](./ui-system.md), forma la fuente de verdad (SSOT) de frontend/UI.
Los componentes aquí citados se definen en `ui-system.md`; este documento explica cómo componerlos.
## 1. Listados
```tsx
<RdxPageFrame>
<RdxPageContainer width="full">
<RdxPageContent>
<RdxPageHeader
title={title}
description={description}
actions={<RdxPageActions primary={primaryAction} />}
/>
{refreshError}
<DataTable topContent={toolbarAndFilters} {...tableProps} />
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
```
El error inicial bloqueante conserva el mismo shell:
```tsx
<RdxPageFrame>
<RdxPageContainer width="content">
<RdxPageContent>
<PageErrorState />
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
```
- El CTA de creación permanece durante loading, empty y errores de refresh; puede desaparecer ante error inicial bloqueante.
- Búsqueda, filtros y controles viven en `DataTable.topContent`.
- TanStack Query conserva los datos remotos y la paginación server-side.
- Búsqueda, filtros, sorting y paginación reflejan su estado relevante en `searchParams` y reinician página cuando corresponda.
- No se calculan contadores globales a partir de una página parcial.
- El click de fila recibe la fila; la página/controller usa explícitamente `row.id`.
- Una vista de composición de dominio puede encapsular el shell si permanece pura.
- Los listados no reconstruyen con HTML local las responsabilidades de Frame, Container o Content. Search, filtros, tabs, tabla y paginación son contenido funcional de `RdxPageContent`; el scroll interno de una tabla o grid es legítimo y no sustituye el scroll de página.
El controller de página concentra URL, filtros, consulta, estados y navegación. La Page define columnas y conecta callbacks; no reproduce reglas de dominio.
## 2. Create / Update
Estructura canónica: providers → RdxPageFrame (header/content/footer) → un único RdxPageContainer → un único RdxPageContent; los dialogs son hermanos del frame.
```tsx
<RdxPageFrame
header={<RdxEditorHeader />}
footer={<RdxEditorActionBar />}
>
<RdxPageContainer width="content">
<RdxPageContent>
{pageError ? <ErrorAlert /> : null}
<Editor />
</RdxPageContent>
</RdxPageContainer>
</RdxPageFrame>
```
Los errores inline no definen su propio spacing estructural. Participan del spacing estándar de RdxPageContent igual que el resto de hijos principales.
Footer y aside son opcionales. `content` es adecuado para formularios focalizados; `full` para editores documentales amplios. Create y Update deben compartir geometría cuando solo cambian defaults, submit o navegación.
Loading y error bloqueante usan también `RdxPageFrame` y `RdxPageContainer`; no reconstruyen el viewport.
El error bloqueante conserva la misma jerarquía: RdxPageFrame → RdxPageContainer → RdxPageContent → PageErrorState.
## 3. Detail
Un detalle normal usa `RdxPageFrame`, `RdxEditorHeader` cuando es fullscreen y la jerarquía `RdxPageContainer → RdxPageContent`; errores, alertas, secciones semánticas y contenido funcional viven dentro de `RdxPageContent`.
Un detalle documental especializado puede usar:
```text
RdxPageFrame content overflow-hidden
├── document pane → scroll interno
└── inspector → scroll interno
```
Solo se usa esta excepción cuando los panes necesitan permanecer visibles simultáneamente. Su scroll interno es parte del workspace documental, no el scroll estructural de la Page. No se crea un layout genérico de detalle si no existe repetición real.
## 4. Settings
Settings posee un layout funcional bajo un único frame:
```text
RdxPageFrame
└── SettingsLayout
├── header
├── navigation
└── Outlet
```
Las páginas hijas no añaden otro `RdxPageFrame` cuando el padre ya controla shell y scroll. El layout de Settings se conserva porque aporta navegación y composición funcional.
## 5. Dialogs
Usar `Dialog` para una tarea breve y focalizada que debe volver al contexto actual. Usar `AlertDialog` para confirmaciones, especialmente destructivas. Una edición prolongada o documental necesita una ruta propia.
Orden recomendado:
```text
DialogContent
├── DialogHeader: título y descripción
├── campo de identidad, si existe
├── secciones agrupadas por intención
└── DialogFooter: cancelar y enviar
```
- La cabecera y el footer se separan visualmente del formulario.
- Los bloques usan `section` y `aria-labelledby` cuando tienen título.
- Campos equivalentes pueden usar dos columnas desde `sm`; en móvil se apilan.
- El botón submit puede apuntar al formulario mediante `form` si el footer está fuera de `<form>`.
- Durante submit se deshabilitan controles y acciones de cierre que puedan interrumpir la operación.
- El controller posee defaults, validación, mutation, reset, feedback y navegación posterior.
- Todo texto visible usa i18n en `es` y `en`.
## 6. Acciones de fila
`RdxRowActions` recibe acciones ya resueltas:
- los listados usan `RdxRowActions` para mantener una presentación uniforme;
- las frecuentes van en `primaryActions` como botones de icono con tooltip; no se usan botones de texto en la columna de acciones;
- las secundarias y destructivas van en `overflowActions`, accesibles desde el menú de más acciones;
- orden, `disabled`, `disabledReason`, permisos y callbacks pertenecen al consumidor;
- una acción deshabilitada puede seguir visible para explicar por qué no está disponible.
No se trasladan entidades, navegación, dialogs ni permisos a `rdx-ui`.
## 7. Loading, error y empty
| Estado | Patrón |
| --- | --- |
| Loading inicial de tabla | `DataTable loading` dentro del shell estable |
| Loading de editor | skeleton con el mismo frame/container que success |
| Error inicial bloqueante | `PageErrorState` dentro de frame y container |
| Error de refresh | `ErrorAlert` inline; mantiene datos y controles |
| Empty | mensaje/estado vacío en el stack del contenido |
No se crean sistemas alternativos de errores o skeletons cuando los componentes existentes cubren el caso.
## 8. Formularios
- React Hook Form y `FormProvider` pertenecen al consumidor.
- `RdxFormGrid` organiza únicamente campos.
- Errores de campo permanecen junto al control; errores de operación usan `ErrorAlert`.
- Dirty state, bloqueo de navegación y submit pertenecen al controller/provider correspondiente.
- La acción principal comunica loading y evita doble submit.
- Una única superficie editable es preferible a cards anidadas.
## 9. Main + aside
El aside es opcional y contiene metadata, resumen o contexto secundario. En desktop acompaña al main y en pantallas menores se apila debajo.
No usar el aside para equilibrar visualmente columnas, trasladar totales o fragmentar arbitrariamente el flujo del formulario. Tablas y line editors mantienen su layout especializado dentro del main.
## 10. Responsive
- Validar móvil, tablet y desktop.
- Evitar widths fijos en campos y controles localizados.
- Mantener acciones utilizables cuando se apilan.
- Confirmar `min-w-0`, truncado intencional y ausencia de overflow horizontal.
- En layouts especializados, comprobar pane sizes y scroll independiente.
## 11. Accesibilidad
- Controles nativos o primitives accesibles; nunca `div` clicables.
- Labels asociadas mediante `htmlFor`/`id`.
- Icon buttons con `aria-label` y tooltips cuando corresponda.
- Activación por teclado en filas y acciones.
- Foco visible, orden DOM lógico y errores anunciables.
- El color no sustituye texto, icono o estado semántico.
## 12. Testing
Usar tests de comportamiento con el runner vigente del workspace y Testing Library cuando existe interacción DOM. No probar detalles internos del controller ni clases visuales incidentales.
Cobertura mínima según patrón:
- listado: CTA, search, filtros, paginación, estados y navegación de fila;
- editor: header, contenido, acciones, dirty/submit y loading/error;
- dialog: título, campos, condicionales, cancelar/enviar y mutation;
- UI pública: contrato observable, accesibilidad y responsabilidades estructurales.
Las traducciones productivas deben probarse sin sustituirlas por un diccionario que oculte claves ausentes.
## 13. Checklist
- [ ] La ruta usa el shell real declarado en `handle.layout`.
- [ ] Existe un único `RdxPageFrame` propietario del scroll.
- [ ] Header editorial o fullscreen elegido por responsabilidad.
- [ ] Width y padding pertenecen a `RdxPageContainer`.
- [ ] El aside es funcional y opcional.
- [ ] No hay viewport o scroll principal manual en la Page.
- [ ] Loading, error, empty y success conservan estructura coherente.
- [ ] Acciones, permisos y navegación permanecen en el consumidor.
- [ ] Responsive, teclado, foco, i18n y light/dark están validados.
- [ ] Implementación, documentación, UI Kit y tests siguen sincronizados.