10 KiB
Frontend ERP – Integración de módulos con Settings
1. Propósito
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.
Aplica a módulos actuales y futuros que necesiten exponer configuración de la empresa activa.
Ejemplos:
customer-invoices
→ VeriFactu
document-series
→ Series documentales
identity/company
→ Datos de empresa
módulo de certificados
→ Certificados
2. Principio operativo
Módulo
→ declara metadata de Settings
→ registra su propia route
→ implementa su propia Page
→ mantiene su propia persistencia
El shell únicamente descubre y presenta la opción.
3. Separación de responsabilidades
Settings shell
Conoce:
id de sección
label
descripción
orden
path
permiso
No conoce:
DTO
Entity
Controller
formulario
endpoint
reglas de negocio
Módulo
Conoce:
routing concreto
Page/Page Controller
controllers
shared/api
shared/adapters
shared/entities
shared/hooks
validación
persistencia
reglas de negocio
4. Flujo completo
Module manifest
│
│ settings contribution
▼
SettingsRegistry
│
▼
SettingsNavigation
│
│ usuario navega
▼
/settings/<section>/<entry>
│
▼
Route del módulo
│
▼
Settings Page del módulo
│
▼
Page Controller
│
▼
Controller
│
▼
shared/hooks
│
▼
shared/api
La cadena de datos del módulo sigue siendo:
API → DTO → Adapter → Entity → Hook → Controller → Page Controller → UI
5. Metadata mínima del módulo
Contrato conceptual:
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;
}
Ejemplo conceptual:
export const customerInvoicesManifest = {
settings: {
sections: [
{
id: "billing",
labelKey: "settings.sections.billing",
order: 200,
},
],
entries: [
{
id: "verifactu",
sectionId: "billing",
labelKey: "customerInvoices.settings.verifactu.title",
descriptionKey: "customerInvoices.settings.verifactu.description",
path: "/settings/billing/verifactu",
order: 100,
permission: "customerInvoices.settings.verifactu.manage",
},
],
},
};
El shape definitivo debe adaptarse al manifest real del repositorio.
6. Regla de routing
Una contribución de Settings no debe registrar un componente React directamente.
Incorrecto:
{
id: "verifactu",
component: VerifactuSettingsPage,
}
Correcto conceptualmente:
{
id: "verifactu",
path: "/settings/billing/verifactu",
}
Y en el routing propio del módulo:
/settings/billing/verifactu
→ VerifactuSettingsPage
El shell y el módulo se acoplan únicamente mediante una ruta estable.
7. Ubicación de código dentro del módulo
La ubicación concreta debe seguir la estructura real del módulo.
Dirección conceptual recomendada:
[module]/
├── manifest.ts
├── [module]-routes.tsx
├── shared/
│ ├── api/
│ ├── adapters/
│ ├── entities/
│ └── hooks/
│
└── settings/
└── [setting]/
├── controllers/
├── entities/
├── ui/
└── index.ts
No introducir esta estructura si el módulo real ya tiene una convención equivalente mejor establecida.
8. Page Controller
Una página de settings debe seguir la misma regla que el resto del ERP.
Responsabilidades del Page Controller:
integrar contexto externo
componer controllers
integrar navegación si aplica
exponer API estructurada a la Page
No debe:
transportar DTO
realizar llamadas API directas
contener lógica visual
9. Controller
Responsable de:
estado del caso de uso
formulario
submit
validaciones de flujo
side effects
coordinación con hooks de shared
No debe:
usar useParams/useNavigate directamente
usar DTO
conocer el SettingsRegistry
10. Shared
Una configuración de módulo reutiliza las reglas generales de shared/.
shared/api
→ transporte remoto
shared/adapters
→ DTO ↔ Entity cuando corresponda
shared/entities
→ contratos frontend
shared/hooks
→ React Query
El hecho de que la UI aparezca bajo /settings no cambia estas responsabilidades.
11. Persistencia
Cada módulo decide su persistencia.
No utilizar por defecto un almacenamiento genérico de Settings.
Ejemplos:
VeriFactu
→ configuración persistida del dominio correspondiente
Series documentales
→ agregado DocumentSeries
Datos de empresa
→ Company/Identity
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:
los datos visibles deben corresponder a la nueva empresa
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:
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:
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.
El shell proporciona:
layout
navegación
título general de Configuración
El módulo proporciona:
título específico
contenido
formularios
acciones
loading/error states
Evitar duplicar dentro del módulo el sidebar global de Settings.
16. Guardado
Cada setting debe guardar de forma independiente.
Ejemplo:
VeriFactu
[Cancelar] [Guardar]
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:
loading inicial
error inicial
refetch error
submit error
validation errors
retry
El shell no interpreta errores de dominio.
Debe reutilizarse el patrón consolidado de PageErrorState, ErrorAlert y errores de formulario cuando corresponda.
18. Unsaved changes
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.
Ejemplo:
/settings/billing/verifactu
El módulo no debe depender de que el usuario haya visitado antes /settings.
20. Varias contribuciones a una misma sección
Ejemplo:
customer-invoices
billing
└── VeriFactu
document-series
billing
└── Series documentales
Resultado:
Facturación
├── VeriFactu
└── Series documentales
Los módulos no se importan entre sí.
21. Caso piloto: VeriFactu
El primer setting real será VeriFactu.
Su contribución debe limitarse a metadata:
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:
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
Manifest
- Declara sección si es necesaria.
- Declara entry con
idestable. - Usa
labelKey. - Usa
descriptionKeysi aporta valor. - Declara
pathestable. - Declara
ordercuando importe el orden semántico. - Declara
permissionsolo si existe permiso real.
Routing
- La route pertenece al módulo.
- Funciona mediante deep-link.
- No requiere estado previo del shell.
Arquitectura
- API → DTO → Adapter → Entity → Hook → Controller → Page Controller → UI.
- No hay DTO en UI/controller.
- 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.
Empresa
- Usa la empresa activa existente.
- No introduce un selector paralelo.
- El cambio de empresa no deja datos stale de la anterior.
Seguridad
- La navegación respeta permisos frontend.
- El backend valida autorización de forma independiente.
23. Anti-patterns
No hacer:
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
24. Regla mental final
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.
Criterio de validación:
Si para añadir una nueva configuración hay que modificar SettingsPage,
SettingsNavigation o un switch central, la integración está mal diseñada.