20 KiB
# 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 entorno de desarrollo canónico es WSL2 con Ubuntu 24.04 LTS.
El repositorio canónico es:
`/home/david/dev/factuges-erp`
Todos los comandos de desarrollo deben ejecutarse dentro de WSL2 usando Linux y `bash`.
No ejecutar Node.js, PNPM, Turborepo, TypeScript, Biome, Git ni tests desde Windows, PowerShell, unidades `Z:`, rutas UNC o rutas montadas bajo `/mnt/c`.
No usar la antigua máquina Ubuntu remota como entorno de desarrollo salvo que la tarea lo requiera expresamente.
Codex debe trabajar sobre la distribución WSL `Ubuntu-24.04` y abrir el repositorio mediante su ruta Linux real:
`/home/david/dev/factuges-erp`
No abrir el proyecto para tareas de desarrollo mediante rutas Windows equivalentes como `\wsl$\...`, `Z:` o `C:`.
Antes de trabajar comprobar:
```bash
pwd
uname -s
uname -r
whoami
git rev-parse --show-toplevel
node --version
pnpm --version
```
Se espera:
```text
/home/david/dev/factuges-erp
Linux
david
/home/david/dev/factuges-erp
Node.js 24.x
pnpm 11.25.0
```
Si el entorno no coincide, no continuar con cambios de código hasta identificar la causa.
Los archivos `.env` locales ya configurados no deben mostrarse, copiarse a respuestas ni versionarse. No imprimir secretos, tokens, contraseñas o cadenas de conexión completas.
La versión de PNPM definida en `packageManager` del `package.json` raíz es vinculante. No actualizar PNPM ni modificar `packageManager` como parte de una tarea no relacionada.
## Stack principal
- Node.js 24.x
- 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.
### La documentación del proyecto
- debe redactarse en castellano.
- Reside en /docs.
- Utilizar ficheros Markdown
- Los nombres de los ficheros deben estar en castellano.
Reglas de estilo para documentación:
- usar lenguaje claro;
- evitar redacción excesivamente técnica cuando no sea necesaria;
- usar frases directas;
- evitar frases rebuscadas, largas o ambiguas;
- priorizar explicar qué hace el sistema, por qué existe una regla y qué comportamiento debe esperar el usuario o el desarrollador;
- usar términos técnicos cuando sean necesarios para ser precisos, pero explicarlos de forma sencilla;
- no convertir la documentación en una transcripción del código;
- evitar párrafos densos si una explicación breve y directa es suficiente.
## 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.
## Referencias arquitectónicas normativas
Para reglas completas, consultar y no duplicar:
- Arquitectura backend
- Inyección de dependencias
- Flujo HTTP y entrada a Application
- DTOs y schemas HTTP
- Pruebas backend por capa
Las reglas resumidas siguientes son recordatorios operativos; las SSOT anteriores prevalecen.
## 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
Para el flujo normal, usar:
```text
RequestDTO -> UseCase({ dto, context }) -> InputMapper -> Value Objects / Props
```
RequestMapper -> Command es opcional y solo procede cuando existe adaptación HTTP propia, por ejemplo para combinar body, params o query. No crear uno solo para renombrar campos que Application volverá a transformar.
### 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 dentro del filesystem Linux de WSL2, en:
`/home/david/dev/factuges-erp`
Ejecutar los comandos directamente en la distribución WSL `Ubuntu-24.04`.
No ejecutar los comandos del proyecto mediante SSH contra la antigua máquina Ubuntu, salvo que la tarea lo requiera expresamente.
No usar como workspace principal rutas Windows, unidades de red, rutas UNC, `\wsl$\...` ni rutas bajo `/mnt/c`.
Preferir `bash` y herramientas Linux disponibles dentro de WSL.
Antes de una tarea de código, comprobar al menos:
```bash
pwd
git status --short
```
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.