FactuGES_ERP/docs/settings/frontend-erp-settings-module-integration-design.md
david 45200284ed feat(verifactu): add company Verifactu profile management features
- Implemented API for updating company Verifactu profile.
- Created entities for company Verifactu profile and defaults.
- Developed hooks for querying and updating the company Verifactu profile.
- Added UI components for displaying and editing company Verifactu settings.
- Introduced validation and error handling for profile updates.
- Created utility functions for building form data and handling feedback states.
- Added tests for utility functions and error handling.
2026-08-12 13:31:31 +02:00

14 KiB
Raw Blame History

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 bajo SettingsLayout.

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.
  • 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:

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.