diff --git a/docs/settings/frontend-erp-settings-module-integration-design.md b/docs/settings/frontend-erp-settings-module-integration-design.md index 6643d8e2..47d39c13 100644 --- a/docs/settings/frontend-erp-settings-module-integration-design.md +++ b/docs/settings/frontend-erp-settings-module-integration-design.md @@ -1,191 +1,253 @@ # Frontend ERP – Integración de módulos con Settings -## 1. Propósito +## 1. Estado del documento -Este documento complementa **Frontend ERP – Sistema modular de Settings** y define cómo debe integrarse un módulo funcional con `/settings` sin acoplarse al shell de aplicación ni a otros módulos. +Estado: **DISEÑO V1 AJUSTADO A LA ARQUITECTURA REAL** -Aplica a módulos actuales y futuros que necesiten exponer configuración de la empresa activa. +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. -Ejemplos: +La V1 se ajusta al funcionamiento real del monorepo: ```txt -customer-invoices - → VeriFactu - -document-series - → Series documentales - -identity/company - → Datos de empresa - -módulo de certificados - → Certificados +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 metadata de Settings +→ 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 ``` -El shell únicamente descubre y presenta la opció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. Separación de responsabilidades +## 3. Alcance -### Settings shell - -Conoce: +Este documento aplica a configuraciones de la empresa activa publicadas bajo: ```txt -id de sección -label -descripción -orden -path -permiso +/settings ``` -No conoce: +No aplica a: ```txt -DTO -Entity -Controller -formulario -endpoint -reglas de negocio -``` - -### Módulo - -Conoce: - -```txt -routing concreto -Page/Page Controller -controllers -shared/api -shared/adapters -shared/entities -shared/hooks -validación -persistencia -reglas de negocio +/account +preferencias personales +localStorage de UI +configuración técnica del frontend ``` --- -## 4. Flujo completo +## 4. Arquitectura de módulo existente + +La arquitectura base de los módulos permanece intacta. ```txt -Module manifest - │ - │ settings contribution - ▼ -SettingsRegistry - │ - ▼ -SettingsNavigation - │ - │ usuario navega - ▼ -/settings/
/ - │ - ▼ -Route del módulo - │ - ▼ -Settings Page del módulo - │ - ▼ -Page Controller - │ - ▼ -Controller - │ - ▼ -shared/hooks - │ - ▼ -shared/api +[module]/ +├── manifest.ts +├── [module]-routes.tsx +├── index.ts +├── shared/ +└── casos de uso ``` -La cadena de datos del módulo sigue siendo: +Para una configuración: ```txt -API → DTO → Adapter → Entity → Hook → Controller → Page Controller → UI +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. Metadata mínima del módulo +## 5. Manifest como extension point -Contrato conceptual: +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 ModuleSettingsSectionContribution { - id: string; - labelKey: string; - order?: number; -} - export interface ModuleSettingsEntryContribution { id: string; sectionId: string; - labelKey: string; - descriptionKey?: string; - path: string; - - order?: number; - permission?: 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 customerInvoicesManifest = { - settings: { - sections: [ - { - id: "billing", - labelKey: "settings.sections.billing", - order: 200, - }, - ], +export const CustomerInvoicesModuleManifest: IModuleClient = { + // metadata y routes existentes + settings: { entries: [ { id: "verifactu", sectionId: "billing", - labelKey: "customerInvoices.settings.verifactu.title", - descriptionKey: "customerInvoices.settings.verifactu.description", + labelKey: "settings.verifactu.title", path: "/settings/billing/verifactu", - order: 100, - permission: "customerInvoices.settings.verifactu.manage", }, ], }, }; ``` -El shape definitivo debe adaptarse al manifest real del repositorio. +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. --- -## 6. Regla de routing +## 7. Secciones -Una contribución de Settings no debe registrar un componente React directamente. +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: @@ -196,7 +258,7 @@ Incorrecto: } ``` -Correcto conceptualmente: +Correcto: ```ts { @@ -205,33 +267,44 @@ Correcto conceptualmente: } ``` -Y en el routing propio del módulo: +El mismo módulo debe registrar una route real para ese path mediante su infraestructura actual. -```txt -/settings/billing/verifactu -→ VerifactuSettingsPage -``` - -El shell y el módulo se acoplan únicamente mediante una ruta estable. +La entry y la route deben permanecer alineadas. --- -## 7. Ubicación de código dentro del módulo +## 10. Subtree `/settings/*` -La ubicación concreta debe seguir la estructura real del módulo. +El router actual todavía no dispone de un `SettingsLayout` compartido. -Dirección conceptual recomendada: +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`. + +No crear dentro del módulo: ```txt -[module]/ +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 -├── shared/ -│ ├── api/ -│ ├── adapters/ -│ ├── entities/ -│ └── hooks/ -│ └── settings/ └── [setting]/ ├── controllers/ @@ -240,66 +313,71 @@ Dirección conceptual recomendada: └── index.ts ``` -No introducir esta estructura si el módulo real ya tiene una convención equivalente mejor establecida. +Esto es una recomendación, no una norma estructural. + +Si el módulo ya posee una organización equivalente más consistente, debe reutilizarse. --- -## 8. Page Controller +## 12. Page Controller -Una página de settings debe seguir la misma regla que el resto del ERP. - -Responsabilidades del Page Controller: +La Page Controller mantiene las responsabilidades normales: ```txt -integrar contexto externo +leer contexto de routing si aplica componer controllers -integrar navegación si aplica -exponer API estructurada a la Page +exponer estado y acciones a Page ``` No debe: ```txt -transportar DTO -realizar llamadas API directas -contener lógica visual +consultar DTOs +llamar API directamente +conocer buildSettingsEntries() +conocer SettingsNavigation ``` +Los controllers de dominio siguen sin usar `useParams` ni `useNavigate`. + --- -## 9. Controller +## 13. Controller Responsable de: ```txt estado del caso de uso formulario +builders/Params submit validaciones de flujo side effects -coordinación con hooks de shared +coordinación con shared hooks ``` -No debe: +No debe conocer: ```txt -usar useParams/useNavigate directamente -usar DTO -conocer el SettingsRegistry +IModuleClient +Settings shell +metadata de navegación +router +DTO ``` --- -## 10. Shared +## 14. Shared -Una configuración de módulo reutiliza las reglas generales de `shared/`. +El hecho de estar bajo `/settings` no altera `shared/`. ```txt shared/api → transporte remoto shared/adapters -→ DTO ↔ Entity cuando corresponda +→ DTO → Entity shared/entities → contratos frontend @@ -308,307 +386,404 @@ shared/hooks → React Query ``` -El hecho de que la UI aparezca bajo `/settings` no cambia estas responsabilidades. +No ubicar metadata de Settings en `shared/`. + +La metadata pertenece al manifest del módulo. --- -## 11. Persistencia +## 15. Empresa activa -Cada módulo decide su persistencia. +Todo setting bajo `/settings` opera sobre la empresa activa. -No utilizar por defecto un almacenamiento genérico de Settings. +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 -→ configuración persistida del dominio correspondiente +→ dominio VeriFactu/customer-invoices Series documentales -→ agregado DocumentSeries +→ document-series -Datos de empresa -→ Company/Identity +Datos empresa +→ identity/company ``` -La navegación compartida no implica almacenamiento compartido. - ---- - -## 12. Empresa activa - -Toda configuración integrada en `/settings` pertenece a la empresa activa. - -Un módulo no debe introducir su propio selector de empresa si la aplicación ya dispone de contexto global. - -La query/mutation debe usar la infraestructura existente para resolver la empresa activa. - -Cuando cambie la empresa activa: +No crear: ```txt -los datos visibles deben corresponder a la nueva empresa +GenericSettingsRepository +settings key/value +localStorage de negocio ``` -La implementación exacta depende de cómo estén construidas actualmente las query keys, headers y mecanismos de invalidación. - --- -## 13. Permisos - -La contribución puede declarar: - -```ts -permission?: string; -``` - -El permiso solo controla la disponibilidad de la navegación frontend. - -La operación backend debe seguir autorizándose independientemente. - -Un módulo no debe implementar un segundo sistema de permisos para Settings. - ---- - -## 14. i18n - -La metadata debe usar claves de traducción. - -Ejemplo: - -```ts -labelKey: "customerInvoices.settings.verifactu.title"; -descriptionKey: "customerInvoices.settings.verifactu.description"; -``` - -El módulo mantiene sus traducciones. - -La sección compartida puede usar una key común del shell si el diseño final establece un catálogo global de secciones. - ---- - -## 15. UI - -La página del módulo debe usar el sistema visual Rdx vigente. +## 18. UI El shell proporciona: ```txt -layout -navegación -título general de Configuración +SettingsLayout +SettingsNavigation +título/estructura general ``` El módulo proporciona: ```txt -título específico +título de la configuración contenido formularios acciones -loading/error states +loading/error ``` -Evitar duplicar dentro del módulo el sidebar global de Settings. +No duplicar el sidebar de Settings dentro de la Page del módulo. + +Usar el sistema visual Rdx vigente. --- -## 16. Guardado +## 19. Formularios -Cada setting debe guardar de forma independiente. +Aplican las mismas reglas de fields del resto del ERP. -Ejemplo: +Inputs nativos simples: ```txt -VeriFactu -[Cancelar] [Guardar] +register ``` -No existe un submit global de `/settings`. - -Si la pantalla tiene varios bloques pero forman una única configuración transaccional, el módulo puede resolverlos con un único formulario/controlador. - ---- - -## 17. Loading y errores - -El módulo conserva responsabilidad sobre: +Controles complejos: ```txt -loading inicial -error inicial -refetch error -submit error -validation errors -retry +Controller / useController ``` -El shell no interpreta errores de dominio. +Mantener: -Debe reutilizarse el patrón consolidado de `PageErrorState`, `ErrorAlert` y errores de formulario cuando corresponda. +```txt +Field +FormFieldLabel +FieldDescription +FieldError +``` + +No crear `GenericSettingsField` ni `GenericSettingsForm`. --- -## 18. Unsaved changes +## 20. Guardado -Cada módulo es responsable de avisar o bloquear navegación cuando existan cambios no guardados. - -No crear un estado dirty global dentro del Settings shell. - ---- - -## 19. Deep linking - -Una entrada debe ser accesible directamente por URL. +Cada entry guarda de forma independiente. Ejemplo: ```txt /settings/billing/verifactu +→ formulario propio +→ mutation propia +→ persistencia propia ``` -El módulo no debe depender de que el usuario haya visitado antes `/settings`. +No existe un submit global para `/settings`. --- -## 20. Varias contribuciones a una misma sección +## 21. Loading y errores -Ejemplo: +La Page del módulo mantiene responsabilidad sobre: -### customer-invoices +```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 +├── VeriFactu customer-invoices +└── Series documentales document-series ``` -### document-series +Este segundo consumidor valida que: -```txt -billing -└── Series documentales -``` +- las secciones centrales funcionan; +- las entries son realmente modulares; +- no hay imports cruzados; +- la agregación no depende de un único módulo. -Resultado: - -```txt -Facturación -├── VeriFactu -└── Series documentales -``` - -Los módulos no se importan entre sí. +Solo entonces evaluar `order`, conflictos u otras extensiones. --- -## 21. Caso piloto: VeriFactu - -El primer setting real será VeriFactu. - -Su contribución debe limitarse a metadata: - -```txt -section -entry -path -orden -permiso si existe -``` - -La implementación funcional de VeriFactu debe documentarse en un documento específico posterior. - -Este documento específico deberá explicar: - -```txt -estado/configuración de la empresa -onboarding inicial -valores por defecto de facturación -certificados si pertenecen al mismo dominio -snapshot de IssuedInvoice -API -DTOs -Entities -controllers -UI -``` - -sin redefinir el framework de Settings. - ---- - -## 22. Checklist para añadir un setting nuevo +## 28. Checklist para añadir una entry ### Manifest -- [ ] Declara sección si es necesaria. -- [ ] Declara entry con `id` estable. -- [ ] Usa `labelKey`. -- [ ] Usa `descriptionKey` si aporta valor. -- [ ] Declara `path` estable. -- [ ] Declara `order` cuando importe el orden semántico. -- [ ] Declara `permission` solo si existe permiso real. +- [ ] 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 -- [ ] La route pertenece al módulo. -- [ ] Funciona mediante deep-link. -- [ ] No requiere estado previo del shell. +- [ ] 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 DTO en UI/controller. +- [ ] No hay DTOs en controller/UI. - [ ] No hay routing en controller. -- [ ] No hay dependencia del SettingsRegistry en dominio/shared. - -### UI - -- [ ] Usa sistema visual Rdx. -- [ ] No duplica SettingsNavigation. -- [ ] Soporta teclado y focus-visible. -- [ ] Maneja loading/error/empty correctamente. +- [ ] `shared/` no conoce Settings. +- [ ] El dominio no conoce `IModuleClient`. ### Empresa -- [ ] Usa la empresa activa existente. -- [ ] No introduce un selector paralelo. -- [ ] El cambio de empresa no deja datos stale de la anterior. +- [ ] Usa empresa activa existente. +- [ ] No añade `companyId` a la route. +- [ ] No introduce selector alternativo. +- [ ] Cambiar empresa no conserva datos stale. -### Seguridad +### UI -- [ ] La navegación respeta permisos frontend. -- [ ] El backend valida autorización de forma independiente. +- [ ] 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. --- -## 23. Anti-patterns +## 29. Anti-patterns No hacer: ```txt -importar SettingsPage desde el shell -registrar componentes React como metadata principal -crear un GenericSettingsForm -crear un GenericSettingsRepository -usar localStorage para configuración de negocio -crear otro selector de company -hacer imports entre módulos para agrupar settings -usar un switch central por setting +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 ``` --- -## 24. Regla mental final +## 30. Criterio de validación + +La integración de un módulo es correcta si: ```txt -El módulo declara que su setting existe. -El shell decide cómo descubrirlo y mostrarlo. -El router decide cómo llegar. -El módulo decide cómo funciona. +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. ``` -Criterio de validación: +--- + +## 31. Regla mental final ```txt -Si para añadir una nueva configuración hay que modificar SettingsPage, -SettingsNavigation o un switch central, la integración está mal diseñada. +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. diff --git a/docs/settings/frontend-erp-settings-system-design.md b/docs/settings/frontend-erp-settings-system-design.md index 465e317f..c9996feb 100644 --- a/docs/settings/frontend-erp-settings-system-design.md +++ b/docs/settings/frontend-erp-settings-system-design.md @@ -1,35 +1,53 @@ # Frontend ERP – Sistema modular de Settings -## 1. Propósito +## 1. Estado del documento -Este documento define la arquitectura del sistema de **Configuración (`Settings`)** del ERP. +Estado: **DISEÑO V1 AJUSTADO A LA ARQUITECTURA REAL DEL REPOSITORIO** -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. +Este documento sustituye la versión inicial del diseño de Settings y recoge las correcciones derivadas de la auditoría del monorepo realizada el 11 de agosto de 2026. -Principio rector: +Decisiones ya consolidadas: ```txt -La aplicación conoce el sistema de Settings. -La aplicación NO conoce los settings concretos de cada módulo. +/settings +→ configuración de la empresa activa -Cada módulo declara las opciones de configuración que aporta. +/account +→ configuración personal del usuario ``` -Este diseño debe integrarse con la arquitectura modular existente del frontend, manteniendo la separación entre routing, datos, controllers y UI. +```txt +Settings shell != dominio != persistencia +``` + +```txt +El shell conoce la navegación de Settings. +Los módulos conocen sus páginas, rutas y lógica. +``` + +La arquitectura modular real del frontend ya compone rutas aportadas por módulos mediante `IModuleClient`, pero no dispone todavía de navegación modular, permisos frontend para navegación ni un subtree `/settings/*` compartido. --- -## 2. Alcance +## 2. Propósito -### 2.1 `/settings` +Definir una infraestructura mínima y extensible para la configuración empresarial del ERP. -`/settings` representa exclusivamente: +Objetivo principal: -```txt -Configuración de la empresa activa -``` +> Añadir una nueva opción de configuración desde un módulo no debe requerir modificar la Page ni la navegación interna del shell de Settings. -Ejemplos potenciales: +El sistema debe aprovechar la infraestructura modular existente y no crear un registry paralelo de rutas ni un framework genérico de persistencia. + +--- + +## 3. Alcance + +### 3.1 `/settings` + +`/settings` representa exclusivamente configuración de la **empresa activa**. + +Ejemplos previstos: ```txt /settings/company/details @@ -38,122 +56,121 @@ Ejemplos potenciales: /settings/billing/certificates ``` -Una modificación realizada aquí afecta a la empresa activa y, potencialmente, a todos los usuarios que trabajan con ella. +Una configuración situada bajo `/settings` debe operar sobre la empresa activa resuelta por la infraestructura global de identidad. -### 2.2 `/account` +### 3.2 `/account` -La configuración personal del usuario queda fuera de este sistema: +`/account` queda reservado para configuración personal del usuario. + +Ejemplos futuros: ```txt -/account -→ configuración personal del usuario +/account/profile +/account/preferences +/account/security ``` -Ejemplos: +No forma parte de este diseño y no debe mezclarse con `/settings`. -```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. +No se introduce `scope: "company" | "user"` en V1. --- -## 3. Objetivos +## 4. Arquitectura real de referencia -El sistema debe permitir: +La aplicación compone actualmente los módulos así: -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. +```txt +App +└─ apps/web/src/app.tsx + ├─ QueryClientProvider + ├─ DataSourceProvider + ├─ IdentityAuthSessionProvider + └─ RouterProvider(getAppRouter) + └─ apps/web/src/routes/app-routes.tsx + ├─ modules = apps/web/src/register-modules.tsx + ├─ buildModuleRoutes(modules, "auth") + ├─ buildModuleRoutes(modules, "app-sidebar") + ├─ buildModuleRoutes(modules, "app-fullscreen") + └─ createBrowserRouter(...) + ├─ AuthLayout + ├─ AppFullscreenLayout + └─ AppSidebarLayout +``` + +Piezas reales relevantes: + +```txt +apps/web/src/register-modules.tsx +→ modules: IModuleClient[] + +modules/core/src/web/lib/modules/module-client.interface.ts +→ IModuleClient +→ ModuleRouteObject +→ ModuleRouteHandle + +apps/web/src/components/module-routes.tsx +→ buildModuleRoutes() +→ normalizeRoute() + +apps/web/src/routes/app-routes.tsx +→ router raíz +``` + +V1 de Settings debe extender esta arquitectura, no sustituirla. --- -## 4. No objetivos +## 5. Principios de diseño -El sistema de Settings no debe convertirse en: +### 5.1 El shell no conoce dominio + +`apps/web` puede conocer: ```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 +secciones de Settings +entries publicadas por módulos +paths +labels layout -estado de sección/entrada activa -filtrado por permisos +navegación ``` -### 5.2 Módulo propietario - -Responsable de: +No debe conocer: ```txt -routing de su configuración +DTOs +Entities +formularios +controllers +endpoints +reglas VeriFactu +reglas de Series +reglas de certificados +``` + +### 5.2 Cada módulo conserva ownership + +El módulo propietario de una configuración mantiene: + +```txt +route Page Page Controller Controllers -Entities -Hooks -API +shared/api +shared/adapters +shared/entities +shared/hooks validación persistencia reglas de negocio UI específica ``` -La arquitectura interna de cada setting debe seguir el flujo general del ERP: +El hecho de que una Page aparezca dentro de `/settings` no cambia su arquitectura interna. + +Flujo obligatorio: ```txt API @@ -166,211 +183,176 @@ API → UI ``` -Los controllers no deben conocer routing. La integración con URL/navegación sigue correspondiendo al Page Controller/Page. +### 5.3 Settings no es persistencia + +No crear: + +```txt +GenericSettingsRepository +settings(key, value) +useSettings() +tabla genérica de settings +``` + +Cada dominio conserva su propio modelo de persistencia. + +### 5.4 Settings no es un router alternativo + +La navegación de Settings referencia rutas reales ya registradas en el router modular. + +No registrar componentes React dentro de metadata de Settings. --- -## 6. Arquitectura general +## 6. Arquitectura objetivo V1 ```txt apps/web │ -├── navegación principal -│ │ -│ └── Configuración -│ │ -│ ▼ -│ /settings +├── register-modules.tsx +│ └── IModuleClient[] │ -├── Settings Shell -│ ├── SettingsLayout / SettingsPage -│ ├── SettingsNavigation -│ └── SettingsRegistry +├── routes/app-routes.tsx +│ └── nueva rama /settings/* +│ └── SettingsLayout +│ └── rutas de módulos bajo namespace /settings/... │ -└── módulos registrados - │ - ├──────────────────────────┬─────────────────────────┐ - ▼ ▼ ▼ -customer-invoices document-series identity - │ │ │ - └─ contribution └─ contribution └─ contribution - │ │ │ - └───────────── Settings Registry ──────────────────┘ +└── settings/ + ├── settings-sections.ts + ├── build-settings-entries.ts + ├── settings-layout.tsx + └── settings-navigation.tsx + ▲ + │ + metadata ligera + │ + ┌─────────┴──────────────────┐ + │ │ +customer-invoices document-series +manifest manifest +settings entries settings entries ``` -El shell consume metadatos declarativos, no implementaciones de dominio. +No se introduce un `SettingsRegistry` autónomo con ciclo de vida propio. + +La pieza de agregación debe ser una función/estructura ligera sobre los `IModuleClient[]` ya registrados en `apps/web`. --- -## 7. Contribuciones de módulos +## 7. `IModuleClient` como extension point -Cada módulo puede declarar las opciones de configuración que aporta. +El `manifest.ts` real del módulo utiliza `IModuleClient`. -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: +Actualmente `IModuleClient` contiene, entre otros conceptos: ```txt -Facturación -├── VeriFactu customer-invoices -├── Series documentales document-series -└── Certificados módulo propietario correspondiente +name +version +description? +dependencies? +protected? +layout? +icon? +routes? ``` -Varios módulos pueden contribuir a: +No contiene Settings. -```txt -sectionId = "billing" -``` +V1 puede ampliar el contrato compartido con metadata ligera de Settings. -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 +Dirección conceptual: ```ts export interface ModuleSettingsEntryContribution { id: string; sectionId: string; - labelKey: string; - descriptionKey?: string; - path: string; +} - order?: number; - permission?: string; +export interface IModuleClient { + // contrato existente + settings?: { + entries: ModuleSettingsEntryContribution[]; + }; } ``` -### 9.3 Contribución completa +El shape definitivo debe adaptarse al tipo real sin duplicar contratos existentes. -```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 -``` +No crear un segundo manifest específico para Settings salvo que aparezca una razón técnica durante la implementación. --- -## 10. Identidad de secciones +## 8. Secciones: catálogo central en V1 -Las secciones deben utilizar IDs semánticos y estables. +En V1, las secciones se definen en el shell, no en los módulos. -Ejemplos: +Motivo: -```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. +- la navegación actual es responsabilidad de `apps/web`; +- las secciones son conceptos de información global; +- evita conflictos de namespace i18n; +- evita implementar prematuramente conflict detection; +- permite que varios módulos contribuyan a una misma sección sin conocerse. Ejemplo: ```ts -{ - id: "billing", - labelKey: "settings.sections.billing", - order: 200, +export interface SettingsSectionDefinition { + id: string; + labelKey: string; +} + +export const SETTINGS_SECTIONS: SettingsSectionDefinition[] = [ + { + id: "company", + labelKey: "settings.sections.company", + }, + { + id: "billing", + labelKey: "settings.sections.billing", + }, +]; +``` + +Esto no implica que el shell conozca las entries concretas. + +El shell conoce: + +```txt +billing = Facturación +``` + +pero no: + +```txt +billing contiene VeriFactu +billing contiene Series documentales +``` + +Estas entries proceden de módulos. + +--- + +## 9. Entries modulares + +Contrato mínimo V1: + +```ts +export interface ModuleSettingsEntryContribution { + id: string; + sectionId: string; + labelKey: string; + path: string; } ``` -Si dos módulos declaran `billing`, el registry debe reconocer que representan la misma sección. +Semántica: ---- +### `id` -## 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 -``` +Identificador estable dentro del módulo. Ejemplos: @@ -381,200 +363,243 @@ certificates company-details ``` -Evitar: +### `sectionId` -```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 -``` +Referencia a una sección conocida por el shell. Ejemplos: ```txt -/settings/company/details +company +billing +``` + +### `labelKey` + +Clave i18n del módulo. + +El namespace se resuelve con `manifest.name`, siguiendo el sistema de traducciones existente. + +### `path` + +Ruta absoluta real de la aplicación. + +Ejemplo: + +```txt /settings/billing/verifactu -/settings/billing/document-series -/settings/billing/certificates ``` -La ruta exacta debe verificarse contra el routing real antes de implementarla. +La metadata no registra ni importa la Page. --- -## 15. Ruta raíz `/settings` +## 10. Campos diferidos -La raíz puede: +No forman parte del contrato mínimo obligatorio V1: ```txt -redirigir a la primera configuración disponible +descriptionKey +order +permission +icon +badge +status +feature flags +keywords +children anidados ``` -o presentar un índice/resumen de configuraciones. +### `descriptionKey` -La decisión final es de UX y puede tomarse al implementar el shell. +Se elimina de V1 porque no existe un patrón real de navegación que lo requiera. -No debe contener lógica de dominio. +### `order` ---- +Se difiere hasta comprobar que el orden central/default no sea suficiente. -## 16. Settings Registry +Si se introduce posteriormente, deberá tener semántica explícita y determinista. -El registry es la pieza responsable de combinar las contribuciones de todos los módulos. +### `permission` -Entrada conceptual: +Existe `PermissionCode = string` en el proyecto, pero el shell no dispone todavía de un mecanismo frontend operativo para filtrar navegación o proteger routes por permiso. -```txt -ModuleManifest[] -``` +Por tanto, no se promete enforcement en V1. -Salida conceptual: +Cuando exista infraestructura frontend real, el contrato podrá evolucionar a: ```ts -export interface SettingsRegistry { - sections: SettingsSection[]; -} +permission?: PermissionCode; +``` -export interface SettingsSection { - id: string; - labelKey: string; - order: number; - entries: SettingsEntry[]; +sin crear un sistema paralelo de autorización. + +--- + +## 11. i18n + +El sistema real usa namespaces por módulo mediante: + +```txt +registerTranslations(moduleName, locale, resources) +``` + +Regla V1: + +```txt +sección +→ traducción del shell/apps-web + +entry +→ traducción del módulo propietario +``` + +Ejemplo conceptual: + +```ts +settings: { + entries: [ + { + id: "verifactu", + sectionId: "billing", + labelKey: "settings.verifactu.title", + path: "/settings/billing/verifactu", + }, + ], } ``` +El shell resolverá esa key usando el namespace del `manifest.name`. + +No introducir `namespace + key` explícitos salvo que aparezca una necesidad real. + --- -## 17. Responsabilidades del registry +## 12. Routing -Debe: +### 12.1 Estado actual + +El router ya permite rutas modulares por layout, pero no existe todavía una rama compartida: ```txt -recoger contribuciones -fusionar secciones comunes -validar IDs -detectar conflictos -ordenar secciones -ordenar entradas -filtrar entradas no disponibles por permisos -producir un resultado determinista +/settings/* ``` -No debe: +con `SettingsLayout`. + +Existe `/settings` como placeholder, no como feature implementada. + +### 12.2 Cambio requerido + +V1 debe añadir una rama explícita en el router shell para Settings. + +Objetivo conceptual: ```txt -consultar APIs de negocio -cargar configuración -persistir valores -renderizar formularios -resolver reglas fiscales +AppSidebarLayout +└── /settings + └── SettingsLayout + ├── index + └── rutas de módulos bajo /settings/... ``` ---- +El detalle exacto debe integrarse con `buildModuleRoutes()` y `ModuleRouteObject`. -## 18. Registro declarativo, no imperativo +No crear un router secundario. -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. +### 12.3 Regla de acoplamiento ```txt -manifest metadata - ≠ -page implementation +SettingsNavigation +→ path + +Router modular +→ route correspondiente + +Módulo +→ Page ``` -El routing del módulo puede mantener lazy loading si la infraestructura actual ya lo soporta. +Nunca: + +```txt +SettingsNavigation +→ React component +``` --- -## 20. Layout +## 13. Ruta raíz `/settings` + +Decisión pendiente de UX. + +Opciones aceptables: + +### A. Índice + +Mostrar un resumen de secciones y configuraciones disponibles. + +### B. Redirección + +Redirigir a una entry predeterminada. + +No cerrar esta decisión hasta implementar el shell y validar la UX. + +La elección no debe afectar al contrato de los módulos. + +--- + +## 14. Agregación de entries + +No crear un objeto `SettingsRegistry` con estado propio. + +Usar una transformación determinista sobre los módulos registrados: + +```txt +IModuleClient[] +→ flatMap(settings.entries) +→ validar sectionId conocido +→ agrupar por sectionId +→ resolver labels +→ SettingsNavigation +``` + +La agregación no debe: + +```txt +consultar APIs +mantener estado +crear context global +resolver permisos inexistentes +registrar routes +``` + +Debe ser una transformación pequeña y testeable. + +--- + +## 15. Navegación principal + +La navegación actual está hardcodeada en `apps/web`. + +Existe ya un menú de Configuración con links placeholder. + +V1 debe: + +1. conservar una única entrada principal **Configuración**; +2. eliminar/reemplazar los links placeholder que dupliquen el nuevo sistema; +3. dirigir a `/settings`; +4. construir la navegación interna de Settings a partir de las secciones centrales + entries modulares. + +No crear un segundo sistema general de navegación para toda la aplicación. + +La modularización se limita inicialmente a las entries internas de Settings. + +--- + +## 16. Layout de Settings + +Responsabilidad de `apps/web`. Modelo recomendado: @@ -583,31 +608,38 @@ 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. +Responsabilidades: -El área de contenido pertenece a la ruta del módulo. +```txt +SettingsLayout +→ estructura + Outlet + +SettingsNavigation +→ secciones + links + +route del módulo +→ contenido +``` + +El estado activo debe derivarse de la URL, no de `useState`. --- -## 21. Sistema visual +## 17. Sistema visual -El shell debe reutilizar los componentes estructurales Rdx existentes cuando sean aplicables. +Settings debe reutilizar los componentes Rdx existentes cuando sean adecuados. -Por ejemplo: +Ejemplos: ```txt RdxPageContainer @@ -615,357 +647,217 @@ RdxPageHeader RdxPageStack ``` -La infraestructura dependiente de routing, manifests, permisos o empresa activa no debe vivir en `packages/rdx-ui`. +No mover a `packages/rdx-ui`: -`rdx-ui` puede contener únicamente piezas visuales genéricas y desacopladas. +```txt +routing +SettingsLayout dependiente del router +agregación de manifests +active company +permissions de aplicación +``` + +`rdx-ui` solo puede albergar componentes visuales genéricos y desacoplados. --- -## 22. Navegación principal +## 18. Empresa activa -La aplicación debe exponer una sola entrada: +Fuente real: ```txt -Configuración +IdentityAuthSessionProvider ``` -El menú principal no debe contener directamente: +Persistencia: + +```txt +active-company-storage.ts +→ react-secure-storage +``` + +HTTP: + +```txt +setup-interceptors.ts +→ X-Company-Id +``` + +Backend: + +```txt +require-company-context.middleware.ts +→ valida company/membership +→ req.user.companyId +``` + +Regla consolidada: + +```txt +/settings NO incluye companyId en la URL +``` + +pero esta decisión depende de mantener: + +```txt +X-Company-Id +RequireCompany guard +reset global de React Query al cambiar empresa +``` + +No introducir un segundo selector o contexto de company dentro de Settings. + +--- + +## 19. React Query y cambio de empresa + +Las query keys actuales no incorporan `companyId`. + +La coherencia entre empresas se mantiene globalmente mediante el cambio de empresa: + +```txt +cancelQueries() +removeQueries({ type: "inactive" }) +resetQueries({ type: "active" }) +``` + +Settings debe reutilizar este mecanismo. + +No crear query keys especiales con `companyId` solo para Settings si el resto del ERP no sigue esa estrategia. + +Debe considerarse una invariante del shell: + +> Cambiar la empresa activa debe resetear los datos visibles antes de reutilizar cualquier Page de Settings. + +--- + +## 20. Persistencia + +Regla consolidada: + +```txt +Settings framework != Settings persistence +``` + +Ejemplos: ```txt VeriFactu -Series +→ configuración propia del dominio + +DocumentSeries +→ document-series + +Company +→ identity/company + Certificados -Configuración de clientes -... +→ módulo propietario ``` -Estas opciones aparecen dentro de `/settings`. - -```txt -App navigation -└── Configuración - └── SettingsNavigation - ├── Empresa - ├── Facturación - ├── ... -``` +La coexistencia en una misma UI no implica modelo de datos compartido. --- -## 23. Empresa activa +## 21. Page / Controller / Shared -Todo `/settings` debe operar sobre la empresa activa. +Cada setting conserva las reglas generales del ERP. -No debe introducirse un segundo selector o una segunda fuente de verdad. - -Conceptualmente: +### Page Controller ```txt -active company changes - │ - ▼ -Settings context changes - │ - ▼ -module queries resolve data for new company +integra contexto/routing +compone controllers ``` -La implementación debe reutilizar la infraestructura de empresa activa existente en el ERP. +### Controller + +```txt +gestiona caso de uso/formulario +construye Params +maneja side effects +``` + +No usa directamente: + +```txt +useParams +useNavigate +DTOs +Settings shell +``` + +### shared + +```txt +shared/api +→ transporte + +shared/adapters +→ DTO → Entity + +shared/entities +→ contratos frontend + +shared/hooks +→ React Query +``` + +No conoce routing ni Settings. --- -## 24. Cambio de empresa activa +## 22. Formularios y guardado -Una página de configuración no debe conservar datos pertenecientes a la empresa anterior después de cambiar la empresa activa. +Cada setting mantiene su propio formulario y transacción. -Debe resolverse mediante la infraestructura ya existente de: +No existe: ```txt -company context -headers -query keys -query invalidation +Guardar toda la configuración ``` -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 +→ Guardar configuración VeriFactu ``` -Esto evita mezclar dominios y transacciones. +Una Page de configuración puede usar el footer/editor pattern existente del ERP si corresponde, pero el shell no impone un `GenericSettingsForm`. --- -## 28. Errores +## 23. Loading y errores -El shell gestiona únicamente errores estructurales del sistema Settings. +El shell solo gestiona errores estructurales de routing/render. -Ejemplos: +Cada módulo gestiona: ```txt -ruta inválida -entry inexistente -acceso no permitido al subtree -error de render del subtree +loading inicial +error de query +retry +submit +validation errors +refetch errors ``` -El módulo sigue gestionando errores de carga/guardado de su configuración mediante los patrones generales del ERP. +mediante los patrones vigentes: + +```txt +PageErrorState +ErrorAlert +ErrorBoundary / route error cuando corresponda +``` + +El shell no interpreta `ApiClientError` de dominios concretos. --- -## 29. Permisos +## 24. Deep links -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 -