FactuGES_ERP/docs/settings/frontend-erp-settings-system-design.md
2026-08-11 18:02:16 +02:00

1383 lines
24 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.

# Frontend ERP – Sistema modular de Settings
## 1. Propósito
Este documento define la arquitectura del sistema de **Configuración (`Settings`)** del ERP.
El objetivo es disponer de una única experiencia de configuración para la empresa activa sin convertir la aplicación base en conocedora de las opciones particulares de cada módulo.
Principio rector:
```txt
La aplicación conoce el sistema de Settings.
La aplicación NO conoce los settings concretos de cada módulo.
Cada módulo declara las opciones de configuración que aporta.
```
Este diseño debe integrarse con la arquitectura modular existente del frontend, manteniendo la separación entre routing, datos, controllers y UI.
---
## 2. Alcance
### 2.1 `/settings`
`/settings` representa exclusivamente:
```txt
Configuración de la empresa activa
```
Ejemplos potenciales:
```txt
/settings/company/details
/settings/billing/verifactu
/settings/billing/document-series
/settings/billing/certificates
```
Una modificación realizada aquí afecta a la empresa activa y, potencialmente, a todos los usuarios que trabajan con ella.
### 2.2 `/account`
La configuración personal del usuario queda fuera de este sistema:
```txt
/account
→ configuración personal del usuario
```
Ejemplos:
```txt
idioma
apariencia
preferencias personales
seguridad de cuenta
```
Por tanto:
```txt
/settings ≠ /account
```
No se introduce en esta fase ningún `scope: "company" | "user"`, porque `/settings` tiene un único ámbito semántico: la empresa activa.
---
## 3. Objetivos
El sistema debe permitir:
1. Disponer de una única entrada **Configuración** en la navegación principal.
2. Reunir en `/settings` configuraciones procedentes de múltiples módulos.
3. Permitir que varios módulos aporten opciones a una misma sección.
4. Mantener a cada módulo como propietario de sus páginas, lógica y datos.
5. Añadir una configuración nueva sin modificar el shell de Settings.
6. Soportar navegación directa mediante URL.
7. Aplicar permisos sobre opciones concretas.
8. Mantener una experiencia visual consistente.
9. Evitar un almacenamiento genérico de configuración.
10. Evitar un framework dinámico excesivamente complejo.
---
## 4. No objetivos
El sistema de Settings no debe convertirse en:
```txt
un almacén genérico key/value
un framework de formularios
un sistema de permisos
un router alternativo
un gestor de estado global
un registro genérico de componentes React
```
Tampoco debe decidir cómo persiste cada dominio su configuración.
Ejemplos:
```txt
VeriFactu → persistencia y reglas propias
Series documentales → módulo document-series
Datos de empresa → módulo propietario de company/identity
Certificados → módulo propietario correspondiente
```
Regla:
```txt
Settings framework ≠ Settings persistence
```
---
## 5. Principio de propiedad
La propiedad se divide en dos niveles.
### 5.1 Shell de Settings
Responsable de:
```txt
descubrimiento de configuraciones
agrupación por secciones
ordenación
navegación
layout
estado de sección/entrada activa
filtrado por permisos
```
### 5.2 Módulo propietario
Responsable de:
```txt
routing de su configuración
Page
Page Controller
Controllers
Entities
Hooks
API
validación
persistencia
reglas de negocio
UI específica
```
La arquitectura interna de cada setting debe seguir el flujo general del ERP:
```txt
API
→ DTO
→ Adapter
→ Entity
→ Hook
→ Controller
→ Page Controller
→ UI
```
Los controllers no deben conocer routing. La integración con URL/navegación sigue correspondiendo al Page Controller/Page.
---
## 6. Arquitectura general
```txt
apps/web
│
├── navegación principal
│ │
│ └── Configuración
│ │
│ ▼
│ /settings
│
├── Settings Shell
│ ├── SettingsLayout / SettingsPage
│ ├── SettingsNavigation
│ └── SettingsRegistry
│
└── módulos registrados
│
├──────────────────────────┬─────────────────────────┐
▼ ▼ ▼
customer-invoices document-series identity
│ │ │
└─ contribution └─ contribution └─ contribution
│ │ │
└───────────── Settings Registry ──────────────────┘
```
El shell consume metadatos declarativos, no implementaciones de dominio.
---
## 7. Contribuciones de módulos
Cada módulo puede declarar las opciones de configuración que aporta.
El punto natural es el `manifest.ts` del módulo, siempre que la auditoría del repositorio confirme que este es el contrato correcto para ampliar metadatos de módulo.
Conceptualmente:
```ts
export const customerInvoicesManifest = {
// configuración existente del módulo
settings: {
sections: [],
entries: [],
},
};
```
El contrato exacto debe adaptarse al shape real del manifest existente.
---
## 8. Secciones compartidas entre módulos
Una sección de Settings no pertenece necesariamente a un único módulo.
Ejemplo:
```txt
Facturación
├── VeriFactu customer-invoices
├── Series documentales document-series
└── Certificados módulo propietario correspondiente
```
Varios módulos pueden contribuir a:
```txt
sectionId = "billing"
```
sin conocerse entre ellos.
El registry se encarga de fusionar esas contribuciones.
---
## 9. Contratos conceptuales
### 9.1 Sección
```ts
export interface ModuleSettingsSectionContribution {
id: string;
labelKey: string;
order?: number;
}
```
### 9.2 Entrada
```ts
export interface ModuleSettingsEntryContribution {
id: string;
sectionId: string;
labelKey: string;
descriptionKey?: string;
path: string;
order?: number;
permission?: string;
}
```
### 9.3 Contribución completa
```ts
export interface ModuleSettingsContribution {
sections?: ModuleSettingsSectionContribution[];
entries?: ModuleSettingsEntryContribution[];
}
```
Este contrato debe mantenerse deliberadamente pequeño.
No añadir en V1 salvo necesidad real:
```txt
component
loader
icon renderer
badge provider
status resolver
search keywords
feature flag resolver
nested tree arbitrario
custom layout
```
---
## 10. Identidad de secciones
Las secciones deben utilizar IDs semánticos y estables.
Ejemplos:
```txt
company
billing
sales
purchases
users
integrations
```
No es necesario convertir inicialmente el conjunto en una unión TypeScript cerrada si eso dificulta la extensibilidad modular.
Ejemplo:
```ts
{
id: "billing",
labelKey: "settings.sections.billing",
order: 200,
}
```
Si dos módulos declaran `billing`, el registry debe reconocer que representan la misma sección.
---
## 11. Conflictos de secciones
El mismo `sectionId` no puede representar semánticas diferentes.
Ejemplo inválido:
```txt
billing + "Facturación"
billing + "Ventas"
```
Regla:
```txt
mismo sectionId
→ misma identidad semántica
→ labels compatibles
```
En desarrollo, el registry debe detectar conflictos en lugar de resolverlos silenciosamente.
---
## 12. Identidad de entradas
El `id` identifica una opción dentro del sistema de Settings.
Debe ser:
```txt
estable
semántico
independiente del texto visible
```
Ejemplos:
```txt
verifactu
document-series
certificates
company-details
```
Evitar:
```txt
setting1
optionA
config
```
Si existe riesgo real de colisión, el registry puede usar internamente una identidad compuesta:
```txt
moduleId + entryId
```
sin obligar a exponer esa representación al consumidor.
---
## 13. Routing
### Regla principal
Settings registra destinos de navegación, no componentes React.
Evitar:
```ts
{
id: "verifactu",
component: VerifactuSettingsPage,
}
```
Preferir:
```ts
{
id: "verifactu",
path: "/settings/billing/verifactu",
}
```
El módulo propietario continúa registrando su propia route.
### Motivo
```txt
Settings Registry
→ conoce navegación
Módulo
→ conoce página
```
Esto evita convertir Settings en un segundo router o sistema de carga dinámica de React.
---
## 14. Namespace de rutas
Convención objetivo:
```txt
/settings/:section/:setting
```
Ejemplos:
```txt
/settings/company/details
/settings/billing/verifactu
/settings/billing/document-series
/settings/billing/certificates
```
La ruta exacta debe verificarse contra el routing real antes de implementarla.
---
## 15. Ruta raíz `/settings`
La raíz puede:
```txt
redirigir a la primera configuración disponible
```
o presentar un índice/resumen de configuraciones.
La decisión final es de UX y puede tomarse al implementar el shell.
No debe contener lógica de dominio.
---
## 16. Settings Registry
El registry es la pieza responsable de combinar las contribuciones de todos los módulos.
Entrada conceptual:
```txt
ModuleManifest[]
```
Salida conceptual:
```ts
export interface SettingsRegistry {
sections: SettingsSection[];
}
export interface SettingsSection {
id: string;
labelKey: string;
order: number;
entries: SettingsEntry[];
}
```
---
## 17. Responsabilidades del registry
Debe:
```txt
recoger contribuciones
fusionar secciones comunes
validar IDs
detectar conflictos
ordenar secciones
ordenar entradas
filtrar entradas no disponibles por permisos
producir un resultado determinista
```
No debe:
```txt
consultar APIs de negocio
cargar configuración
persistir valores
renderizar formularios
resolver reglas fiscales
```
---
## 18. Registro declarativo, no imperativo
Preferir:
```ts
export const manifest = {
settings: ...,
};
```
frente a:
```ts
registerSetting(...);
registerSetting(...);
```
Ventajas:
- evita side effects durante imports;
- facilita validación;
- hace visible la superficie pública del módulo;
- permite inspección;
- permite ordenar antes de renderizar;
- encaja con una arquitectura basada en manifests.
---
## 19. Lazy loading
El metadata usado para construir la navegación debe ser ligero.
El shell no debería necesitar importar toda una página para saber que existe una opción.
```txt
manifest metadata
≠
page implementation
```
El routing del módulo puede mantener lazy loading si la infraestructura actual ya lo soporta.
---
## 20. Layout
Modelo recomendado:
```txt
┌───────────────────────────────────────────────────────────┐
│ Configuración │
│ Configura el comportamiento de la empresa activa │
├─────────────────┬─────────────────────────────────────────┤
│ │ │
│ Empresa │ │
│ Datos │ │
│ │ contenido del módulo │
│ Facturación │ │
│ VeriFactu │ │
│ Series │ │
│ Certificados │ │
│ │ │
│ Integraciones │ │
│ │ │
└─────────────────┴─────────────────────────────────────────┘
```
El panel izquierdo pertenece al framework.
El área de contenido pertenece a la ruta del módulo.
---
## 21. Sistema visual
El shell debe reutilizar los componentes estructurales Rdx existentes cuando sean aplicables.
Por ejemplo:
```txt
RdxPageContainer
RdxPageHeader
RdxPageStack
```
La infraestructura dependiente de routing, manifests, permisos o empresa activa no debe vivir en `packages/rdx-ui`.
`rdx-ui` puede contener únicamente piezas visuales genéricas y desacopladas.
---
## 22. Navegación principal
La aplicación debe exponer una sola entrada:
```txt
Configuración
```
El menú principal no debe contener directamente:
```txt
VeriFactu
Series
Certificados
Configuración de clientes
...
```
Estas opciones aparecen dentro de `/settings`.
```txt
App navigation
└── Configuración
└── SettingsNavigation
├── Empresa
├── Facturación
├── ...
```
---
## 23. Empresa activa
Todo `/settings` debe operar sobre la empresa activa.
No debe introducirse un segundo selector o una segunda fuente de verdad.
Conceptualmente:
```txt
active company changes
│
▼
Settings context changes
│
▼
module queries resolve data for new company
```
La implementación debe reutilizar la infraestructura de empresa activa existente en el ERP.
---
## 24. Cambio de empresa activa
Una página de configuración no debe conservar datos pertenecientes a la empresa anterior después de cambiar la empresa activa.
Debe resolverse mediante la infraestructura ya existente de:
```txt
company context
headers
query keys
query invalidation
```
No mediante estado duplicado dentro del framework Settings.
---
## 25. Persistencia
Regla fundamental:
```txt
Settings framework
≠
Settings persistence
```
No crear únicamente por este sistema:
```txt
settings table
settings(key, value)
GenericSettingsRepository
useSettings()
```
Cada dominio conserva su propia persistencia y validación.
---
## 26. Formularios
Cada setting mantiene sus propios formularios.
Ejemplo:
```txt
VerifactuSettingsPage
│
▼
VerifactuSettingsEditor
│
▼
React Hook Form
```
Settings no proporciona un `GenericSettingsForm`.
Los formularios deben seguir las convenciones ya consolidadas del ERP para fields y React Hook Form.
---
## 27. Guardado
No debe existir un botón global:
```txt
Guardar toda la configuración del ERP
```
Cada entrada mantiene su propia transacción de guardado.
Ejemplos:
```txt
/settings/billing/verifactu
→ Guardar VeriFactu
/settings/company/details
→ Guardar datos de empresa
```
Esto evita mezclar dominios y transacciones.
---
## 28. Errores
El shell gestiona únicamente errores estructurales del sistema Settings.
Ejemplos:
```txt
ruta inválida
entry inexistente
acceso no permitido al subtree
error de render del subtree
```
El módulo sigue gestionando errores de carga/guardado de su configuración mediante los patrones generales del ERP.
---
## 29. Permisos
Una entrada puede declarar opcionalmente:
```ts
permission?: string;
```
El shell reutiliza la infraestructura central de autorización para decidir si la entrada está disponible.
Ejemplos conceptuales:
```txt
VeriFactu
→ manage_verifactu_settings
Certificados
→ manage_company_certificates
```
Settings no interpreta el permiso ni implementa autorización propia.
---
## 30. Ocultar vs deshabilitar
Para una opción de Settings sin permiso, la decisión inicial es:
```txt
no mostrarla
```
La navegación representa capacidades administrativas disponibles.
Esto no sustituye la autorización backend.
---
## 31. Seguridad
Ocultar una entrada del menú no constituye autorización.
El backend debe seguir validando:
```txt
usuario
permiso
empresa activa
operación solicitada
```
El frontend únicamente adapta la experiencia de navegación.
---
## 32. i18n
Los manifests deben declarar claves, no textos hardcodeados:
```ts
labelKey
descriptionKey
```
Ejemplo:
```ts
labelKey: "customerInvoices.settings.verifactu.title";
```
El módulo propietario mantiene sus traducciones.
Las secciones compartidas deben utilizar claves semánticamente compatibles.
---
## 33. Ordenación
Cada sección y entrada puede declarar:
```ts
order?: number;
```
Ejemplo:
```txt
100 Empresa
200 Facturación
300 Ventas
400 Compras
500 Integraciones
```
Dentro de Facturación:
```txt
100 VeriFactu
200 Series documentales
300 Certificados
```
No usar orden alfabético como criterio principal porque los labels dependen del idioma.
Si `order` no existe, el resultado debe seguir siendo determinista mediante un valor por defecto y un desempate estable por `id`.
---
## 34. Conflictos
El registry debe detectar al menos en desarrollo:
### Section ID conflict
```txt
mismo id
labels incompatibles
```
### Entry ID conflict
```txt
misma identidad registrada más de una vez
```
### Path conflict
```txt
dos entries incompatibles apuntan a la misma ruta
```
### Missing section
```txt
entry apunta a una section inexistente
```
Los conflictos no deben resolverse silenciosamente.
---
## 35. Responsive
Desktop:
```txt
navegación lateral + contenido
```
Viewport estrecho:
```txt
navegación adaptada
→ selección/listado de settings
→ contenido
```
Debe mantenerse el mismo registry y las mismas rutas.
No crear dos sistemas de Settings diferentes.
---
## 36. Accesibilidad
La navegación debe usar semántica real:
```html
<nav>
<a>
```
Requisitos mínimos:
```txt
focus-visible
aria-current="page"
uso completo con teclado
jerarquía correcta de headings
estado activo perceptible sin depender solo del color
```
---
## 37. Deep links
Cada configuración debe ser direccionable directamente.
Ejemplo:
```txt
/settings/billing/verifactu
```
Debe funcionar con:
```txt
refresh
deep-link
bookmark
navegación interna
```
El estado activo se deriva de la URL, no de un `setSelectedSetting(...)` global.
---
## 38. Cambios sin guardar
El shell no debe inspeccionar ni gestionar globalmente formularios dirty de los módulos.
Cada página propietaria debe usar el mecanismo estándar del ERP para:
```txt
unsaved changes
navigation guard
cancel
```
---
## 39. Queries y caché
Cada módulo conserva sus query keys y estrategia de caché.
El framework no debe crear una query genérica:
```txt
["settings", ...]
```
para todos los dominios.
Ejemplos:
```txt
VeriFactu settings query key
→ customer-invoices
Document Series query keys
→ document-series
```
---
## 40. Caso piloto: VeriFactu
VeriFactu será el primer consumidor real del framework.
Conceptualmente:
```txt
customer-invoices
└── manifest
└── settings
├── section: billing
└── entry: verifactu
```
Ruta objetivo:
```txt
/settings/billing/verifactu
```
La ubicación física de su implementación debe definirse después de auditar el módulo real.
---
## 41. Independencia entre Settings y módulos
Debe cumplirse:
```txt
eliminar customer-invoices
→ desaparecen automáticamente sus settings
Settings continúa funcionando
```
Y también:
```txt
añadir módulo nuevo
→ puede aportar Settings
→ sin modificar SettingsPage
→ sin modificar SettingsNavigation
```
Este es el principal criterio de aceptación del sistema.
---
## 42. Independencia entre módulos
Un módulo no debe importar otro únicamente para registrar sus settings.
Incorrecto:
```txt
customer-invoices
→ importa document-series settings
```
Correcto:
```txt
customer-invoices manifest ─┐
├→ registry
document-series manifest ───┘
```
---
## 43. Anti-patterns prohibidos
### Configuración central hardcoded
```ts
const SETTINGS = [
VerifactuSettings,
DocumentSeriesSettings,
CertificateSettings,
];
```
### Registro central de componentes de dominio
```ts
{
component: VerifactuSettingsPage,
}
```
como API principal del registry.
### Persistencia genérica indiscriminada
```txt
settings
├── key
└── value
```
para almacenar configuraciones de dominios diferentes.
### Switch central
```ts
switch (setting.id) {
case "verifactu":
case "certificates":
}
```
### Imports cruzados por Settings
```txt
apps/web/settings
→ importa páginas concretas de todos los módulos
```
si el sistema modular de routing puede evitarlos.
### Routing o manifests en `rdx-ui`
No permitido.
---
## 44. Extensiones futuras posibles
Solo cuando aparezca necesidad real:
```txt
buscador de configuración
badges de estado
feature flags
keywords
settings favoritos
navegación reciente
secciones anidadas
links externos
estado "requiere atención"
```
No forman parte de V1.
---
## 45. Criterios de aceptación
El diseño se considera correctamente implementado si:
1. Añadir un setting nuevo no modifica el shell.
2. Eliminar un módulo elimina automáticamente sus settings.
3. Varios módulos pueden contribuir a la misma sección.
4. Cada setting tiene URL propia.
5. El módulo conserva control sobre sus datos y UI.
6. Settings no persiste datos de negocio.
7. `rdx-ui` no conoce routing ni manifests.
8. No existen imports cruzados entre módulos por Settings.
9. La empresa activa es el único scope de `/settings`.
10. `/account` permanece independiente.
11. El resultado del registry es determinista.
12. Los conflictos de metadata se detectan en desarrollo.
---
## 46. Fases de implementación recomendadas
### Fase 0 — Auditoría
Investigar sin modificar código:
```txt
manifest actual
module registry
routing composition
sidebar
active company
permissions
i18n
layouts
```
### Fase 1 — Contrato de contribuciones
Añadir únicamente:
```txt
module manifest
→ settings metadata
```
y el registry correspondiente.
### Fase 2 — Shell de Settings
Implementar:
```txt
/settings
SettingsLayout
SettingsNavigation
agrupación por sección
routing
permissions
```
Sin introducir todavía lógica de dominios concretos.
### Fase 3 — Consumidor piloto
Integrar VeriFactu como primera contribución real.
### Fase 4 — Segundo módulo
Integrar una configuración aportada por otro módulo, preferentemente `document-series`, para validar que dos módulos contribuyen a `billing` sin conocerse.
### Fase 5 — Consolidación
Solo después revisar abstracciones comunes, tests y documentación del UI Kit.
---
## 47. Tests mínimos
### Registry
Debe comprobar:
```txt
fusiona secciones
ordena entries
detecta IDs duplicados
detecta conflictos
respeta permisos
resultado determinista
```
### Routing
Debe comprobar:
```txt
deep-link directo
ruta inexistente
navegación entre entries
refresh
```
### UI
Con Testing Library:
```txt
navegación accesible
aria-current
entry visible/no visible por permisos
render del contenido de la route
```
No testear implementación interna de cada módulo desde Settings.
---
## 48. Decisiones consolidadas
```txt
/settings
→ configuración de la empresa activa
```
```txt
/account
→ configuración personal del usuario
```
```txt
Una única entrada "Configuración"
en la navegación principal.
```
```txt
Varios módulos pueden contribuir
a una misma sección.
```
```txt
La aplicación base no conoce
los settings específicos de los módulos.
```
```txt
Los módulos declaran sus settings
mediante metadata.
```
```txt
Settings registra navegación,
no implementaciones React.
```
```txt
Cada dominio conserva
su propia persistencia.
```
```txt
Settings no pertenece a rdx-ui.
```
```txt
VeriFactu será el primer consumidor real.
```
---
## 49. Pendientes que requieren auditoría
No deben cerrarse sin inspeccionar el repositorio:
```txt
shape exacto de manifest.ts
ubicación física del SettingsRegistry
forma de agregar manifests
routing modular actual
sistema real de permisos
fuente concreta de active company
API de traducciones en manifests
estrategia de lazy routes
ruta exacta del SettingsLayout
mecanismo de ErrorBoundary del shell
```
---
## 50. Regla mental final
```txt
App
→ conoce Settings
Settings
→ conoce contribuciones
Contribución
→ conoce metadata
Módulo
→ conoce dominio
Ruta
→ conecta Settings con el módulo
```
Criterio principal:
```txt
Añadir una opción de configuración desde un módulo
NO debe requerir modificar el shell de Settings.
```