# Frontend ERP – Integración de módulos con Settings ## 1. Estado del documento Estado: **DISEÑO V1 AJUSTADO A LA ARQUITECTURA REAL** Este documento complementa **Frontend ERP – Sistema modular de Settings** y define cómo un módulo frontend publica una opción de configuración empresarial. La V1 se ajusta al funcionamiento real del monorepo: ```txt IModuleClient[] → routing modular existente → shell de aplicación ``` Settings amplía metadata de módulo; no crea un segundo sistema de módulos ni un router paralelo. --- ## 2. Principio operativo ```txt Módulo → declara una entry de Settings en su manifest → registra su propia route → implementa su propia Page → mantiene su propia persistencia Shell → agrega entries → agrupa por sección central → muestra navegación ``` La integración entre shell y módulo se realiza por metadata + ruta. Nunca por import directo de una Page desde `apps/web`. --- ## 3. Alcance Este documento aplica a configuraciones de la empresa activa publicadas bajo: ```txt /settings ``` No aplica a: ```txt /account preferencias personales localStorage de UI configuración técnica del frontend ``` --- ## 4. Arquitectura de módulo existente La arquitectura base de los módulos permanece intacta. ```txt [module]/ ├── manifest.ts ├── [module]-routes.tsx ├── index.ts ├── shared/ └── casos de uso ``` Para una configuración: ```txt Route → Page → Page Controller → Controller → shared/hooks → shared/api ``` Flujo de datos: ```txt API → DTO → Adapter → Entity → Hook → Controller → Page Controller → UI ``` Settings no introduce excepciones a estas reglas. --- ## 5. Manifest como extension point Los manifests reales implementan `IModuleClient`. Actualmente el contrato ya expone metadata del módulo y `routes()`. V1 añade metadata opcional de Settings. Contrato conceptual mínimo: ```ts export interface ModuleSettingsEntryContribution { id: string; sectionId: string; labelKey: string; path: string; } export interface ModuleSettingsContribution { entries: ModuleSettingsEntryContribution[]; } export interface IModuleClient { // contrato existente settings?: ModuleSettingsContribution; } ``` El shape definitivo debe implementarse respetando el fichero real: ```txt modules/core/src/web/lib/modules/module-client.interface.ts ``` No duplicar `ModuleMetadata`, routing ni tipos ya existentes. --- ## 6. Qué declara un módulo Una entry V1 contiene únicamente: ```txt id sectionId labelKey path ``` Ejemplo conceptual: ```ts export const CustomerInvoicesModuleManifest: IModuleClient = { // metadata y routes existentes settings: { entries: [ { id: "verifactu", sectionId: "billing", labelKey: "settings.verifactu.title", path: "/settings/billing/verifactu", }, ], }, }; ``` No añadir en V1: ```txt descriptionKey order permission component icon loader featureFlag badge ``` salvo que una necesidad real aparezca durante el piloto y se documente antes. --- ## 7. Secciones Los módulos **no declaran secciones en V1**. Las secciones son un catálogo del shell. Ejemplo: ```txt company billing ``` Un módulo solo referencia: ```ts sectionId: "billing"; ``` Esto permite: ```txt customer-invoices → billing / VeriFactu document-series → billing / Series documentales ``` sin imports entre módulos y sin duplicar metadata de sección. Si un módulo usa un `sectionId` inexistente, la agregación debe detectarlo en desarrollo/test. --- ## 8. i18n de la entry El proyecto registra traducciones por namespace de módulo. Por tanto: ```txt manifest.name → namespace i18n entry.labelKey → key dentro de ese namespace ``` Ejemplo: ```ts labelKey: "settings.verifactu.title"; ``` No es necesario repetir el namespace en la key si el agregador ya conoce el módulo propietario. Las labels de las secciones pertenecen al shell. --- ## 9. Routing La entry de Settings no contiene un componente React. Incorrecto: ```ts { id: "verifactu", component: VerifactuSettingsPage, } ``` Correcto: ```ts { id: "verifactu", path: "/settings/billing/verifactu", } ``` El mismo módulo debe registrar una route real para ese path mediante su infraestructura actual. La entry y la route deben permanecer alineadas. --- ## 10. Subtree `/settings/*` El router actual todavía no dispone de un `SettingsLayout` compartido. La V1 del shell debe añadir esa capacidad. Desde el punto de vista del módulo, la regla es: > Registrar una route compatible con el namespace `/settings/...` y con el mecanismo que el shell defina para montar esas routes bajo `SettingsLayout`. Implementación V1: ```txt buildSettingsRoute(...) → rama /settings → SettingsLayout → buildModuleRoutes(..., pathPrefix: "settings") ``` Las routes de módulos bajo `/settings/...` se recortan y anidan bajo el shell sin crear un router paralelo. No crear dentro del módulo: ```txt SettingsRouter nested RouterProvider routing alternativo ``` --- ## 11. Ubicación de código No se impone una nueva estructura rígida en V1. El código debe respetar la convención real del módulo. Una posible organización, si encaja con el módulo, es: ```txt [module]/src/web/ ├── manifest.ts ├── [module]-routes.tsx └── settings/ └── [setting]/ ├── controllers/ ├── entities/ ├── ui/ └── index.ts ``` Esto es una recomendación, no una norma estructural. Si el módulo ya posee una organización equivalente más consistente, debe reutilizarse. --- ## 12. Page Controller La Page Controller mantiene las responsabilidades normales: ```txt leer contexto de routing si aplica componer controllers exponer estado y acciones a Page ``` No debe: ```txt consultar DTOs llamar API directamente conocer buildSettingsEntries() conocer SettingsNavigation ``` Los controllers de dominio siguen sin usar `useParams` ni `useNavigate`. --- ## 13. Controller Responsable de: ```txt estado del caso de uso formulario builders/Params submit validaciones de flujo side effects coordinación con shared hooks ``` No debe conocer: ```txt IModuleClient Settings shell metadata de navegación router DTO ``` --- ## 14. Shared El hecho de estar bajo `/settings` no altera `shared/`. ```txt shared/api → transporte remoto shared/adapters → DTO → Entity shared/entities → contratos frontend shared/hooks → React Query ``` No ubicar metadata de Settings en `shared/`. La metadata pertenece al manifest del módulo. --- ## 15. Empresa activa Todo setting bajo `/settings` opera sobre la empresa activa. El módulo debe reutilizar: ```txt IdentityAuthSessionProvider activeCompanyStorage X-Company-Id RequireCompany guard ``` No debe: ```txt añadir companyId a la URL solo por ser Settings crear selector local de company crear un contexto alternativo de company ``` Las operaciones remotas deben seguir el mismo mecanismo de tenant que el resto del ERP. --- ## 16. React Query y cambio de empresa Las query keys actuales del proyecto no incluyen `companyId`. El cambio de empresa resetea globalmente React Query. Un setting debe seguir ese patrón y no introducir una estrategia particular salvo que se rediseñe globalmente la política tenant/cache del ERP. Criterio: ```txt cambiar empresa → nunca mostrar datos de la empresa anterior ``` --- ## 17. Persistencia Cada setting conserva su persistencia de dominio. Ejemplos: ```txt VeriFactu → dominio VeriFactu/customer-invoices Series documentales → document-series Datos empresa → identity/company ``` No crear: ```txt GenericSettingsRepository settings key/value localStorage de negocio ``` --- ## 18. UI El shell proporciona: ```txt SettingsLayout SettingsNavigation título/estructura general ``` El módulo proporciona: ```txt título de la configuración contenido formularios acciones loading/error ``` No duplicar el sidebar de Settings dentro de la Page del módulo. Usar el sistema visual Rdx vigente. --- ## 19. Formularios Aplican las mismas reglas de fields del resto del ERP. Inputs nativos simples: ```txt register ``` Controles complejos: ```txt Controller / useController ``` Mantener: ```txt Field FormFieldLabel FieldDescription FieldError ``` No crear `GenericSettingsField` ni `GenericSettingsForm`. --- ## 20. Guardado Cada entry guarda de forma independiente. Ejemplo: ```txt /settings/billing/verifactu → formulario propio → mutation propia → persistencia propia ``` No existe un submit global para `/settings`. --- ## 21. Loading y errores La Page del módulo mantiene responsabilidad sobre: ```txt loading inicial error inicial retry refetch error submit error validation errors ``` Debe usar los patrones generales existentes, como `PageErrorState` y `ErrorAlert`, cuando corresponda. El shell no interpreta errores de dominio. --- ## 22. Unsaved changes Cada módulo gestiona sus cambios sin guardar. No existe un dirty state global de Settings. --- ## 23. Deep linking La route debe funcionar directamente: ```txt /settings/billing/verifactu ``` sin visitar antes `/settings`. Debe ser compatible con: ```txt refresh bookmark deep link ``` --- ## 24. Permisos En V1 no se exige `permission` en metadata porque el frontend todavía no dispone de un sistema operativo de filtrado de navegación/route guards por permiso. Existe el tipo: ```ts PermissionCode = string; ``` Cuando se implemente infraestructura frontend real, el contrato podrá evolucionar a: ```ts permission?: PermissionCode; ``` Reglas futuras: ```txt frontend → oculta navegación no disponible backend → sigue autorizando siempre ``` No implementar permisos específicos de Settings antes de disponer del mecanismo global. --- ## 25. Orden `order` no forma parte obligatoria de V1. Mientras exista un único piloto, el shell puede usar orden determinista sencillo. Cuando dos o más módulos contribuyan realmente a una misma sección, se revisará si hace falta: ```ts order?: number; ``` No depender del orden accidental de imports. --- ## 26. Caso piloto: VeriFactu `customer-invoices` es el primer consumidor previsto. La integración de Settings de VeriFactu debe limitarse inicialmente a: ```txt manifest entry route bajo /settings/billing/verifactu Page Page Controller controllers/shared del dominio ``` No trasladar al shell conocimiento de: ```txt obligada/exenta renovación certificados AEAT régimen calificación causas de exención ``` Todo eso se diseñará en el documento específico de VeriFactu. --- ## 27. Segundo consumidor recomendado Después de VeriFactu, integrar una entry real de otro módulo, preferentemente `document-series`. Objetivo: ```txt billing ├── VeriFactu customer-invoices └── Series documentales document-series ``` Este segundo consumidor valida que: - las secciones centrales funcionan; - las entries son realmente modulares; - no hay imports cruzados; - la agregación no depende de un único módulo. Solo entonces evaluar `order`, conflictos u otras extensiones. --- ## 28. Checklist para añadir una entry ### Manifest - [ ] Usa `settings.entries`. - [ ] `id` es estable y semántico. - [ ] `sectionId` existe en el catálogo del shell. - [ ] `labelKey` existe en el namespace del módulo. - [ ] `path` es absoluto y estable. - [ ] No registra un componente React. ### Routing - [ ] Existe una route real que corresponde a `path`. - [ ] Funciona por deep-link. - [ ] Se integra con el subtree `/settings/*`. - [ ] No crea un RouterProvider propio. ### Arquitectura - [ ] API → DTO → Adapter → Entity → Hook → Controller → Page Controller → UI. - [ ] No hay DTOs en controller/UI. - [ ] No hay routing en controller. - [ ] `shared/` no conoce Settings. - [ ] El dominio no conoce `IModuleClient`. ### Empresa - [ ] Usa empresa activa existente. - [ ] No añade `companyId` a la route. - [ ] No introduce selector alternativo. - [ ] Cambiar empresa no conserva datos stale. ### UI - [ ] No duplica `SettingsNavigation`. - [ ] Usa Rdx cuando corresponda. - [ ] Soporta teclado y `focus-visible`. - [ ] Maneja loading/error correctamente. ### Persistencia - [ ] Usa persistencia del dominio. - [ ] No crea storage genérico de Settings. - [ ] No usa localStorage para configuración empresarial. --- ## 29. Anti-patterns No hacer: ```txt apps/web importa VerifactuSettingsPage module settings declara component SettingsRouter paralelo SettingsRegistry con estado global secciones declaradas por cada módulo en V1 GenericSettingsForm GenericSettingsRepository SettingsProvider global companyId duplicado en URL selector de empresa dentro del setting imports entre módulos para agrupar entries switch(setting.id) para renderizar Pages ``` --- ## 30. Criterio de validación La integración de un módulo es correcta si: ```txt 1. el shell descubre su entry desde IModuleClient; 2. el shell no importa su Page; 3. la route pertenece al módulo; 4. el módulo no conoce SettingsNavigation; 5. shared no conoce Settings; 6. los datos pertenecen a la empresa activa; 7. el backend conserva la autorización; 8. la persistencia sigue siendo del dominio; 9. eliminar el módulo elimina su entry; 10. añadir otra entry no obliga a modificar SettingsLayout. ``` --- ## 31. Regla mental final ```txt Manifest → declara que la configuración existe Shell → la muestra Router → lleva hasta ella Módulo → la implementa Dominio → la persiste ``` Si para añadir una Page de configuración hay que modificar `SettingsLayout`, importar su componente en `apps/web` o añadir un `switch` central, la integración está mal diseñada.