- Implemented PaymentTermRowActions component for editing, toggling, and deleting payment terms. - Created PaymentTermSegmentedBar for visual representation of payment term segments. - Added PaymentTermsEmptyState and PaymentTermsNoResultsState for handling empty and no results scenarios. - Developed PaymentTermsSettingsSkeleton for loading state representation. - Introduced utility functions for building payment term forms and parameters. - Added criteria building functions for payment terms list filtering. - Implemented error handling view model for payment terms settings page. - Created tests for payment terms list criteria and due rules utilities. - Added shell script to check development environment setup.
552 lines
17 KiB
Markdown
552 lines
17 KiB
Markdown
# 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.
|
|
|
|
## Entorno de ejecución
|
|
|
|
El repositorio canónico es:
|
|
|
|
`/home/rodax/Documentos/uecko-erp`
|
|
|
|
Todos los comandos de desarrollo deben ejecutarse en Linux.
|
|
|
|
No ejecutar Node.js, PNPM, Turborepo, TypeScript, Biome ni tests
|
|
desde rutas Windows, unidades `Z:` ni rutas UNC.
|
|
|
|
Antes de trabajar comprobar:
|
|
|
|
```bash
|
|
pwd
|
|
uname -s
|
|
git rev-parse --show-toplevel
|
|
```
|
|
Se espera:
|
|
|
|
```
|
|
Linux
|
|
/home/rodax/Documentos/uecko-erp
|
|
|
|
```
|
|
|
|
## 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.
|