docs: actualizar convenciones y añadir buenas prácticas de implementación

This commit is contained in:
David Arranz 2026-10-08 13:39:01 +02:00
parent bada13a50e
commit fbfa64509b

208
AGENTS.md
View File

@ -18,7 +18,7 @@ No imprimir, copiar ni versionar .env locales o secretos.
- Documentación en /docs, con nombres de ficheros en castellano.
- Comentarios que expliquen decisiones, riesgos o contratos, nunca que repitan el código.
- Mantener es y en para textos de interfaz; no insertar literales en áreas con i18n.
- En las sentencia condicionales (if) Utiliza siempre llaves cuando el bloque contiene una sola intrucción.
- Utilizar siempre llaves en if, else, for, while y estructuras equivalentes, incluso cuando el bloque contenga una sola instrucción.
## Disciplina
@ -97,3 +97,209 @@ Informar cambios, archivos principales, comandos ejecutados, resultados y pendie
No afirmar validaciones no ejecutadas ni ocultar fallos conocidos.
Precedencia: instrucciones actuales del usuario, AGENTS.md más cercano, este archivo y convenciones existentes.
## Buenas prácticas de implementación
Estas reglas complementan las convenciones y documentos de arquitectura existentes. Antes de introducir un patrón nuevo, comprobar cómo resuelve el repositorio casos equivalentes y mantener coherencia con ellos.
### Idioma
- Responder, explicar decisiones, documentar cambios y redactar comentarios y TSDoc en castellano.
- Mantener código, identificadores, nombres de tipos, funciones, variables, componentes, hooks y ficheros en inglés.
- No traducir nombres de APIs, bibliotecas, protocolos ni conceptos técnicos cuando su denominación habitual esté en inglés.
- Los mensajes destinados al usuario final deben pasar por el sistema de internacionalización cuando corresponda.
### TypeScript
- Mantener TypeScript en modo estricto y resolver los errores de tipos en su origen; no debilitarlos para conseguir que compile.
- Preferir inferencia cuando el tipo sea evidente y anotaciones explícitas cuando formen parte de un contrato público o mejoren realmente la comprensión.
- Usar `unknown` para datos cuyo tipo todavía no haya sido validado. No sustituir `unknown` por `any` para evitar realizar narrowing.
- Validar todos los datos procedentes de fronteras externas: HTTP, base de datos cuando corresponda, variables de entorno, almacenamiento, mensajes, APIs externas y datos serializados.
- No considerar un cast `as Tipo` como validación.
- Preferir narrowing, type guards, schemas y discriminated unions frente a assertions.
- Preferir `satisfies` cuando se quiera comprobar que un valor cumple un contrato conservando su inferencia concreta.
- Evitar enums cuando una unión de literales o un objeto constante tipado represente mejor un conjunto cerrado.
- Hacer exhaustivos los `switch` sobre discriminated unions mediante `never` cuando sea útil.
- Evitar parámetros booleanos cuyo significado no resulte evidente en la llamada; utilizar opciones o tipos semánticos cuando mejoren la legibilidad.
- No crear tipos contenedores, aliases o genéricos que solamente oculten otro tipo sin aportar semántica.
- No duplicar tipos que puedan derivarse de una única fuente normativa.
- Mantener la opcionalidad exacta del dominio: distinguir cuando sea relevante entre propiedad ausente, `undefined` y `null`.
- Preferir estructuras inmutables cuando los datos no deban modificarse.
- No capturar excepciones únicamente para volver a lanzarlas sin aportar contexto, conversión o recuperación.
- No usar excepciones como flujo normal para resultados de negocio esperables.
- Las funciones deben tener contratos claros y evitar efectos laterales inesperados.
### Node.js
- No bloquear el event loop con operaciones síncronas de I/O dentro de rutas, servicios ejecutados por petición o procesos concurrentes.
- Preferir APIs asíncronas para filesystem, red, criptografía y procesos externos cuando formen parte del flujo de ejecución normal.
- Toda operación externa susceptible de quedar bloqueada debe disponer de timeout cuando la API utilizada lo permita.
- Utilizar `AbortSignal` o mecanismos equivalentes cuando sea necesario propagar cancelación.
- No dispersar accesos a `process.env` por la aplicación. Cargar, validar y normalizar la configuración en un punto definido durante el arranque.
- Fallar durante el arranque si falta configuración imprescindible, en lugar de descubrirla durante una petición.
- No mantener estado mutable global dependiente de una petición.
- No almacenar en memoria información cuya persistencia o coherencia dependa de varias instancias del servidor.
- Gestionar explícitamente el cierre ordenado de recursos persistentes cuando corresponda: servidor HTTP, conexiones de base de datos, colas y otros recursos externos.
- No ocultar `unhandledRejection`, `uncaughtException` ni errores de infraestructura para mantener artificialmente el proceso funcionando.
### Express
- Mantener las rutas y controladores pequeños.
- El controlador HTTP traduce entre HTTP y la aplicación; no contiene lógica de negocio.
- No acceder directamente al ORM desde routers o controladores salvo que la arquitectura documentada establezca expresamente otra cosa.
- Validar `params`, `query`, `headers` y `body` en la frontera HTTP antes de utilizarlos.
- No confiar en tipos TypeScript para considerar válidos datos recibidos por HTTP.
- Centralizar la transformación de errores a respuestas HTTP.
- No repetir bloques `try/catch` en cada handler si el mecanismo de errores de Express ya propaga correctamente los rechazos.
- Distinguir errores esperables de aplicación, errores de validación, autenticación/autorización, conflictos y errores inesperados de infraestructura.
- No enviar al cliente mensajes internos de excepciones, SQL, stack traces ni detalles de implementación.
- Una petición debe producir una única respuesta. Evitar ramas que puedan continuar después de enviar la respuesta.
- Los middleware deben tener una responsabilidad concreta y efectos explícitos.
- No utilizar middleware global para comportamiento que solo pertenece a una ruta o módulo.
- Mantener la composición de routers, middleware, controladores, casos de uso y repositorios según la arquitectura documentada del proyecto.
### React 19
- Los componentes y hooks deben ser puros durante render.
- No producir efectos laterales durante render.
- No modificar props, estado, valores recibidos por hooks ni objetos pertenecientes a renders anteriores.
- No llamar componentes como funciones ordinarias; renderizarlos mediante JSX.
- Respetar estrictamente las Rules of Hooks.
- No utilizar hooks condicionalmente ni después de retornos que hagan variar su orden entre renders.
#### Organización de componentes
- Como norma general, cada fichero `.tsx` contiene un único componente React principal.
- No agrupar varios componentes funcionales relevantes en un mismo fichero por comodidad.
- Extraer un componente a su propio fichero cuando tenga props propias, estado, hooks, lógica de interacción, lógica de renderizado significativa o una responsabilidad identificable.
- Un subcomponente privado solo puede permanecer en el mismo fichero cuando sea pequeño, puramente presentacional, inseparable del componente principal y no dificulte la lectura.
- Si aparecen varios subcomponentes privados en el mismo fichero, reconsiderar la descomposición y extraerlos.
- No crear componentes únicamente para reducir el número de líneas de JSX. Extraer por responsabilidad, semántica o reutilización real.
- Evitar componentes monolíticos que mezclen consulta de datos, estado complejo, transformación de datos y grandes bloques de presentación.
- Mantener próximos entre sí el componente y los elementos que sean exclusivamente suyos, sin convertir los directorios en estructuras artificialmente profundas.
#### Estado y efectos
- Mantener el estado mínimo necesario.
- No guardar en estado valores que puedan calcularse correctamente a partir de props, estado existente o datos de consulta durante render.
- No utilizar `useEffect` para sincronizar un estado React con otro estado React.
- No utilizar `useEffect` para responder a una acción concreta del usuario; ejecutar esa lógica desde el event handler correspondiente.
- Utilizar efectos para sincronización real con sistemas externos, suscripciones, APIs imperativas del navegador u otros recursos externos.
- Todo efecto que cree una suscripción, listener, temporizador o recurso debe realizar su cleanup correspondiente.
- Diseñar efectos de forma que soporten correctamente montaje, desmontaje y nueva ejecución.
- No introducir `useMemo` o `useCallback` por defecto. Utilizarlos cuando exista una razón concreta de rendimiento, estabilidad referencial requerida por un contrato o evidencia obtenida mediante profiling.
- No duplicar en estado local datos gestionados por TanStack Query salvo que exista una necesidad explícita de edición local temporal.
- No copiar props a estado salvo que exista una semántica concreta que lo justifique.
- Preferir estado local al componente que lo consume y elevarlo únicamente cuando varios consumidores necesiten compartirlo.
#### Hooks
- Un custom hook debe encapsular comportamiento React reutilizable o una responsabilidad React claramente identificable.
- No crear hooks como simple sustitución de funciones normales.
- No crear hooks genéricos anticipando reutilización futura sin consumidores reales.
- Mantener los hooks independientes de detalles visuales cuando sea posible.
- No ocultar efectos importantes o llamadas remotas dentro de hooks con nombres ambiguos.
#### Componentes visuales
- Los componentes presentacionales no deben conocer endpoints, Express, Sequelize ni detalles de persistencia.
- Los componentes reutilizables no deben depender directamente de DTOs específicos de Metis Desk cuando puedan expresarse mediante props más pequeñas y semánticas.
- Preferir composición mediante `children` y props explícitas frente a componentes con multitud de flags booleanos.
- Evitar componentes con muchas variantes booleanas que produzcan combinaciones de estados difíciles de comprender.
- Mantener accesibilidad semántica: elemento HTML correcto, etiquetas, relaciones entre controles, navegación por teclado y foco.
- No implementar manualmente primitivas que shadcn/ui o Base UI ya proporcionen adecuadamente.
### Formularios
- Mantener una única fuente de verdad para cada campo.
- Utilizar React Hook Form cuando el formulario tenga complejidad suficiente y siguiendo los patrones existentes del repositorio.
- Utilizar schemas Zod como fuente normativa de validación cuando corresponda.
- No duplicar manualmente reglas de validación entre schema, formulario y llamada HTTP.
- Mostrar errores junto al contexto que los origina.
- Diferenciar errores de campo, errores globales del formulario y errores inesperados del servidor.
- Evitar envíos duplicados y reflejar visualmente estados pendientes.
- No limpiar o cerrar un formulario hasta confirmar que la operación que lo requiere se ha completado correctamente.
### Acceso a datos en frontend
- Centralizar query keys y mantenerlas estables.
- Las funciones de acceso a API no deben depender de componentes React.
- No realizar `fetch` directamente dentro de componentes si el módulo dispone de una capa de API o query establecida.
- No utilizar `useEffect` como sustituto de TanStack Query para cargar datos remotos.
- Después de una mutación, actualizar o invalidar únicamente las queries afectadas con una estrategia explícita.
- No invalidar indiscriminadamente toda la caché para evitar razonar sobre dependencias.
- Gestionar explícitamente estados de carga inicial, actualización, error, ausencia de datos y permisos insuficientes.
### Estructura de ficheros
- Cada fichero debe tener una responsabilidad reconocible.
- Evitar ficheros genéricos como `utils.ts`, `helpers.ts`, `common.ts`, `types.ts` o `constants.ts` convertidos en cajones de sastre.
- Ubicar utilidades, tipos y constantes junto al dominio o funcionalidad a la que pertenecen.
- Extraerlos a una ubicación compartida únicamente cuando existan consumidores reales en diferentes áreas.
- No crear ficheros `index.ts` o barrels indiscriminadamente.
- Evitar barrels que oculten dependencias, provoquen ciclos de importación o dificulten localizar el origen de un símbolo.
- No crear directorios con un único fichero sin que aporten una frontera conceptual útil.
- No reorganizar carpetas solamente por preferencia personal durante una tarea no relacionada.
### Funciones y responsabilidades
- Preferir funciones pequeñas con una responsabilidad definida, pero no fragmentar una operación sencilla en multitud de funciones triviales.
- Los nombres deben describir intención, no implementación accidental.
- Una función que realiza una consulta no debe modificar estado salvo que su contrato lo indique claramente.
- Separar cálculo puro de I/O cuando esa separación mejore pruebas y razonamiento.
- Reducir anidamiento mediante retornos tempranos cuando mejoren la legibilidad.
- No utilizar retornos tempranos que oculten invariantes o hagan difícil seguir el flujo.
- No crear parámetros, opciones o extensibilidad que ningún consumidor necesite actualmente.
### Dependencias
- Antes de añadir una dependencia, comprobar si el proyecto o la plataforma ya resuelven el problema.
- No instalar una biblioteca por una única función trivial que pueda implementarse claramente con APIs estándar.
- No sustituir una dependencia ya establecida por otra equivalente sin una razón explícita.
- Comprobar compatibilidad con las versiones reales de Node.js, React, TypeScript y demás dependencias del repositorio.
- No asumir APIs de una versión distinta a la instalada.
### Errores y observabilidad
- Los errores deben conservar causa y contexto suficiente para poder diagnosticarlos.
- No registrar el mismo error repetidamente en todas las capas.
- Registrar el error en la capa responsable de observarlo o resolverlo.
- No registrar tokens, contraseñas, cookies, cuerpos sensibles ni credenciales.
- No utilizar `console.log` como mecanismo permanente de observabilidad cuando exista infraestructura de logging.
- Los mensajes de error internos deben ser útiles para diagnóstico; los destinados al usuario no deben revelar detalles internos.
### Pruebas
- Probar comportamiento observable y contratos, no detalles internos de implementación.
- No escribir tests cuyo único objetivo sea aumentar cobertura.
- Evitar mocks cuando pueda utilizarse una dependencia real rápida y determinista o un fake sencillo.
- Mockear fronteras externas, no cada función interna.
- Mantener los tests legibles y con preparación mínima.
- Cada corrección de un bug reproducible debe considerar una prueba que falle antes de la corrección y pase después.
- Para lógica con estados cerrados, cubrir los casos relevantes y los límites, no solamente el camino feliz.
### Criterio antes de escribir código
Antes de implementar:
1. Buscar implementaciones equivalentes dentro del repositorio.
2. Consultar la documentación normativa del proyecto aplicable.
3. Identificar qué capa es responsable del comportamiento.
4. Comprobar si ya existe un componente, hook, schema, utilidad, Value Object o servicio reutilizable.
5. Elegir la solución más pequeña que respete los contratos existentes.
6. Evitar introducir una abstracción nueva si un patrón existente resuelve adecuadamente el problema.
Durante la implementación:
- No ampliar el alcance para corregir cuestiones no relacionadas.
- Si se detecta una mejora ajena a la tarea, mencionarla al finalizar en lugar de modificarla automáticamente.
- No alterar APIs públicas, contratos HTTP, schemas, migraciones o comportamiento compartido como efecto secundario accidental.
- No crear compatibilidad hacia atrás que nadie haya solicitado cuando se pueda actualizar de forma controlada a todos los consumidores.
- No dejar código muerto, implementaciones alternativas comentadas ni TODO vagos.
Al finalizar:
- Revisar el diff completo, no únicamente los ficheros editados individualmente.
- Comprobar que no se hayan introducido duplicaciones, componentes excesivamente grandes, varios componentes relevantes por fichero, imports no usados, casts innecesarios ni efectos React evitables.
- Ejecutar las validaciones apropiadas para el alcance real del cambio.