metis-desk/docs/auditorias/resources-global-scope/01-modelo-resource.md
2026-10-25 01:19:17 +02:00

15 KiB

01 — Modelo Resource actual

Fecha de corte: 24 de octubre de 2026. Investigación del árbol de trabajo, incluidos cambios locales previos, sin modificar aplicación ni datos. Las líneas enlazadas corresponden a ese árbol, no necesariamente a HEAD. Método, limitaciones y huellas en 12; decisiones en 11.

Etiquetas para toda la serie: CONFIRMADO (código/esquema del repositorio), DERIVABLE (consecuencia de contratos), DOCUMENTADO (texto sin ejecución verificada), PROPUESTO (diseño futuro), NO CONFIRMADO (evidencia insuficiente), RIESGO (requiere mitigación). «Esquema actual» significa esquema que construyen las migraciones del repositorio; no se inspeccionó ninguna BD desplegada.

Arquitectura y mapa de dependencias

CONFIRMADO. TypeScript, React 19.2.5, TanStack Query 5.98.0, Zod 4.3.6 y Sequelize 6.37.8 figuran en los manifiestos. MariaDB se configura en apps/server/src/config/database.ts y en Docker Compose. Resources, Assets, Companies e Identity son módulos con composition roots explícitos. exports: package.json:6 runtime: identity-runtime.ts:17.

flowchart TD
  UI[ResourcesPage / ResourceEditor] --> API[inventoryApi + schemas Zod]
  API --> HTTP[registerResourceRoutes]
  HTTP --> UC[ResourceUseCases]
  UC --> PORT[IResourceRepository]
  PORT --> REPO[SequelizeResourceRepository]
  REPO --> DOM[Resource + ResourceValues + ResourceSpecification + ResourceRevision]
  REPO --> SQL[resources y tablas relacionales de valores/especificaciones/revisiones]
  CAT[ResourceCatalog + ResourceDefinition] --> REPO
  REPO --> PROJ[Proyecciones Mailbox / Access / Directorio / SMB]
  COMP[Companies: instalaciones y configuraciones] --> SERVICE[IResourceInventoryService]
  SERVICE --> PORT

Dependencias de compilación: Infrastructure → Application → Domain. El puerto se declara en Application; el adaptador lo implementa en Infrastructure; el diagrama representa llamadas, no una dependencia del puerto hacia Sequelize. port: resource.repository.ts:21 repository: sequelize-resource.repository.ts:32.

Superficies públicas: @metis/resources/common, /client, /module, /services. /services exporta puertos de inventario, directorio, red y escritores de contexto. Companies consume esos servicios; Assets consume lectores de dominios/SMB. No importar ResourcePersistenceMapper, ResourceCatalogModel ni repositorios privados desde otros módulos. La búsqueda no localizó imports privados de Resources en consumidores de producción; sí imports relativos internos en pruebas de integración de apps/server (template-propagation, directory-domains, smb-access y fixtures), que no son APIs públicas de negocio. CONFIRMADO: existen consultas SQL propietarias de Resources en la migración y repositorio de asistencia de Assets; son acoplamiento transversal existente, no una API recomendada. NO CONFIRMADO: un framework dinámico «Plugin Architecture v1»; los registros de relaciones tipadas no lo constituyen. module: module.ts:39 services: services.ts:11 supportRead: sequelize-support.repository.ts:21.

Inventario de implementación

Capa Entradas concretas Responsabilidad
Dominio aggregate: resource.aggregate.ts:17 values: resource-values.vo.ts:123 definition: resource-definition.ts:23 Identidad, valores, especificación y revisiones
Application save: resource.use-cases.ts:64 mapInput: resource-input.mapper.ts:13 capture: resource-input.mapper.ts:69 Alta/edición, unicidad visible y referencias
Puertos/adaptador port: resource.repository.ts:21 load: sequelize-resource.repository.ts:58 Contexto por compañía, bloqueo y persistencia
Esquema/SQL sql: resource.model.ts:19 columns: resource-relational-schema.ts:25 read: resource-inventory-relational.ts:195 write: resource-inventory-relational.ts:315 Tablas normalizadas, sin documento JSON
HTTP/DTO routes: resource.routes.ts:54 dto: inventory.ts:35 Validación de params/body, traducción de resultados
Web inventoryAPI: inventory-api.ts:11 resourceController: use-resources-page-controller.ts:24 resourceForm: use-resource-form-controller.ts:16 resourceDetail: resource-detail.tsx:11 Consultas, filtros, formulario y ficha/historial
i18n modules/resources/src/common/locales/es.json, en.json Etiquetas, ayuda y errores; traducciones operativas en Companies
Pruebas unitResource: resource.test.ts:38 unitLifecycle: resource-lifecycle.test.ts:94 integrationResource: resources.integration.ts:19 integrationLifecycle: resource-lifecycle.integration.ts:11 Dominio, ciclo de vida y contratos HTTP/SQL

Los formularios, selectores y demás consumidores se detallan en 05 y 06.

Propiedades de raíz y transporte

CONFIRMADO. ResourceProps solo contiene compañía, especificación, valores e historial; id viene de AggregateRoot. values, specification e history son objetos reconstruidos, no columnas JSON. owner: resource.aggregate.ts:11 input: resource-values.ts:40 spec: resource-values.ts:49 sql: resource.model.ts:19 typed: resource-relational-schema.ts:98.

Campo Tipo Nullable Obligatorio Restricción Uso
id UniqueID / UUID No Sí; generado al crear PK, UUID v7 nuevo; UUID válido al rehidratar Identidad estable independiente del nombre
companyId UniqueID / UUID No Sí FK companies, RESTRICT Propiedad y contexto de carga/escritura
templateId / values.templateId UUID No Sí FK resource_definitions; plantilla fija Selección por ID, no por nombre
values.name string(1..100) No Sí Name normalizado; duplicado controlado en Application Identificación visible
values.active boolean No Sí Sin default en entrada Activar/desactivar sin borrar
values.notes string ≤10000 / TEXT No Sí; admite vacío Zod Observaciones
values.characteristics record UUID → string/number/boolean No Sí; admite objeto vacío si los campos lo permiten Solo fields de specification, tipo y required Valores dinámicos; fecha es string ISO
values.integration mailbox/access discriminado Omitible Condicional Campos y referencias validados; contexto fijo Compatibilidad operativa
specification.templateId/templateName UUID/string ≤100 No Sí Captura de plantilla Estructura actual
specification.fields array ≥1 No Sí fieldId único, tipos text/number/date/boolean/choice Nombre, unidad, opciones, required, sectionId opcional
specification.sections array de id/name/description Omitible No Referencias coherentes Organización visual
history ResourceRevision[] ≥1 en DTO/mapper No Sí Orden, fechas y última revisión coherentes Auditoría
directory / networkShare DTO derivado Omitible Solo capacidad aplicable Proyección de dominio/SMB No son propiedades de ResourceProps

Integración persistida: integrationKind VARCHAR(10), addressFieldId, contextFieldId, accountId, referenceFieldId, resourceTypeId, systemId, environmentId UUID nullable; mailboxKind VARCHAR(10) nullable. Los UUID referencian definiciones o Access según columna. templateName VARCHAR(100) NOT NULL y sectionsDefined boolean NOT NULL describen la especificación actual. columns: resource-relational-schema.ts:25.

Cada revisión persiste id, resourceId, actorId, recordedAt VARCHAR(30), position, templateId, nombre/actividad/notas e integración. Valores y especificación histórica están en tablas hijas. Actor tiene FK a users. actorName es resuelto al construir respuesta, no una instantánea del nombre del operador. sql: resource.model.ts:19 mapper: resource-persistence.mapper.ts:24 history: resource-revision-specifications.ts:39.

CONFIRMADO por inventario estático: no hay scope, externalId, createdAt, updatedAt, deletedAt, columna de versión optimista ni identificador canónico de producto. Una característica propia puede contener una referencia textual, sin unicidad ni semántica externa automática. No hay control optimista de ediciones con una versión enviada por cliente. Con integración, validateIntegration limita notes a 4000 y referencias documentales a 255; el máximo genérico de notes sigue siendo 10000. La concurrencia se ordena mediante transacciones/bloqueos y posición única del historial.

Ciclo de vida y reglas

  1. Alta: el path aporta companyId; no se deduce de una compañía de la sesión. El actor sí procede de sesión. ResourceUseCases.save convierte UUID, carga el contexto y usa mapResourceInput.
  2. Validación: captura plantilla visible y activa; verifica integración/referencias y características con ResourceValues.create. Nombre repetido se rechaza por plantilla y contexto de acceso en la compañía; direcciones de buzón tienen control adicional.
  3. Persistencia: el repositorio bloquea catálogo global, catálogo de accesos cuando procede y compañía; writeInventory escribe raíz, tablas actuales y revisiones nuevas en transacción, más proyecciones. No hay una transacción distribuida de plugins.
  4. Consulta: readInventory(companyId) carga raíces de esa compañía y sus tablas hijas por IDs; mapper comprueba UUID, tipos, orden y última revisión. Repositorio comprueba plantilla/campos visibles para esa compañía.
  5. Edición: plantilla fija (TEMPLATE_FIXED); varios atributos de integración son fijos (CONTEXT_FIXED). Igualdad semántica evita nueva revisión, aunque el adaptador de Resource puede reescribir tablas actuales.
  6. Estado: setActive construye valores nuevos y aplica update; conservar ID y primera revisión. La desactivación no retira instalaciones ni configuraciones automáticamente.
  7. Borrado: físico y explícito, no paranoid/soft delete. removeResource bloquea raíces y descubre FKs entrantes mediante information_schema; rechaza RESOURCE_IN_USE si hay una asociación externa actual o histórica; elimina datos propios y proyecciones admitidas cuando no los hay. La FK no protege referencias textuales sin FK.

Evidencia: save: resource.use-cases.ts:64 load: sequelize-resource.repository.ts:58 update: resource.aggregate.ts:100 remove: remove-resource.ts:37. No se encontraron recursos válidos sin compañía en este contrato; NO CONFIRMADO su ausencia física en una BD real, no consultada.

Historial e impacto del ámbito global

La compañía no se repite como dato independiente en cada revisión de Resource: se hereda de la raíz. Cambiar el propietario cambiaría cómo se interpreta todo el historial; por ello se propone ámbito/propietario inmutables. Un ID global único permite conservar las FKs simples de software/perfil, pero los mappers y lectores actuales fallarían ante companyId NULL. No equivale a soportar globales. mapper: resource-persistence.mapper.ts:24.

DERIVABLE: al desactivar un recurso referenciado, softwareAvailable y applicationAvailable pasan a false; sus builders marcan needsReview para registros abiertos. Las referencias históricas siguen resolviéndose si la lectura incluye inactivos. El futuro catálogo deberá mantener ese comportamiento para todas las compañías consumidoras. softwareResponse: software-installation-response.builder.ts:7 applicationPolicy: application-configuration-policy.ts:5. Decisiones de autorización y consulta están en 04 y 07.