* 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.
14 KiB
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:
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
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:
/settings
No aplica a:
/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.
[module]/
├── manifest.ts
├── [module]-routes.tsx
├── index.ts
├── shared/
└── casos de uso
Para una configuración:
Route
→ Page
→ Page Controller
→ Controller
→ shared/hooks
→ shared/api
Flujo de datos:
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:
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:
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:
id
sectionId
labelKey
path
Ejemplo conceptual:
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:
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:
company
billing
Un módulo solo referencia:
sectionId: "billing";
Esto permite:
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:
manifest.name
→ namespace i18n
entry.labelKey
→ key dentro de ese namespace
Ejemplo:
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:
{
id: "verifactu",
component: VerifactuSettingsPage,
}
Correcto:
{
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 bajoSettingsLayout.
Implementación V1:
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:
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:
[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:
leer contexto de routing si aplica
componer controllers
exponer estado y acciones a Page
No debe:
consultar DTOs
llamar API directamente
conocer buildSettingsEntries()
conocer SettingsNavigation
Los controllers de dominio siguen sin usar useParams ni useNavigate.
13. Controller
Responsable de:
estado del caso de uso
formulario
builders/Params
submit
validaciones de flujo
side effects
coordinación con shared hooks
No debe conocer:
IModuleClient
Settings shell
metadata de navegación
router
DTO
14. Shared
El hecho de estar bajo /settings no altera shared/.
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:
IdentityAuthSessionProvider
activeCompanyStorage
X-Company-Id
RequireCompany guard
No debe:
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:
cambiar empresa
→ nunca mostrar datos de la empresa anterior
17. Persistencia
Cada setting conserva su persistencia de dominio.
Ejemplos:
VeriFactu
→ dominio VeriFactu/customer-invoices
Series documentales
→ document-series
Datos empresa
→ identity/company
No crear:
GenericSettingsRepository
settings key/value
localStorage de negocio
18. UI
El shell proporciona:
SettingsLayout
SettingsNavigation
título/estructura general
El módulo proporciona:
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:
register
Controles complejos:
Controller / useController
Mantener:
Field
FormFieldLabel
FieldDescription
FieldError
No crear GenericSettingsField ni GenericSettingsForm.
20. Guardado
Cada entry guarda de forma independiente.
Ejemplo:
/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:
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:
/settings/billing/verifactu
sin visitar antes /settings.
Debe ser compatible con:
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:
PermissionCode = string;
Cuando se implemente infraestructura frontend real, el contrato podrá evolucionar a:
permission?: PermissionCode;
Reglas futuras:
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:
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:
manifest entry
route bajo /settings/billing/verifactu
Page
Page Controller
controllers/shared del dominio
No trasladar al shell conocimiento de:
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:
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. ides estable y semántico.sectionIdexiste en el catálogo del shell.labelKeyexiste en el namespace del módulo.pathes 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
companyIda 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:
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:
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
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.