Refactor documentation for ERP architecture and customer invoices
- Updated general requirements document to clarify its historical context and remove outdated technical details. - Revised template documentation to serve as a base for writing prompts, emphasizing its auxiliary nature. - Enhanced architecture documentation with clear distinctions between canonical and complementary documents. - Added a new document for backend prompt base to centralize common context for backend prompts. - Created a dedicated README for customer invoices, organizing current contracts, operational support, and historical context. - Improved clarity and structure in various customer invoice documents, consolidating operational semantics and referencing canonical sources.
This commit is contained in:
parent
9cade597d7
commit
e5e73a5a13
525
AGENTS.md
525
AGENTS.md
@ -0,0 +1,525 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Ámbito
|
||||||
|
|
||||||
|
Estas instrucciones se aplican a todo el monorepo, salvo que exista un archivo `AGENTS.md` más específico en un subdirectorio.
|
||||||
|
|
||||||
|
Los archivos `AGENTS.md` descendientes deben añadir o restringir reglas para su ámbito, no duplicar innecesariamente este documento.
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Trabajar sobre el ERP mediante cambios:
|
||||||
|
|
||||||
|
- correctos;
|
||||||
|
- mínimos;
|
||||||
|
- comprobables;
|
||||||
|
- coherentes con la arquitectura existente;
|
||||||
|
- fáciles de entender y mantener por otros desarrolladores.
|
||||||
|
|
||||||
|
Antes de modificar código, revisar las implementaciones y patrones existentes en el área afectada. No introducir patrones nuevos si ya existe una solución equivalente en el proyecto.
|
||||||
|
|
||||||
|
## Stack principal
|
||||||
|
|
||||||
|
- Node.js >= 22
|
||||||
|
- TypeScript
|
||||||
|
- PNPM
|
||||||
|
- Turborepo
|
||||||
|
- ExpressJS
|
||||||
|
- React
|
||||||
|
- MariaDB
|
||||||
|
- Sequelize v6
|
||||||
|
- Zod v4
|
||||||
|
- Biome
|
||||||
|
|
||||||
|
## Principios obligatorios
|
||||||
|
|
||||||
|
Aplicar de forma pragmática:
|
||||||
|
|
||||||
|
- SOLID;
|
||||||
|
- Clean Code;
|
||||||
|
- DRY;
|
||||||
|
- KISS;
|
||||||
|
- YAGNI;
|
||||||
|
- SSOT;
|
||||||
|
- Fail Fast;
|
||||||
|
- programación defensiva;
|
||||||
|
- inmutabilidad cuando resulte adecuada;
|
||||||
|
- idempotencia;
|
||||||
|
- principio de menor privilegio;
|
||||||
|
- observabilidad en APIs y servicios.
|
||||||
|
|
||||||
|
Priorizar siempre:
|
||||||
|
|
||||||
|
```text
|
||||||
|
claridad y explicitud
|
||||||
|
>
|
||||||
|
abstracción prematura
|
||||||
|
```
|
||||||
|
|
||||||
|
No introducir capas, adaptadores, factories, genéricos, eventos, contenedores DI o abstracciones sin una necesidad real y actual.
|
||||||
|
|
||||||
|
## Idioma y convenciones de nombres
|
||||||
|
|
||||||
|
- Código e identificadores en inglés.
|
||||||
|
- Comentarios y TSDoc en castellano.
|
||||||
|
- Ficheros y directorios en `kebab-case`.
|
||||||
|
- Clases, tipos e interfaces en `PascalCase`.
|
||||||
|
- Variables, funciones y métodos en `camelCase`.
|
||||||
|
- Constantes globales en `UPPER_SNAKE_CASE` solo cuando realmente sean constantes globales.
|
||||||
|
- Mantener la convención de prefijo `I` para interfaces cuando el área existente ya la utilice.
|
||||||
|
|
||||||
|
## Comentarios y documentación
|
||||||
|
|
||||||
|
Los comentarios deben explicar:
|
||||||
|
|
||||||
|
- por qué existe una decisión;
|
||||||
|
- qué riesgo evita;
|
||||||
|
- qué contrato debe respetarse;
|
||||||
|
- qué efectos laterales o errores son relevantes.
|
||||||
|
|
||||||
|
No deben repetir lo que ya expresa el código.
|
||||||
|
|
||||||
|
Documentar mediante TSDoc las APIs públicas cuando aporten información sobre:
|
||||||
|
|
||||||
|
- propósito;
|
||||||
|
- parámetros;
|
||||||
|
- valor devuelto;
|
||||||
|
- errores;
|
||||||
|
- efectos laterales;
|
||||||
|
- invariantes o restricciones.
|
||||||
|
|
||||||
|
Después de un refactor, actualizar o eliminar los comentarios que hayan quedado obsoletos.
|
||||||
|
|
||||||
|
## Disciplina de cambios
|
||||||
|
|
||||||
|
- Realizar cambios incrementales.
|
||||||
|
- No modificar código no relacionado con la tarea.
|
||||||
|
- No renombrar, mover o reformatear archivos sin necesidad.
|
||||||
|
- No introducir dependencias nuevas sin una justificación concreta.
|
||||||
|
- No copiar código si ya existe una abstracción adecuada y estable.
|
||||||
|
- No crear una abstracción nueva para eliminar una duplicación pequeña y local.
|
||||||
|
- Mantener compatibilidad con los contratos existentes salvo que la tarea exija explícitamente romperlos.
|
||||||
|
- No afirmar que una validación ha pasado si no se ha ejecutado correctamente.
|
||||||
|
- No ocultar fallos de compilación, lint o tests.
|
||||||
|
- No añadir supresiones de TypeScript, Biome o ESLint sin justificar el motivo en el código o en la respuesta final.
|
||||||
|
|
||||||
|
## Uso de `Result`, `Maybe` y `Collection`
|
||||||
|
|
||||||
|
Usar los patrones existentes del proyecto:
|
||||||
|
|
||||||
|
- `Result` para operaciones que pueden fallar;
|
||||||
|
- `Maybe` para ausencia semántica;
|
||||||
|
- `Collection` para conjuntos de objetos del mismo tipo cuando el dominio requiera comportamiento propio.
|
||||||
|
|
||||||
|
No sustituirlos por excepciones, `null`, arrays o tipos ad hoc si el área existente ya utiliza estos patrones.
|
||||||
|
|
||||||
|
No usar excepciones para flujo de negocio esperado salvo que el diseño actual del módulo lo establezca expresamente.
|
||||||
|
|
||||||
|
## Gestión de operaciones asíncronas
|
||||||
|
|
||||||
|
El proyecto aplica la regla de Biome:
|
||||||
|
|
||||||
|
```text
|
||||||
|
lint/complexity/noVoid
|
||||||
|
```
|
||||||
|
|
||||||
|
Está prohibido usar el operador `void` para descartar el resultado de una promesa.
|
||||||
|
|
||||||
|
No generar:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
void handleDuplicate();
|
||||||
|
void handleDownload();
|
||||||
|
void asyncOperation();
|
||||||
|
```
|
||||||
|
|
||||||
|
Toda `Promise` debe gestionarse explícitamente mediante una de estas opciones:
|
||||||
|
|
||||||
|
### Esperar la operación
|
||||||
|
|
||||||
|
```ts
|
||||||
|
await handleDuplicate();
|
||||||
|
```
|
||||||
|
|
||||||
|
Usar esta opción cuando la función actual pueda ser `async` y el flujo deba esperar el resultado.
|
||||||
|
|
||||||
|
### Devolver la promesa
|
||||||
|
|
||||||
|
```ts
|
||||||
|
return handleDuplicate();
|
||||||
|
```
|
||||||
|
|
||||||
|
Usar esta opción cuando el llamador deba controlar el ciclo de vida de la operación.
|
||||||
|
|
||||||
|
### Gestionar expresamente el error
|
||||||
|
|
||||||
|
```ts
|
||||||
|
handleDuplicate().catch(handleUnexpectedError);
|
||||||
|
```
|
||||||
|
|
||||||
|
Usar esta opción solo cuando la operación sea deliberadamente no bloqueante.
|
||||||
|
|
||||||
|
Reglas adicionales:
|
||||||
|
|
||||||
|
- No eliminar `void` dejando una promesa flotante.
|
||||||
|
- No silenciar errores mediante `.catch(() => undefined)` o equivalentes sin justificación explícita.
|
||||||
|
- Antes de convertir un callback en `async`, comprobar si su contrato acepta `Promise<void>` o exige `void`.
|
||||||
|
- Si el contrato exige `void`, encapsular la operación asíncrona y gestionar explícitamente su error.
|
||||||
|
- No usar `void` como mecanismo para indicar intención.
|
||||||
|
|
||||||
|
## TypeScript
|
||||||
|
|
||||||
|
### Imports de tipos
|
||||||
|
|
||||||
|
Usar siempre imports de tipos explícitos en la cabecera del fichero mediante `import type`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Result } from "@repo/rdx-utils";
|
||||||
|
```
|
||||||
|
|
||||||
|
No usar referencias de tipo inline mediante `import("...").Tipo`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Prohibido
|
||||||
|
companyId: import("@repo/rdx-ddd").UniqueID;
|
||||||
|
```
|
||||||
|
|
||||||
|
Aplicar estas reglas:
|
||||||
|
|
||||||
|
- Usar `import type { ... } from "..."` para toda dependencia utilizada exclusivamente como tipo.
|
||||||
|
- Mantener los imports de tipos en la cabecera del fichero, junto con el resto de imports.
|
||||||
|
- No mezclar imports de tipo y de runtime cuando puedan separarse con claridad.
|
||||||
|
- No usar `import("...").Tipo` en código fuente ordinario.
|
||||||
|
- Solo se admite un tipo inline en declaraciones generadas, archivos `.d.ts`, module augmentation o casos excepcionales técnicamente justificados y documentados.
|
||||||
|
- No introducir imports de runtime para símbolos usados únicamente como tipos.
|
||||||
|
|
||||||
|
Ejemplo correcto:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
export interface IIdentityTenantCompanyFinder {
|
||||||
|
existsActiveById(params: {
|
||||||
|
companyId: UniqueID;
|
||||||
|
transaction?: unknown;
|
||||||
|
}): Promise<Result<boolean, Error>>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Evitar `any`. Usar `unknown` cuando el tipo todavía no sea conocido.
|
||||||
|
- No usar type assertions salvo que exista una garantía verificable que el compilador no pueda inferir.
|
||||||
|
- Evitar `!` salvo que la invariante esté demostrada localmente.
|
||||||
|
- Preferir tipos semánticos frente a primitivas genéricas.
|
||||||
|
- Modelar conjuntos cerrados mediante unions o enums existentes.
|
||||||
|
- Mantener exhaustividad en `switch` cuando el tipo represente un conjunto cerrado.
|
||||||
|
- No añadir ramas `default` que oculten casos no contemplados cuando pueda usarse comprobación exhaustiva.
|
||||||
|
- No duplicar tipos derivados de schemas Zod.
|
||||||
|
- No usar `Partial<T>` si el schema o el tipo de entrada ya representa explícitamente la opcionalidad.
|
||||||
|
- Mantener inmutabilidad en valores y estructuras cuando no exista necesidad de mutación.
|
||||||
|
|
||||||
|
## Zod y DTOs
|
||||||
|
|
||||||
|
Zod es la fuente única de verdad para los contratos HTTP.
|
||||||
|
|
||||||
|
Reglas:
|
||||||
|
|
||||||
|
- Inferir tipos con `z.infer<typeof Schema>`.
|
||||||
|
- No duplicar DTOs mediante interfaces manuales equivalentes.
|
||||||
|
- Cada endpoint debe tener sus DTOs de request y response.
|
||||||
|
- Reutilizar subschemas solo cuando sean realmente estables y compartidos.
|
||||||
|
- No mezclar validación de transporte con lógica de negocio.
|
||||||
|
- Evitar `.default()` y `.transform()` en DTOs de negocio salvo necesidad justificada.
|
||||||
|
- Diferenciar explícitamente entre campo omitido, `null`, cadena vacía y colección vacía.
|
||||||
|
- Usar schemas semánticos para UUID, fechas, timestamps, porcentajes, importes y cantidades.
|
||||||
|
- Los valores decimales exactos no deben viajar como `number`.
|
||||||
|
|
||||||
|
## Arquitectura backend
|
||||||
|
|
||||||
|
El backend sigue DDD pragmático, Clean Architecture y arquitectura hexagonal.
|
||||||
|
|
||||||
|
Separación obligatoria:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Domain
|
||||||
|
no conoce Application ni Infrastructure
|
||||||
|
|
||||||
|
Application
|
||||||
|
depende de Domain, contratos y Core
|
||||||
|
no conoce Express, Sequelize ni modelos ORM
|
||||||
|
|
||||||
|
Infrastructure
|
||||||
|
depende de Application, Domain y Core
|
||||||
|
```
|
||||||
|
|
||||||
|
No se permite:
|
||||||
|
|
||||||
|
- `Domain -> Application`;
|
||||||
|
- `Domain -> Infrastructure`;
|
||||||
|
- `Application -> Infrastructure`;
|
||||||
|
- importar infraestructura interna de otro módulo;
|
||||||
|
- compartir repositorios entre módulos;
|
||||||
|
- usar Service Locator;
|
||||||
|
- introducir DI implícita o mágica.
|
||||||
|
|
||||||
|
### Flujo de entrada
|
||||||
|
|
||||||
|
Respetar:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RequestDTO
|
||||||
|
-> RequestMapper
|
||||||
|
-> Command
|
||||||
|
-> PropsMapper
|
||||||
|
-> Aggregate o Entity
|
||||||
|
```
|
||||||
|
|
||||||
|
No mapear directamente:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DTO -> Aggregate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Casos de uso
|
||||||
|
|
||||||
|
Los casos de uso deben:
|
||||||
|
|
||||||
|
- orquestar;
|
||||||
|
- gestionar transacciones cuando corresponda;
|
||||||
|
- delegar en servicios de aplicación;
|
||||||
|
- devolver `Result`;
|
||||||
|
- construir respuestas mediante builders o mappers de salida.
|
||||||
|
|
||||||
|
No deben:
|
||||||
|
|
||||||
|
- contener lógica HTTP;
|
||||||
|
- usar Sequelize directamente;
|
||||||
|
- construir agregados desde DTOs HTTP;
|
||||||
|
- asumir responsabilidades propias de repositorios o controllers.
|
||||||
|
|
||||||
|
### Servicios de aplicación
|
||||||
|
|
||||||
|
Preferir servicios pequeños y explícitos con una responsabilidad clara.
|
||||||
|
|
||||||
|
Evitar nombres genéricos como:
|
||||||
|
|
||||||
|
- `Manager`;
|
||||||
|
- `GenericService`;
|
||||||
|
- `CommandService`;
|
||||||
|
- `BaseRepository`;
|
||||||
|
- `Helper` sin semántica concreta.
|
||||||
|
|
||||||
|
### Repositorios
|
||||||
|
|
||||||
|
- Cada repositorio gestiona un agregado o una responsabilidad concreta.
|
||||||
|
- Sequelize no debe salir de Infrastructure.
|
||||||
|
- Los repositorios deben usar mappers de persistencia.
|
||||||
|
- No crear repositorios base genéricos sin una necesidad demostrada.
|
||||||
|
- Mantener aislamiento por `companyId` y `branchId` cuando corresponda.
|
||||||
|
|
||||||
|
### Mappers de persistencia
|
||||||
|
|
||||||
|
Usar un mapper bidireccional por agregado:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
toPersistence();
|
||||||
|
toDomain();
|
||||||
|
```
|
||||||
|
|
||||||
|
Infrastructure debe reconstruir entidades mediante `rehydrate()`.
|
||||||
|
|
||||||
|
### `create()` y `rehydrate()`
|
||||||
|
|
||||||
|
Mantener separadas estas operaciones:
|
||||||
|
|
||||||
|
```text
|
||||||
|
create()
|
||||||
|
creación funcional
|
||||||
|
valida invariantes
|
||||||
|
puede fallar
|
||||||
|
devuelve Result
|
||||||
|
|
||||||
|
rehydrate()
|
||||||
|
reconstrucción desde persistencia
|
||||||
|
no valida reglas de creación
|
||||||
|
no genera eventos
|
||||||
|
no devuelve Result
|
||||||
|
```
|
||||||
|
|
||||||
|
Reglas:
|
||||||
|
|
||||||
|
- Infrastructure usa `rehydrate()`.
|
||||||
|
- Application y Domain usan `create()` para nuevas instancias.
|
||||||
|
- `update()` no usa `create()` ni `rehydrate()`.
|
||||||
|
- Los Value Objects no tienen `rehydrate()`; se reconstruyen mediante `create()`.
|
||||||
|
|
||||||
|
### Comunicación entre módulos
|
||||||
|
|
||||||
|
Los módulos solo deben colaborar mediante servicios públicos explícitos.
|
||||||
|
|
||||||
|
No exponer:
|
||||||
|
|
||||||
|
- repositorios;
|
||||||
|
- modelos Sequelize;
|
||||||
|
- mappers internos;
|
||||||
|
- servicios de escritura internos;
|
||||||
|
- detalles de infraestructura.
|
||||||
|
|
||||||
|
El composition root de cada módulo debe construir sus dependencias explícitamente.
|
||||||
|
|
||||||
|
## Snapshots y builders de respuesta
|
||||||
|
|
||||||
|
Los schemas Zod definen el contrato de salida.
|
||||||
|
|
||||||
|
Los snapshot builders solo materializan ese contrato.
|
||||||
|
|
||||||
|
Reglas:
|
||||||
|
|
||||||
|
- Tipar el builder con el DTO inferido del schema exacto.
|
||||||
|
- No crear interfaces `I*Snapshot` que dupliquen un DTO.
|
||||||
|
- El builder debe ser determinista y no contener lógica de negocio.
|
||||||
|
- Puede convertir `Maybe` a `null`, Value Objects a primitivas y enums internos a valores de contrato.
|
||||||
|
- No puede acceder a repositorios ni infraestructura.
|
||||||
|
- Si cambia el schema, el builder debe fallar en compilación si deja de cumplir el contrato.
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
|
||||||
|
- Mantener React, TanStack Query, TanStack Table, React Hook Form, Zod, shadcn/ui, Tailwind e i18n según los patrones existentes.
|
||||||
|
- No duplicar estado remoto en estado local sin necesidad.
|
||||||
|
- Mantener las operaciones de servidor en TanStack Query.
|
||||||
|
- Centralizar las claves de query siguiendo la convención existente.
|
||||||
|
- Invalidar o actualizar caché de forma explícita tras mutaciones.
|
||||||
|
- No introducir efectos para derivar estado que pueda calcularse durante render.
|
||||||
|
- Evitar componentes monolíticos; extraer responsabilidades cuando exista una frontera clara.
|
||||||
|
- No extraer componentes triviales que solo oculten JSX sin aportar semántica.
|
||||||
|
- Mantener accesibilidad, estados disabled, loading, empty y error.
|
||||||
|
- No eliminar acciones no disponibles si la UX exige mostrarlas deshabilitadas.
|
||||||
|
- Mantener traducciones en `es` y `en` cuando se añadan textos visibles.
|
||||||
|
- No insertar textos de interfaz directamente si el área usa i18n.
|
||||||
|
- Mantener `tabular-nums` en columnas monetarias cuando corresponda.
|
||||||
|
|
||||||
|
## APIs y errores
|
||||||
|
|
||||||
|
- Usar errores tipados y predecibles.
|
||||||
|
- No devolver detalles internos, SQL, stack traces o información sensible.
|
||||||
|
- Mapear errores de dominio y aplicación a errores HTTP en Infrastructure.
|
||||||
|
- Mantener contratos de error estables.
|
||||||
|
- Aplicar idempotencia en operaciones que puedan repetirse.
|
||||||
|
- Validar autorización y aislamiento tenant antes de acceder o modificar datos.
|
||||||
|
- No confiar en identificadores de compañía enviados por el cliente sin verificar el contexto autenticado.
|
||||||
|
- Registrar información suficiente para diagnosticar fallos sin exponer datos sensibles.
|
||||||
|
|
||||||
|
## Seguridad
|
||||||
|
|
||||||
|
- Aplicar principio de menor privilegio.
|
||||||
|
- No introducir secretos, tokens, claves o credenciales en el repositorio.
|
||||||
|
- No registrar datos sensibles.
|
||||||
|
- Validar toda entrada externa.
|
||||||
|
- Usar consultas parametrizadas y APIs seguras de Sequelize.
|
||||||
|
- Evitar interpolación manual de SQL.
|
||||||
|
- Revisar autorización además de autenticación.
|
||||||
|
- Mantener aislamiento multiempresa en todas las consultas y mutaciones.
|
||||||
|
|
||||||
|
## Base de datos y Sequelize
|
||||||
|
|
||||||
|
- Mantener nombres de tablas y columnas coherentes con las convenciones existentes.
|
||||||
|
- No introducir acceso directo a modelos Sequelize fuera de Infrastructure.
|
||||||
|
- Ejecutar operaciones agregadas dentro de una transacción cuando deban ser atómicas.
|
||||||
|
- Diseñar migraciones idempotentes cuando el procedimiento del proyecto lo requiera.
|
||||||
|
- No eliminar columnas, tablas o datos legacy sin una auditoría previa de referencias.
|
||||||
|
- No confiar únicamente en restricciones de aplicación cuando una invariante crítica pueda reforzarse en base de datos.
|
||||||
|
- Evitar `JSON.stringify` para detectar cambios de dominio.
|
||||||
|
- Comparar Value Objects semánticamente mediante sus primitivas o métodos de igualdad adecuados.
|
||||||
|
|
||||||
|
## Comandos y entorno de ejecución
|
||||||
|
|
||||||
|
El código se encuentra físicamente en una máquina Ubuntu compartida, aunque pueda aparecer en Windows como unidad `Z:`.
|
||||||
|
|
||||||
|
Preferir comandos para Linux ejecutados mediante SSH sobre Ubuntu.
|
||||||
|
|
||||||
|
No usar rutas Windows como opción principal.
|
||||||
|
|
||||||
|
Ejemplos preferidos:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter <workspace> typecheck
|
||||||
|
pnpm --filter <workspace> lint
|
||||||
|
pnpm --filter <workspace> test
|
||||||
|
```
|
||||||
|
|
||||||
|
Usar comandos acotados al workspace afectado cuando sea suficiente.
|
||||||
|
|
||||||
|
Usar comandos globales cuando el cambio pueda afectar contratos compartidos o varios workspaces.
|
||||||
|
|
||||||
|
## Validación obligatoria
|
||||||
|
|
||||||
|
Antes de finalizar un cambio, ejecutar las comprobaciones relevantes disponibles para el área modificada:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm typecheck
|
||||||
|
pnpm lint
|
||||||
|
pnpm test
|
||||||
|
```
|
||||||
|
|
||||||
|
Cuando corresponda, ejecutar además:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm check
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
Criterios:
|
||||||
|
|
||||||
|
- Preferir validación dirigida al paquete o módulo afectado.
|
||||||
|
- Ejecutar validación global si se modifican paquetes compartidos, contratos o configuración transversal.
|
||||||
|
- Informar exactamente qué comandos se ejecutaron.
|
||||||
|
- Informar claramente cualquier validación no ejecutada y su motivo.
|
||||||
|
- No considerar completada una tarea con errores conocidos de lint, typecheck o tests salvo que el usuario haya limitado expresamente el alcance.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
- Añadir o actualizar tests cuando cambie comportamiento observable.
|
||||||
|
- No escribir tests que dependan de detalles internos irrelevantes.
|
||||||
|
- Cubrir casos válidos, inválidos, límites e idempotencia cuando corresponda.
|
||||||
|
- Mantener tests deterministas.
|
||||||
|
- No usar esperas arbitrarias.
|
||||||
|
- No desactivar tests para hacer pasar la suite.
|
||||||
|
|
||||||
|
## Refactors
|
||||||
|
|
||||||
|
Un refactor debe mantener el comportamiento salvo que se indique lo contrario.
|
||||||
|
|
||||||
|
Antes de eliminar código legacy:
|
||||||
|
|
||||||
|
1. buscar referencias en todo el monorepo;
|
||||||
|
2. clasificar cada referencia como activa, legacy no cableada, migración, documentación o falso positivo;
|
||||||
|
3. revisar rutas, composition roots, DI, imports, modelos, repositorios, mappers, tests y scripts;
|
||||||
|
4. eliminar únicamente lo demostrado como no utilizado;
|
||||||
|
5. ejecutar typecheck, lint y tests relevantes;
|
||||||
|
6. documentar referencias residuales y motivos.
|
||||||
|
|
||||||
|
## Respuesta final del agente
|
||||||
|
|
||||||
|
Al terminar una tarea de código, indicar de forma concisa:
|
||||||
|
|
||||||
|
- cambios realizados;
|
||||||
|
- decisiones relevantes;
|
||||||
|
- archivos principales modificados;
|
||||||
|
- comandos de validación ejecutados;
|
||||||
|
- resultado de las validaciones;
|
||||||
|
- riesgos o pendientes reales.
|
||||||
|
|
||||||
|
No afirmar que algo compila, funciona o está probado si no se ha verificado.
|
||||||
|
|
||||||
|
## Regla de precedencia
|
||||||
|
|
||||||
|
En caso de conflicto, aplicar este orden:
|
||||||
|
|
||||||
|
1. instrucciones explícitas del usuario para la tarea actual;
|
||||||
|
2. `AGENTS.md` más cercano al archivo modificado;
|
||||||
|
3. este `AGENTS.md` raíz;
|
||||||
|
4. convenciones existentes del código;
|
||||||
|
5. preferencias generales del agente.
|
||||||
|
|
||||||
|
Si una instrucción contradice la arquitectura o genera un riesgo relevante, señalarlo antes de introducir el cambio.
|
||||||
72
README.md
72
README.md
@ -1,28 +1,60 @@
|
|||||||
# ERP Monorepo
|
# ERP Monorepo
|
||||||
|
|
||||||
Monorepo con aplicaciones, servidor y paquetes modulares para el ERP.
|
Monorepo del ERP con aplicaciones, módulos de dominio y paquetes compartidos.
|
||||||
|
|
||||||
## Estado actual
|
El `README` raíz actúa como puerta de entrada del repositorio. La documentación funcional, operativa y arquitectónica vive en [`docs/README.md`](./docs/README.md).
|
||||||
|
|
||||||
- El runtime nuevo de autenticación y sesión vive en `modules/identity`.
|
## Estructura
|
||||||
- `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
|
```text
|
||||||
|
apps/ aplicaciones ejecutables
|
||||||
|
modules/ módulos de negocio y backend
|
||||||
|
packages/ librerías compartidas
|
||||||
|
docs/ documentación operativa y arquitectónica
|
||||||
|
scripts/ utilidades de desarrollo
|
||||||
|
tools/ herramientas auxiliares del workspace
|
||||||
|
```
|
||||||
|
|
||||||
- [Docs index](./docs/README.md)
|
Áreas principales del workspace:
|
||||||
- [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
|
- `apps/web`: frontend principal del ERP.
|
||||||
|
- `apps/server`: runtime servidor del monorepo.
|
||||||
|
- `modules/*`: módulos como `identity`, `companies`, `customer-invoices` o `document-series`.
|
||||||
|
- `packages/*`: utilidades compartidas como `rdx-ddd`, `rdx-utils`, `rdx-ui` e `i18n`.
|
||||||
|
|
||||||
- Backend nuevo: usar `@erp/identity/api`.
|
## Requisitos
|
||||||
- Rutas solo autenticadas: `requireIdentityAuthenticated(params)`.
|
|
||||||
- Rutas tenant-scoped: `requireIdentityTenant(params)`.
|
- Node.js `>= 24`
|
||||||
- `GET /companies/available` es el endpoint vigente para companies accesibles.
|
- PNPM `10.33.0`
|
||||||
- No documentar ni introducir `GET /identity/companies`.
|
|
||||||
- No meter `companyId` ni `companySlug` dentro del access token.
|
## Primeros pasos
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Comandos frecuentes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dev:server
|
||||||
|
pnpm dev:client
|
||||||
|
pnpm lint
|
||||||
|
pnpm check
|
||||||
|
pnpm typecheck
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentación
|
||||||
|
|
||||||
|
- Índice general: [`docs/README.md`](./docs/README.md)
|
||||||
|
- Frontend: [`apps/web/README.md`](./apps/web/README.md)
|
||||||
|
- Identity: [`modules/identity/README.md`](./modules/identity/README.md)
|
||||||
|
- Companies: [`modules/companies/README.md`](./modules/companies/README.md)
|
||||||
|
- Auth legacy: [`modules/auth/README.md`](./modules/auth/README.md)
|
||||||
|
|
||||||
|
## Forma de uso
|
||||||
|
|
||||||
|
- Usa este `README` para orientarte en el repositorio y arrancar el entorno.
|
||||||
|
- Usa `docs/` para contratos, decisiones de arquitectura, migraciones y runbooks.
|
||||||
|
- Usa los `README` de cada app o módulo para detalles locales de implementación.
|
||||||
|
|||||||
@ -2,45 +2,24 @@
|
|||||||
|
|
||||||
Aplicación frontend principal del ERP.
|
Aplicación frontend principal del ERP.
|
||||||
|
|
||||||
|
## Documentación canónica
|
||||||
|
|
||||||
|
- Flujo de autenticación y selección de empresa: [`../../docs/frontend/auth-and-company-selection.md`](../../docs/frontend/auth-and-company-selection.md)
|
||||||
|
- Contexto tenant y reglas de `X-Company-Id`: [`../../docs/architecture/tenant-context.md`](../../docs/architecture/tenant-context.md)
|
||||||
|
|
||||||
## Runtime actual de autenticación
|
## Runtime actual de autenticación
|
||||||
|
|
||||||
La app usa:
|
La app usa `@erp/identity/client` como runtime actual.
|
||||||
|
|
||||||
```txt
|
|
||||||
@erp/identity/client
|
|
||||||
IdentityAuthSessionProvider
|
|
||||||
DataSourceProvider
|
|
||||||
Axios compartido
|
|
||||||
```
|
|
||||||
|
|
||||||
No debe usarse `@erp/auth/client` como runtime nuevo.
|
|
||||||
|
|
||||||
## Flujo actual
|
## Flujo actual
|
||||||
|
|
||||||
```txt
|
- La sesión y la empresa activa se resuelven según el flujo documentado en `docs/frontend/auth-and-company-selection.md`.
|
||||||
login
|
- Las rutas tenant-scoped deben respetar las reglas de `X-Company-Id` documentadas en `docs/architecture/tenant-context.md`.
|
||||||
-> 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
|
## Headers
|
||||||
|
|
||||||
No enviar `X-Company-Id` a:
|
- No duplicar aquí la matriz de headers.
|
||||||
|
- Consultar `docs/frontend/auth-and-company-selection.md` para endpoints excluidos y `tenant-context.md` para reglas tenant-scoped.
|
||||||
```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
|
## App company switcher
|
||||||
|
|
||||||
@ -62,13 +41,10 @@ Reglas:
|
|||||||
- no llamar directamente a `GET /companies/available`
|
- no llamar directamente a `GET /companies/available`
|
||||||
- no usar `companySlug` para auth
|
- no usar `companySlug` para auth
|
||||||
- no modificar el access token
|
- no modificar el access token
|
||||||
|
- respetar el flujo canónico documentado en `docs/frontend/auth-and-company-selection.md`
|
||||||
|
|
||||||
## Refresh automático
|
## Refresh automático
|
||||||
|
|
||||||
La app implementa refresh automático global sobre `401` con retry único por request y `refreshPromise` compartida.
|
La app implementa refresh automático global sobre `401`.
|
||||||
|
|
||||||
Si el refresh falla:
|
La semántica completa de retry, exclusiones y limpieza de sesión se documenta en `docs/frontend/auth-and-company-selection.md`.
|
||||||
|
|
||||||
- limpiar sesión
|
|
||||||
- limpiar empresa activa persistida
|
|
||||||
- redirigir a `/login`
|
|
||||||
|
|||||||
427
docs/API.md
427
docs/API.md
@ -1,422 +1,19 @@
|
|||||||
## **Diseño de API RESTful**
|
# API (Legacy Draft / Obsoleto)
|
||||||
|
|
||||||
### **1. Autenticación y Gestión de Sesiones**
|
Este documento conserva un borrador antiguo de API REST y ya no describe el runtime vigente del ERP.
|
||||||
|
|
||||||
#### **1.1. Inicio de sesión**
|
## No usar como fuente de verdad
|
||||||
|
|
||||||
**Endpoint:**
|
Los endpoints, payloads y rutas aquí recogidos pertenecen a una fase anterior del proyecto y entran en conflicto con la arquitectura actual basada en `modules/identity`, `modules/companies` y los contratos vigentes de cada módulo.
|
||||||
`POST /api/auth/login`
|
|
||||||
|
|
||||||
**Solicitud:**
|
## Fuentes actuales
|
||||||
|
|
||||||
```json
|
- Auth y sesión: [architecture/identity-auth-api.md](./architecture/identity-auth-api.md)
|
||||||
{
|
- Contexto tenant: [architecture/tenant-context.md](./architecture/tenant-context.md)
|
||||||
"email": "usuario@empresa.com",
|
- Relación `identity` / `companies`: [architecture/identity-and-companies.md](./architecture/identity-and-companies.md)
|
||||||
"password": "contraseña_segura"
|
- Frontend auth: [frontend/auth-and-company-selection.md](./frontend/auth-and-company-selection.md)
|
||||||
}
|
- Customer invoices y proformas: [customer-invoices/README.md](./customer-invoices/README.md)
|
||||||
```
|
|
||||||
|
|
||||||
**Respuesta:**
|
## Motivo de conservación
|
||||||
|
|
||||||
```json
|
Se mantiene solo por trazabilidad de descubrimiento inicial y comparación histórica.
|
||||||
{
|
|
||||||
"token": "eyJhbGciOi...",
|
|
||||||
"user": {
|
|
||||||
"id": 123,
|
|
||||||
"username": "usuario",
|
|
||||||
"email": "usuario@empresa.com"
|
|
||||||
},
|
|
||||||
"companies": [
|
|
||||||
{ "id": 1, "name": "Empresa A" },
|
|
||||||
{ "id": 2, "name": "Empresa B" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### **1.2. Cierre de sesión**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`POST /api/auth/logout`
|
|
||||||
|
|
||||||
**Encabezados:**
|
|
||||||
|
|
||||||
```
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Sesión cerrada exitosamente"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### **1.3. Selección de empresa activa**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`POST /api/auth/select-company`
|
|
||||||
|
|
||||||
**Solicitud:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"company_id": 1
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Empresa seleccionada exitosamente"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **2. Gestión de Empresas**
|
|
||||||
|
|
||||||
#### **2.1. Listar empresas disponibles**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`GET /api/companies`
|
|
||||||
|
|
||||||
**Encabezados:**
|
|
||||||
|
|
||||||
```
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "Empresa A",
|
|
||||||
"country_code": "ES",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
},
|
|
||||||
{ "id": 2, "name": "Empresa B", "country_code": "US", "currency_code": "USD" }
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### **2.2. Crear una nueva empresa**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`POST /api/companies`
|
|
||||||
|
|
||||||
**Solicitud:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "Nueva Empresa",
|
|
||||||
"country_code": "ES",
|
|
||||||
"currency_code": "EUR"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"message": "Empresa creada exitosamente"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### **2.3. Actualizar datos de una empresa**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`PUT /api/companies/{company_id}`
|
|
||||||
|
|
||||||
**Solicitud:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "Empresa Actualizada",
|
|
||||||
"currency_code": "USD"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Empresa actualizada correctamente"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### **2.4. Eliminar una empresa**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`DELETE /api/companies/{company_id}`
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Empresa eliminada exitosamente"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **3. Gestión de Sucursales**
|
|
||||||
|
|
||||||
#### **3.1. Listar sucursales de una empresa**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`GET /api/companies/{company_id}/branches`
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{ "id": 10, "name": "Sucursal Madrid" },
|
|
||||||
{ "id": 20, "name": "Sucursal Barcelona" }
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### **3.2. Crear una sucursal**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`POST /api/companies/{company_id}/branches`
|
|
||||||
|
|
||||||
**Solicitud:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "Sucursal Sevilla",
|
|
||||||
"location": "Avenida Principal 123"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": 30,
|
|
||||||
"message": "Sucursal creada exitosamente"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### **3.3. Actualizar datos de una sucursal**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`PUT /api/branches/{branch_id}`
|
|
||||||
|
|
||||||
**Solicitud:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "Sucursal Actualizada",
|
|
||||||
"location": "Calle Nueva 456"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Sucursal actualizada correctamente"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### **3.4. Eliminar una sucursal**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`DELETE /api/branches/{branch_id}`
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Sucursal eliminada exitosamente"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **4. Gestión de Usuarios**
|
|
||||||
|
|
||||||
#### **4.1. Listar usuarios de una empresa**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`GET /api/companies/{company_id}/users`
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"username": "admin",
|
|
||||||
"email": "admin@empresa.com",
|
|
||||||
"roles": ["Administrador"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"username": "usuario",
|
|
||||||
"email": "usuario@empresa.com",
|
|
||||||
"roles": ["Ventas"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### **4.2. Crear un usuario**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`POST /api/users`
|
|
||||||
|
|
||||||
**Solicitud:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"username": "nuevo_usuario",
|
|
||||||
"email": "nuevo@empresa.com",
|
|
||||||
"password": "clave_segura",
|
|
||||||
"company_id": 1,
|
|
||||||
"roles": ["Ventas"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"message": "Usuario creado exitosamente"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### **4.3. Actualizar usuario**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`PUT /api/users/{user_id}`
|
|
||||||
|
|
||||||
**Solicitud:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"email": "nuevo_correo@empresa.com",
|
|
||||||
"roles": ["Compras"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Usuario actualizado correctamente"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### **4.4. Eliminar usuario**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`DELETE /api/users/{user_id}`
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Usuario eliminado exitosamente"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **5. Gestión de Permisos**
|
|
||||||
|
|
||||||
#### **5.1. Obtener permisos de un usuario en una empresa**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`GET /api/companies/{company_id}/users/{user_id}/permissions`
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"modules": {
|
|
||||||
"Presupuestos": ["read", "write"],
|
|
||||||
"Facturas": ["read"],
|
|
||||||
"Clientes": ["read", "write", "delete"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### **5.2. Actualizar permisos de un usuario**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`PUT /api/users/{user_id}/permissions`
|
|
||||||
|
|
||||||
**Solicitud:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"modules": {
|
|
||||||
"Presupuestos": ["read", "write"],
|
|
||||||
"Inventario": ["read"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Permisos actualizados correctamente"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### **6. Auditoría**
|
|
||||||
|
|
||||||
#### **6.1. Obtener historial de cambios en documentos**
|
|
||||||
|
|
||||||
**Endpoint:**
|
|
||||||
`GET /api/audit/documents/{document_id}`
|
|
||||||
|
|
||||||
**Respuesta:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"timestamp": "2025-01-22T14:00:00Z",
|
|
||||||
"user": "admin",
|
|
||||||
"change": "Se modificó el precio de 100 a 120"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"timestamp": "2025-01-20T10:30:00Z",
|
|
||||||
"user": "usuario",
|
|
||||||
"change": "Se creó el documento"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|||||||
@ -1,187 +1,96 @@
|
|||||||
# 📝 Guía de Convenciones para Nombres de Schemas
|
# Convención de Nombres de Schemas DTO
|
||||||
|
|
||||||
Esta guía define cómo nombrar los Schemas de **Requests** y **Responses** según el tipo de operación y el contexto (recurso único o colección).
|
Guía específica de naming para schemas DTO. Complementa, pero no sustituye, a [DTOS - GUÍA DE DISEÑO.md](./DTOS - GUÍA DE DISEÑO.md), que sigue siendo la referencia principal de diseño.
|
||||||
|
|
||||||
---
|
## Alcance
|
||||||
|
|
||||||
## 1️⃣ Operaciones sobre recurso único
|
Usa este documento cuando la duda sea cómo nombrar un schema. Si la duda es de semántica, tipado, opcionalidad o modelado del contrato, usa antes la guía general de DTOs.
|
||||||
|
|
||||||
**Patrón**
|
## Reglas base
|
||||||
<Verbo><Entidad><Condición><TipoDeSchema>
|
|
||||||
|
|
||||||
|
1. Usar singular para el nombre base de la entidad: `Customer`, `Product`, `Proforma`.
|
||||||
|
2. En operaciones sobre recurso único, el verbo va primero.
|
||||||
|
3. En listados, el sustantivo principal va primero: `CustomerList`, nunca `ListCustomer`.
|
||||||
|
4. Mantener sufijos estables según el rol del schema.
|
||||||
|
5. No mezclar convenciones distintas para la misma familia de endpoints.
|
||||||
|
|
||||||
- **Verbo**: describe la acción (`Get`, `Delete`, `Update`, `Patch`, `Create`).
|
## Patrones recomendados
|
||||||
- **Entidad**: en singular (`Product`, `Customer`, `Invoice`).
|
|
||||||
- **Condición**: criterio de selección (`ById`, `BySlug`, `ByEmail`).
|
|
||||||
- **TipoDeSchema**: origen o propósito (`ParamsSchema`, `CommandSchema`, `QuerySchema`).
|
|
||||||
|
|
||||||
**Ejemplos ✅**
|
### Recurso único
|
||||||
- `GetProductByIdParamsSchema`
|
|
||||||
|
```text
|
||||||
|
<Verb><Entity><Condition><SchemaRole>
|
||||||
|
```
|
||||||
|
|
||||||
|
Ejemplos:
|
||||||
|
|
||||||
|
- `GetCustomerByIdParamsSchema`
|
||||||
- `DeleteCustomerByIdParamsSchema`
|
- `DeleteCustomerByIdParamsSchema`
|
||||||
- `UpdateInvoiceByIdCommandSchema`
|
- `UpdateIssuedInvoiceByIdCommandSchema`
|
||||||
|
|
||||||
**Ejemplos ❌**
|
### Colecciones
|
||||||
- `ProductGetByIdParamsSchema` (orden invertido, menos natural).
|
|
||||||
- `GetProductsByIdParamsSchema` (plural innecesario en recurso único).
|
|
||||||
|
|
||||||
**Lectura natural**: *"Esquema de parámetros para obtener un producto por id"*.
|
```text
|
||||||
|
<Entity>List<CriteriaOrFilter><SchemaRole>
|
||||||
|
```
|
||||||
|
|
||||||
---
|
Ejemplos:
|
||||||
|
|
||||||
## 2️⃣ Operaciones sobre colecciones
|
- `CustomerListCriteriaSchema`
|
||||||
|
- `ProformaListResponseSchema`
|
||||||
|
- `IssuedInvoiceListQuerySchema`
|
||||||
|
|
||||||
**Patrón**
|
## Sufijos estables
|
||||||
<Entidad>List<Criterio/Filtro><TipoDeSchema>
|
|
||||||
|
|
||||||
- **Entidad**: en singular (`Product`, `Customer`).
|
### Request
|
||||||
- **List**: indica conjunto/listado.
|
|
||||||
- **Criterio/Filtro**: tipo de filtrado (`Criteria`, `Query`).
|
|
||||||
- **TipoDeSchema**: `Schema` o `ResponseSchema` según corresponda.
|
|
||||||
|
|
||||||
**Ejemplos ✅**
|
- `CommandSchema`: body de create, update o patch
|
||||||
- `ProductListCriteriaSchema`
|
- `ParamsSchema`: route params
|
||||||
- `CustomerListResponseSchema`
|
- `QuerySchema`: query string raw
|
||||||
- `InvoiceListQuerySchema`
|
- `CriteriaSchema`: query normalizada con patrón Criteria
|
||||||
|
|
||||||
**Ejemplos ❌**
|
### Response
|
||||||
- `ListProductCriteriaSchema` (el orden “ListProduct” suena a verbo y no a colección).
|
|
||||||
- `ProductsListCriteriaSchema` (plural innecesario en el nombre base).
|
|
||||||
|
|
||||||
**Lectura natural**: *"Esquema de criterios para una lista de productos"*.
|
- `SummarySchema`: item de listado
|
||||||
|
- `ResourceSchema`: representación completa del recurso
|
||||||
|
- `DetailResponseSchema`: response de detalle
|
||||||
|
- `ListResponseSchema`: response de listado
|
||||||
|
- `CreatedResponseSchema`: confirmación o payload de creación
|
||||||
|
- `DeletedResponseSchema`: confirmación explícita de borrado cuando no se usa `204`
|
||||||
|
|
||||||
---
|
## Ejemplos
|
||||||
|
|
||||||
## 3️⃣ Reglas generales
|
### Customers
|
||||||
|
|
||||||
1. **Singular siempre** para el nombre base de la entidad.
|
|
||||||
2. El **verbo solo** en operaciones sobre un recurso único o mutaciones.
|
|
||||||
3. En listados, el **sustantivo principal primero** (`ProductList`, `CustomerList`), nunca `ListProduct`.
|
|
||||||
4. Sufijos estándar:
|
|
||||||
- `CommandSchema` → body de mutaciones (create/update/patch).
|
|
||||||
- `ParamsSchema` → route params (`/:id`).
|
|
||||||
- `QuerySchema` → query string no normalizada.
|
|
||||||
- `CriteriaSchema` → query normalizada con patrón Criteria.
|
|
||||||
- `ResponseSchema` → respuesta de la API.
|
|
||||||
5. Mantener **consistencia**: si usas `ProductListCriteriaSchema`, no alternes con `ProductsCriteriaSchema`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4️⃣ Ejemplos positivos y negativos
|
|
||||||
|
|
||||||
| ✅ Correcto | ❌ Incorrecto | Motivo del error |
|
|
||||||
|------------------------------------------|---------------------------------------------|----------------------------------------------|
|
|
||||||
| `GetCustomerByIdParamsSchema` | `CustomerGetByIdParamsSchema` | Orden invertido. |
|
|
||||||
| `ProductListCriteriaSchema` | `ListProductCriteriaSchema` | Orden “ListProduct” no natural en inglés. |
|
|
||||||
| `CustomerListResponseSchema` | `CustomersListResponseSchema` | Plural innecesario. |
|
|
||||||
| `DeleteInvoiceByIdParamsSchema` | `DeleteInvoicesByIdParamsSchema` | Plural innecesario en recurso único. |
|
|
||||||
| `UpdateProductByIdCommandSchema` | `UpdateProductCommandByIdSchema` | `ById` debe ir antes del tipo de esquema. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
# Convenciones de nombres para Schemas (Requests y Responses)
|
|
||||||
|
|
||||||
## Tabla de rutas ↔ Schemas
|
|
||||||
|
|
||||||
| **Método** | **Ruta** | **Caso de uso** | **Request Schema (validación)** | **Response Schema (DTO)** | **Notas** |
|
|
||||||
|-----------:|---------------------|--------------------------------------------------|--------------------------------------------|-------------------------------------------|-----------|
|
|
||||||
| GET | `/entities` | Listado con criterios (Criteria normalizado) | `EntityListCriteriaSchema` | `EntityListResponseSchema` | `data: EntitySummary[]`, `pageNumber`, `pageSize`, `totalItems`. |
|
|
||||||
| GET | `/entities` | Búsqueda/filtrado por query params “raw” | `SearchEntityQuerySchema` | `EntityListResponseSchema` | Úsalo si **no** aplicas patrón Criteria. |
|
|
||||||
| GET | `/entities/:id` | Obtener por ID | `GetEntityByIdParamsSchema` | `EntityDetailResponseSchema` | Si devuelves el recurso “puro”, puedes usar `EntityResourceSchema`. |
|
|
||||||
| POST | `/entities` | Crear recurso | `CreateEntityCommandSchema` | `EntityCreatedResponseSchema` **o** `EntityResourceSchema` | Si devuelves 201 + recurso completo, usa `EntityResourceSchema`. |
|
|
||||||
| PUT | `/entities/:id` | Reemplazar recurso (idempotente) | `UpdateEntityCommandSchema` + `UpdateEntityByIdParamsSchema` | `EntityResourceSchema` | También válido con PATCH para cambios parciales. |
|
|
||||||
| PATCH | `/entities/:id` | Actualizar parcialmente | `PatchEntityCommandSchema` + `UpdateEntityByIdParamsSchema` | `EntityResourceSchema` | Usa un comando separado si el payload difiere del PUT. |
|
|
||||||
| DELETE | `/entities/:id` | Eliminar por ID | `DeleteEntityByIdParamsSchema` | — (204 No Content) **o** `EntityDeletedResponseSchema` | 204 recomendado. Si devuelves body, usa `EntityDeletedResponseSchema`. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Esquemas reutilizables (Responses)
|
|
||||||
|
|
||||||
- **Item para listados:**
|
|
||||||
`EntitySummarySchema`
|
|
||||||
|
|
||||||
- **Recurso completo:**
|
|
||||||
`EntityResourceSchema`
|
|
||||||
|
|
||||||
- **Respuestas compuestas:**
|
|
||||||
- `EntityListResponseSchema` → `data: EntitySummarySchema[]` + paginación/meta
|
|
||||||
- `EntityDetailResponseSchema` → típicamente `EntityResourceSchema`
|
|
||||||
- `EntityCreatedResponseSchema` → `{ id, createdAt, ... }` si no devuelves el recurso completo
|
|
||||||
- `EntityDeletedResponseSchema` → `{ id, deleted: true }` si no usas 204
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Convenciones de sufijos
|
|
||||||
|
|
||||||
### Requests
|
|
||||||
- `...CommandSchema` → **body** (mutaciones)
|
|
||||||
- `...ParamsSchema` → **route params** (`/:id`)
|
|
||||||
- `...QuerySchema` → **query string** (`?page=...`)
|
|
||||||
- `...CriteriaSchema` → para queries normalizadas con patrón Criteria
|
|
||||||
|
|
||||||
### Responses
|
|
||||||
- `...ListResponseSchema` → listado paginado
|
|
||||||
- `...SummarySchema` → item de listado
|
|
||||||
- `...DetailResponseSchema` → detalle de recurso
|
|
||||||
- `...ResourceSchema` → representación completa del recurso
|
|
||||||
- `...CreatedResponseSchema` → recurso recién creado
|
|
||||||
- `...DeletedResponseSchema` → confirmación de borrado (si no usas 204)
|
|
||||||
|
|
||||||
# Ejemplos de nombres de Schemas por entidad
|
|
||||||
|
|
||||||
## 1. Customers
|
|
||||||
|
|
||||||
### Requests
|
|
||||||
- `CreateCustomerCommandSchema`
|
- `CreateCustomerCommandSchema`
|
||||||
- `UpdateCustomerCommandSchema`
|
|
||||||
- `PatchCustomerCommandSchema`
|
|
||||||
- `DeleteCustomerByIdParamsSchema`
|
|
||||||
- `GetCustomerByIdParamsSchema`
|
- `GetCustomerByIdParamsSchema`
|
||||||
- `CustomerListCriteriaSchema`
|
- `CustomerListCriteriaSchema`
|
||||||
- `SearchCustomerQuerySchema`
|
|
||||||
|
|
||||||
### Responses
|
|
||||||
- `CustomerSummarySchema`
|
- `CustomerSummarySchema`
|
||||||
- `CustomerResourceSchema`
|
- `CustomerResourceSchema`
|
||||||
- `CustomerListResponseSchema`
|
- `CustomerListResponseSchema`
|
||||||
- `CustomerDetailResponseSchema`
|
|
||||||
- `CustomerCreatedResponseSchema`
|
|
||||||
- `CustomerDeletedResponseSchema`
|
|
||||||
|
|
||||||
---
|
### Proformas
|
||||||
|
|
||||||
## 2. Products
|
- `CreateProformaCommandSchema`
|
||||||
|
- `UpdateProformaByIdParamsSchema`
|
||||||
|
- `ProformaListCriteriaSchema`
|
||||||
|
- `ProformaSummarySchema`
|
||||||
|
- `ProformaDetailResponseSchema`
|
||||||
|
|
||||||
### Requests
|
## Errores a evitar
|
||||||
- `CreateProductCommandSchema`
|
|
||||||
- `UpdateProductCommandSchema`
|
|
||||||
- `PatchProductCommandSchema`
|
|
||||||
- `DeleteProductByIdParamsSchema`
|
|
||||||
- `GetProductByIdParamsSchema`
|
|
||||||
- `ProductListCriteriaSchema`
|
|
||||||
- `SearchProductQuerySchema`
|
|
||||||
|
|
||||||
### Responses
|
- `ListCustomerCriteriaSchema`
|
||||||
- `ProductSummarySchema`
|
- `CustomersListResponseSchema`
|
||||||
- `ProductResourceSchema`
|
- `UpdateCustomerCommandByIdSchema`
|
||||||
- `ProductListResponseSchema`
|
- `ProductGetByIdParamsSchema`
|
||||||
- `ProductDetailResponseSchema`
|
|
||||||
- `ProductCreatedResponseSchema`
|
|
||||||
- `ProductDeletedResponseSchema`
|
|
||||||
|
|
||||||
---
|
## Relación con la guía general
|
||||||
|
|
||||||
## Notas
|
La guía de DTOs define:
|
||||||
- Reutilizar esquemas básicos (`EntitySummarySchema`, `EntityResourceSchema`) en respuestas compuestas para cumplir el principio **DRY**.
|
|
||||||
- **Sufijos**: mantener siempre los sufijos `CommandSchema`, `ParamsSchema`, `QuerySchema`, `CriteriaSchema`, `ListResponseSchema`, `SummarySchema`, `DetailResponseSchema`, `ResourceSchema`, `CreatedResponseSchema`, `DeletedResponseSchema`.
|
- organización por carpetas
|
||||||
- **Pluralización**: usar singular para el nombre de la entidad en los Schemas (ej. `Customer`, `Product`), incluso si la ruta es plural (`/customers`, `/products`).
|
- tipado semántico
|
||||||
- **Carpetas sugeridas**:
|
- manejo de `null`, `optional()` y colecciones
|
||||||
modules/
|
- diseño de `create`, `update`, `list` y `detail`
|
||||||
<entity>/
|
|
||||||
application/
|
Este documento solo fija el naming.
|
||||||
dto/
|
|
||||||
requests/
|
|
||||||
responses/
|
|
||||||
domain/
|
|
||||||
infrastructure/
|
|
||||||
api/
|
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
# Guía de diseño de DTOs para el ERP
|
# Guía de diseño de DTOs para el ERP
|
||||||
|
|
||||||
|
Documento principal para el diseño de DTOs y schemas HTTP del proyecto.
|
||||||
|
|
||||||
|
Para reglas de naming más puntuales, consultar también [CONVENCION NOMBRADO DE ESQUEMAS DTO.md](./CONVENCION NOMBRADO DE ESQUEMAS DTO.md).
|
||||||
|
|
||||||
## 1. Principios generales
|
## 1. Principios generales
|
||||||
|
|
||||||
- Un DTO representa únicamente el contrato de transporte entre cliente y servidor.
|
- Un DTO representa únicamente el contrato de transporte entre cliente y servidor.
|
||||||
|
|||||||
@ -1,56 +1,20 @@
|
|||||||
|
# Facturas de Cliente (Notas Históricas)
|
||||||
|
|
||||||
|
Este fichero contenía notas sueltas y ejemplos tempranos sobre facturación. Ya no actúa como documento operativo.
|
||||||
|
|
||||||
Funcionalidades
|
## Estado
|
||||||
---------------
|
|
||||||
|
|
||||||
- Llamar a las facturas "borrador" como PROFORMA??
|
- histórico
|
||||||
|
- no canónico
|
||||||
|
- supersedido por la documentación de `customer-invoices`
|
||||||
|
|
||||||
- Baja lógica de facturas
|
## Documentación vigente
|
||||||
- Los datos del cliente están en la factura
|
|
||||||
- Descuento general (2 decimales)
|
|
||||||
- Resumen de impuestos
|
|
||||||
- Referencia de cliente -> ???
|
|
||||||
- Idioma
|
|
||||||
- Divisa
|
|
||||||
- Observaciones
|
|
||||||
- Descripción de la operación -> obligatorio según Verifactu
|
|
||||||
|
|
||||||
|
- [Índice de `customer-invoices`](./customer-invoices/README.md)
|
||||||
|
- [Contrato de create de proformas](./customer-invoices/proforma-create-contract.md)
|
||||||
|
- [Contrato de series de proformas](./customer-invoices/proforma-series-contract.md)
|
||||||
|
- [Reports de proformas](./customer-invoices/proformas-reports.md)
|
||||||
|
|
||||||
Detalles:
|
## Motivo de conservación
|
||||||
- Descripción
|
|
||||||
- Cantidad -> 2 decimales
|
|
||||||
- Importe unidad -> 4 decimales
|
|
||||||
- IVA en los detalles
|
|
||||||
- Descuento -> 2 decimales
|
|
||||||
|
|
||||||
Futuros:
|
Se mantiene solo como rastro de brainstorming previo y no debe emplearse para extraer contratos, payloads ni reglas funcionales actuales.
|
||||||
- Unidad de medida -> texto
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Formas de pago:
|
|
||||||
- Sin plazo -> texto + establecer una fecha manualemente en el futuro
|
|
||||||
- Con plazo -> texto + fechas pre-establecidas no editables
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
"serie": "A",
|
|
||||||
"numero": "2",
|
|
||||||
"fecha_expedicion": "15-07-2025",
|
|
||||||
"tipo_factura": "F1",
|
|
||||||
"descripcion": "Mensualidad de soporte",
|
|
||||||
"nif": "A15022510",
|
|
||||||
"nombre": "Empresa de prueba SL",
|
|
||||||
"lineas": [
|
|
||||||
{
|
|
||||||
"base_imponible": "200",
|
|
||||||
"tipo_impositivo": "21",
|
|
||||||
"impuesto": "01",
|
|
||||||
"clave_regimen": "01",
|
|
||||||
"cuota_repercutida": "42"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"importe_total": "242"
|
|
||||||
}
|
|
||||||
|
|||||||
122
docs/README.md
122
docs/README.md
@ -1,87 +1,83 @@
|
|||||||
# ERP Docs
|
# ERP Docs
|
||||||
|
|
||||||
Este directorio reúne la documentación operativa y arquitectónica del ERP.
|
Este directorio reúne la documentación técnica, operativa y de apoyo del ERP.
|
||||||
|
|
||||||
## Contexto técnico
|
## Cómo leer esta documentación
|
||||||
Monorepo con:
|
|
||||||
- Node.js >= 22
|
|
||||||
- TypeScript
|
|
||||||
- PNPM
|
|
||||||
- Turborepo
|
|
||||||
- ExpressJS
|
|
||||||
- MariaDB
|
|
||||||
- Sequelize v6
|
|
||||||
- Vitest
|
|
||||||
|
|
||||||
Arquitectura:
|
- Empieza por este índice si necesitas orientarte.
|
||||||
- DDD pragmático
|
- Usa los documentos en subdirectorios temáticos como fuente de verdad operativa.
|
||||||
- Clean Architecture
|
- Trata los documentos marcados como históricos o legacy como contexto, no como contrato vigente.
|
||||||
- Separación Domain / Application / Infrastructure
|
|
||||||
- SOLID, DRY, KISS, YAGNI, SSOT
|
|
||||||
- Fail Fast
|
|
||||||
- Defensive Programming
|
|
||||||
- Cambios incrementales
|
|
||||||
- Evitar sobreingeniería
|
|
||||||
|
|
||||||
## Documentación vigente
|
## Estructura documental
|
||||||
|
|
||||||
### Arquitectura general
|
### Documentación vigente
|
||||||
|
|
||||||
- [Arquitectura de identity y companies](./architecture/identity-and-companies.md)
|
#### Arquitectura y runtime
|
||||||
|
|
||||||
|
- [Arquitectura general](./architecture/README.md)
|
||||||
- [Contexto tenant y `X-Company-Id`](./architecture/tenant-context.md)
|
- [Contexto tenant y `X-Company-Id`](./architecture/tenant-context.md)
|
||||||
- [API de autenticación identity](./architecture/identity-auth-api.md)
|
- [API de autenticación de `identity`](./architecture/identity-auth-api.md)
|
||||||
- [Estado de migración a identity](./architecture/identity-migration-status.md)
|
- [Relación entre `identity` y `companies`](./architecture/identity-and-companies.md)
|
||||||
- [Flujo frontend de auth y selección de empresa](./frontend/auth-and-company-selection.md)
|
- [Estado de migración a `identity`](./architecture/identity-migration-status.md)
|
||||||
|
|
||||||
### Document series
|
#### Frontend
|
||||||
|
|
||||||
|
- [Flujo de auth y selección de empresa](./frontend/auth-and-company-selection.md)
|
||||||
|
- [README de `apps/web`](../apps/web/README.md)
|
||||||
|
|
||||||
|
#### Customer invoices y proformas
|
||||||
|
|
||||||
|
- [Índice de `customer-invoices`](./customer-invoices/README.md)
|
||||||
|
- [Contrato de series de proformas](./customer-invoices/proforma-series-contract.md)
|
||||||
|
- [Contrato de create de proformas](./customer-invoices/proforma-create-contract.md)
|
||||||
|
- [Configuración fiscal persistida en proformas](./customer-invoices/proforma-tax-config.md)
|
||||||
|
- [Reports de proformas](./customer-invoices/proformas-reports.md)
|
||||||
|
|
||||||
|
#### Document series
|
||||||
|
|
||||||
- [README de `document-series`](./document-series/README.md)
|
- [README de `document-series`](./document-series/README.md)
|
||||||
- [Diseño de `document-series`](./document-series/document-series-design.md)
|
- [Diseño de `document-series`](./document-series/document-series-design.md)
|
||||||
- [Validación manual de `document-series`](./document-series/manual-validation.md)
|
- [Validación manual de `document-series`](./document-series/manual-validation.md)
|
||||||
|
- [Validación manual de concurrencia](./document-series/manual-concurrency-validation.md)
|
||||||
|
|
||||||
### Customer-invoices / proformas
|
#### Módulos y runtime local
|
||||||
|
|
||||||
- [Contrato de series de proformas](./customer-invoices/proforma-series-contract.md)
|
|
||||||
- [Contrato de create de proformas](./customer-invoices/proforma-create-contract.md)
|
|
||||||
- [Tax config persistido en proformas](./customer-invoices/proforma-tax-config.md)
|
|
||||||
|
|
||||||
### SQL operativos / runbooks vigentes
|
|
||||||
|
|
||||||
- [DDL de series documentales en proformas](./customer-invoices/sql/add-proforma-document-series-columns.sql)
|
|
||||||
- [DDL de `tax_config` en proformas](./customer-invoices/sql/add-proforma-tax-config-columns.sql)
|
|
||||||
- [Validación de migración a `document_series`](./document-series/sql/validate-document-series-migration.sql)
|
|
||||||
- [Validación de `tax_config` en proformas](./customer-invoices/sql/validate-proforma-tax-config.sql)
|
|
||||||
|
|
||||||
### Referencias de módulos
|
|
||||||
|
|
||||||
- [README de `modules/auth`](../modules/auth/README.md)
|
|
||||||
- [README de `modules/identity`](../modules/identity/README.md)
|
- [README de `modules/identity`](../modules/identity/README.md)
|
||||||
- [README de `modules/companies`](../modules/companies/README.md)
|
- [README de `modules/companies`](../modules/companies/README.md)
|
||||||
- [README de `apps/web`](../apps/web/README.md)
|
- [README de `modules/auth`](../modules/auth/README.md)
|
||||||
|
|
||||||
## Histórico / migraciones transicionales
|
#### Guías y estándares
|
||||||
|
|
||||||
- [Migración desde `customer_invoice_series` a `document_series`](./document-series/migration-from-customer-invoice-series.md)
|
- [Guía de diseño de DTOs](./DTOS - GUÍA DE DISEÑO.md)
|
||||||
- [Split histórico Proforma / IssuedInvoice](./customer-invoices/split-proformas-issued-invoices-migration.md)
|
- [Convención de nombres de schemas DTO](./CONVENCION NOMBRADO DE ESQUEMAS DTO.md)
|
||||||
- [InvoiceSeries legacy](./customer-invoices/invoice-series.md)
|
|
||||||
- [Drop manual de `customer_invoice_series`](./document-series/sql/drop-customer-invoice-series-legacy.sql)
|
#### SQL operativos y runbooks
|
||||||
- [SQL histórico de `customer_invoice_series`](./dev/customer-invoice-series.sql)
|
|
||||||
- [Runbooks SQL históricos de `customer-invoices`](./customer-invoices/sql/README.md)
|
- [SQL de `customer-invoices`](./customer-invoices/sql/README.md)
|
||||||
|
- [Validación de migración a `document-series`](./document-series/sql/validate-document-series-migration.sql)
|
||||||
- [Seed local de admin](./dev/seed-local-admin.sql)
|
- [Seed local de admin](./dev/seed-local-admin.sql)
|
||||||
|
|
||||||
## Estado actual resumido
|
### Material auxiliar
|
||||||
|
|
||||||
- El backend nuevo usa `modules/identity` para autenticación, sesión y control de acceso a companies.
|
- [Base común para prompts backend](./backend-prompt-base.md)
|
||||||
- `modules/companies` es la fuente de verdad de la ficha operativa de empresa.
|
- [Prompt de auth](./prompt-auth.md)
|
||||||
- El frontend ERP ya usa `@erp/identity/client` como runtime de autenticación.
|
- [Prompt de customer invoices](./prompt-customer-invoices.md)
|
||||||
- El flujo actual resuelve sesión, companies disponibles, auto-selección de empresa única y refresh automático sobre `401`.
|
- [Plantilla auxiliar](./TEMPLATE.md)
|
||||||
|
|
||||||
## Reglas de lectura rápida
|
### Histórico, transicional o de descubrimiento
|
||||||
|
|
||||||
- `modules/auth` debe considerarse legacy/deprecated para backend nuevo.
|
Estos documentos se conservan por trazabilidad, auditoría o contexto de decisiones. No deben usarse como fuente de verdad del runtime actual sin contrastarlos con la documentación vigente.
|
||||||
- `GET /companies/available` es el endpoint vigente para cargar companies accesibles.
|
|
||||||
- `GET /identity/companies` no es un endpoint válido del diseño actual.
|
- [Borrador legacy de API](./API.md)
|
||||||
- `GET /account/companies` tampoco es un endpoint válido del diseño actual.
|
- [Requisitos generales históricos](./REQUISITOS GENERALES.md)
|
||||||
- Las rutas tenant-scoped deben usar `Authorization` + `X-Company-Id`.
|
- [Requisitos históricos del módulo de clientes](./REQUISITOS CLIENTES.md)
|
||||||
- `/document-series` es la API canónica de series documentales.
|
- [Notas históricas de facturas de cliente](./FACTURAS DE CLIENTE.md)
|
||||||
- `/catalogs/invoice-series` no es un endpoint activo.
|
- [Split histórico Proforma / IssuedInvoice](./customer-invoices/split-proformas-issued-invoices-migration.md)
|
||||||
|
- [Migración desde `customer_invoice_series`](./document-series/migration-from-customer-invoice-series.md)
|
||||||
|
- [InvoiceSeries legacy](./customer-invoices/invoice-series.md)
|
||||||
|
|
||||||
|
## Regla práctica de mantenimiento
|
||||||
|
|
||||||
|
- Los documentos operativos vigentes deben vivir en subdirectorios temáticos y tener una responsabilidad clara.
|
||||||
|
- Los documentos raíz en mayúsculas o con naming heredado deben considerarse legacy o auxiliares salvo que este índice los declare explícitamente vigentes.
|
||||||
|
- Si una regla ya tiene fuente canónica, los demás documentos deben resumirla y enlazarla, no copiarla.
|
||||||
|
|||||||
@ -1,165 +1,32 @@
|
|||||||
# **Especificaciones del Módulo de Clientes - ERP**
|
# Requisitos del Módulo de Clientes (Histórico / Descubrimiento)
|
||||||
|
|
||||||
El **módulo de clientes** del ERP permite gestionar la información de clientes, asegurando compatibilidad con la gestión multiempresa, multiidioma y multi-moneda. A continuación, se detallan las especificaciones, incluyendo los campos clave y su función.
|
Este documento recoge requisitos tempranos del módulo de clientes y ya no debe leerse como contrato técnico vigente.
|
||||||
|
|
||||||
---
|
## Estado
|
||||||
|
|
||||||
## **1. Funcionalidades del Módulo de Clientes**
|
- Documento histórico de análisis funcional.
|
||||||
El módulo de clientes debe permitir:
|
- Aporta vocabulario de negocio y posibles campos de cliente.
|
||||||
- **Crear, leer, actualizar y eliminar clientes** (CRUD).
|
- No define endpoints, DTOs ni persistencia actuales del módulo.
|
||||||
- **Asociar clientes a una empresa** y, opcionalmente, a una o más sucursales.
|
|
||||||
- **Registrar clientes como personas físicas o empresas**.
|
|
||||||
- **Gestionar datos fiscales, comerciales y de contacto**.
|
|
||||||
- **Definir configuraciones de facturación y pago personalizadas**.
|
|
||||||
- **Aplicar descuentos en distintos niveles**.
|
|
||||||
- **Manejar aspectos financieros como retenciones y recargos**.
|
|
||||||
- **Identificar clientes con riesgo financiero**.
|
|
||||||
- **Asignar comerciales o delegados**.
|
|
||||||
- **Controlar el estado del cliente** (activo/inactivo).
|
|
||||||
- **Registrar auditoría de cambios**.
|
|
||||||
|
|
||||||
---
|
## Qué puede seguir siendo útil
|
||||||
|
|
||||||
## **2. Estructura del Cliente y sus Campos**
|
- tipologías de datos de cliente
|
||||||
Cada cliente tiene los siguientes atributos:
|
- necesidades de facturación, fiscalidad y contacto
|
||||||
|
- consideraciones de auditoría y permisos
|
||||||
|
|
||||||
### **Datos Generales**
|
## Qué no debe asumirse como vigente
|
||||||
| Campo | Tipo de Dato | Descripción |
|
|
||||||
|-----------------------|-----------------|-------------|
|
|
||||||
| `id` | `INT` (PK) | Identificador único del cliente. |
|
|
||||||
| `company_id` | `INT` (FK) | Empresa a la que pertenece el cliente. |
|
|
||||||
| `is_company` | `BOOLEAN` | Indica si el cliente es una empresa (`true`) o una persona física (`false`). |
|
|
||||||
| `fiscal_name` | `VARCHAR(255)` | Nombre fiscal del cliente (solo si es empresa). |
|
|
||||||
| `commercial_name` | `VARCHAR(255)` | Nombre comercial del cliente (si aplica). |
|
|
||||||
| `name` | `VARCHAR(255)` | Nombre de la persona o empresa. |
|
|
||||||
| `email` | `VARCHAR(100)` | Correo electrónico único del cliente. |
|
|
||||||
| `phone` | `VARCHAR(20)` | Número de teléfono de contacto. |
|
|
||||||
| `address` | `TEXT` | Dirección del cliente. |
|
|
||||||
| `country_code` | `CHAR(2)` | Código del país del cliente (ISO 3166-1 alpha-2). |
|
|
||||||
| `origin` | `VARCHAR(100)` | Origen del cliente (ej. publicidad, redes sociales, feria, escaparate). |
|
|
||||||
|
|
||||||
### **Configuración de Moneda e Idioma**
|
- rutas `/api/...`
|
||||||
| Campo | Tipo de Dato | Descripción |
|
- tipos SQL y tablas propuestas
|
||||||
|---------------------|-----------------|-------------|
|
- naming técnico de campos y relaciones
|
||||||
| `currency_code` | `CHAR(3)` | Código de la divisa con la que trabaja el cliente (ISO 4217). |
|
- contratos HTTP o middleware de acceso
|
||||||
| `language_code` | `CHAR(5)` | Idioma preferido del cliente para documentos y comunicación (ej. `es-ES`, `en-US`). |
|
|
||||||
|
|
||||||
### **Información Fiscal y Financiera**
|
## Referencias actuales
|
||||||
| Campo | Tipo de Dato | Descripción |
|
|
||||||
|------------------------|----------------|-------------|
|
|
||||||
| `vat_percentage` | `DECIMAL(5,2)` | Porcentaje de IVA aplicable al cliente. |
|
|
||||||
| `equivalence_charge` | `BOOLEAN` | Indica si el cliente aplica recargo de equivalencia. |
|
|
||||||
| `withholding_percentage` | `DECIMAL(5,2)` | Porcentaje de retención en facturas del cliente. |
|
|
||||||
| `payment_method` | `VARCHAR(100)` | Forma de pago habitual del cliente (ej. transferencia, tarjeta, efectivo). |
|
|
||||||
| `payment_day` | `INT` | Día del mes en que el cliente realiza pagos (1-31). |
|
|
||||||
| `risk` | `BOOLEAN` | Indica si el cliente tiene riesgo financiero o historial de morosidad. |
|
|
||||||
|
|
||||||
### **Descuentos y Tarifas Especiales**
|
- Arquitectura del repositorio: [README.md](../README.md)
|
||||||
| Campo | Tipo de Dato | Descripción |
|
- Índice de docs: [README.md](./README.md)
|
||||||
|---------------------|----------------|-------------|
|
- Reglas de DTO y schemas: [DTOS - GUÍA DE DISEÑO.md](./DTOS - GUÍA DE DISEÑO.md) y [CONVENCION NOMBRADO DE ESQUEMAS DTO.md](./CONVENCION NOMBRADO DE ESQUEMAS DTO.md)
|
||||||
| `discount_line` | `DECIMAL(5,2)` | Descuento aplicado a nivel de línea en presupuestos. |
|
|
||||||
| `discount_chapter` | `DECIMAL(5,2)` | Descuento aplicado a nivel de capítulo en presupuestos. |
|
|
||||||
| `discount_global` | `DECIMAL(5,2)` | Descuento global aplicado al presupuesto. |
|
|
||||||
| `price_point` | `DECIMAL(10,2)` | Valor de "precio punto" del cliente, usado para calcular costos personalizados en catálogo. |
|
|
||||||
|
|
||||||
### **Clasificación y Estado**
|
## Motivo de conservación
|
||||||
| Campo | Tipo de Dato | Descripción |
|
|
||||||
|---------------|----------------|-------------|
|
|
||||||
| `client_type` | `VARCHAR(100)` | Tipo de cliente (ej. profesionales, constructoras, distribuidores, particulares). |
|
|
||||||
| `is_active` | `BOOLEAN` | Indica si el cliente está activo (`true`) o dado de baja (`false`). |
|
|
||||||
|
|
||||||
### **Delegados y Comerciales**
|
Se conserva como material de descubrimiento funcional previo al diseño actual.
|
||||||
| Campo | Tipo de Dato | Descripción |
|
|
||||||
|-----------------|----------------|-------------|
|
|
||||||
| `Client_Sales_Rep` | `Tabla Relacional` | Relación con los comerciales asignados a este cliente. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## **3. Auditoría y Seguridad**
|
|
||||||
Cada cambio en los datos de un cliente debe registrarse en una tabla de auditoría:
|
|
||||||
```sql
|
|
||||||
CREATE TABLE Client_Audit (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
client_id INT NOT NULL REFERENCES Clients(id) ON DELETE CASCADE,
|
|
||||||
user_id INT NOT NULL REFERENCES Users(id),
|
|
||||||
change_description TEXT,
|
|
||||||
change_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
Esto permitirá rastrear modificaciones y garantizar transparencia en la gestión de clientes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## **4. Endpoints Principales de la API**
|
|
||||||
### **4.1. Listar Clientes**
|
|
||||||
```http
|
|
||||||
GET /api/companies/{company_id}/clients
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
### **4.2. Crear Cliente**
|
|
||||||
```http
|
|
||||||
POST /api/companies/{company_id}/clients
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
**Solicitud:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"is_company": true,
|
|
||||||
"fiscal_name": "Empresa XYZ SL",
|
|
||||||
"commercial_name": "XYZ Comercial",
|
|
||||||
"name": "Empresa XYZ",
|
|
||||||
"email": "contacto@xyz.com",
|
|
||||||
"phone": "+34912345678",
|
|
||||||
"country_code": "ES",
|
|
||||||
"origin": "Publicidad",
|
|
||||||
"currency_code": "EUR",
|
|
||||||
"language_code": "es-ES",
|
|
||||||
"vat_percentage": 21.00,
|
|
||||||
"payment_method": "Transferencia bancaria",
|
|
||||||
"discount_line": 5.00,
|
|
||||||
"discount_chapter": 2.50,
|
|
||||||
"discount_global": 10.00,
|
|
||||||
"price_point": 1.20,
|
|
||||||
"payment_day": 15,
|
|
||||||
"risk": false,
|
|
||||||
"equivalence_charge": false,
|
|
||||||
"withholding_percentage": 10.00,
|
|
||||||
"client_type": "Distribuidor",
|
|
||||||
"is_active": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### **4.3. Obtener Cliente por ID**
|
|
||||||
```http
|
|
||||||
GET /api/clients/{client_id}
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
### **4.4. Actualizar Cliente**
|
|
||||||
```http
|
|
||||||
PUT /api/clients/{client_id}
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
### **4.5. Baja Lógica de Cliente**
|
|
||||||
```http
|
|
||||||
PATCH /api/clients/{client_id}/deactivate
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
### **4.6. Asignar Comercial a Cliente**
|
|
||||||
```http
|
|
||||||
POST /api/clients/{client_id}/sales-reps
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## **5. Seguridad y Control de Acceso**
|
|
||||||
- **Middleware de autenticación** para validar que solo usuarios autorizados gestionen clientes.
|
|
||||||
- **Permisos basados en roles**, como `Clientes: leer`, `Clientes: escribir`, `Clientes: eliminar`.
|
|
||||||
- **Reglas de validación** para evitar datos erróneos o incompletos.
|
|
||||||
- **Registro de auditoría** para cada modificación.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|||||||
@ -1,207 +1,28 @@
|
|||||||
# **Requisitos Técnicos del ERP**
|
# Requisitos Generales (Histórico / Descubrimiento)
|
||||||
|
|
||||||
## **1. Administración de Usuarios**
|
Este documento resume requisitos funcionales tempranos del ERP. No define el runtime actual ni sustituye la documentación operativa vigente.
|
||||||
|
|
||||||
- **Roles y Perfiles**:
|
## Estado
|
||||||
|
|
||||||
- Los usuarios pueden tener uno o más perfiles predefinidos (por ejemplo, "Ventas", "Compras").
|
- Documento histórico de descubrimiento.
|
||||||
- Los perfiles controlan permisos generales a nivel de empresa y refinados a nivel de sucursal.
|
- Útil para entender intención de producto y necesidades de negocio amplias.
|
||||||
- Un usuario puede tener diferentes perfiles en distintas sucursales de la misma empresa.
|
- No usar como contrato técnico actual de autenticación, tenant context, JWT, base de datos o endpoints.
|
||||||
|
|
||||||
- **Permisos**:
|
## Temas que sí conserva como contexto
|
||||||
|
|
||||||
- Configurables por módulo (Presupuestos, Facturas, etc.).
|
- multiempresa y multisucursal
|
||||||
- Granularidad: permitir acciones específicas como "leer", "escribir", "eliminar".
|
- auditoría y trazabilidad
|
||||||
- Un usuario puede tener permisos para acceder a una o más sucursales de la empresa, pero debe trabajar en una empresa activa por sesión.
|
- seguridad y permisos
|
||||||
- Si el usuario solo tiene permisos para una sucursal, se selecciona automáticamente. Si tiene acceso a varias, se selecciona solo cuando sea necesario.
|
- soporte multiidioma
|
||||||
|
- extensibilidad modular
|
||||||
|
|
||||||
- **Superadministrador**:
|
## Documentación vigente relacionada
|
||||||
- Puede crear, modificar y eliminar usuarios.
|
|
||||||
- Gestiona perfiles, permisos y asociaciones con empresas y sucursales.
|
|
||||||
|
|
||||||
---
|
- Arquitectura y tenant context: [architecture/README.md](./architecture/README.md)
|
||||||
|
- Auth actual: [architecture/identity-auth-api.md](./architecture/identity-auth-api.md)
|
||||||
|
- Flujo frontend: [frontend/auth-and-company-selection.md](./frontend/auth-and-company-selection.md)
|
||||||
|
- Módulos documentales: [customer-invoices/README.md](./customer-invoices/README.md) y [document-series/README.md](./document-series/README.md)
|
||||||
|
|
||||||
## **2. Gestión Multiempresa y Multisucursal**
|
## Nota
|
||||||
|
|
||||||
- **Usuarios y Empresas**:
|
Las decisiones antiguas sobre JWT con `active_company_id`, `tab_id`, tablas SQL de ejemplo y flujos de selección tempranos quedaron superadas por la arquitectura actual y se conservan solo a nivel histórico.
|
||||||
|
|
||||||
- Un usuario puede estar asociado a múltiples empresas.
|
|
||||||
- En cada sesión o pestaña, el usuario puede trabajar con una empresa específica.
|
|
||||||
- La empresa activa se elige al inicio de sesión.
|
|
||||||
|
|
||||||
- **Sucursales**:
|
|
||||||
|
|
||||||
- Cada empresa debe tener al menos una sucursal, pero puede tener varias.
|
|
||||||
- Los usuarios de una sucursal pueden gestionar datos de otras sucursales si tienen los permisos adecuados.
|
|
||||||
- Se permite definir permisos para operar en múltiples sucursales dentro de la misma empresa.
|
|
||||||
|
|
||||||
- **Flujo de Selección**:
|
|
||||||
- Al iniciar sesión, el usuario elige la empresa activa.
|
|
||||||
- Si tiene acceso a una única sucursal, esta se selecciona automáticamente.
|
|
||||||
- Si tiene acceso a múltiples sucursales, la selección de sucursal ocurre solo cuando sea necesario para una operación específica.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## **3. Soporte Multiidioma y Regionalización**
|
|
||||||
|
|
||||||
- **Idiomas**:
|
|
||||||
|
|
||||||
- La interfaz del ERP debe estar disponible en varios idiomas.
|
|
||||||
- Los documentos (presupuestos, facturas) pueden generarse en el idioma del cliente.
|
|
||||||
- El idioma de usuario es configurable en su perfil.
|
|
||||||
|
|
||||||
- **Monedas e Impuestos**:
|
|
||||||
- Configuración de moneda por empresa, con posibilidad de conversión.
|
|
||||||
- Definición de impuestos según país (IVA, retenciones, tasas locales).
|
|
||||||
- Flexibilidad para configurar tarifas por categoría de productos o servicios.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## **4. Contexto de Sesión y Pestaña**
|
|
||||||
|
|
||||||
- **Contexto por Pestaña**:
|
|
||||||
|
|
||||||
- Cada pestaña tiene un contexto independiente (`tab_id`), que incluye la empresa activa.
|
|
||||||
- Si se requiere una sucursal para la operación, se solicitará en ese momento.
|
|
||||||
- Se almacena el `tab_id` junto con la empresa activa en el backend para cada sesión de usuario.
|
|
||||||
|
|
||||||
- **Gestión de Sesiones**:
|
|
||||||
- Tokens JWT deben incluir información sobre:
|
|
||||||
- `user_id`
|
|
||||||
- `tab_id`
|
|
||||||
- `active_company_id`
|
|
||||||
- Lista de permisos específicos
|
|
||||||
- Se debe registrar el contexto de pestaña en la base de datos para auditoría y trazabilidad.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## **5. Auditoría y Registro de Actividades**
|
|
||||||
|
|
||||||
- **Registro de Cambios en Documentos**:
|
|
||||||
|
|
||||||
- Presupuestos, facturas y otros documentos deben mantener un historial detallado de modificaciones.
|
|
||||||
- Se registra la siguiente información:
|
|
||||||
- Usuario que realizó el cambio.
|
|
||||||
- Fecha y hora.
|
|
||||||
- Cambios específicos (antes y después).
|
|
||||||
|
|
||||||
- **Trazabilidad Global**:
|
|
||||||
- Auditar las actividades del usuario a nivel de empresa, sucursal y pestaña.
|
|
||||||
- Se debe asociar cada acción al `tab_id` para identificar actividades por pestaña específica.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## **6. Documentos Operativos**
|
|
||||||
|
|
||||||
- **Historial de Cambios**:
|
|
||||||
|
|
||||||
- Cada documento (presupuestos, facturas, etc.) debe registrar todas las modificaciones con la posibilidad de revertirlas si es necesario.
|
|
||||||
|
|
||||||
- **Exportación Multiidioma**:
|
|
||||||
- Generación de documentos en diferentes idiomas según la configuración del cliente.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## **7. Seguridad**
|
|
||||||
|
|
||||||
- **Autenticación**:
|
|
||||||
|
|
||||||
- Autenticación basada en tokens JWT.
|
|
||||||
- Contraseñas encriptadas con bcrypt o Argon2.
|
|
||||||
- Soporte para autenticación en dos pasos (2FA) en futuras iteraciones.
|
|
||||||
|
|
||||||
- **Control de Acceso**:
|
|
||||||
- Validación de permisos en cada solicitud al backend.
|
|
||||||
- Aplicación del principio de mínimos privilegios para usuarios.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## **8. Extensibilidad y Modularidad**
|
|
||||||
|
|
||||||
- **Estructura Modular**:
|
|
||||||
- El sistema debe permitir la integración de nuevos módulos sin afectar los permisos o el funcionamiento existente.
|
|
||||||
- Módulos actuales planificados:
|
|
||||||
- Presupuestos
|
|
||||||
- Facturas
|
|
||||||
- Clientes
|
|
||||||
- Pedidos
|
|
||||||
- Inventario (futuro)
|
|
||||||
- Recursos Humanos (futuro)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## **9. Base de Datos - Diseño Técnico**
|
|
||||||
|
|
||||||
### **Tablas Principales**
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Usuarios
|
|
||||||
CREATE TABLE Users (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
username VARCHAR(50) UNIQUE NOT NULL,
|
|
||||||
email VARCHAR(100) UNIQUE NOT NULL,
|
|
||||||
password VARCHAR(255) NOT NULL,
|
|
||||||
is_active BOOLEAN DEFAULT TRUE,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Empresas
|
|
||||||
CREATE TABLE Companies (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
name VARCHAR(100) UNIQUE NOT NULL,
|
|
||||||
country_code CHAR(2),
|
|
||||||
currency_code CHAR(3),
|
|
||||||
tax_config JSONB,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Sucursales
|
|
||||||
CREATE TABLE Branches (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
name VARCHAR(100) NOT NULL,
|
|
||||||
company_id INT NOT NULL REFERENCES Companies(id) ON DELETE CASCADE,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Relación Usuario - Empresa
|
|
||||||
CREATE TABLE User_Company (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
user_id INT NOT NULL REFERENCES Users(id),
|
|
||||||
company_id INT NOT NULL REFERENCES Companies(id),
|
|
||||||
is_active BOOLEAN DEFAULT FALSE,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Relación Usuario - Sucursal
|
|
||||||
CREATE TABLE User_Branch (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
user_id INT NOT NULL REFERENCES Users(id),
|
|
||||||
branch_id INT NOT NULL REFERENCES Branches(id),
|
|
||||||
permissions JSONB NOT NULL
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Contexto por pestaña
|
|
||||||
CREATE TABLE Tab_Context (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
user_id INT NOT NULL REFERENCES Users(id),
|
|
||||||
tab_id UUID NOT NULL,
|
|
||||||
company_id INT NOT NULL REFERENCES Companies(id),
|
|
||||||
branch_id INT REFERENCES Branches(id)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|||||||
@ -1,43 +1,15 @@
|
|||||||
### Prompt para tu README / CLI
|
# TEMPLATE
|
||||||
|
|
||||||
**Generar un nuevo módulo ERP desde la plantilla**
|
Plantilla auxiliar para redactar documentación o prompts de trabajo.
|
||||||
|
|
||||||
Nuestro ERP permite crear módulos de forma **estandarizada** usando una **plantilla base** (Hexagonal/DDD, TS, Express, Sequelize) y un **generador Plop**.
|
## Estado
|
||||||
|
|
||||||
**Opción A — con Plop (recomendada)**
|
- material auxiliar
|
||||||
|
- no describe comportamiento del ERP
|
||||||
1. Instala dependencias de desarrollo si no las tienes:
|
- no debe enlazarse como documentación funcional o arquitectónica del proyecto
|
||||||
|
|
||||||
* `pnpm add -D plop`
|
|
||||||
2. Asegúrate de tener en el repo:
|
|
||||||
|
|
||||||
* `plopfile.js` con el generador `module`
|
|
||||||
* Carpeta `templates/new-module/` con la plantilla (placeholders Handlebars: `{{kebabCase name}}`, `{{pascalCase name}}`, etc.)
|
|
||||||
3. Ejecuta el generador:
|
|
||||||
|
|
||||||
* `pnpm plop module`
|
|
||||||
4. Introduce el nombre del módulo en **kebab-case** (p. ej., `suppliers`).
|
|
||||||
5. Se creará `packages/suppliers/` con:
|
|
||||||
|
|
||||||
* `src/api/{application,domain,infrastructure,helpers}` + `common`
|
|
||||||
* Casos de uso CRUD, presenters (FULL/LIST), specs comunes y contratos de repositorio.
|
|
||||||
6. Integra en el monorepo:
|
|
||||||
|
|
||||||
* Añade targets en `turbo.json` si aplica.
|
|
||||||
* Ejecuta `pnpm -w build` para validar compilación.
|
|
||||||
|
|
||||||
**Opción B — manual (desde ZIP de plantilla)**
|
|
||||||
|
|
||||||
1. Descomprime `new-module-template.zip`.
|
|
||||||
2. Copia `new-module/` a `packages/<mi-modulo>/`.
|
|
||||||
3. Renombra los archivos con `entity`/`{{name}}` por el nombre real o usa búsqueda global.
|
|
||||||
4. Ajusta `package.json` (`name`, `scripts`), y registra el módulo en `turbo.json`.
|
|
||||||
5. `pnpm -w install && pnpm -w build`.
|
|
||||||
|
|
||||||
**Convenciones clave**
|
|
||||||
|
|
||||||
* Nombres de archivos/directorios en **kebab-case**; código en **Inglés**; comentarios **TSDoc en castellano**.
|
|
||||||
* Mantén SOLID, SoC, alta cohesión/bajo acoplamiento, y hexagonal (puertos/adaptadores).
|
|
||||||
* Observabilidad (logs estructurados), idempotencia en endpoints, y principio de menor privilegio.
|
|
||||||
|
|
||||||
|
## Uso recomendado
|
||||||
|
|
||||||
|
- reutilizarla solo como apoyo editorial
|
||||||
|
- adaptar el contenido final al área concreta del proyecto
|
||||||
|
- no duplicar desde aquí reglas ya documentadas en los índices y guías vigentes
|
||||||
|
|||||||
@ -1,18 +1,25 @@
|
|||||||
# ERP Architecture Docs
|
# ERP Architecture Docs
|
||||||
|
|
||||||
## Documentos vigentes
|
Índice de arquitectura vigente y documentos transicionales relacionados con autenticación, tenant context y migración a `identity`.
|
||||||
|
|
||||||
|
## Documentación canónica vigente
|
||||||
|
|
||||||
- [Identity and companies](./identity-and-companies.md)
|
- [Identity and companies](./identity-and-companies.md)
|
||||||
- [Tenant context](./tenant-context.md)
|
- [Tenant context](./tenant-context.md)
|
||||||
- [Identity auth API](./identity-auth-api.md)
|
- [Identity auth API](./identity-auth-api.md)
|
||||||
- [Identity backend architecture](./identity-backend-architecture.md)
|
|
||||||
- [Identity migration status](./identity-migration-status.md)
|
- [Identity migration status](./identity-migration-status.md)
|
||||||
- [Identity roadmap](./identity-roadmap.md)
|
- [Identity roadmap](./identity-roadmap.md)
|
||||||
|
|
||||||
|
## Documentación complementaria
|
||||||
|
|
||||||
|
- [Identity backend architecture](./identity-backend-architecture.md)
|
||||||
|
|
||||||
|
`identity-backend-architecture.md` se mantiene como nota complementaria de diseño backend. Para contratos operativos concretos, prevalecen `tenant-context.md`, `identity-auth-api.md` y los README de módulo.
|
||||||
|
|
||||||
## Resumen actual
|
## Resumen actual
|
||||||
|
|
||||||
- `identity` se encarga de autenticación, sesión, refresh y memberships.
|
- `identity` gestiona autenticación, sesión, refresh y memberships.
|
||||||
- `companies` se encarga de la ficha operativa de empresa.
|
- `companies` gestiona la ficha operativa de empresa.
|
||||||
- `GET /companies/available` es la vía actual para listar companies accesibles.
|
- `GET /companies/available` es la vía vigente para companies accesibles.
|
||||||
- El contexto tenant se resuelve con `X-Company-Id` y `requireIdentityTenant(params)`.
|
- El contexto tenant se resuelve con `X-Company-Id` y `requireIdentityTenant(params)`.
|
||||||
- El frontend ERP ya usa el runtime nuevo de `identity` con auto-selección de empresa y refresh automático.
|
- El frontend ERP ya usa el runtime nuevo de `identity`.
|
||||||
|
|||||||
@ -1,5 +1,14 @@
|
|||||||
# Identity Backend Architecture
|
# Identity Backend Architecture
|
||||||
|
|
||||||
|
Documento complementario de diseño backend.
|
||||||
|
|
||||||
|
Para contratos operativos vigentes, prevalecen:
|
||||||
|
|
||||||
|
- `tenant-context.md`
|
||||||
|
- `identity-auth-api.md`
|
||||||
|
- `identity-and-companies.md`
|
||||||
|
- `modules/identity/README.md`
|
||||||
|
|
||||||
## Objetivo del módulo `identity`
|
## Objetivo del módulo `identity`
|
||||||
|
|
||||||
`identity` sustituye progresivamente al módulo legacy `auth`.
|
`identity` sustituye progresivamente al módulo legacy `auth`.
|
||||||
|
|||||||
@ -1,22 +1,10 @@
|
|||||||
# Identity Migration Status
|
# Identity Migration Status
|
||||||
|
|
||||||
## Patrón final de middleware
|
## Referencias canónicas
|
||||||
|
|
||||||
Rutas tenant-scoped:
|
- Reglas de contexto tenant y middlewares públicos: [tenant-context.md](./tenant-context.md)
|
||||||
|
- Flujo frontend de autenticación y selección de empresa: [../frontend/auth-and-company-selection.md](../frontend/auth-and-company-selection.md)
|
||||||
```ts
|
- Runtime y responsabilidades del módulo `identity`: [../../modules/identity/README.md](../../modules/identity/README.md)
|
||||||
import { requireIdentityTenant } from "@erp/identity/api";
|
|
||||||
|
|
||||||
router.use(...requireIdentityTenant(params));
|
|
||||||
```
|
|
||||||
|
|
||||||
Rutas autenticadas sin tenant:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { requireIdentityAuthenticated } from "@erp/identity/api";
|
|
||||||
|
|
||||||
router.use(...requireIdentityAuthenticated(params));
|
|
||||||
```
|
|
||||||
|
|
||||||
## Migrado a `identity`
|
## Migrado a `identity`
|
||||||
|
|
||||||
@ -34,17 +22,11 @@ Estado:
|
|||||||
- sin `@erp/auth/api`
|
- sin `@erp/auth/api`
|
||||||
- sin `mockUser`
|
- sin `mockUser`
|
||||||
- sin helpers locales de auth
|
- sin helpers locales de auth
|
||||||
- usan `requireIdentityTenant(params)`
|
- usan el middleware tenant canónico documentado en `tenant-context.md`
|
||||||
|
|
||||||
### Backend autenticado sin tenant
|
### Backend autenticado sin tenant
|
||||||
|
|
||||||
`modules/companies` usa:
|
`modules/companies` usa el middleware autenticado sin tenant documentado en `tenant-context.md`.
|
||||||
|
|
||||||
```ts
|
|
||||||
import { requireIdentityAuthenticated } from "@erp/identity/api";
|
|
||||||
|
|
||||||
router.use(...requireIdentityAuthenticated(params));
|
|
||||||
```
|
|
||||||
|
|
||||||
Esto aplica a:
|
Esto aplica a:
|
||||||
|
|
||||||
@ -59,25 +41,9 @@ POST /companies/:company_id/disable
|
|||||||
|
|
||||||
## Frontend actual
|
## Frontend actual
|
||||||
|
|
||||||
El runtime nuevo ya usa:
|
El runtime nuevo ya usa `@erp/identity/client`.
|
||||||
|
|
||||||
- `@erp/identity/client`
|
Los detalles del flujo implementado, headers y refresh automático se mantienen en `frontend/auth-and-company-selection.md`.
|
||||||
- `IdentityAuthSessionProvider`
|
|
||||||
- `DataSourceProvider`
|
|
||||||
- Axios compartido con refresh automático
|
|
||||||
|
|
||||||
Flujo implementado:
|
|
||||||
|
|
||||||
```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
|
|
||||||
```
|
|
||||||
|
|
||||||
## Legacy pendiente
|
## Legacy pendiente
|
||||||
|
|
||||||
|
|||||||
49
docs/backend-prompt-base.md
Normal file
49
docs/backend-prompt-base.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# Backend Prompt Base
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Centralizar el contexto comun para prompts de backend del ERP y evitar copiar la misma plantilla en varios ficheros.
|
||||||
|
|
||||||
|
## Contexto comun
|
||||||
|
|
||||||
|
- API en `Node.js`, `TypeScript`, `Express.js` y `Sequelize`
|
||||||
|
- Arquitectura Hexagonal, DDD pragmatico y SOLID
|
||||||
|
- Controllers responsables del contrato HTTP
|
||||||
|
- Repositorios desacoplados de Sequelize
|
||||||
|
- DTOs y presenters para respuestas
|
||||||
|
|
||||||
|
## Principios de diseno
|
||||||
|
|
||||||
|
- agregados encapsulan su estado y exponen solo lo necesario
|
||||||
|
- cada agregado usa su mapper de dominio <-> persistencia
|
||||||
|
- repositorios trabajan con agregados, no con modelos ORM expuestos
|
||||||
|
- SRP, OCP, LSP, ISP y DIP aplicados de forma explicita
|
||||||
|
- dependencias expresadas mediante interfaces de aplicacion
|
||||||
|
|
||||||
|
## Reglas tecnicas
|
||||||
|
|
||||||
|
- no usar Sequelize directamente en servicios de aplicacion
|
||||||
|
- no acoplar servicios a implementaciones concretas si ya existe contrato
|
||||||
|
- mantener transformacion a DTO en controllers o presenters
|
||||||
|
- capturar errores de persistencia y mapearlos a errores de dominio o API con semantica clara
|
||||||
|
- tipar de forma explicita entradas, salidas y resultados
|
||||||
|
|
||||||
|
## Persistencia y transacciones
|
||||||
|
|
||||||
|
- los repositorios encapsulan acceso a persistencia
|
||||||
|
- los mappers reconstruyen y serializan agregados
|
||||||
|
- las transacciones se gestionan mediante el mecanismo del proyecto, no desde el controller
|
||||||
|
- mantener convenciones existentes de tablas, columnas y timestamps
|
||||||
|
|
||||||
|
## Testing y seguridad
|
||||||
|
|
||||||
|
- cubrir servicios con tests de unidad cuando cambie comportamiento observable
|
||||||
|
- cubrir rutas con tests HTTP cuando aplique
|
||||||
|
- no exponer datos sensibles en responses
|
||||||
|
- devolver codigos HTTP coherentes con el contrato del endpoint
|
||||||
|
|
||||||
|
## Uso
|
||||||
|
|
||||||
|
- `prompt-auth.md` extiende esta base para autenticacion
|
||||||
|
- `prompt-customer-invoices.md` extiende esta base para customer invoices
|
||||||
|
- si aparece otro prompt backend, debe reutilizar esta base en lugar de copiarla
|
||||||
37
docs/customer-invoices/README.md
Normal file
37
docs/customer-invoices/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# customer-invoices
|
||||||
|
|
||||||
|
Índice de documentación del área de `customer-invoices`, con separación explícita entre contratos vigentes, soporte operativo e histórico.
|
||||||
|
|
||||||
|
## Documentación vigente
|
||||||
|
|
||||||
|
### Contratos funcionales y de transporte
|
||||||
|
|
||||||
|
- [Contrato de series de proformas](./proforma-series-contract.md)
|
||||||
|
- [Contrato de create de proformas](./proforma-create-contract.md)
|
||||||
|
- [Configuración fiscal persistida en proformas](./proforma-tax-config.md)
|
||||||
|
- [Contrato de listado de proformas](./proforma-list-contract.md)
|
||||||
|
- [Contrato de archivado de proformas](./proforma-archive-contract.md)
|
||||||
|
- [Contrato de borrado de proformas](./proforma-delete-contract.md)
|
||||||
|
- [Contrato de duplicado de proformas](./proforma-duplicate-contract.md)
|
||||||
|
- [Contrato de series de issued invoices](./issued-invoice-series-contract.md)
|
||||||
|
- [Contrato de snapshot documental](./document-series-snapshot-contract.md)
|
||||||
|
|
||||||
|
### Reports y flujos integrados
|
||||||
|
|
||||||
|
- [Reports de proformas](./proformas-reports.md)
|
||||||
|
- [FactuGES -> Proformas V2](../factuges/create-proforma-from-factuges.md)
|
||||||
|
|
||||||
|
### SQL y validaciones
|
||||||
|
|
||||||
|
- [Índice SQL de `customer-invoices`](./sql/README.md)
|
||||||
|
|
||||||
|
## Histórico o transicional
|
||||||
|
|
||||||
|
- [Split Proformas / Issued Invoices](./split-proformas-issued-invoices-migration.md)
|
||||||
|
- [InvoiceSeries legacy](./invoice-series.md)
|
||||||
|
|
||||||
|
## Criterio de lectura
|
||||||
|
|
||||||
|
- Para semántica vigente de negocio, empieza por `proforma-series-contract.md`, `proforma-create-contract.md` y `proforma-tax-config.md`.
|
||||||
|
- Para SQL manual o runbooks, usa `sql/README.md`.
|
||||||
|
- Para decisiones antiguas, migraciones o trazabilidad, consulta solo la sección histórica.
|
||||||
@ -25,11 +25,8 @@ En proformas:
|
|||||||
- se pide cliente
|
- se pide cliente
|
||||||
- `proforma_series_code` es opcional; si falta, se usa la serie default activa de `document_type = proforma`
|
- `proforma_series_code` es opcional; si falta, se usa la serie default activa de `document_type = proforma`
|
||||||
- `target_invoice_series_code` es opcional; si falta, se persiste `NULL`
|
- `target_invoice_series_code` es opcional; si falta, se persiste `NULL`
|
||||||
- la creacion de proforma genera una cabecera valida aunque no existan lineas
|
|
||||||
- `items` puede omitirse o enviarse como `[]`
|
|
||||||
- si no hay lineas valoradas, la response debe devolver `items = []`, `taxes = []` y totales a `0`
|
|
||||||
- `tax_regime_code` en create representa configuracion fiscal inicial, no impuestos ya aplicados
|
|
||||||
- la UI obtiene ambas listas desde `GET /document-series`, filtrando por `document_type`
|
- la UI obtiene ambas listas desde `GET /document-series`, filtrando por `document_type`
|
||||||
|
- la semantica detallada de create sin lineas, `tax_config`, `taxes` y `totals` vive en `proforma-create-contract.md`
|
||||||
|
|
||||||
### Editar draft
|
### Editar draft
|
||||||
|
|
||||||
@ -63,17 +60,14 @@ En proformas:
|
|||||||
|
|
||||||
- el backend emite `target_invoice_series_code` como nombre preferente
|
- el backend emite `target_invoice_series_code` como nombre preferente
|
||||||
- `series` puede mantenerse solo como alias legacy/deprecated de salida mientras existan consumidores antiguos
|
- `series` puede mantenerse solo como alias legacy/deprecated de salida mientras existan consumidores antiguos
|
||||||
- `taxes` representa impuestos calculados a partir de lineas valoradas
|
- la semantica de `taxes`, `tax_config` y `totals` se documenta en `proforma-create-contract.md`
|
||||||
- `tax_config` representa la configuracion fiscal persistida de cabecera
|
|
||||||
- `totals` representa importes calculados a partir de lineas valoradas
|
|
||||||
- create no debe inventar filas fiscales sobre base `0`
|
|
||||||
|
|
||||||
## Regla de emision
|
## Regla de emision
|
||||||
|
|
||||||
- si `target_invoice_series_code` tiene valor, la emision usa ese `seriesCode` para `document_type = issued_invoice`
|
- si `target_invoice_series_code` tiene valor, la emision usa ese `seriesCode` para `document_type = issued_invoice`
|
||||||
- si `target_invoice_series_code` es `NULL`, `document-series` resuelve la serie default activa de `issued_invoice`
|
- si `target_invoice_series_code` es `NULL`, `document-series` resuelve la serie default activa de `issued_invoice`
|
||||||
- la UI nunca debe consumir numeracion directa desde `POST /document-series/assign-next`
|
- la UI nunca debe consumir numeracion directa desde `POST /document-series/assign-next`
|
||||||
- los impuestos y totales reales se recalculan cuando la proforma ya tiene lineas en update
|
- el recalculo de impuestos y totales reales al editar lineas se documenta en `proforma-create-contract.md`
|
||||||
|
|
||||||
## Regla de borrado y numeracion
|
## Regla de borrado y numeracion
|
||||||
|
|
||||||
|
|||||||
@ -111,17 +111,15 @@ Preparar el esquema fisico para separar:
|
|||||||
|
|
||||||
## Regla operativa actual de series en proformas
|
## Regla operativa actual de series en proformas
|
||||||
|
|
||||||
- al crear una proforma, `document-series` se invoca con `document_type = proforma` para resolver la numeracion visible de la proforma
|
La semantica operativa vigente ya no se mantiene en este documento historico.
|
||||||
- ese resultado solo alimenta `document_series_id`, `proforma_number` y `proforma_reference`
|
|
||||||
- `target_invoice_series_code` no se rellena automaticamente con la serie documental de proforma
|
Fuente canonica actual:
|
||||||
- si el usuario no selecciona serie futura de factura, `target_invoice_series_code` se persiste en `NULL`
|
|
||||||
- si el usuario informa `target_invoice_series_code`, el backend lo conserva solo si corresponde a una serie activa de `issued_invoice`
|
- `docs/customer-invoices/proforma-series-contract.md`
|
||||||
- al emitir una proforma, la factura definitiva se numera con `document_type = issued_invoice`
|
- `docs/customer-invoices/proforma-create-contract.md`
|
||||||
- si la proforma trae `target_invoice_series_code`, se usa como `seriesCode`
|
- `docs/customer-invoices/proforma-tax-config.md`
|
||||||
- si `target_invoice_series_code` es `NULL`, `document-series` resuelve la serie default activa de `issued_invoice`
|
|
||||||
- la referencia final devuelta por `document-series` se persiste en `issued_invoices.invoice_reference`
|
Este documento conserva solo el contexto de migracion, fases y SQL historico.
|
||||||
- `invoice_reference` es snapshot documental y no se recalcula despues
|
|
||||||
- `proforma_series_code` es un input de create para `document_type = proforma` y no vuelve a ser editable despues
|
|
||||||
|
|
||||||
## Estado Fase 2B
|
## Estado Fase 2B
|
||||||
|
|
||||||
@ -142,30 +140,19 @@ Preparar el esquema fisico para separar:
|
|||||||
## Estado Fase 2D
|
## Estado Fase 2D
|
||||||
|
|
||||||
- el contrato publico de proformas ya soporta `target_invoice_series_code` en create y update
|
- el contrato publico de proformas ya soporta `target_invoice_series_code` en create y update
|
||||||
- `series` se mantiene como alias legacy temporal en responses
|
- la semantica detallada del alias legacy `series` se consolido despues en `proforma-series-contract.md`
|
||||||
- si `series` y `target_invoice_series_code` llegan a la vez con distinto valor, el request falla por validacion
|
|
||||||
- las responses de proforma prefieren `target_invoice_series_code`
|
|
||||||
- `series` se mantiene temporalmente en responses como alias legacy con el mismo valor
|
|
||||||
- el cliente interno del modulo ya emite `target_invoice_series_code` en requests nuevos
|
- el cliente interno del modulo ya emite `target_invoice_series_code` en requests nuevos
|
||||||
|
|
||||||
## Estado Fase 2E
|
## Estado Fase 2E
|
||||||
|
|
||||||
- create separa `proforma_series_code` de `target_invoice_series_code`
|
- create separa `proforma_series_code` de `target_invoice_series_code`
|
||||||
- `proforma_series_code` solo se usa para numerar la proforma
|
- las reglas operativas exactas se consolidaron despues en `proforma-series-contract.md`
|
||||||
- update ya no usa `series` en el flujo nuevo ni permite cambiar la serie propia de proforma
|
|
||||||
- `target_invoice_series_code` sigue siendo el unico concepto de serie futura de factura emitida
|
|
||||||
- la UI nueva de proformas ya envia `proforma_series_code` y `target_invoice_series_code`
|
- la UI nueva de proformas ya envia `proforma_series_code` y `target_invoice_series_code`
|
||||||
|
|
||||||
## Estado Fase 2F
|
## Estado Fase 2F
|
||||||
|
|
||||||
- create de proforma se consolida como creacion de cabecera/documento inicial
|
- create de proforma se consolida como creacion de cabecera/documento inicial
|
||||||
- una proforma sin lineas es valida y debe persistirse con `items = []`
|
- la semantica funcional exacta de create se consolido despues en `proforma-create-contract.md`
|
||||||
- `tax_regime_code` queda como configuracion fiscal inicial de la cabecera
|
|
||||||
- `tax_config` queda persistido en `proformas` como configuracion fiscal de cabecera
|
|
||||||
- `proforma_taxes` solo representa impuestos calculados, no defaults fiscales ni placeholders sobre base `0`
|
|
||||||
- si una proforma nace sin lineas, la response debe devolver `taxes = []` y totales monetarios a `0`
|
|
||||||
- update sigue siendo el punto donde se anaden lineas y se materializan impuestos/totales reales
|
|
||||||
- la UI deja de prometer que la fiscalidad queda "aplicada" en create y pasa a hablar de valores iniciales
|
|
||||||
|
|
||||||
## Estado Fase 2G
|
## Estado Fase 2G
|
||||||
|
|
||||||
@ -174,9 +161,7 @@ Preparar el esquema fisico para separar:
|
|||||||
- el archivado es logico mediante `archived_at`
|
- el archivado es logico mediante `archived_at`
|
||||||
- el listado V2 de proformas usa `Criteria filters[]` para `archived_at` y `status`
|
- el listado V2 de proformas usa `Criteria filters[]` para `archived_at` y `status`
|
||||||
- `GET /proformas/:id` permite abrir proformas archivadas
|
- `GET /proformas/:id` permite abrir proformas archivadas
|
||||||
- el borrado de `draft` no depende de `archived_at`
|
- las reglas funcionales exactas de borrado y numeracion se consolidaron despues en `proforma-series-contract.md`
|
||||||
- solo `draft` puede borrarse
|
|
||||||
- `rejected` sigue fuera del alcance y queda reservado para archivado futuro
|
|
||||||
- la validacion de borrado comprueba tambien `linked_invoice_id` y `issued_invoices.source_proforma_id`
|
- la validacion de borrado comprueba tambien `linked_invoice_id` y `issued_invoices.source_proforma_id`
|
||||||
- la numeracion de `document-series` no se reutiliza ni se decrementa tras un borrado
|
- la numeracion de `document-series` no se reutiliza ni se decrementa tras un borrado
|
||||||
|
|
||||||
|
|||||||
@ -1,83 +1,77 @@
|
|||||||
📌 CONTEXTO DEL PROYECTO:
|
# Prompt Auth
|
||||||
Estoy desarrollando una API en **Node.js** con **TypeScript**, **Express.js**, **Sequelize**, y **Arquitectura Hexagonal (Ports & Adapters)** bajo los principios de **DDD (Domain-Driven Design)** y **SOLID**.
|
|
||||||
La API maneja **autenticación con PassportJS**, soporte para **JWT y LocalStrategy**, gestión segura de contraseñas con **bcrypt**, y transformación de respuestas con **DTOs (Data Transfer Objects)**.
|
|
||||||
|
|
||||||
📌 PRINCIPIOS Y PATRONES A APLICAR:
|
Base comun reutilizada: [`backend-prompt-base.md`](./backend-prompt-base.md)
|
||||||
✅ **DDD (Domain-Driven Design):**
|
|
||||||
- Los **agregados** encapsulan su estado y exponen solo lo necesario.
|
|
||||||
- Cada **agregado** tiene su propio **mapper** para conversión entre persistencia y dominio.
|
|
||||||
- **Repositorios** manejan **agregados** y deben desacoplarse de Sequelize.
|
|
||||||
|
|
||||||
✅ **SOLID (Principios de Diseño):**
|
Este prompt añade solo las reglas especificas de autenticacion sobre esa base.
|
||||||
- **SRP:** Cada clase tiene una única responsabilidad.
|
|
||||||
- **OCP:** El código debe ser fácil de extender sin modificarlo.
|
|
||||||
- **LSP:** Los objetos derivados deben poder sustituir a sus clases base.
|
|
||||||
- **ISP:** Usar interfaces específicas en lugar de dependencias directas.
|
|
||||||
- **DIP:** Usar **interfaces** (`IAuthProvider`, `ITransactionManager`) en lugar de acoplarse a implementaciones concretas.
|
|
||||||
|
|
||||||
📌 GESTIÓN DE DEPENDENCIAS:
|
## Contexto especifico
|
||||||
✅ **Factory Pattern (`createAuthService`)**
|
|
||||||
- `AuthService` debe crearse con un **Factory** (`createAuthService()`), permitiendo cambiar su implementación sin afectar la API.
|
|
||||||
- `AuthService` depende de `IAuthenticatedUserRepository`, `ITransactionManager` y `IAuthProvider`.
|
|
||||||
|
|
||||||
📌 AUTENTICACIÓN CON `PassportJS`:
|
La API maneja autenticacion con `PassportJS`, soporte para `JWT` y `LocalStrategy`, gestion segura de contrasenas con `bcrypt` y transformacion de respuestas con DTOs.
|
||||||
✅ **Soporte para JWT y LocalStrategy**
|
|
||||||
- `PassportAuthProvider` maneja **JWTStrategy** para autenticación con tokens y **LocalStrategy** para validación con email y contraseña.
|
|
||||||
- Se debe usar `bcrypt` para cifrar contraseñas antes de almacenarlas.
|
|
||||||
- `validateUser()` en `PassportAuthProvider` verifica credenciales con `LocalStrategy`.
|
|
||||||
|
|
||||||
📌 GESTIÓN SEGURA DE CONTRASEÑAS:
|
## Gestion de dependencias
|
||||||
✅ **Uso de `bcrypt` para cifrado de contraseñas**
|
|
||||||
- `PasswordHash.create()` valida la fuerza de la contraseña antes de cifrarla.
|
|
||||||
- `bcrypt.compare()` se usa para validar la contraseña al hacer login.
|
|
||||||
- Las contraseñas **nunca deben almacenarse en texto plano en la base de datos**.
|
|
||||||
|
|
||||||
📌 GESTIÓN DE ERRORES:
|
- `AuthService` debe crearse con un factory como `createAuthService()`
|
||||||
✅ **Uso de `ApiError` para respuestas de error estructuradas**
|
- `AuthService` depende de `IAuthenticatedUserRepository`, `ITransactionManager` y `IAuthProvider`
|
||||||
- `ExpressController` debe devolver errores con `ApiError` (`status`, `title`, `detail`, `timestamp`).
|
|
||||||
- `RegisterController` y `LoginController` deben usar `ApiError` en caso de errores (`409 Conflict`, `401 Unauthorized`, `500 Internal Server Error`).
|
|
||||||
- Se deben capturar errores de **Sequelize** en `BaseRepository` (`UniqueConstraintError`, `ConnectionError`, `TimeoutError`).
|
|
||||||
- **Errores de unicidad (`SequelizeUniqueConstraintError`) deben ser personalizados según el contexto usando `errorMapper` en los repositorios.**
|
|
||||||
|
|
||||||
📌 FLUJO DE LA API `/register`:
|
## Autenticacion con `PassportJS`
|
||||||
1️⃣ **Request llega a `RegisterController`**.
|
|
||||||
2️⃣ **Valida `email`, `username`, `password` como Value Objects**.
|
|
||||||
3️⃣ **Llama a `AuthService.registerUser()`**.
|
|
||||||
4️⃣ **`AuthService` usa `validateUser()` para evitar usuarios duplicados**.
|
|
||||||
5️⃣ **Si el usuario es nuevo, se cifra la contraseña con `bcrypt.hash()`**.
|
|
||||||
6️⃣ **Se almacena en la BD con `AuthenticatedUserRepository.create()`**.
|
|
||||||
7️⃣ **Se generan `accessToken` y `refreshToken` con `PassportAuthProvider`**.
|
|
||||||
8️⃣ **Se responde con los tokens y el `userId`**.
|
|
||||||
|
|
||||||
📌 FLUJO DE LA API `/login`:
|
- `PassportAuthProvider` maneja `JWTStrategy` y `LocalStrategy`
|
||||||
1️⃣ **Request llega a `LoginController`**.
|
- `validateUser()` verifica credenciales con `LocalStrategy`
|
||||||
2️⃣ **Llama a `AuthService.loginUser()` con `email` y `password`**.
|
- usar `bcrypt` para cifrar contrasenas antes de almacenarlas
|
||||||
3️⃣ **`AuthService` busca el usuario en `AuthenticatedUserRepository.findUserByEmail()`**.
|
- `PasswordHash.create()` valida la fuerza de la contrasena antes de cifrarla
|
||||||
4️⃣ **Si el usuario existe, compara contraseñas con `bcrypt.compare()`**.
|
- `bcrypt.compare()` valida la contrasena al hacer login
|
||||||
5️⃣ **Si la contraseña es correcta, genera `accessToken` y `refreshToken`**.
|
- las contrasenas nunca deben almacenarse en texto plano
|
||||||
6️⃣ **Devuelve los tokens en la respuesta**.
|
|
||||||
|
|
||||||
📌 TRANSFORMACIÓN DE RESPUESTAS:
|
## Gestion de errores
|
||||||
✅ **Los Controladores son responsables de la transformación de datos antes de enviarlos.**
|
|
||||||
✅ **Se usan `Presenters` (`AuthResponsePresenter`) para mapear datos del dominio a DTOs (`AuthResponseDTO`).**
|
|
||||||
✅ **Se inyectan `Presenters` en los controladores para desacoplar la lógica de transformación.**
|
|
||||||
|
|
||||||
📌 SEGURIDAD Y MEJORES PRÁCTICAS:
|
- `ExpressController` debe devolver errores con `ApiError`
|
||||||
✅ **Usar `TransactionManager.complete()` para manejar transacciones.**
|
- `RegisterController` y `LoginController` deben mapear errores a `409`, `401` o `500` segun contrato
|
||||||
✅ **Evitar acoplamientos directos entre servicios y repositorios.**
|
- capturar errores de Sequelize relevantes en repositorios
|
||||||
✅ **Usar `bcrypt` con `SALT_ROUNDS=12` configurables en `.env`.**
|
- personalizar errores de unicidad mediante `errorMapper`
|
||||||
✅ **Si ocurre un error en la BD, `rollback()` debe ejecutarse automáticamente.**
|
|
||||||
✅ **Devolver códigos HTTP adecuados (`401 Unauthorized`, `409 Conflict`, `500 Internal Server Error`).**
|
|
||||||
|
|
||||||
📌 SOLICITUDES QUE PUEDO HACERTE:
|
## Flujo de `/register`
|
||||||
- **Generar código** cumpliendo con estas reglas.
|
|
||||||
- **Revisar código y detectar violaciones de SOLID o DDD**.
|
|
||||||
- **Optimizar `AuthService` para mejorar la seguridad y escalabilidad**.
|
|
||||||
- **Implementar `/refresh` para renovar `accessTokens`**.
|
|
||||||
- **Escribir pruebas unitarias y de integración para `registerUser()` y `loginUser()`**.
|
|
||||||
- **Optimizar `AuthResponsePresenter` para soportar más formatos (`XML`, `CSV`).**
|
|
||||||
|
|
||||||
⚠️ **IMPORTANTE:**
|
1. Request llega a `RegisterController`
|
||||||
- **NO generes código que acople dependencias directamente**.
|
2. valida `email`, `username` y `password` como Value Objects
|
||||||
- **NO uses Sequelize directamente en los servicios**.
|
3. llama a `AuthService.registerUser()`
|
||||||
- **SIEMPRE usa interfaces (`IAuthProvider`, `ITransactionManager`) en lugar de instancias concretas**.
|
4. `AuthService` evita usuarios duplicados con `validateUser()`
|
||||||
|
5. si el usuario es nuevo, cifra la contrasena con `bcrypt.hash()`
|
||||||
|
6. persiste con `AuthenticatedUserRepository.create()`
|
||||||
|
7. genera `accessToken` y `refreshToken`
|
||||||
|
8. responde con tokens y `userId`
|
||||||
|
|
||||||
|
## Flujo de `/login`
|
||||||
|
|
||||||
|
1. Request llega a `LoginController`
|
||||||
|
2. llama a `AuthService.loginUser()` con `email` y `password`
|
||||||
|
3. busca el usuario en `AuthenticatedUserRepository.findUserByEmail()`
|
||||||
|
4. compara contrasenas con `bcrypt.compare()`
|
||||||
|
5. si la contrasena es correcta, genera tokens
|
||||||
|
6. devuelve los tokens en la response
|
||||||
|
|
||||||
|
## Transformacion de respuestas
|
||||||
|
|
||||||
|
- los controladores transforman antes de responder
|
||||||
|
- usar `AuthResponsePresenter` para mapear dominio a `AuthResponseDTO`
|
||||||
|
- inyectar presenters en controllers cuando ayude a desacoplar
|
||||||
|
|
||||||
|
## Seguridad y mejores practicas
|
||||||
|
|
||||||
|
- usar `TransactionManager.complete()` para manejar transacciones
|
||||||
|
- evitar acoplamientos directos entre servicios y repositorios
|
||||||
|
- usar `bcrypt` con `SALT_ROUNDS=12` configurable en `.env`
|
||||||
|
- si ocurre un error en BD, `rollback()` debe ejecutarse automaticamente
|
||||||
|
|
||||||
|
## Solicitudes tipicas
|
||||||
|
|
||||||
|
- generar codigo cumpliendo estas reglas
|
||||||
|
- revisar codigo y detectar violaciones de SOLID o DDD
|
||||||
|
- optimizar `AuthService` para seguridad y escalabilidad
|
||||||
|
- implementar `/refresh`
|
||||||
|
- escribir pruebas unitarias y de integracion para `registerUser()` y `loginUser()`
|
||||||
|
|
||||||
|
## Restricciones
|
||||||
|
|
||||||
|
- no acoplar dependencias directamente
|
||||||
|
- no usar Sequelize directamente en servicios
|
||||||
|
- usar interfaces como `IAuthProvider` e `ITransactionManager`
|
||||||
|
|||||||
@ -1,22 +1,23 @@
|
|||||||
📌 CONTEXTO DEL PROYECTO:
|
# Prompt Customer Invoices
|
||||||
Estoy desarrollando una API en **Node.js** con **TypeScript**, **Express.js**, **Sequelize**, y **Arquitectura Hexagonal (Ports & Adapters)** bajo los principios de **DDD (Domain-Driven Design)** y **SOLID**.
|
|
||||||
Este módulo es para **facturas de cliente (Customer Invoices)** y debe cumplir con los estándares definidos previamente para el contexto de autenticación, adaptados a los datos, reglas y operaciones de las facturas.
|
|
||||||
|
|
||||||
📌 PRINCIPIOS Y PATRONES A APLICAR:
|
Base comun reutilizada: [`backend-prompt-base.md`](./backend-prompt-base.md)
|
||||||
✅ **DDD (Domain-Driven Design)**
|
|
||||||
- Las **facturas** serán agregados raíz (`CustomerInvoice`) con propiedades como `id`, `customerId`, `invoiceNumber`, `date`, `lines`, `totals`, etc.
|
|
||||||
- Las **líneas de factura** (`InvoiceLine`) se modelan como entidades o value objects dentro del agregado.
|
|
||||||
- Se usará un `Mapper` para convertir entre dominio y persistencia.
|
|
||||||
- Repositorios (`ICustomerInvoiceRepository`) solo manejan agregados.
|
|
||||||
- Operaciones como `createInvoice`, `updateInvoice`, `getInvoiceById` serán gestionadas en `CustomerInvoiceApplicationService`.
|
|
||||||
|
|
||||||
✅ **SOLID**
|
Este prompt añade solo las reglas especificas de `customer-invoices`.
|
||||||
- Usar SRP: cada clase con una responsabilidad clara.
|
|
||||||
- Usar interfaces como `ICustomerInvoiceRepository`, `ITransactionManager`, `IInvoicePresenter`.
|
|
||||||
- Evitar acoplamientos entre capa de aplicación y capa de infraestructura.
|
|
||||||
- Controladores deben transformar los datos en DTOs antes de enviarlos.
|
|
||||||
|
|
||||||
📌 ESTRUCTURA DE DATOS:
|
## Contexto especifico
|
||||||
|
|
||||||
|
Este modulo es para facturas de cliente y adapta la base comun a datos, reglas y operaciones de facturacion.
|
||||||
|
|
||||||
|
## DDD y SOLID aplicados
|
||||||
|
|
||||||
|
- `CustomerInvoice` es agregado raiz
|
||||||
|
- `InvoiceLine` se modela como entidad o Value Object dentro del agregado
|
||||||
|
- usar mapper para dominio <-> persistencia
|
||||||
|
- `ICustomerInvoiceRepository` maneja agregados
|
||||||
|
- operaciones como `createInvoice`, `updateInvoice` y `getInvoiceById` viven en `CustomerInvoiceApplicationService`
|
||||||
|
- usar SRP y contratos como `ICustomerInvoiceRepository`, `ITransactionManager` e `IInvoicePresenter`
|
||||||
|
|
||||||
|
## Estructura de datos
|
||||||
|
|
||||||
La entidad `CustomerInvoice` tendrá:
|
La entidad `CustomerInvoice` tendrá:
|
||||||
- `id`: UUID (ValueObject `UniqueID`)
|
- `id`: UUID (ValueObject `UniqueID`)
|
||||||
@ -37,8 +38,8 @@ La entidad `CustomerInvoice` tendrá:
|
|||||||
- `tax`: Amount (number) and scale (number) (ValueObject Percentage)
|
- `tax`: Amount (number) and scale (number) (ValueObject Percentage)
|
||||||
- `totalAmount`: Amount (number), scale (number) and currency (string) (ValueObject MoneyValue)
|
- `totalAmount`: Amount (number), scale (number) and currency (string) (ValueObject MoneyValue)
|
||||||
|
|
||||||
|
## Operaciones principales
|
||||||
|
|
||||||
📌 OPERACIONES PRINCIPALES:
|
|
||||||
- `createInvoice(data: CreateCustomerInvoiceDTO)`
|
- `createInvoice(data: CreateCustomerInvoiceDTO)`
|
||||||
- `getInvoiceById(id: string)`
|
- `getInvoiceById(id: string)`
|
||||||
- `updateInvoice(id: string, changes: UpdateCustomerInvoiceDTO)`
|
- `updateInvoice(id: string, changes: UpdateCustomerInvoiceDTO)`
|
||||||
@ -46,39 +47,43 @@ La entidad `CustomerInvoice` tendrá:
|
|||||||
- `deleteInvoice(id: string)`
|
- `deleteInvoice(id: string)`
|
||||||
- `markAsPaid(id: string)`
|
- `markAsPaid(id: string)`
|
||||||
|
|
||||||
📌 VALIDACIONES:
|
## Validaciones
|
||||||
✅ Usar `Zod` para validaciones en Value Objects (`InvoiceNumber`, `InvoiceDate`, `InvoiceLine`).
|
|
||||||
✅ No se deben lanzar excepciones para errores de validación. Usar `Result<T, E>` como en autenticación.
|
|
||||||
|
|
||||||
📌 PRESENTACIÓN:
|
- usar `Zod` en Value Objects como `InvoiceNumber`, `InvoiceDate` e `InvoiceLine`
|
||||||
✅ Los controladores convierten el agregado `CustomerInvoice` a `CustomerInvoiceDTO`.
|
- no lanzar excepciones para errores de validacion esperados; usar `Result<T, E>`
|
||||||
✅ Se usan `Presenters` (`CustomerInvoicePresenter`) para construir los DTOs.
|
|
||||||
✅ DTOs solo exponen campos necesarios: nada sensible, nada interno.
|
|
||||||
✅ Se puede extender con `CsvInvoicePresenter`, `PdfInvoicePresenter`, etc.
|
|
||||||
|
|
||||||
📌 AUTORIZACIÓN Y ACCESO:
|
## Presentacion
|
||||||
✅ Se protegerán rutas con middleware JWT (`authenticateJWT`).
|
|
||||||
✅ Se validará que el usuario autenticado pueda acceder/modificar la factura (p. ej. pertenece a su empresa).
|
|
||||||
|
|
||||||
📌 PERSISTENCIA:
|
- los controladores convierten el agregado a `CustomerInvoiceDTO`
|
||||||
✅ `SequelizeCustomerInvoiceRepository` no accede directamente al modelo de Sequelize, lo hace a través de `Mapper`.
|
- usar `CustomerInvoicePresenter` para construir DTOs
|
||||||
✅ La clase `CustomerInvoiceMapper` mapea de/agregado <-> persistencia.
|
- DTOs solo exponen campos necesarios
|
||||||
✅ Soporta transacciones Sequelize (`TransactionManager.complete(...)`).
|
- se puede extender con `CsvInvoicePresenter`, `PdfInvoicePresenter`, etc.
|
||||||
✅ Los nombres de los campos y las tablas en la base de datos seguirán la notación "snake_case".
|
|
||||||
✅ Los nombres de los campos en los modelos Sequelize seguirán la notación "snake_case".
|
|
||||||
✅ Los modelos Sequelize de los agregados tienen campos 'timestamp' con los nombres "created_at", "updated_at" y "deleted_at".
|
|
||||||
|
|
||||||
|
## Autorizacion y acceso
|
||||||
|
|
||||||
📌 ERRORES:
|
- proteger rutas con middleware JWT o equivalente vigente del modulo
|
||||||
✅ Usar `ApiError` para errores en la API.
|
- validar que el usuario autenticado pueda acceder o modificar la factura de su empresa
|
||||||
✅ Los repositorios deben capturar errores de Sequelize (`UniqueConstraintError`, etc.) y convertirlos a errores de dominio con mensajes claros y específicos (mediante `errorMapper`).
|
|
||||||
|
|
||||||
📌 TESTING:
|
## Persistencia
|
||||||
✅ Los servicios (`CustomerInvoiceApplicationService`) serán testeados con mocks de repositorio.
|
|
||||||
✅ Las rutas serán testeadas con `supertest`.
|
- `SequelizeCustomerInvoiceRepository` accede mediante mapper
|
||||||
✅ Las validaciones de ValueObjects tendrán pruebas unitarias.
|
- `CustomerInvoiceMapper` mapea agregado <-> persistencia
|
||||||
|
- soportar transacciones con `TransactionManager.complete(...)`
|
||||||
|
- seguir `snake_case` en tablas, columnas y timestamps `created_at`, `updated_at`, `deleted_at`
|
||||||
|
|
||||||
|
## Errores
|
||||||
|
|
||||||
|
- usar `ApiError` para errores de API
|
||||||
|
- capturar errores de Sequelize y convertirlos con mensajes claros mediante `errorMapper`
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- testear `CustomerInvoiceApplicationService` con mocks de repositorio
|
||||||
|
- testear rutas con `supertest`
|
||||||
|
- cubrir Value Objects con pruebas unitarias
|
||||||
|
|
||||||
|
## Nomenclatura
|
||||||
|
|
||||||
📌 NOMENCLATURA:
|
|
||||||
- Agregado: `CustomerInvoice`
|
- Agregado: `CustomerInvoice`
|
||||||
- Línea: `CustomerInvoiceLine`
|
- Línea: `CustomerInvoiceLine`
|
||||||
- DTOs: `CustomerInvoiceDTO`, `InvoiceLineDTO`
|
- DTOs: `CustomerInvoiceDTO`, `InvoiceLineDTO`
|
||||||
@ -86,8 +91,9 @@ La entidad `CustomerInvoice` tendrá:
|
|||||||
- Repositorio: `ICustomerInvoiceRepository`, `SequelizeCustomerInvoiceRepository`
|
- Repositorio: `ICustomerInvoiceRepository`, `SequelizeCustomerInvoiceRepository`
|
||||||
- ValueObjects: `CustomerInvoiceNumber`, `CustomerInvoiceDate`, `CustomerInvoiceTotal`, `CustomerInvoiceItemQuantity`, etc.
|
- ValueObjects: `CustomerInvoiceNumber`, `CustomerInvoiceDate`, `CustomerInvoiceTotal`, `CustomerInvoiceItemQuantity`, etc.
|
||||||
|
|
||||||
⚠️ IMPORTANTE:
|
## Restricciones
|
||||||
- NO incluir `passwords`, `tokens`, o datos sensibles en respuestas.
|
|
||||||
- NO usar Sequelize directamente en servicios ni controladores.
|
- no incluir `passwords`, `tokens` u otros datos sensibles en responses
|
||||||
- NO devolver el agregado como respuesta bruta. Usar DTOs.
|
- no usar Sequelize directamente en servicios ni controllers
|
||||||
- TODO debe estar tipado, incluso los métodos de los servicios (`Promise<Result<T, Error>>`).
|
- no devolver el agregado bruto; usar DTOs
|
||||||
|
- tipar explicitamente metodos y resultados
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user