# 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` 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>; } ``` - 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` 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`. - 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 typecheck pnpm --filter lint pnpm --filter 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.