metis-desk/docs/architecture/resources-global-scope/01-contrato-arquitectonico.md

118 lines
19 KiB
Markdown
Raw Normal View History

2026-10-24 23:19:17 +00:00
# R2.0 — Contrato arquitectónico de Resources Global Scope
> **Ratificación posterior — 24 de octubre de 2026:** D01–D10 y D04-R ratificadas en los términos recomendados de R2.0; variante A, Resource único con `scope` explícito. Solo se autoriza implementar R2.1a. El registro vigente de alcance y evidencias está en [11 — Lectores compatibles](11-r2-1a-lectores-compatibles.md). Los estados «propuesto», «recomendado» y «pendiente» del cuerpo se conservan como evidencia de la versión documental inicial y quedan actualizados por este registro; ratificación conceptual no equivale a ejecución de R2.1b–R2.6.
Versión documental 1, 24 de octubre de 2026. Estado: **CONTRATO PROPUESTO PARA RATIFICACIÓN**, sin implementación autorizada. «Debe» especifica el contrato futuro bajo las decisiones recomendadas; no convierte una recomendación en aprobación. El registro de estados y alcance aprobado es [02](02-decisiones-d01-d10.md); la entrada de revisión es [10](10-resumen-para-aprobacion.md).
## 1. Alcance y precedencia
Extender Resource para registrar una definición de aplicación compartida sin copias por compañía. Instalación, configuración, licencia, activo, perfil y permisos siguen siendo locales según sus contratos. Se conserva Resources como módulo propietario, UUID estable, persistencia relacional y composición explícita. No se crea ApplicationCatalog como modelo paralelo ni un framework de plugins.
Los doce documentos de la auditoría son evidencia histórica; esta serie formaliza sus decisiones sin modificarlos. Los requisitos expresos del usuario prevalecen. La ruta `docs/architecture/resources-global-scope/` se utiliza por petición expresa; no renombra `docs/arquitectura/` ni altera su normativa. Sources y discrepancias se registran al final de este documento.
Fuentes normativas de integración: [capas](../../arquitectura/capas-del-servidor.md), [composición](../../arquitectura/composicion-de-modulos.md), [HTTP](../../arquitectura/contratos-http.md), [DDD](../../arquitectura/modelo-ddd-e-identificadores.md). Application no importa Sequelize; los puertos públicos de Resources no exponen repositorios ni modelos.
## 2. Qué existe y qué cambia
**CONFIRMADO:** ResourceProps exige companyId UniqueID; `resources.companyId` tiene NOT NULL/FK; ResourceSchema exige UUID. El inventario público lista exclusivamente por compañía. El catálogo de definiciones ya admite companyId NULL y stockKey estable. [resource.aggregate.ts:11](../../../modules/resources/src/api/domain/resource.aggregate.ts#L11) [resource.model.ts:19](../../../modules/resources/src/api/infrastructure/persistence/resource.model.ts#L19) [inventory.ts:35](../../../modules/resources/src/common/inventory.ts#L35) [resource-inventory.service.ts:7](../../../modules/resources/src/api/application/resource-inventory.service.ts#L7) [resource-catalog.model.ts:48](../../../modules/resources/src/api/infrastructure/persistence/resource-catalog.model.ts#L48).
**APROBADA PARA IMPLEMENTACIÓN por requisito expreso:** una aplicación global sigue siendo Resource, puede ser usada por compañías distintas y no representa permiso/licencia/instalación. Solo la plantilla de serie `template:application` produce globales inicialmente. No se modifican automáticamente referencias históricas. Esto establece restricciones del futuro trabajo, **no autoriza implementar R2.1 en esta tarea**.
**RECOMENDADA D01:** un Resource raíz porta `scope` declarado global/company y propietario discriminado. Los valores propios del producto permanecen en ResourceValues; scope/owner pertenecen a la raíz, no a características dinámicas. El ámbito se representa una vez y se valida al construir/reconstruir; no introducir un Value Object contenedor sin comportamiento necesario.
| Campo conceptual | Global | Compañía | Contrato |
| --- | --- | --- | --- |
| id | UUID v7 nuevo | UUID v7 nuevo | Identidad global única; IDs persistidos válidos se conservan |
| scope | global | company | NOT NULL, conjunto cerrado; datos anteriores company |
| companyId | null | UUID de companies | Dueño, no compañía seleccionada ni tenant del operador |
| values.templateId | UUID resuelto por stockKey de serie | UUID de plantilla visible local/global | Inmutable después de alta |
| values.active | boolean | boolean | Existente, sin nuevo draft/published |
| values/name/characteristics/notes | Datos de producto | Datos locales propios de plantilla | Mismos límites y validación dinámica; integración prohibida en global |
| specification/history | Capturas de estructura/valores | Capturas locales | Conservar IDs, actor/fecha y revisiones; scope/owner heredados de raíz inmutable |
Los pares global+UUID, company+NULL, scope desconocido o ausente en un esquema ya migrado son inválidos. Dominio usa UniqueID para referencias locales, DTO UUID string/null y SQL tipo compatible con UUID existente. Backfill solo marca scope; no mueve Resource a NULL ni crea UUID nuevos. Una lectura legacy sin columna puede derivar company exclusivamente dentro del adaptador transitorio, si companyId es válido; no es la representación final B.
## 3. Plantillas y clasificación
Regla global: definición de kind template, stockKey exacto `template:application`, propietaria global, todos sus campos globales; templateId es el UUID real de esa definición. `builtinKey` es nombre de transporte de stockKey, no otro identificador. No usar nombre, icono, fieldId provider ni usage por sí solo para habilitar globales. La plantilla de serie puede no tener usage persistido: el clasificador actual la reconoce por stockKey. [resource-inventory.service.ts:54](../../../modules/resources/src/api/application/resource-inventory.service.ts#L54) [resource-catalog-seed.ts:140](../../../modules/resources/src/api/domain/resource-catalog-seed.ts#L140) [index.ts:19](../../../modules/resources/src/common/index.ts#L19).
Locales: conservar plantillas propias de uso application y la serie; software admite además OS local según sus políticas. Una copia de Aplicación no se convierte en plantilla de serie. Los usos SMB, license, certificate, directory, support, os y desconocidos no producen globales. Cambio de plantilla o scope/owner queda fuera del CRUD, incluso sin referencias.
Ampliar elegibilidad requiere nuevo ADR, revisión de consumidores/privacidad, pruebas negativas y despliegue de lectores antes de cambiar una allowlist del servidor. No añadir un booleano editable globalEligible al formulario como vía para elevar privilegios.
**RECOMENDADA D06:** desactivar la definición de plantilla bloquea altas de Resource bajo ella, pero no bloquea asociaciones a un Resource existente activo. El override local de plantillas controla creación local, no es una política de uso del catálogo global. Edición de un producto existente usa su specification y validación vigente; nuevas obligatoriedades propagadas se exigen en la siguiente edición. Propagación global/local transaccional y preservación de campos retirados con valor siguen la normativa existente. No hay versión numérica de plantilla ni herencia viva; no inventarlas. [resource-input.mapper.ts:69](../../../modules/resources/src/api/application/resource-input.mapper.ts#L69) [prepare-resource-template-updates.ts:15](../../../modules/resources/src/api/infrastructure/persistence/prepare-resource-template-updates.ts#L15).
## 4. Lecturas con responsabilidad explícita
| Superficie conceptual | Datos | Autoridad y límites |
| --- | --- | --- |
| Gestión local existente | Resource de C, DTO legacy sin scope añadido | Sesión/modelo compartido actual; nunca global ni recurso de B |
| Catálogo global público al espacio de soporte | Definición mínima, activos e inactivos | Sesión activa, datos de producto, sin historial administrativo |
| Administración global | Resource global completo y su historial paginado | Capacidad conceptual resources.global.manage, propuesta D03 |
| Candidatos por propósito/C | Global application activa + local elegible activo; software añade OS local | Filtro servidor antes de paginar; nunca licencia ni otro local B |
| Resolución por UUID/C/propósito | Referencia mínima activa o inactiva elegible | Repetir seguridad/tipo; resolver inactivos no habilita nuevas escrituras |
| Otros consumidores | Catálogos/relaciones locales como hoy | Conservan sus propios lectores/políticas; no ampliar list genérico |
Resources decide alcance/tipo de Resource. Companies decide compañía, activo/perfil/persona, disponibilidad operativa y licencia; Assets conserva SMB/directorio/support. El propósito nace del caso de uso de servidor al escribir, nunca se confía en propósito/scope enviados por navegador. Ver [03](03-contrato-seguridad.md), [04](04-contratos-api-dto.md) y [06](06-matriz-consumidores.md).
## 5. Ciclo de vida y relaciones
Crear requiere permiso global, plantilla elegible activa y campos válidos. Tras alta, active determina disponibilidad: no hay estado de publicación adicional. Editar conserva UUID, ámbito, propietario, plantilla y primeras revisiones. Actualización semánticamente idéntica o repetir status no añade revisión.
Desactivar conserva Resource e historial; candidatos nuevos lo excluyen; lectura por UUID lo resuelve; no elimina ni retira instalaciones/configuraciones/licencias, no reasigna a otro producto. Reactivar devuelve disponibilidad, sin modificar estados locales. Borrado físico global se **difiere**: la primera versión no ofrece comando ni endpoint de borrado global. El borrado local existente conserva sus controles. Un futuro borrado global exige ausencia de toda referencia actual/histórica, control de reintentos y auditoría; no se habilita por reutilizar removeResource.
`needsReview` no es universal: hoy lo calculan los builders de SoftwareInstallation y ApplicationConfiguration como `!closed && (!available || state==='removal_pending')`. Al desactivar el producto, las relaciones abiertas cumplen !available; las cerradas mantienen needsReview false. No persistir un flag en Resource, no escribir todas las compañías ni emitir revisiones operativas por una desactivación global. Reactivar puede quitar esa causa del aviso, pero no otras. [software-installation-response.builder.ts:7](../../../modules/companies/src/api/application/software-installation-response.builder.ts#L7) [application-configuration-response.builder.ts:7](../../../modules/companies/src/api/application/application-configuration-response.builder.ts#L7).
Regla exacta de edición operativa preservada: nuevo registro o cambio de state/version (y delivery para perfil) que termine pending/configured requiere referencia disponible. Añadir solo notas, guardar sin cambios o avanzar retirada sigue permitido con recurso inactivo, mientras el estado y demás invariantes lo permitan. Una nueva asociación a inactivo se rechaza incluso si se pide estado terminal. Cambiar licenseResourceId exige licencia local disponible; no se deduce compatibilidad licencia-producto. [software-installation-input.mapper.ts:15](../../../modules/companies/src/api/application/software-installation-input.mapper.ts#L15) [application-configuration-input.mapper.ts:11](../../../modules/companies/src/api/application/application-configuration-input.mapper.ts#L11).
## 6. Invariantes transversales
Todos son requisitos futuros. «Base» identifica principios ya establecidos por el usuario, no evidencia de implementación v2. El resto depende de ratificación según D01–D10. Errores con * son nombres conceptuales nuevos; los demás existen. Se mapearán a Result y HTTP en 04. Cada fila tiene prueba futura obligatoria en [08](08-plan-pruebas.md).
| ID | Descripción / validación | Nivel | Error esperado | Prueba | Fase / origen |
| --- | --- | --- | --- | --- | --- |
| INV-01 | Local tiene companyId UUID y FK companies válida | Dominio/HTTP/SQL | INVALID_REQUEST / COMPANY_NOT_FOUND | T01 | R2.1; Base |
| INV-02 | Global carece de propietario; par discriminado coherente | Dominio/DTO/SQL CHECK | INVALID_REQUEST | T02 | R2.1; Base + D01 |
| INV-03 | Solo serie Aplicación global y campos globales, sin integración | Resources dominio/Application | RESOURCE_GLOBAL_TEMPLATE_NOT_ALLOWED* | T03 | R2.1/R2.2; Base/D02 |
| INV-04 | Inst./config./licencia conservan compañía propia NOT NULL | Dominio/SQL consumidor | INVALID_REQUEST | T04 | R2.4/R2.5; Base |
| INV-05 | Software: global application o local application/os de C | Resources + Companies | SOFTWARE_REFERENCE_NOT_FOUND/UNAVAILABLE | T05 | R2.4; Base |
| INV-06 | Perfil: global application o local application de C | Resources + Companies | APPLICATION_REFERENCE_NOT_FOUND/UNAVAILABLE | T06 | R2.5; Base |
| INV-07 | licenseResourceId solo local license de C; activo/vigencia al asociar | Companies | SOFTWARE_REFERENCE_NOT_FOUND/UNAVAILABLE | T07 | R2.4; Base |
| INV-08 | Nunca local B en operación de A, tampoco para admin | Policy por propósito | NOT_FOUND o unavailable sin datos de B | T08 | R2.2–R2.5; Base |
| INV-09 | Desactivar global no elimina/modifica relaciones ni historia | Resource Application | Sin error en desactivación válida | T09 | R2.3–R2.5; Base |
| INV-10 | Escribir/historial admin global requiere autorización vigente | Identity/HTTP/Application | UNAUTHENTICATED/FORBIDDEN | T10 | R2.2; Base, asignación D03 |
| INV-11 | Candidatos/resolución no exponen operación ni notas/historial ajenos | DTO/consulta | FORBIDDEN o unavailable | T11 | R2.2; Base/D05 |
| INV-12 | UUID históricos se conservan y resuelven incluso inactivos | Persistencia/lectores | Corrupción interna si FK/ámbito inválido | T12 | R2.1/R2.4/R2.5; Base |
| INV-13 | scope/owner/template/destinos operativos inmutables por update | Dominio/HTTP estricto | TEMPLATE_FIXED / *_TARGET_IMMUTABLE / INVALID_REQUEST | T13 | R2.1–R2.5; D01 |
| INV-14 | Gestión local y list genérico excluyen globales | Repo/servicio/HTTP | RESOURCE_NOT_FOUND para ID global en CRUD local | T14 | R2.1/R2.2; Base |
| INV-15 | Validar elegibilidad dentro de la misma unidad de escritura | Infrastructure ligada a servicio público | *_REFERENCE_UNAVAILABLE | T15 | R2.2/R2.4/R2.5; D10 |
| INV-16 | Solo activo crea asociación nueva; existente inactivo permite recuperación | Policy consumidor | *_REFERENCE_UNAVAILABLE o éxito según edición | T16 | R2.4/R2.5; D06 |
| INV-17 | UUID identifica producto; coincidencia textual no fusiona | Resources/UX | Sin error solo por homónimo global | T17 | R2.3/R2.6; Base/D07 |
| INV-18 | Reintento de alta global conserva UUID y primera revisión | Nuevo comando/persistencia | RESOURCE_CREATION_CONFLICT* si cambia petición | T18 | R2.3; D07/D09 |
| INV-19 | DTO legacy local no recibe scope nuevo ni NULL | Builders/compatibilidad | RESOURCE_CLIENT_UPGRADE_REQUIRED* para operación global no soportada | T19 | R2.1–R2.5; Base/D09 |
| INV-20 | CHECK/FKs no se eliminan sin restaurar garantías; no datos operativos NULL | Migración/SQL | Constraint SQL, error interno saneado | T20 | R2.1; Base/D10 |
| INV-21 | Propagación procesa global y local conservando snapshots | Resources repositorio | Rollback total de DML si invariantes fallan | T21 | R2.1; D10 |
| INV-22 | Invalidar/refetch nombres/avisos con claves de contexto y propósito | API web/Query | Error de refetch visible, no vacío falso | T22 | R2.3–R2.5; D09 |
| INV-23 | Catálogo de productos no es catálogo de plantillas ni plugin nuevo | Módulo/contratos | No endpoint legacy reinterpretado | T23 | R2.2/R2.3; Base |
| INV-24 | No global nuevo con backend/lector incompatible activo | Despliegue/gates | RESOURCE_GLOBAL_FEATURE_DISABLED* | T24 | R2.1–R2.5; Base/D10 |
| INV-25 | Adopción no convierte propietario ni reescribe historia; sin automatismo | Comando futuro | No operación R2.6 sin contrato adicional | T25 | R2.6 posterior; Base/D08 |
| INV-26 | Dashboard/procesos/fichas resuelven usados sin ampliar consumidores operativos | Lectores secundarios | unavailable visible, nunca identidad ajena | T26 | R2.4/R2.5; Base/D04 |
## 7. Discrepancias y precisiones resueltas
Se leyeron íntegramente los doce Markdown y al inicio coincidían las 99 huellas del código de la auditoría, HEAD fa9d95fa9e227a66a45e278d98e2895d8a9995ad. Durante la formalización aparecieron cambios concurrentes de búsqueda/listados ajenos a esta tarea: en la revisión de cierre 97 huellas siguen iguales y cambian dos fuentes web auditadas (C09). No se detecta implementación nueva de ámbito, ACL o catálogo en el backend contrastado. Estos ajustes son decisiones de contrato, no bugs corregidos:
| ID | Tensión de la auditoría/solicitud | Resolución R2.0 |
| --- | --- | --- |
| C01 | D04 original es modelo operador–compañía; solicitud desarrolla además resolución bajo D04 | Preservar D04 original, subcontrato D04-R para propósito; correspondencia exacta en 02 |
| C02 | Auditoría 02 contempla que plantilla inactiva bloquee nuevas selecciones; 06 conserva disponibilidad por Resource | Bloquea altas Resource, no asociaciones de producto activo ya existente; no política local de producto en v2 inicial |
| C03 | Auditoría 09 recomienda unicidad de nombre por ámbito; petición actual prohíbe usar nombre como clave de unicidad | UUID es única identidad; homónimos globales no se rechazan solo por nombre. Reglas de nombre de locales siguen intactas; ver D07 |
| C04 | Borrado global sin uso se consideró posible | Se difiere operación global de borrado; desactivación suficiente inicialmente, lectura histórica garantizada |
| C05 | Migración/lectores aparecen en órdenes distintos | R2.1a lectores tolerantes al esquema anterior → R2.1b expansión SQL → R2.2 seguridad → R2.3 catálogo → flags consumidores |
| C06 | needsReview puede leerse como atributo genérico o tarea masiva | Solo derivado en dos builders existentes, sin nuevo atributo universal ni revisión operativa automática |
| C07 | No versión SQL exacta en auditoría | compose declara mariadb:11.8; soporte CHECK documental confirmado, versión patch/modos/DDL efectivo no inspeccionados |
| C08 | Inventario local debe conservar contrato, pero usados globales necesitan nombres en otras vistas | Servicio explícito de referencias y DTO mínimo; jamás ampliar list para remoto/certificados/Access |
| C09 | Cambios concurrentes introducen createTextMatcher en listados; software-installations-panel y use-resources-page-controller difieren de la huella de auditoría | No son cambios R2.0 ni soporte global; se releen y actualizan anclas. El helper pliega acentos vocálicos sin convertir ñ en n: el contrato nuevo de búsqueda conserva esa semántica en servidor, sin asumir que una collation genérica sea equivalente |
La fecha/versiones de ejecución de tests de la auditoría no se trasladan como resultados R2.0. En esta fase solo se comprueban documentos, fuentes y consistencia. Evidencia enlazada en cada contrato y resultados finales en 10; no se ejecuta código funcional ni BD.