# Dominios Windows · Modelo e identidad (paso 2.1) Un dominio Windows se registra como `Resource`. Conserva el UUID, nombre, compañía, estado, notas, características e historial nativos. No existe una nueva raíz `WindowsDomain`, ni depende de un activo, servidor o controlador. ## Catálogo y capacidad La semilla añade la plantilla **Dominio Windows** al final del catálogo, sin cambiar la selección inicial ni el orden de las plantillas anteriores. Contiene Nombre DNS (texto obligatorio) y Nombre NetBIOS (texto opcional). No duplica el nombre o estado del recurso. La inicialización es idempotente y no repone los nombres personalizados. La clave persistida e inmutable `stockKey = template:windows_domain` se traduce explícitamente a `directory:domain` mediante `resourceTemplateCapability`. Se reutiliza la clasificación ya existente: `stockKey` no forma parte del contrato editable del catálogo. No se deduce por nombre visible, idioma, UUID o presencia de campos. Las claves de campo `field:domain_dns` y `field:domain_netbios` identifican los campos semánticos cuyos UUID se generan mediante `UniqueID`, como los demás. No hay un motor universal ni un catálogo de capacidades independiente. La función podrá ampliarse con otra correspondencia explícita cuando exista un consumidor de `network:smb-share`. Una plantilla creada por el usuario con los mismos nombres no adquiere la capacidad. Renombrar o reordenar la plantilla conserva su semántica. Se impide retirar DNS/NetBIOS y hacer opcional DNS; el tipo texto de los campos se protege con las reglas existentes de edición del catálogo. ## Identidad y validación `DirectoryDomainValues` valida los datos documentales, sin consultar DNS o AD: - Elimina espacios exteriores y un único punto final DNS. Conserva las mayúsculas del nombre introducido y obtiene `normalizedDns` en minúsculas. `Empresa.Local.` y `empresa.local` comparten clave de negocio. - Rechaza vacío, espacios interiores, protocolos, direcciones numéricas, etiquetas vacías, guiones en extremos y caracteres fuera de letras ASCII, números y guiones. - Admite etiquetas de hasta 63 caracteres y un nombre AD de 2 a 64 caracteres. Se aceptan etiquetas únicas heredadas; registrarlas no es una recomendación para desplegar nuevos dominios de etiqueta única. - Política IDN v1: se introduce el A-label ASCII (punycode), por ejemplo `xn--espaa-rta.local`. No se convierte silenciosamente Unicode ni se utilizan bibliotecas nuevas o resoluciones de red. - NetBIOS es opcional, se recorta y normaliza a mayúsculas, con máximo 15 caracteres, sin caracteres de control, separadores reservados ni nombres exclusivamente numéricos. No se impone que coincida con la primera etiqueta DNS. La política ASCII y los límites de AD se apoyan en las [convenciones de nombres de Microsoft](https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/naming-conventions-for-computer-domain-site-ou). La elección de A-label explícito y el registro de etiquetas únicas heredadas son políticas documentales de Metis; no certifican que exista o sea desplegable el dominio. El mapper de entrada normaliza las características antes de crear o actualizar `ResourceValues`. El repositorio vuelve a obtener la identidad semántica antes de proyectarla. Actualizar el DNS cambia características y proyección en la misma transacción y conserva el UUID. También los cambios de estado pasan por esa comprobación. La propagación de plantillas conserva los campos semánticos y sus valores; no puede retirar el DNS obligatorio de la plantilla de dominio. ## Contexto de accesos y responsabilidad modular Se elige **creación automática de un contexto dedicado**. Al crear el primer dominio de una compañía, Companies crea un `AccessItem(system)` clasificado como AD. Cada dominio obtiene un `AccessItem(environment)` propio dentro de ese sistema. Un segundo dominio reutiliza el sistema clasificado y obtiene otro entorno. Los nombres visibles no clasifican sistemas: un sistema anterior llamado «Active Directory» no se adopta automáticamente. Si su nombre impide crear el sistema dedicado, se devuelve un conflicto de contexto y se revierte el alta; no se reasignan cuentas existentes. La vinculación o adopción explícita de entornos anteriores queda fuera de este paso. `Resources` es propietario de la identidad DNS y del vínculo con el entorno. `Companies` conserva sistemas, entornos, cuentas, grupos y pertenencias. Se reutilizan sus agregados, políticas, validaciones, persistencia e historial; no se crean cuentas ni grupos en Resources. Identity sigue reservado a usuarios de autenticación Metis. El contrato público nuevo es `IDirectoryContextWriter`, exportado por `@metis/resources/services`. No contiene ORM, HTTP ni operaciones genéricas. Companies, que ya depende de Resources, lo implementa. Su composition root devuelve `directoryContexts(transaction): IDirectoryContextWriter`. El servidor inyecta esa factoría en `createResourceModule`. La transacción solo aparece en composición e Infrastructure; Application consume el puerto ya vinculado a ella. No hay import `Resources → Companies`, dependencias nuevas, imports privados entre módulos ni cambios en `@metis/relationship-contracts`. `SequelizeResourceRepository.save` coordina un único commit. Resource, su historial, el sistema/entorno, sus revisiones, referencias y la asociación se guardan con la misma conexión y transacción Sequelize. Un Result de fallo posterior a escribir se convierte en excepción interna para provocar rollback y se traduce de nuevo a Result fuera del callback gestionado. No se coordinan peticiones HTTP separadas. Orden de bloqueos al guardar con el participante configurado: catálogo de recursos, catálogo global de accesos, compañía y workspace. Mantiene el orden usado por los repositorios de catálogo y accesos, y serializa altas concurrentes. Las restricciones SQL siguen protegiendo frente a escrituras ajenas a esa coordinación. ## Persistencia relacional Las migraciones explícitas siguen el mecanismo de arranque existente, con tablas, índices y restricciones completables al repetirse. Solo se han ejecutado contra `metis_desk_test` durante este trabajo, nunca contra datos de desarrollo o producción. | Tabla | Responsabilidad y restricciones | |---|---| | `access_directory_systems` | Clasifica un sistema AD por workspace de compañía. PK `systemId`, unicidad de workspace, FK a `(id, workspaceId, kind)` de `access_items`, CHECK `kind=system` y FK de compañía al workspace. | | `access_directory_environments` | Clasifica los entornos de ese sistema. PK `environmentId`, FK al AccessItem de tipo environment, FK al sistema clasificado del mismo workspace y compañía, clave compuesta para referencias desde Resources. | | `resource_directory_domains` | Proyección mínima: PK/FK `resourceId`, compañía, plantilla, DNS normalizado, entorno y actor/fecha de asociación. No copia nombre, estado, notas, NetBIOS ni historial del recurso. | En la proyección de dominios: - UNIQUE `(companyId, normalizedDns)` incluye dominios desactivados. Un mismo DNS se admite en compañías distintas. - UNIQUE `environmentId` impide asociar dos dominios al mismo entorno. - FK compuesta a `(id, companyId, templateId)` de Resources garantiza compañía y plantilla; otra a `(id, stockKey)` del catálogo y un CHECK restringen la semántica a la plantilla de dominio, sin codificar su UUID. - FK `(environmentId, companyId)` exige un entorno AD clasificado de esa compañía. - FK al actor y fecha inmutables trazan la creación de la asociación. El historial Resource audita nombres, DNS, NetBIOS y estado; AccessRevision audita el contexto. - DNS se almacena en columna ASCII con comparación binaria, CHECK de no vacío y representación canónica sin mayúsculas, espacios exteriores ni punto final. - Los índices compuestos auxiliares de las tablas raíz permiten las FK de tipo, compañía y plantilla. No son identidades de negocio adicionales. Las identidades auxiliares son las de Resource y AccessItem, no UUID sustitutos de esas entidades. No se usa JSON. Ninguna FK nueva apunta a `access_environment_values` o `access_system_values`: esas tablas se borran y reinsertan al editar. El sistema de un entorno ya es inmutable por el dominio AccessItem; se protege además la desactivación lateral de contextos clasificados. Las escrituras válidas siguen pasando por los agregados de Companies. ## Operaciones e interfaz Se mantienen los endpoints existentes de Resources: - `POST /api/resources/:companyId`: alta mediante la plantilla de dominio. - `POST /api/resources/:companyId/:id/update`: nombre, notas y características, incluidos DNS y NetBIOS. - `GET /api/resources/:companyId`: consulta con metadatos opcionales `directory` únicamente cuando existe una asociación real confirmada. - `GET /api/resources/:companyId?environmentId=`: consulta inversa, filtrada por compañía. Valida query estricta y no crea un endpoint transversal nuevo. - `POST /api/resources/:companyId/:id/status`: activa o desactiva el Resource sin desactivar el entorno ni sus cuentas/grupos. `directory` contiene capacidad, DNS normalizado y UUID de sistema y entorno; los nombres editables DNS/NetBIOS siguen en las características. No se aceptan estos metadatos de salida como privilegios o asociación de entrada. Se conserva la protección de autenticación/sesión del router y el ámbito de compañía existente. No se añade una ACL nueva ni se confunden grupos externos con permisos Metis. El formulario actual muestra DNS/NetBIOS y ayuda contextual inferior es/en. Antes de guardar indica que el contexto **se creará**, sin simular que ya existe. En edición presenta los nombres del sistema y entorno realmente asociados. Se reutilizan selector, campos, estados, historial y diálogo existentes; no hay pantallas nuevas. El contexto es fijo: no hay sustitución, traslado ni desvinculación en este paso. Cambiar DNS tampoco renombra silenciosamente el entorno existente: el vínculo sigue siendo su UUID, aunque su etiqueta documental conserve el nombre inicial o sea editada desde accesos. Las cuentas y grupos mantienen sus referencias. El borrado definitivo continúa usando la protección por referencias de Resources: la asociación estructural impide eliminar un dominio vinculado, incluso sin cuentas. La operación soportada para retirarlo es desactivarlo; se reserva el DNS y se conserva trazabilidad. Liberar o eliminar contextos requerirá una política explícita posterior, sin borrar cuentas/grupos silenciosamente. ## Pruebas y alcance restante Las pruebas nuevas cubren clasificación, renombrado, plantilla protegida, normalización y límites DNS/NetBIOS, contexto dedicado, referencias incompatibles, compañía, conservación de UUID/historial e idempotencia de semillas. El formulario se verifica en es/en para estados pendiente y confirmado. La integración dedicada `pnpm --filter @metis/server test:directory-domains` exige `NODE_ENV=test` y `DB_NAME=metis_desk_test`. Usa UUID de fixture, no limpia datos ajenos y cierra conexiones. Comprueba repetición de migraciones, dos altas concurrentes, unicidad y FK reales, compañía, lectura directa/inversa por HTTP, edición legítima de valores de entorno, cuentas/grupos conservados, archivo, borrado protegido y rollback tras crear contexto. El script usa el runner nativo con tsx y sin aislamiento por subprocesos para ejecutar y enumerar los casos en este entorno. No se implementan LDAP, descubrimiento, sincronización, controladores, equipos unidos, relaciones con activos, SMB, FolderMapping, paneles de cuentas/grupos ni navegación mediante Typed Relationships. No se modifica Asset, Workstation o WorkProfile. ## Verificación de esta entrega - Typecheck de `@metis/resources`, `@metis/companies` y `@metis/server`: correcto. - Biome check y ESLint de los 38 ficheros de código/configuración afectados: correctos. - Suite Resources, ejecutada con el script del paquete y `--test-isolation=none`: **53/53** pruebas aprobadas. - Suite Companies con el mismo modo del runner: **169/171** aprobadas. Fallan dos expectativas de textos en `companies-page.test.tsx` (líneas 180 y 210), sobre «Personas (1)» y «Esta persona no tiene acceso a Metis Desk». Sus pruebas y componentes de pantalla no se han modificado. No se han eliminado ni omitido para presentar un resultado global satisfactorio. - Las cuatro pruebas nuevas de `directory-context-plan.test.ts` pasan también de forma dirigida. - `test:directory-domains`: integración real en `metis_desk_test` aprobada, incluidos rollback y concurrencia; datos propios eliminados al finalizar. - Las 11 pruebas de `@metis/relationship-contracts` siguen aprobadas, sin cambios en el paquete durante el paso 2.1. - Grafo de los 13 paquetes del workspace sin ciclos; se conserva la dirección Companies → Resources. - Build de `@metis/server` y `git diff --check`: correctos. No se ejecuta la suite global de persistencia ni se aplica ninguna migración a bases de desarrollo o producción. En este entorno PNPM requiere `--config.verify-deps-before-run=false` para ejecutar los comandos con las dependencias ya instaladas; no se añade esa opción a la configuración del proyecto. ## Archivos principales | Ámbito | Archivos | |---|---| | Semántica y transporte | `modules/resources/src/common/resource-capabilities.ts`, `directory-domains.ts`, `inventory.ts`, `index.ts`, `resource-template-icons.ts` y traducciones `locales/es.json`, `locales/en.json`. | | Dominio y catálogo | `modules/resources/src/api/domain/value-objects/directory-domain-values.vo.ts`, `resource-catalog-seed.ts`, `resource-catalog.aggregate.ts` y pruebas de dominio/catálogo. | | Aplicación Resources | `modules/resources/src/api/application/directory-context-writer.ts`, `directory-domain-input.ts`, `resource-input.mapper.ts`, `resource.repository.ts`, `resource-response.builder.ts`. | | Persistencia y API Resources | `modules/resources/src/api/infrastructure/persistence/directory-domain-migration.ts`, `directory-domain-projection.ts`, `sequelize-resource.repository.ts`; `infrastructure/http/resource.routes.ts`, `api/module.ts` y `api/services.ts`. | | Contexto Companies | `modules/companies/src/api/application/directory-context-plan.ts` y su prueba; `infrastructure/persistence/directory-context-migration.ts`, `directory-context-writer.ts`, `access-persistence.ts` y `api/module.ts`. | | Formulario existente | `modules/resources/src/web/resources/shared/controllers/use-resource-form-controller.ts`, `ui/resource-form.tsx`, `ui/resource-fields.tsx` y `resources.test.tsx`. | | Composición y ejecución | `apps/server/src/infrastructure/http/identity-runtime.ts`, `apps/server/src/directory-domains.integration.test.ts` y scripts en los `package.json` de servidor, Resources y Companies. | ## Integración visual · Paso 2.4 La ficha de recurso compone ahora las pertenencias y la navegación contextual a cuentas/grupos. La ficha de activo permite consultar, asociar, cambiar rol, trasladar y finalizar pertenencias con los comandos existentes. Véase [integración visual de dominios Windows](integracion-visual-de-dominios-windows.md) para rutas, paginación, componentes, pruebas y límites.