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