From ba1a4243932366b0c20aa3741c6578ae0153684f Mon Sep 17 00:00:00 2001 From: david Date: Tue, 11 Aug 2026 18:15:05 +0200 Subject: [PATCH] Refactor frontend ERP settings documentation to align with updated architecture and design principles. Enhance clarity on modular contributions, navigation structure, and separation of concerns between settings and modules. Introduce new sections for document state, purpose, and architectural goals while removing outdated objectives and anti-patterns. Ensure consistency in terminology and improve overall readability. --- ...-erp-settings-module-integration-design.md | 869 ++++---- .../frontend-erp-settings-system-design.md | 1784 +++++++---------- 2 files changed, 1259 insertions(+), 1394 deletions(-) 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 -