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

24 KiB
Raw Blame History

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:

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:

Configuración de la empresa activa

Ejemplos potenciales:

/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:

/account
→ configuración personal del usuario

Ejemplos:

idioma
apariencia
preferencias personales
seguridad de cuenta

Por tanto:

/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:

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:

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:

Settings framework ≠ Settings persistence

5. Principio de propiedad

La propiedad se divide en dos niveles.

5.1 Shell de Settings

Responsable de:

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:

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:

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

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:

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:

Facturación
├── VeriFactu               customer-invoices
├── Series documentales     document-series
└── Certificados            módulo propietario correspondiente

Varios módulos pueden contribuir a:

sectionId = "billing"

sin conocerse entre ellos.

El registry se encarga de fusionar esas contribuciones.


9. Contratos conceptuales

9.1 Sección

export interface ModuleSettingsSectionContribution {
  id: string;
  labelKey: string;
  order?: number;
}

9.2 Entrada

export interface ModuleSettingsEntryContribution {
  id: string;
  sectionId: string;

  labelKey: string;
  descriptionKey?: string;

  path: string;

  order?: number;
  permission?: string;
}

9.3 Contribución completa

export interface ModuleSettingsContribution {
  sections?: ModuleSettingsSectionContribution[];
  entries?: ModuleSettingsEntryContribution[];
}

Este contrato debe mantenerse deliberadamente pequeño.

No añadir en V1 salvo necesidad real:

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:

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:

{
  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:

billing + "Facturación"
billing + "Ventas"

Regla:

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:

estable
semántico
independiente del texto visible

Ejemplos:

verifactu
document-series
certificates
company-details

Evitar:

setting1
optionA
config

Si existe riesgo real de colisión, el registry puede usar internamente una identidad compuesta:

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:

{
  id: "verifactu",
  component: VerifactuSettingsPage,
}

Preferir:

{
  id: "verifactu",
  path: "/settings/billing/verifactu",
}

El módulo propietario continúa registrando su propia route.

Motivo

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:

/settings/:section/:setting

Ejemplos:

/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:

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:

ModuleManifest[]

Salida conceptual:

export interface SettingsRegistry {
  sections: SettingsSection[];
}

export interface SettingsSection {
  id: string;
  labelKey: string;
  order: number;
  entries: SettingsEntry[];
}

17. Responsabilidades del registry

Debe:

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:

consultar APIs de negocio
cargar configuración
persistir valores
renderizar formularios
resolver reglas fiscales

18. Registro declarativo, no imperativo

Preferir:

export const manifest = {
  settings: ...,
};

frente a:

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.

manifest metadata
    ≠
page implementation

El routing del módulo puede mantener lazy loading si la infraestructura actual ya lo soporta.


20. Layout

Modelo recomendado:

┌───────────────────────────────────────────────────────────┐
│ 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:

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:

Configuración

El menú principal no debe contener directamente:

VeriFactu
Series
Certificados
Configuración de clientes
...

Estas opciones aparecen dentro de /settings.

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:

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:

company context
headers
query keys
query invalidation

No mediante estado duplicado dentro del framework Settings.


25. Persistencia

Regla fundamental:

Settings framework
≠
Settings persistence

No crear únicamente por este sistema:

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:

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:

Guardar toda la configuración del ERP

Cada entrada mantiene su propia transacción de guardado.

Ejemplos:

/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:

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:

permission?: string;

El shell reutiliza la infraestructura central de autorización para decidir si la entrada está disponible.

Ejemplos conceptuales:

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:

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:

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:

labelKey
descriptionKey

Ejemplo:

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:

order?: number;

Ejemplo:

100 Empresa
200 Facturación
300 Ventas
400 Compras
500 Integraciones

Dentro de Facturación:

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

mismo id
labels incompatibles

Entry ID conflict

misma identidad registrada más de una vez

Path conflict

dos entries incompatibles apuntan a la misma ruta

Missing section

entry apunta a una section inexistente

Los conflictos no deben resolverse silenciosamente.


35. Responsive

Desktop:

navegación lateral + contenido

Viewport estrecho:

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:

<nav>
<a>

Requisitos mínimos:

focus-visible
aria-current="page"
uso completo con teclado
jerarquía correcta de headings
estado activo perceptible sin depender solo del color

Cada configuración debe ser direccionable directamente.

Ejemplo:

/settings/billing/verifactu

Debe funcionar con:

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:

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:

["settings", ...]

para todos los dominios.

Ejemplos:

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:

customer-invoices
└── manifest
      └── settings
            ├── section: billing
            └── entry: verifactu

Ruta objetivo:

/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:

eliminar customer-invoices
→ desaparecen automáticamente sus settings

Settings continúa funcionando

Y también:

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:

customer-invoices
→ importa document-series settings

Correcto:

customer-invoices manifest ─┐
                            ├→ registry
document-series manifest ───┘

43. Anti-patterns prohibidos

Configuración central hardcoded

const SETTINGS = [
  VerifactuSettings,
  DocumentSeriesSettings,
  CertificateSettings,
];

Registro central de componentes de dominio

{
  component: VerifactuSettingsPage,
}

como API principal del registry.

Persistencia genérica indiscriminada

settings
├── key
└── value

para almacenar configuraciones de dominios diferentes.

Switch central

switch (setting.id) {
  case "verifactu":
  case "certificates":
}

Imports cruzados por Settings

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:

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:

manifest actual
module registry
routing composition
sidebar
active company
permissions
i18n
layouts

Fase 1 — Contrato de contribuciones

Añadir únicamente:

module manifest
→ settings metadata

y el registry correspondiente.

Fase 2 — Shell de Settings

Implementar:

/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:

fusiona secciones
ordena entries
detecta IDs duplicados
detecta conflictos
respeta permisos
resultado determinista

Routing

Debe comprobar:

deep-link directo
ruta inexistente
navegación entre entries
refresh

UI

Con Testing Library:

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

/settings
→ configuración de la empresa activa
/account
→ configuración personal del usuario
Una única entrada "Configuración"
en la navegación principal.
Varios módulos pueden contribuir
a una misma sección.
La aplicación base no conoce
los settings específicos de los módulos.
Los módulos declaran sus settings
mediante metadata.
Settings registra navegación,
no implementaciones React.
Cada dominio conserva
su propia persistencia.
Settings no pertenece a rdx-ui.
VeriFactu será el primer consumidor real.

49. Pendientes que requieren auditoría

No deben cerrarse sin inspeccionar el repositorio:

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

App
→ conoce Settings

Settings
→ conoce contribuciones

Contribución
→ conoce metadata

Módulo
→ conoce dominio

Ruta
→ conecta Settings con el módulo

Criterio principal:

Añadir una opción de configuración desde un módulo
NO debe requerir modificar el shell de Settings.