25 KiB
Instrucciones de Metis Desk
Alcance y entorno
Proyecto independiente de factuges-erp. Se conserva su estilo, no sus funcionalidades ni dependencias locales.
El repositorio vive en /home/david/dev/metis-desk, en WSL2 Ubuntu-24.04.
Ejecutar las herramientas del proyecto dentro de WSL con bash, nunca desde Windows, rutas UNC o /mnt/c.
Antes de modificar código: comprobar pwd, git status --short, node --version y pnpm --version.
Usar Node.js 24.x y la versión de PNPM fijada en package.json.
No imprimir, copiar ni versionar .env locales o secretos.
packages/shadcn-ui contiene código de terceros/vendor. No refactorizar, reformatear, reorganizar ni modificar su implementación salvo instrucción explícita del usuario. Puede consultarse su API pública para integrarlo desde código propio.
Convenciones
- Código e identificadores en inglés; comentarios, TSDoc y documentación 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.
- Interfaces con prefijo I cuando el área ya siga esa convención.
- 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.
- Utilizar siempre llaves en if, else, for, while y estructuras equivalentes, incluso cuando el bloque contenga una sola instrucción.
Disciplina
Aplicar SOLID, Clean Code, DRY, KISS, YAGNI, SSOT, fail fast y programación defensiva. Priorizar claridad y explicitud frente a abstracción prematura. Cambios incrementales y acotados. No renombrar, reformatear o mover código ajeno a la tarea. No crear capas, factories, genéricos, eventos, contenedores DI ni repositorios base sin necesidad real. Justificar cada nueva dependencia. Preferir APIs nativas cuando basten. No añadir supresiones de TypeScript o Biome sin justificación. No eliminar legado sin buscar y clasificar sus referencias en todo el monorepo.
TypeScript y asincronía
- Importar tipos explícitamente con import type en la cabecera.
- No usar import("...").Tipo en código ordinario.
- Evitar any, assertions y non-null assertions salvo garantías verificables.
- Inferir DTOs de schemas Zod; no duplicarlos mediante interfaces.
- Usar unions para conjuntos cerrados y mantener switches exhaustivos.
- No usar Partial si el contrato ya representa la opcionalidad.
- No usar el operador void para descartar promesas.
- Toda promesa se espera, se devuelve o gestiona su error expresamente.
- No dejar promesas flotantes ni capturas que oculten fallos.
- Comprobar el contrato del callback antes de convertirlo a async.
- Usar Result para fallos de negocio y Maybe para ausencia semántica cuando se implementen esas operaciones.
- Collection solo cuando el conjunto necesite comportamiento de dominio; no introducir estos patrones sin consumidores.
Arquitectura y contratos
Las fuentes normativas se consultan, no se duplican:
- Capas del servidor
- Composición de módulos
- Flujo HTTP
- Contratos HTTP
- Pruebas
- Modelo DDD e identificadores
Seguridad y persistencia
- Menor privilegio y validación de toda entrada externa.
- Autenticación y autorización antes de acceder a datos de clientes.
- El cliente atendido no es el tenant de la empresa que presta soporte.
- Verificar siempre permisos y pertenencia; no confiar en companyId enviado por el navegador.
- No exponer SQL, stacks, datos sensibles o credenciales en respuestas ni logs.
- Registrar identificadores de petición y resultados suficientes para diagnosticar fallos.
- MariaDB y Sequelize v6 para persistencia; ORM exclusivamente en Infrastructure.
- Consultas parametrizadas, transacciones para cambios atómicos y restricciones en base de datos.
- Migraciones explícitas; no usar sync({ alter: true }) como sustituto.
- Toda entidad de negocio, incluidas las entidades internas de un agregado, tiene identidad UniqueID (UUID v7 para nuevas entidades), siguiendo factuges-erp.
- Declarar Value Objects, DomainEntity y AggregateRoot según docs/arquitectura/modelo-ddd-e-identificadores.md.
- Mantener semántica de create, rehydrate y update según la arquitectura.
- Comparar cambios de dominio semánticamente, no mediante JSON.stringify.
- Añadir idempotencia donde una repetición pueda producir efectos duplicados.
Frontend
React, TanStack Query, Tailwind y shadcn/ui (Base UI, estilo Vega). Los componentes compartidos viven en @repo/shadcn-ui. Incorporar TanStack Table y React Hook Form cuando haya consumidores. Estado remoto en TanStack Query; claves centralizadas e invalidación explícita después de mutaciones. No duplicar estado remoto ni usar efectos para derivar valores calculables durante render. Mantener accesibilidad y estados de carga, error, vacío y acciones deshabilitadas. Separar componentes por responsabilidades reales, sin fragmentar JSX trivial. Antes de crear o modificar pantallas, reconocer activamente patrones visuales y de interacción comunes entre ellas, así como componentes repetidos dentro de una misma pantalla. Aplicar soluciones coherentes a todos los casos equivalentes, conservando las necesidades propias de cada entidad. Reutilizar y extender la biblioteca visual existente antes de duplicar estructura, estilos o comportamiento. Extraer componentes compartidos cuando haya repetición real: cabeceras, listados, formularios modales, grupos de campos, selectores con alta, estados y ayuda contextual. Construir progresivamente una biblioteca visual básica con contratos claros, accesibilidad, textos traducibles y composición sencilla. Mantener los componentes genéricos independientes de entidades, DTO, consultas y endpoints de Metis Desk; el módulo consumidor aporta datos, textos y acciones. Documentar patrones y componentes en docs/componentes-de-interfaz.md y en el estándar correspondiente. Preparar esa independencia para poder agrupar o extraer posteriormente componentes en un paquete reutilizable en otras aplicaciones. Aprovechar @repo/shadcn-ui y crear abstracciones conforme aparezcan consumidores reales. Las pantallas de listado de entidades siguen el patrón de listados, reutilizando los componentes comunes de @repo/shadcn-ui. Los listados no muestran formularios de alta ni edición junto a los datos. El alta se inicia con un botón en la cabecera, a la derecha; formularios en modal shadcn/ui como norma general, con anchura y desplazamiento adaptados a su contenido. La edición se abre mediante una acción explícita, siguiendo el mismo criterio. Mantener foco, cierre por teclado, cancelación y errores dentro del formulario. Incluir ayuda contextual con finalidad, ámbito y ejemplos, sin rellenar los campos automáticamente. En formularios, colocar la ayuda en la parte inferior, inmediatamente antes del pie del modal con Guardar y Cancelar.
Validación y entrega
Ejecutar pnpm typecheck, pnpm lint, pnpm test, pnpm check y pnpm build en cambios transversales. Preferir comprobaciones dirigidas al paquete cuando sean suficientes. Añadir pruebas de comportamiento observable: válidos, inválidos, límites e idempotencia cuando corresponda. Tests deterministas, sin esperas arbitrarias ni desactivaciones. Informar cambios, archivos principales, comandos ejecutados, resultados y pendientes reales. 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
unknownpara datos cuyo tipo todavía no haya sido validado. No sustituirunknownporanypara 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 Tipocomo validación. - Preferir narrowing, type guards, schemas y discriminated unions frente a assertions.
- Preferir
satisfiescuando 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
switchsobre discriminated unions mediantenevercuando 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,
undefinedynull. - 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
AbortSignalo mecanismos equivalentes cuando sea necesario propagar cancelación. - No dispersar accesos a
process.envpor 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,uncaughtExceptionni 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,headersybodyen 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/catchen 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
.tsxcontiene 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
useEffectpara sincronizar un estado React con otro estado React. - No utilizar
useEffectpara 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
useMemoouseCallbackpor 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
childreny 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.
Diálogos, modales y ventanas emergentes
- Páginas y listados no deben implementar directamente el contenido de ventanas modales funcionales. Los diálogos de alta, edición, confirmación, selección o detalle son componentes independientes.
- Toda ventana modal, cuadro de diálogo o formulario modal con contenido y responsabilidad propios debe extraerse a un componente React independiente y vivir en su propio fichero
.tsx. - Esta regla se aplica especialmente a componentes construidos con primitivas de shadcn/ui como
Dialog,AlertDialog,Sheet,Drawero componentes propios comoFormDialog, pero no depende del nombre concreto utilizado. - Si un bloque representa una interacción modal reconocible por sí misma —por ejemplo alta, edición, confirmación, selección, detalle, ayuda o cambio de estado— debe considerarse un componente independiente.
- El componente padre debe encargarse de decidir cuándo se abre el diálogo y de suministrarle los datos o callbacks necesarios, evitando contener internamente todo el JSX del modal.
- El componente del diálogo debe encapsular su estructura visual y la lógica de interacción que le pertenezca.
- No mantener grandes bloques de JSX de
DialogContent, formularios completos o confirmaciones dentro del fichero de una página o listado cuando puedan identificarse como una unidad funcional independiente. - Los formularios que se muestran dentro de un diálogo pueden separarse además en un componente propio cuando tengan suficiente entidad, reutilización o complejidad.
- No extraer a otro fichero un
Dialogtrivial cuyo único contenido sea un mensaje mínimo sin lógica, estado, formulario ni responsabilidad propia, salvo que forme parte de un patrón reutilizado. - Antes de crear un diálogo nuevo, comprobar si ya existe un componente equivalente o un patrón común reutilizable en el proyecto.
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
fetchdirectamente dentro de componentes si el módulo dispone de una capa de API o query establecida. - No utilizar
useEffectcomo 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.tsoconstants.tsconvertidos 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.tso 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.logcomo 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:
- Buscar implementaciones equivalentes dentro del repositorio.
- Consultar la documentación normativa del proyecto aplicable.
- Identificar qué capa es responsable del comportamiento.
- Comprobar si ya existe un componente, hook, schema, utilidad, Value Object o servicio reutilizable.
- Elegir la solución más pequeña que respete los contratos existentes.
- 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.