# 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