diff --git a/README.md b/README.md index 0aca0240..f4f7a335 100644 --- a/README.md +++ b/README.md @@ -1 +1,28 @@ -Monorepo con cliente, servidor y packages modulares. +# ERP Monorepo + +Monorepo con aplicaciones, servidor y paquetes modulares para el ERP. + +## Estado actual + +- El runtime nuevo de autenticación y sesión vive en `modules/identity`. +- `modules/companies` es la fuente de verdad de la ficha operativa de empresa. +- `modules/auth` debe considerarse legacy/deprecated para backend nuevo. +- El frontend ERP ya usa `@erp/identity/client` como runtime actual. + +## Documentación clave + +- [Docs index](./docs/README.md) +- [Identity and companies](./docs/architecture/identity-and-companies.md) +- [Tenant context y `X-Company-Id`](./docs/architecture/tenant-context.md) +- [Identity auth API](./docs/architecture/identity-auth-api.md) +- [Frontend auth and company selection](./docs/frontend/auth-and-company-selection.md) +- [Seed local admin](./docs/dev/seed-local-admin.sql) + +## Reglas rápidas + +- Backend nuevo: usar `@erp/identity/api`. +- Rutas solo autenticadas: `requireIdentityAuthenticated(params)`. +- Rutas tenant-scoped: `requireIdentityTenant(params)`. +- `GET /companies/available` es el endpoint vigente para companies accesibles. +- No documentar ni introducir `GET /identity/companies`. +- No meter `companyId` ni `companySlug` dentro del access token. diff --git a/apps/web/README.md b/apps/web/README.md new file mode 100644 index 00000000..5be50921 --- /dev/null +++ b/apps/web/README.md @@ -0,0 +1,74 @@ +# `apps/web` + +Aplicación frontend principal del ERP. + +## Runtime actual de autenticación + +La app usa: + +```txt +@erp/identity/client +IdentityAuthSessionProvider +DataSourceProvider +Axios compartido +``` + +No debe usarse `@erp/auth/client` como runtime nuevo. + +## Flujo actual + +```txt +login +-> GET /identity/auth/session +-> GET /companies/available +-> resolver empresa activa +-> auto-selección si hay una sola empresa +-> selector si hay varias +-> /no-companies si no hay empresas +-> X-Company-Id en rutas tenant-scoped +``` + +## Headers + +No enviar `X-Company-Id` a: + +```txt +POST /identity/auth/login +POST /identity/auth/refresh +POST /identity/auth/logout +GET /identity/auth/session +GET /companies/available +``` + +Sí enviarlo a rutas tenant-scoped cuando exista `activeCompanyId`. + +## App company switcher + +Componente: + +```txt +apps/web/src/layout/app-company-switcher.tsx +``` + +Propósito: + +- mostrar la empresa activa real en el layout autenticado +- permitir cambiar entre `availableCompanies` de la sesión actual + +Reglas: + +- usar el contexto de `identity` +- no manejar tokens +- no llamar directamente a `GET /companies/available` +- no usar `companySlug` para auth +- no modificar el access token + +## Refresh automático + +La app implementa refresh automático global sobre `401` con retry único por request y `refreshPromise` compartida. + +Si el refresh falla: + +- limpiar sesión +- limpiar empresa activa persistida +- redirigir a `/login` diff --git a/docs/README.md b/docs/README.md index e1a09292..253d66bf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,6 +20,10 @@ docs/ - [Estado de migración a identity](./architecture/identity-migration-status.md) - [Flujo frontend de auth y selección de empresa](./frontend/auth-and-company-selection.md) - [Seed local de admin](./dev/seed-local-admin.sql) +- [README de `modules/auth`](../modules/auth/README.md) +- [README de `modules/identity`](../modules/identity/README.md) +- [README de `modules/companies`](../modules/companies/README.md) +- [README de `apps/web`](../apps/web/README.md) ## Estado actual resumido @@ -33,4 +37,5 @@ docs/ - `modules/auth` debe considerarse legacy/deprecated para backend nuevo. - `GET /companies/available` es el endpoint vigente para cargar companies accesibles. - `GET /identity/companies` no es un endpoint válido del diseño actual. +- `GET /account/companies` tampoco es un endpoint válido del diseño actual. - Las rutas tenant-scoped deben usar `Authorization` + `X-Company-Id`. diff --git a/docs/architecture/identity-and-companies.md b/docs/architecture/identity-and-companies.md index cae868be..01b9e8f8 100644 --- a/docs/architecture/identity-and-companies.md +++ b/docs/architecture/identity-and-companies.md @@ -17,8 +17,8 @@ Gestiona: - session - `RefreshToken` - `CompanyMembership` +- acceso `account-company` - roles/permisos futuros -- validación de acceso `accountId + companyId` Endpoints activos: @@ -83,6 +83,7 @@ INDEX(tin) - `Company` vive en `companies`. - No mover memberships a `companies`. - No evolucionar `identity.Company` como ficha operativa ERP. +- No usar `customers` para representar la empresa tenant. ## Servicios públicos @@ -133,6 +134,8 @@ GET /companies/available Authorization: Bearer ``` +Tampoco debe documentarse ni introducirse `GET /account/companies` como variante válida. + ## Dependencias permitidas Para módulos tenant-scoped que usan `requireIdentityTenant(params)`, la dependencia explícita debe ser hacia: diff --git a/docs/architecture/identity-auth-api.md b/docs/architecture/identity-auth-api.md index fa06ba15..aac7371c 100644 --- a/docs/architecture/identity-auth-api.md +++ b/docs/architecture/identity-auth-api.md @@ -10,6 +10,21 @@ GET /identity/auth/session GET /companies/available ``` +## Endpoints que no forman parte del diseño vigente + +No documentar ni crear como ruta válida: + +```http +GET /identity/companies +GET /account/companies +``` + +Motivo: + +- `identity` no debe devolver fichas completas de empresa. +- `companies` ya depende de `identity` para autenticación. +- Un endpoint de ese tipo introduciría el ciclo `identity -> companies -> identity`. + ## POST `/identity/auth/login` ### Request @@ -159,6 +174,7 @@ export type AvailableCompanyDTO = { - no requiere `X-Company-Id` - usa `requireIdentityAuthenticated(params)` - devuelve solo companies activas accesibles para la cuenta autenticada +- no modifica sesión ni access token ## Errores esperados diff --git a/docs/dev/seed-local-admin.sql b/docs/dev/seed-local-admin.sql index 8ce4802e..6abc28f3 100644 --- a/docs/dev/seed-local-admin.sql +++ b/docs/dev/seed-local-admin.sql @@ -6,6 +6,10 @@ -- Purpose: -- Creates a local admin account, one active company and the -- active membership between them. +-- Tables touched: +-- - identity_accounts +-- - companies +-- - identity_company_memberships -- -- Intended use: -- Local/dev environments only. diff --git a/docs/frontend/auth-and-company-selection.md b/docs/frontend/auth-and-company-selection.md index a0d6346d..98818a3f 100644 --- a/docs/frontend/auth-and-company-selection.md +++ b/docs/frontend/auth-and-company-selection.md @@ -118,6 +118,36 @@ export type AvailableCompaniesResponseDTO = { }; ``` +## App company switcher + +Componente documentado: + +```txt +apps/web/src/layout/app-company-switcher.tsx +``` + +Propósito: + +- cambiar de empresa activa una vez iniciada la sesión +- mostrar la empresa activa actual dentro del layout autenticado + +Reglas: + +- consume `availableCompanies` y `activeCompany` desde `useIdentityAuthSession()` +- usa `changeActiveCompany()` o `selectActiveCompany()` del contexto, no requests directas +- no llama directamente a `GET /companies/available` si el contexto ya está hidratado +- no maneja tokens +- no usa `@erp/auth/client` +- no usa `companySlug` para autenticación +- no modifica el access token +- al cambiar empresa, las siguientes requests tenant-scoped deben salir con el nuevo `X-Company-Id` + +Comportamiento esperado: + +- `0` empresas: no rompe; render deshabilitado o equivalente +- `1` empresa: muestra la empresa activa; no hace falta selector operativo +- `N` empresas: permite cambiar entre las permitidas y marca la activa + ## Headers ### No deben llevar `X-Company-Id` @@ -172,3 +202,14 @@ Comportamiento: /identity/auth/session /companies/available ``` + +## Pruebas manuales recomendadas + +1. Login con usuario sin empresas: debe ir a `/no-companies`. +2. Login con usuario con una empresa: debe auto-seleccionarse y entrar al área privada. +3. Login con usuario con varias empresas: debe mostrarse `/company-selection`. +4. Cambio de empresa desde `app-company-switcher`: la request tenant-scoped siguiente debe llevar el nuevo `X-Company-Id`. +5. Access token expirado con refresh válido: debe ejecutarse refresh + retry único. +6. Refresh token inválido: debe limpiarse la sesión y redirigir a `/login`. +7. Endpoints auth y `GET /companies/available`: no deben llevar `X-Company-Id`. +8. Rutas tenant-scoped: deben llevar `X-Company-Id` cuando exista `activeCompanyId`. diff --git a/modules/auth/README.md b/modules/auth/README.md index ff65531b..7505cc68 100644 --- a/modules/auth/README.md +++ b/modules/auth/README.md @@ -14,3 +14,5 @@ Importante: - Este paquete sigue exponiendo `@erp/auth/client`, por lo que no debe retirarse todavía. - No cambiar ni borrar exports legacy mientras `supplier` siga sin migrarse. +- No usar `mockUser` ni middlewares legacy de auth en backend nuevo. +- No borrar `modules/auth` hasta retirar consumidores legacy reales. diff --git a/modules/companies/README.md b/modules/companies/README.md new file mode 100644 index 00000000..5a3a5437 --- /dev/null +++ b/modules/companies/README.md @@ -0,0 +1,78 @@ +# `modules/companies` + +`companies` es el módulo independiente y fuente de verdad de la ficha operativa de empresa. + +## Responsabilidades + +Gestiona la entidad `Company` funcional del ERP: + +- datos legales +- datos operativos básicos +- `slug` +- `status` + +Separación arquitectónica: + +- `modules/identity`: autenticación, cuentas, memberships, acceso account-company +- `modules/companies`: ficha real de empresa y validación de company activa + +## Tabla `companies` + +Campos actuales: + +```txt +id +legal_name +trade_name +tin +slug +email +phone +website +status +created_at +updated_at +``` + +Índices esperados: + +```txt +UNIQUE(slug) +INDEX(status) +INDEX(tin) +``` + +## Servicios públicos + +El módulo expone: + +```txt +companies:general +``` + +Responsabilidad: + +- validar existencia y estado activo de la company +- devolver datos públicos/operativos de empresa + +No debe asumir: + +- memberships +- autenticación por cuenta +- roles/permisos de identity + +## Dependencias + +`modules/companies` depende de `identity`. + +Uso actual: + +- sus endpoints usan `requireIdentityAuthenticated(params)` +- `GET /companies/available` no usa `requireIdentityTenant(params)` + +## Reglas + +- `CompanyMembership` vive en `identity`. +- No mover memberships a `companies`. +- No usar `customers` para representar la empresa tenant. +- `companySlug` no debe venir de token, `X-Company-Slug` ni `req.user.companySlug`. diff --git a/modules/customer-invoices/src/api/application/issued-invoices/services/issued-invoice-document-properties-factory.ts b/modules/customer-invoices/src/api/application/issued-invoices/services/issued-invoice-document-properties-factory.ts index bcfecb56..2526af5e 100644 --- a/modules/customer-invoices/src/api/application/issued-invoices/services/issued-invoice-document-properties-factory.ts +++ b/modules/customer-invoices/src/api/application/issued-invoices/services/issued-invoice-document-properties-factory.ts @@ -1,6 +1,6 @@ import type { IDocumentProperties, IDocumentPropertiesFactory } from "@erp/core/api"; -import type { IssuedInvoiceReportSnapshot } from "../application-models"; +import type { IssuedInvoiceReportSnapshot } from "../models"; /** * Construye los metadatos del documento PDF de una factura emitida. diff --git a/modules/identity/README.md b/modules/identity/README.md index d18fa902..82bddb18 100644 --- a/modules/identity/README.md +++ b/modules/identity/README.md @@ -1,14 +1,123 @@ -# Identity Module +# `modules/identity` -`identity` convivirá temporalmente con `@erp/auth`. +`identity` es el runtime nuevo de autenticación, sesión y acceso por memberships del ERP. -`@erp/auth` sigue siendo el runtime de autenticación actual. +## Responsabilidades -`identity` incorporará progresivamente la implementación V1 aprobada: -- `email/password` -- `access token` -- `refresh token` persistido hasheado -- tenant activo mediante `X-Company-Id` -- roles y permisos company-scoped +Gestiona: -No se deben registrar middlewares ni reemplazar dependencias de `@erp/auth` hasta que `identity` tenga implementación funcional validada. +- `Account` +- login +- refresh +- logout +- session +- `RefreshToken` +- `CompanyMembership` +- acceso `account-company` +- roles/permisos futuros + +No gestiona: + +- la ficha operativa completa de empresa +- `companySlug` en auth +- el tenant dentro del access token + +## Endpoints activos + +```http +POST /identity/auth/login +POST /identity/auth/refresh +POST /identity/auth/logout +GET /identity/auth/session +``` + +El endpoint para companies accesibles es: + +```http +GET /companies/available +``` + +Ese endpoint pertenece al flujo actual, pero no implica que `identity` deba exponer `GET /identity/companies`. + +## Access token + +El access token contiene solo: + +```ts +{ + accountId: string; + email: string; +} +``` + +No debe contener: + +- `companyId` +- `companySlug` +- roles +- permisos + +## Middlewares públicos + +Para rutas autenticadas sin tenant: + +```ts +router.use(...requireIdentityAuthenticated(params)); +``` + +Para rutas tenant-scoped: + +```ts +router.use(...requireIdentityTenant(params)); +``` + +Código backend nuevo no debe usar: + +- `@erp/auth/api` +- `mockUser` +- `requireAuthenticated` legacy +- `requireCompanyContext` legacy + +## Servicios públicos + +`identity` expone el servicio: + +```txt +identity:general +``` + +Con acceso a `companyAccess` para casos como: + +```ts +canAccessCompany(...) +findAccessibleCompanyIds(...) +``` + +Responsabilidad: + +- `identity` decide qué `companyId` puede usar una cuenta según memberships activas. +- no devuelve la ficha completa de empresa. + +## Tenant context + +`requireIdentityTenant(params)` compone: + +- `identity:general.companyAccess` +- `companies:general.finder` + +Validación esperada: + +1. access token válido +2. cuenta autenticable +3. `X-Company-Id` presente +4. `X-Company-Id` UUID válido +5. membership activa +6. company activa +7. `req.user.companyId` poblado + +## Reglas de mantenimiento + +- Backend nuevo: usar `@erp/identity/api`. +- No documentar ni crear `GET /identity/companies`. +- No borrar `modules/auth` mientras existan consumidores legacy reales. +- `modules/supplier` sigue pendiente de migración si continúa usando el runtime legacy.