metis-desk/docs/estructura-web-por-entidades-y-operaciones.md

13 KiB

Estructura web por entidades y operaciones

Fuente normativa de la organización interna del código web de Metis Desk, referenciada desde AGENTS.md. Todo módulo nuevo y toda entidad, operación o componente que se añada a un módulo existente debe seguir esta estructura. Los módulos core, identity, assets, resources y companies proporcionan ejemplos de su aplicación.

Regla de organización

El orden obligatorio es módulo → entidad o área funcional → operación → responsabilidad:

modules/<module>/src/web/<entity>/<operation>/<responsibility>/

Por ejemplo, web/tickets/create/ui/, no web/create/tickets/ ni un directorio global web/components/ para componentes de entidades distintas. Utilizar nombres en inglés y kebab-case. Las áreas sin una entidad de negocio propia, como dashboard o workspace, siguen el mismo orden con operaciones descriptivas como view.

Esta regla organiza el código del navegador, no prescribe nuevas entidades DDD ni cambia la estructura de api y common, la composición del servidor o los contratos HTTP. No obliga a crear carpetas para todas las operaciones posibles ni todas las responsabilidades: cada directorio debe contener una responsabilidad real de la implementación actual.

Ubicación de las responsabilidades

Ejemplo real de tickets en modules/companies/src/web/:

tickets/
  create/
    ui/
      intervention-ticket-create-dialog.tsx
      ticket-quick-editor.tsx
      ticket-quick-forms.tsx
      ticket-target-create-dialog.tsx
      index.ts
    index.ts
  detail/
    ui/
      ticket-detail.tsx
      ticket-detail-dialog.tsx
      index.ts
    index.ts
  list/
    controllers/
      use-tickets-page-controller.ts
      index.ts
    helpers/
      tickets-filtering.ts
      index.ts
    types/
      tickets-page-props.ts
      index.ts
    ui/
      tickets-page.tsx
      index.ts
    index.ts
  shared/
    api/
      ticket-api.ts
      index.ts
    controllers/
      use-ticket-form-controller.ts
      index.ts
    helpers/
      ticket-target-options.ts
      index.ts
    types/
      ticket-form-props.ts
      index.ts
    ui/
      ticket-form.tsx
      ticket-editor-dialog.tsx
      ticket-targets.tsx
      ticket-classification-fields.tsx
      ticket-feedback.tsx
      ticket-help.tsx
      index.ts
    tickets.test.tsx
    index.ts
  index.ts

El árbol muestra las ubicaciones, no implica exportar todos los ficheros. Los índices declaran únicamente los símbolos que sus consumidores necesitan. Las operaciones sin una responsabilidad propia no reciben directorios vacíos: por ejemplo, el editor común de alta y edición permanece en tickets/shared/ui/.

  • ui: componentes y formularios. Un componente principal por fichero, sin nuevas subdivisiones de JSX trivial.
  • api: funciones de acceso HTTP y claves de consultas de su ámbito, sin dependencias de UI.
  • queries: claves y configuración de TanStack Query cuando ya tengan una responsabilidad separada, como health/shared/queries/core.query-keys.ts; no duplicarlas también en api.
  • controllers: coordinación React de una operación cuando necesite una responsabilidad propia, como catalog/shared/controllers/use-catalog-save.ts.
  • hooks: comportamiento React reutilizable o una responsabilidad React identificable, como work-environments/detail/hooks/use-environment-queries.ts.
  • types y helpers: contratos locales y cálculos, próximos a sus consumidores.
  • schemas, adapters, entities o utils: solo cuando existan validaciones de formulario, adaptaciones, modelos de UI o utilidades concretas que requieran esos ficheros. No duplicar DTOs ni schemas HTTP de common, trasladar entidades de dominio a web o crear cajones de sastre genéricos.
  • <entity>/shared: código utilizado por varias operaciones de la misma entidad y pruebas que cubren conjuntamente esas operaciones.
  • web/shared: recursos de varias entidades del módulo, incluidas traducciones y componentes comunes. No sustituye a common, que sigue conteniendo contratos y schemas compartidos con el servidor.

Crear únicamente los subdirectorios que la operación necesite. Una pantalla sencilla no requiere un controller, hook o adapter artificial. Los elementos privados inseparables del componente pueden permanecer junto a él; separar por responsabilidad, no por tamaño ni para completar el árbol de un ejemplo.

Entidades y ámbitos compartidos

Los listados, detalles, altas específicas e historiales se sitúan en list, detail, create y history. Las operaciones particulares conservan nombres explícitos, como change-logo, change, start, update o end.

Áreas antes agrupadas por una categoría amplia quedan identificadas por sus entidades: mailboxes, mailbox-uses y mail-configurations; work-profiles y work-environments; processes y process-templates; interventions e intervention-templates. Los componentes de dashboard y workspace siguen el mismo criterio funcional.

Un nuevo módulo conserva los niveles de entidad y operación aunque inicialmente contenga una sola entidad. Para una entidad nueva, comprobar antes si pertenece a un módulo existente: la estructura web no es un motivo para crear un paquete independiente. Para una operación nueva, utilizar un nombre que describa su intención y no duplicar operaciones que ya cubran el mismo comportamiento.

Ubicar código privado de una operación dentro de ella. Promoverlo a <entity>/shared cuando lo consuman varias operaciones de esa entidad, o a web/shared cuando lo consuman varias entidades del módulo. No usar shared como ubicación por defecto ni anticipar reutilización. Las traducciones siguen su estándar; los componentes visuales genéricos compartidos siguen componentes de interfaz.

Los formularios y diálogos que sirven tanto para crear como para editar se mantienen en el shared/ui de su entidad. No se duplican ni se introducen wrappers de alta y edición sin comportamiento propio. Un diálogo de alta de tickets pertenece a tickets/create, aunque se abra desde una intervención; el consumidor no determina la identidad del componente.

Situar el acceso HTTP en el ámbito más específico que corresponda a sus consumidores: <entity>/<operation>/api, <entity>/shared/api o web/shared/api. Los clientes existentes que abarcan varias entidades se separan por sus operaciones reales. Por ejemplo, users/create/api/create-user.api.ts, people/shared/api/save-person.api.ts y process-templates/list/api/list-process-templates.api.ts. El transporte, errores y claves compartidos no se duplican en cada operación. Las composiciones públicas existentes de identidad e inventario conservan sus consumidores externos, pero los controllers del propio módulo utilizan las funciones de su operación. No dividir clientes pequeños y cohesivos ni alterar firmas, queries o contratos únicamente para reproducir el árbol de ejemplo.

Separación lógica de la operación

El ejemplo de customer-invoices orienta la separación de responsabilidades, no introduce sus dependencias, servicios ni arquitectura de persistencia en Metis Desk:

  • ui: JSX, traducciones de presentación, accesibilidad y adaptación de eventos del DOM. Los modales y formularios significativos conservan su componente independiente.
  • controllers: hooks use-…-controller para coordinación de formularios, queries, mutaciones, invalidación, altas relacionadas y callbacks. Los detalles de ciclo de vida utilizados para evitar callbacks después del desmontaje quedan privados del controller.
  • adapters: transformación de valores de formulario a entradas de la operación cuando exista una conversión real, como vacíos a null o líneas a instrucciones. No envolver objetos idénticos ni duplicar la validación de los schemas.
  • helpers: selección, filtrado y cálculos sin acceso a React, transporte o persistencia. Mantener nombres que identifiquen su propósito y su entidad.
  • schemas y types: validación exclusiva del formulario y contratos locales inferidos de su schema. Los contratos HTTP siguen perteneciendo a common.
  • api y queries: transporte validado y claves de caché separadas de la presentación.

Un controller no es una capa nueva de negocio ni un repositorio: debe conservar la operación existente, no ocultar efectos tras nombres genéricos. No necesita useMemo o useCallback por defecto. Formularios pequeños y consultas simples pueden permanecer en su componente cuando no mezclen responsabilidades significativas; un hook existente que ya encapsule la consulta no requiere un segundo controller que solo lo envuelva.

Los controllers, adapters y helpers no importan implementaciones de UI para acceder a cálculos o constantes. Utilizar imports directos entre APIs cuando pasar por un índice agregado reintroduzca la composición pública en su propio transporte. Los índices exponen solo contratos de su responsabilidad utilizados por otras unidades, no reexports internos de compatibilidad desde el antiguo componente.

Índices y dependencias

Cada nivel debe tener un index.ts solo cuando contenga símbolos exportables para otras unidades con consumidores reales. El índice enumera ese contrato, no todos los exports de los ficheros internos. No se crean índices vacíos, exportaciones *, exportaciones de tests ni índices para detalles privados utilizados exclusivamente dentro de su propio directorio. Los tipos se reexportan mediante export type.

Los consumidores utilizan el índice de la unidad correspondiente cuando ello no añade ciclos. Los imports entre ficheros de la misma unidad permanecen directos. También se conservan imports directos específicos cuando un índice agregado añadiría un ciclo de dependencias, por ejemplo en flujos de altas anidadas. No se amplía la API pública para resolver esas dependencias internas.

web/index.ts compone los índices internos y define los símbolos públicos de @metis/<module>/client cuando haya consumidores externos. Las aplicaciones y otros módulos utilizan ese contrato, no caminos internos del módulo. Al ampliar un módulo existente, mantener sus contratos públicos salvo un cambio deliberado con revisión de consumidores. No añadir reexports de compatibilidad para caminos internos antiguos cuando todos sus consumidores puedan actualizarse de forma segura.

Pruebas y mantenimiento

Las pruebas existentes acompañan al ámbito que comprueban. Los scripts de cada paquete referencian sus nuevas ubicaciones; los imports, traducciones y enlaces de documentación se actualizan junto con los movimientos.

Las pruebas nuevas específicas de una operación viven junto a la responsabilidad que comprueban. Las que cubren varias operaciones se sitúan en el shared correspondiente. No exportar tests desde los índices. Registrar los nuevos ficheros en el script del paquete cuando utilice una enumeración explícita.

Lista de comprobación para nuevos desarrollos

  1. Consultar esta guía, las capas aplicables y las implementaciones equivalentes existentes.
  2. Identificar módulo, entidad o área funcional y operación antes de crear ficheros.
  3. Crear solo responsabilidades necesarias y elegir el ámbito compartido por sus consumidores.
  4. Reutilizar formularios de alta/edición y primitivas visuales; separar modales significativos en su propio .tsx sin trasladar todo su contenido al listado.
  5. Definir índices explícitos únicamente donde haya contratos exportables a otras unidades; comprobar imports de tipos, consumidores y ausencia de nuevos ciclos.
  6. Mantener DTOs y schemas compartidos en common, traducciones es/en y acceso HTTP fuera de la presentación; no introducir capas o dependencias para completar una estructura.
  7. Añadir pruebas observables, actualizar scripts y enlaces, y revisar el diff completo.
  8. Ejecutar comprobaciones dirigidas al paquete o las cinco validaciones globales cuando el cambio sea transversal, según pruebas.

Alcance de la migración realizada

La reorganización inicial trasladó ficheros y ajustó sus imports, sin cambiar comportamiento ni contratos. No extrajo lógica nueva a controllers, hooks, schemas o adapters; esa auditoría se aplazó por decisión del usuario. La auditoría posterior separó la lógica de formularios, listados y coordinación en los cinco módulos, excluyendo apps/web/src y vendor. Su alcance, criterios y comprobaciones se registran en la auditoría del cliente. La limitación de la migración inicial no exime al código nuevo de las reglas de separación de responsabilidades de AGENTS.md.

Para futuros cambios, consultar también componentes de interfaz, inventario de modales, patrón de listados y pruebas. No trasladar una responsabilidad compartida a una operación concreta ni extraer lógica nueva únicamente para reproducir un árbol de directorios.