156 lines
6.8 KiB
Markdown
156 lines
6.8 KiB
Markdown
# Arquitectura frontend
|
|
|
|
> **Propósito:** explicar cómo está construido el frontend. Este documento, junto con [UI system](./ui-system.md) y [UI patterns](./ui-patterns.md), forma la fuente de verdad (SSOT) de frontend/UI.
|
|
|
|
## 1. Stack
|
|
|
|
El frontend usa React 19, TypeScript estricto, React Router, TanStack Query, React Hook Form, Axios, Tailwind CSS v4, Shadcn/UI y `@repo/rdx-ui`.
|
|
|
|
## 2. Arquitectura por capas
|
|
|
|
El flujo habitual de una feature es:
|
|
|
|
```text
|
|
API → DTO → adapter → Entity → query hook → controller → page controller → Page → UI
|
|
```
|
|
|
|
- `shared/api`: transporte y contratos HTTP.
|
|
- `shared/adapters`: transforma DTOs en entidades de frontend.
|
|
- `shared/entities`: modelos consumidos por la aplicación.
|
|
- `shared/hooks`: queries, mutations y query keys de TanStack Query.
|
|
- `controllers`: orquestación funcional, formularios, navegación y efectos.
|
|
- `page controllers`: componen el estado completo de una pantalla.
|
|
- `Page`: conecta controller y componentes; debe tener poco estado propio.
|
|
- `ui`: renderiza props preparadas y emite eventos.
|
|
|
|
La UI no debe contener reglas de dominio, fetch manual ni traducciones entre DTO y entidad. Los controllers no deben convertirse en servicios de dominio.
|
|
|
|
## 3. Módulos frontend
|
|
|
|
Cada módulo publica sus rutas y su cliente desde `modules/<module>/src/web`. Las features se organizan por caso de uso (`list`, `create`, `update`, `detail`) y comparten transporte, entidades y hooks mediante `shared` cuando existe reutilización real.
|
|
|
|
Las dependencias deben apuntar hacia contratos compartidos, no hacia páginas concretas. Un componente de dominio puede envolver una foundation visual si aporta defaults o vocabulario propios, pero no debe recrear el layout de página.
|
|
|
|
## 4. Routing y layouts
|
|
|
|
Las rutas declaran su shell en `handle.layout`:
|
|
|
|
- `auth`: login y recuperación, bajo `AuthLayout`.
|
|
- `app-sidebar`: dashboard, listados, settings y navegación general.
|
|
- `app-fullscreen`: tareas focalizadas de create/update/detail y Account cuando la ruta así lo declara.
|
|
|
|
`apps/web/src/routes/app-routes.tsx` agrupa las rutas por ese valor y las monta bajo el layout correspondiente. `protected` y `requireCompany` expresan guards independientes del shell. La elección debe seguir las rutas productivas del área; no se infiere únicamente por el nombre de la pantalla.
|
|
|
|
## 5. Shells de aplicación
|
|
|
|
```text
|
|
Application shell
|
|
├── AppSidebarLayout
|
|
│ ├── AppSidebar
|
|
│ ├── AppTopbar
|
|
│ └── AppMain
|
|
├── AppFullscreenLayout
|
|
│ └── AppMain
|
|
└── AuthLayout
|
|
```
|
|
|
|
- `AppSidebarLayout` proporciona navegación y topbar.
|
|
- `AppFullscreenLayout` ocupa el viewport para una tarea focalizada.
|
|
- `AuthLayout` centra las pantallas públicas de identidad.
|
|
- `AppMain` entrega el espacio disponible. No es propietario del scroll de una página.
|
|
|
|
Dentro de `AppMain`, una pantalla usa `RdxPageFrame`; el sistema visual y sus reglas se documentan en [UI system](./ui-system.md).
|
|
|
|
```text
|
|
AppMain
|
|
└── RdxPageFrame
|
|
├── header
|
|
├── content
|
|
│ └── RdxPageContainer
|
|
│ └── RdxPageContent
|
|
└── footer opcional
|
|
```
|
|
|
|
```text
|
|
RdxPageFrame
|
|
→ estructura y scroll
|
|
|
|
RdxPageContainer
|
|
→ ancho y padding
|
|
|
|
RdxPageContent
|
|
→ composición de los hijos principales
|
|
```
|
|
|
|
Regla: Las Pages no deben reconstruir estas responsabilidades con wrappers HTML locales. En Create/Update, la composición canónica es RdxPageFrame → RdxPageContainer → RdxPageContent: los errores inline y el editor son hijos directos de RdxPageContent; los dialogs son hermanos del frame; RdxPanel se reserva para secciones semánticas reales.
|
|
|
|
## 6. Identity y sesión
|
|
|
|
El runtime actual se compone en `apps/web/src/app.tsx`:
|
|
|
|
```text
|
|
DataSourceProvider
|
|
└── IdentityAuthSessionProvider
|
|
└── router
|
|
```
|
|
|
|
El login usa `POST /identity/auth/login`. Tras autenticar:
|
|
|
|
1. persiste `accessToken` y `refreshToken`;
|
|
2. elimina cualquier empresa activa anterior;
|
|
3. obtiene la sesión con `GET /identity/auth/session`;
|
|
4. carga `GET /companies/available`;
|
|
5. resuelve la empresa activa y el destino autenticado.
|
|
|
|
Al restaurar una sesión, el provider valida el token, hidrata identidad y empresas y limpia ambos estados si la restauración falla. El frontend nuevo usa `@erp/identity/client`; no debe introducir dependencias nuevas sobre el runtime legacy de auth.
|
|
|
|
## 7. Selección de empresa
|
|
|
|
- Sin empresas: navega a `/no-companies`; no existe `activeCompanyId` y el logout permanece disponible.
|
|
- Una empresa: se selecciona automáticamente.
|
|
- Varias empresas: navega a `/company-selection` hasta que el usuario elige.
|
|
- Empresa persistida no disponible: vuelve a aplicar las tres reglas anteriores.
|
|
|
|
El switcher autenticado consume `availableCompanies`, `activeCompany` y `changeActiveCompany()` desde `useIdentityAuthSession()`. No obtiene tokens ni repite la consulta directamente.
|
|
|
|
## 8. Contexto tenant y `X-Company-Id`
|
|
|
|
Las requests tenant-scoped incluyen `X-Company-Id` cuando existe una empresa activa. No lo incluyen:
|
|
|
|
```text
|
|
POST /identity/auth/login
|
|
POST /identity/auth/refresh
|
|
POST /identity/auth/logout
|
|
GET /identity/auth/session
|
|
GET /companies/available
|
|
```
|
|
|
|
Los guards de compañía resuelven la navegación; los componentes visuales no deben inspeccionar headers HTTP ni decidir tenancy.
|
|
|
|
## 9. Refresh de Axios
|
|
|
|
Ante un `401` autenticado, el interceptor:
|
|
|
|
1. inicia `POST /identity/auth/refresh` si existe refresh token;
|
|
2. comparte una única `refreshPromise` entre requests concurrentes;
|
|
3. persiste los tokens renovados;
|
|
4. reintenta una sola vez la request original mediante `_retry`;
|
|
5. limpia sesión y navega a `/login` si el refresh falla.
|
|
|
|
Login, refresh, logout, session y companies available están excluidos del bucle de refresh y del header tenant. Una request tenant reintentada conserva el contexto de empresa.
|
|
|
|
## 10. Navegación y contexto
|
|
|
|
La navegación derivada de una operación pertenece al controller. Las páginas conectan callbacks y las vistas puras no importan React Router. El retorno desde editores debe reutilizar el mecanismo de `returnTo`/navegación existente en vez de reconstruir rutas localmente.
|
|
|
|
El estado remoto permanece en TanStack Query. No se duplica en estado local salvo estado efímero de presentación. Queries, invalidaciones y mutations viven en hooks compartidos del dominio.
|
|
|
|
## 11. Reglas de contribución arquitectónica
|
|
|
|
- Mantener TypeScript estricto y `import type` en cabecera.
|
|
- Naming y código en inglés; documentación y comentarios arquitectónicos en español.
|
|
- Preferir composición a frameworks CRUD o controllers genéricos.
|
|
- No introducir acceso a red en UI ni reglas de negocio en Pages.
|
|
- Mantener routing, sesión, tenant y sistema visual como responsabilidades separadas.
|
|
- Cambios de API visual pública deben sincronizar implementación, [UI system](./ui-system.md), UI Kit y tests.
|