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.
- 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;