- Added services and use cases for managing account details, including changing passwords and updating profiles. - Introduced error handling for duplicate emails and password mismatches. - Created controllers for handling account-related requests in the Express framework. - Updated API routes to include new account management endpoints. - Enhanced the account profile schema to support email updates. - Implemented tests for account management functionalities to ensure reliability and correctness.
1190 lines
22 KiB
Markdown
1190 lines
22 KiB
Markdown
# Frontend ERP – Sistema modular de Settings
|
||
|
||
## 1. Estado del documento
|
||
|
||
Estado: **DISEÑO V1 AJUSTADO A LA ARQUITECTURA REAL DEL REPOSITORIO**
|
||
|
||
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.
|
||
|
||
Decisiones ya consolidadas:
|
||
|
||
```txt
|
||
/settings
|
||
→ configuración de la empresa activa
|
||
|
||
/account
|
||
→ configuración personal del usuario
|
||
```
|
||
|
||
```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. Propósito
|
||
|
||
Definir una infraestructura mínima y extensible para la configuración empresarial del ERP.
|
||
|
||
Objetivo principal:
|
||
|
||
> 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.
|
||
|
||
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
|
||
/settings/billing/verifactu
|
||
/settings/billing/document-series
|
||
/settings/billing/certificates
|
||
```
|
||
|
||
Una configuración situada bajo `/settings` debe operar sobre la empresa activa resuelta por la infraestructura global de identidad.
|
||
|
||
Caso ya implementado:
|
||
|
||
```txt
|
||
/settings/company/details
|
||
-> GET /companies/current
|
||
-> PATCH /companies/current
|
||
```
|
||
|
||
La pantalla no selecciona el target mediante `companyId`.
|
||
|
||
### 3.2 `/account`
|
||
|
||
`/account` queda reservado para configuración personal del usuario.
|
||
|
||
Ejemplos futuros:
|
||
|
||
```txt
|
||
/account/profile
|
||
/account/preferences
|
||
/account/security
|
||
```
|
||
|
||
No forma parte de este diseño y no debe mezclarse con `/settings`.
|
||
|
||
Backend ya preparado actualmente para el subtree de usuario:
|
||
|
||
```txt
|
||
GET /identity/account
|
||
PATCH /identity/account
|
||
POST /identity/account/change-password
|
||
```
|
||
|
||
Regla de acceso:
|
||
|
||
```txt
|
||
/account
|
||
→ requiere usuario autenticado
|
||
→ no requiere empresa activa
|
||
→ no requiere X-Company-Id
|
||
```
|
||
|
||
No se introduce `scope: "company" | "user"` en V1.
|
||
|
||
---
|
||
|
||
## 4. Arquitectura real de referencia
|
||
|
||
La aplicación compone actualmente los módulos así:
|
||
|
||
```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.
|
||
|
||
---
|
||
|
||
## 5. Principios de diseño
|
||
|
||
### 5.1 El shell no conoce dominio
|
||
|
||
`apps/web` puede conocer:
|
||
|
||
```txt
|
||
secciones de Settings
|
||
entries publicadas por módulos
|
||
paths
|
||
labels
|
||
layout
|
||
navegación
|
||
```
|
||
|
||
No debe conocer:
|
||
|
||
```txt
|
||
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
|
||
shared/api
|
||
shared/adapters
|
||
shared/entities
|
||
shared/hooks
|
||
validación
|
||
persistencia
|
||
reglas de negocio
|
||
UI específica
|
||
```
|
||
|
||
En `company/details`, los campos editables actuales son:
|
||
|
||
```txt
|
||
legal_name
|
||
trade_name
|
||
tin
|
||
email
|
||
phone
|
||
website
|
||
default_payment_term_id
|
||
```
|
||
|
||
La UI actual de esta pantalla se organiza en:
|
||
|
||
```txt
|
||
Datos generales
|
||
→ legal_name
|
||
→ trade_name
|
||
→ tin
|
||
|
||
Datos de contacto
|
||
→ email
|
||
→ phone
|
||
→ website
|
||
|
||
Condiciones comerciales
|
||
→ default_payment_term_id
|
||
```
|
||
|
||
Semántica de guardado esperada para esta pantalla:
|
||
|
||
```txt
|
||
PATCH parcial
|
||
→ solo viajan los campos dirty
|
||
|
||
campo omitido
|
||
→ no modificar
|
||
|
||
campo nullable enviado como null
|
||
→ limpiar valor
|
||
```
|
||
|
||
La pantalla sigue operando exclusivamente contra:
|
||
|
||
```txt
|
||
GET /companies/current
|
||
PATCH /companies/current
|
||
```
|
||
|
||
sin usar `companyId` como selector del target.
|
||
|
||
Tras un guardado correcto, la respuesta del servidor debe seguir siendo la fuente única de verdad:
|
||
|
||
```txt
|
||
response
|
||
→ set/reset del formulario
|
||
→ sincronización de active company visible
|
||
```
|
||
|
||
Política de errores consolidada para esta pantalla:
|
||
|
||
```txt
|
||
error inicial sin datos requeridos
|
||
→ PageErrorState
|
||
|
||
error de refetch con datos previos
|
||
→ mantener contenido
|
||
→ ErrorAlert inline
|
||
|
||
cancelled
|
||
→ no elevar a pantalla bloqueante
|
||
|
||
422 de backend con path mapeable
|
||
→ error de campo en RHF
|
||
|
||
error general de mutation
|
||
→ feedback local
|
||
```
|
||
|
||
Fuera de alcance del formulario actual:
|
||
|
||
```txt
|
||
slug
|
||
status
|
||
fiscalidad avanzada
|
||
VeriFactu
|
||
document series
|
||
payment methods
|
||
branding
|
||
bank accounts
|
||
defaultPaymentMethodId
|
||
```
|
||
|
||
El hecho de que una Page aparezca dentro de `/settings` no cambia su arquitectura interna.
|
||
|
||
Flujo obligatorio:
|
||
|
||
```txt
|
||
API
|
||
→ DTO
|
||
→ Adapter
|
||
→ Entity
|
||
→ Hook
|
||
→ Controller
|
||
→ Page Controller
|
||
→ UI
|
||
```
|
||
|
||
### 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 objetivo V1
|
||
|
||
```txt
|
||
apps/web
|
||
│
|
||
├── register-modules.tsx
|
||
│ └── IModuleClient[]
|
||
│
|
||
├── routes/app-routes.tsx
|
||
│ └── nueva rama /settings/*
|
||
│ └── SettingsLayout
|
||
│ └── rutas de módulos bajo namespace /settings/...
|
||
│
|
||
└── settings/
|
||
├── settings-section-catalog.ts
|
||
├── build-settings-navigation.ts
|
||
├── build-settings-routes.tsx
|
||
├── settings-layout.tsx
|
||
├── settings-navigation.tsx
|
||
└── settings-index-page.tsx
|
||
▲
|
||
│
|
||
metadata ligera
|
||
│
|
||
┌─────────┴──────────────────┐
|
||
│ │
|
||
customer-invoices document-series
|
||
manifest manifest
|
||
settings entries settings entries
|
||
```
|
||
|
||
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. `IModuleClient` como extension point
|
||
|
||
El `manifest.ts` real del módulo utiliza `IModuleClient`.
|
||
|
||
Actualmente `IModuleClient` contiene, entre otros conceptos:
|
||
|
||
```txt
|
||
name
|
||
version
|
||
description?
|
||
dependencies?
|
||
protected?
|
||
layout?
|
||
icon?
|
||
routes?
|
||
```
|
||
|
||
No contiene Settings.
|
||
|
||
V1 puede ampliar el contrato compartido con metadata ligera de Settings.
|
||
|
||
Dirección conceptual:
|
||
|
||
```ts
|
||
export interface ModuleSettingsEntryContribution {
|
||
id: string;
|
||
sectionId: string;
|
||
labelKey: string;
|
||
path: string;
|
||
}
|
||
|
||
export interface ModuleSettingsContribution {
|
||
entries: ModuleSettingsEntryContribution[];
|
||
}
|
||
|
||
export interface IModuleClient {
|
||
// contrato existente
|
||
settings?: ModuleSettingsContribution;
|
||
}
|
||
```
|
||
|
||
El shape definitivo debe adaptarse al tipo real sin duplicar contratos existentes.
|
||
|
||
No crear un segundo manifest específico para Settings salvo que aparezca una razón técnica durante la implementación.
|
||
|
||
---
|
||
|
||
## 8. Secciones: catálogo central en V1
|
||
|
||
En V1, las secciones se definen en el shell, no en los módulos.
|
||
|
||
Motivo:
|
||
|
||
- 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
|
||
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;
|
||
}
|
||
```
|
||
|
||
Semántica:
|
||
|
||
### `id`
|
||
|
||
Identificador estable dentro del módulo.
|
||
|
||
Ejemplos:
|
||
|
||
```txt
|
||
verifactu
|
||
document-series
|
||
certificates
|
||
company-details
|
||
```
|
||
|
||
### `sectionId`
|
||
|
||
Referencia a una sección conocida por el shell.
|
||
|
||
Ejemplos:
|
||
|
||
```txt
|
||
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
|
||
```
|
||
|
||
La metadata no registra ni importa la Page.
|
||
|
||
---
|
||
|
||
## 10. Campos diferidos
|
||
|
||
No forman parte del contrato mínimo obligatorio V1:
|
||
|
||
```txt
|
||
descriptionKey
|
||
order
|
||
permission
|
||
icon
|
||
badge
|
||
status
|
||
feature flags
|
||
keywords
|
||
children anidados
|
||
```
|
||
|
||
### `descriptionKey`
|
||
|
||
Se elimina de V1 porque no existe un patrón real de navegación que lo requiera.
|
||
|
||
### `order`
|
||
|
||
Se difiere hasta comprobar que el orden central/default no sea suficiente.
|
||
|
||
Si se introduce posteriormente, deberá tener semántica explícita y determinista.
|
||
|
||
### `permission`
|
||
|
||
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.
|
||
|
||
Por tanto, no se promete enforcement en V1.
|
||
|
||
Cuando exista infraestructura frontend real, el contrato podrá evolucionar a:
|
||
|
||
```ts
|
||
permission?: PermissionCode;
|
||
```
|
||
|
||
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.
|
||
|
||
---
|
||
|
||
## 12. Routing
|
||
|
||
### 12.1 Estado actual
|
||
|
||
El router ya permite rutas modulares por layout, pero no existe todavía una rama compartida:
|
||
|
||
```txt
|
||
/settings/*
|
||
```
|
||
|
||
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
|
||
AppSidebarLayout
|
||
└── /settings
|
||
└── SettingsLayout
|
||
├── index
|
||
└── rutas de módulos bajo /settings/...
|
||
```
|
||
|
||
El detalle exacto debe integrarse con `buildModuleRoutes()` y `ModuleRouteObject`.
|
||
|
||
No crear un router secundario.
|
||
|
||
### 12.3 Regla de acoplamiento
|
||
|
||
```txt
|
||
SettingsNavigation
|
||
→ path
|
||
|
||
Router modular
|
||
→ route correspondiente
|
||
|
||
Módulo
|
||
→ Page
|
||
```
|
||
|
||
Nunca:
|
||
|
||
```txt
|
||
SettingsNavigation
|
||
→ React component
|
||
```
|
||
|
||
---
|
||
|
||
## 13. Ruta raíz `/settings`
|
||
|
||
Decisión cerrada en V1:
|
||
|
||
```txt
|
||
si existe al menos una entry
|
||
→ redirección determinista a la primera entry agregada
|
||
|
||
si no existe ninguna entry
|
||
→ estado vacío útil del shell
|
||
```
|
||
|
||
La elección sigue sin 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:
|
||
|
||
```txt
|
||
┌───────────────────────────────────────────────────────────┐
|
||
│ Configuración │
|
||
│ Configura el comportamiento de la empresa activa │
|
||
├─────────────────┬─────────────────────────────────────────┤
|
||
│ Empresa │ │
|
||
│ ... │ │
|
||
│ │ <Outlet /> │
|
||
│ Facturación │ │
|
||
│ VeriFactu │ │
|
||
│ Series │ │
|
||
│ │ │
|
||
└─────────────────┴─────────────────────────────────────────┘
|
||
```
|
||
|
||
Responsabilidades:
|
||
|
||
```txt
|
||
SettingsLayout
|
||
→ estructura + Outlet
|
||
|
||
SettingsNavigation
|
||
→ secciones + links
|
||
|
||
route del módulo
|
||
→ contenido
|
||
```
|
||
|
||
El estado activo debe derivarse de la URL, no de `useState`.
|
||
|
||
---
|
||
|
||
## 17. Sistema visual
|
||
|
||
Settings debe reutilizar los componentes Rdx existentes cuando sean adecuados.
|
||
|
||
Ejemplos:
|
||
|
||
```txt
|
||
RdxPageContainer
|
||
RdxPageHeader
|
||
RdxPageStack
|
||
```
|
||
|
||
No mover a `packages/rdx-ui`:
|
||
|
||
```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.
|
||
|
||
---
|
||
|
||
## 18. Empresa activa
|
||
|
||
Fuente real:
|
||
|
||
```txt
|
||
IdentityAuthSessionProvider
|
||
```
|
||
|
||
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
|
||
→ configuración propia del dominio
|
||
|
||
DocumentSeries
|
||
→ document-series
|
||
|
||
Company
|
||
→ identity/company
|
||
|
||
Certificados
|
||
→ módulo propietario
|
||
```
|
||
|
||
La coexistencia en una misma UI no implica modelo de datos compartido.
|
||
|
||
---
|
||
|
||
## 21. Page / Controller / Shared
|
||
|
||
Cada setting conserva las reglas generales del ERP.
|
||
|
||
### Page Controller
|
||
|
||
```txt
|
||
integra contexto/routing
|
||
compone controllers
|
||
```
|
||
|
||
### 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.
|
||
|
||
---
|
||
|
||
## 22. Formularios y guardado
|
||
|
||
Cada setting mantiene su propio formulario y transacción.
|
||
|
||
No existe:
|
||
|
||
```txt
|
||
Guardar toda la configuración
|
||
```
|
||
|
||
Ejemplo:
|
||
|
||
```txt
|
||
/settings/billing/verifactu
|
||
→ Guardar configuración VeriFactu
|
||
```
|
||
|
||
Una Page de configuración puede usar el footer/editor pattern existente del ERP si corresponde, pero el shell no impone un `GenericSettingsForm`.
|
||
|
||
---
|
||
|
||
## 23. Loading y errores
|
||
|
||
El shell solo gestiona errores estructurales de routing/render.
|
||
|
||
Cada módulo gestiona:
|
||
|
||
```txt
|
||
loading inicial
|
||
error de query
|
||
retry
|
||
submit
|
||
validation errors
|
||
refetch errors
|
||
```
|
||
|
||
mediante los patrones vigentes:
|
||
|
||
```txt
|
||
PageErrorState
|
||
ErrorAlert
|
||
ErrorBoundary / route error cuando corresponda
|
||
```
|
||
|
||
El shell no interpreta `ApiClientError` de dominios concretos.
|
||
|
||
---
|
||
|
||
## 24. Deep links
|
||
|
||
Cada entry debe ser una ruta real y refrescable:
|
||
|
||
```txt
|
||
/settings/billing/verifactu
|
||
```
|
||
|
||
Debe funcionar con:
|
||
|
||
```txt
|
||
refresh
|
||
bookmark
|
||
deep link
|
||
navegación interna
|
||
```
|
||
|
||
No depender de estado previo del shell.
|
||
|
||
---
|
||
|
||
## 25. Unsaved changes
|
||
|
||
Cada módulo es responsable de su política de cambios sin guardar.
|
||
|
||
No crear un `dirty` global dentro del Settings shell.
|
||
|
||
---
|
||
|
||
## 26. Accesibilidad
|
||
|
||
La navegación debe usar semántica real:
|
||
|
||
```txt
|
||
<nav>
|
||
<a>/<Link>
|
||
aria-current="page"
|
||
focus-visible
|
||
jerarquía correcta de headings
|
||
```
|
||
|
||
No utilizar `div` clicables.
|
||
|
||
En responsive debe conservarse el mismo modelo de rutas y metadata.
|
||
|
||
---
|
||
|
||
## 27. Seguridad
|
||
|
||
La navegación frontend no es una barrera de seguridad.
|
||
|
||
Aunque en el futuro una entry declare `permission`, el backend debe validar siempre autorización y company context.
|
||
|
||
No duplicar autorización backend dentro de Settings.
|
||
|
||
---
|
||
|
||
## 28. Testing V1
|
||
|
||
### Agregador
|
||
|
||
Probar:
|
||
|
||
```txt
|
||
recoge entries de varios módulos
|
||
agrupa por sectionId
|
||
ignora módulos sin settings
|
||
rechaza sectionId desconocido
|
||
resultado determinista
|
||
```
|
||
|
||
No implementar todavía conflict detection compleja.
|
||
|
||
### Routing
|
||
|
||
Probar:
|
||
|
||
```txt
|
||
/settings existe
|
||
deep link a una route modular
|
||
refresh
|
||
ruta inexistente bajo settings
|
||
```
|
||
|
||
### UI
|
||
|
||
Testing Library:
|
||
|
||
```txt
|
||
nav semántica
|
||
aria-current
|
||
navegación entre entries
|
||
render de Outlet
|
||
```
|
||
|
||
---
|
||
|
||
## 29. Caso piloto: VeriFactu
|
||
|
||
`customer-invoices` es un primer consumidor viable.
|
||
|
||
El módulo ya dispone de:
|
||
|
||
```txt
|
||
manifest
|
||
routing modular
|
||
lazy routes
|
||
i18n por namespace
|
||
public exports
|
||
```
|
||
|
||
El trabajo previo necesario está en el shell:
|
||
|
||
```txt
|
||
rama /settings/*
|
||
SettingsLayout
|
||
agregación de entries
|
||
navegación interna
|
||
```
|
||
|
||
La implementación funcional de VeriFactu se documentará por separado.
|
||
|
||
Settings no debe incorporar conocimiento de:
|
||
|
||
```txt
|
||
obligada/exenta
|
||
certificados
|
||
AEAT
|
||
régimen
|
||
calificación de operación
|
||
causa de exención
|
||
```
|
||
|
||
---
|
||
|
||
## 30. No objetivos V1
|
||
|
||
No implementar todavía:
|
||
|
||
```txt
|
||
settings sections dinámicas por módulo
|
||
registro modular de navegación global
|
||
permission filtering real
|
||
order configurable por módulo
|
||
descriptionKey
|
||
search de settings
|
||
badges
|
||
feature flags
|
||
favoritos
|
||
recent settings
|
||
nested sections
|
||
GenericSettingsRepository
|
||
SettingsProvider global
|
||
SettingsRegistry con estado
|
||
```
|
||
|
||
---
|
||
|
||
## 31. Criterios de aceptación
|
||
|
||
La V1 es correcta si:
|
||
|
||
1. `/settings` representa únicamente la empresa activa.
|
||
2. `/account` permanece separado.
|
||
3. Existe una única entrada Configuración en la navegación principal.
|
||
4. Las secciones son definidas por el shell.
|
||
5. Varios módulos pueden publicar entries en la misma sección.
|
||
6. Las entries se descubren desde `IModuleClient[]`.
|
||
7. El shell no importa Pages de dominio.
|
||
8. Cada entry apunta a una route real del módulo.
|
||
9. El router sigue siendo la única fuente de verdad de navegación.
|
||
10. No se introduce persistencia genérica.
|
||
11. No se crea un segundo contexto de company.
|
||
12. `rdx-ui` no conoce routing ni manifests.
|
||
13. Añadir una nueva entry no requiere modificar `SettingsLayout` ni `SettingsNavigation`.
|
||
|
||
---
|
||
|
||
## 32. Decisiones abiertas
|
||
|
||
Pendientes de cerrar durante la implementación del shell:
|
||
|
||
1. Si `order` resulta necesario en el primer segundo consumidor.
|
||
2. Cuándo introducir `permission?: PermissionCode`.
|
||
3. Si en el futuro se necesita namespace i18n explícito.
|
||
|
||
Estas decisiones no deben bloquear la V1 mínima.
|
||
|
||
---
|
||
|
||
## 33. Regla mental final
|
||
|
||
```txt
|
||
App shell
|
||
→ define secciones y layout
|
||
|
||
IModuleClient
|
||
→ declara entries
|
||
|
||
Router
|
||
→ resuelve paths
|
||
|
||
Módulo
|
||
→ implementa dominio
|
||
|
||
Settings
|
||
→ compone navegación, no negocio
|
||
```
|
||
|
||
Criterio principal:
|
||
|
||
> Añadir una opción de configuración desde un módulo no debe requerir modificar `SettingsLayout`, `SettingsNavigation` ni un switch central de Pages.
|