metis-desk/docs/identidades-locales-y-cuentas-de-carpetas.md
David fe6ec361b7 feat(companies): vincular identidades locales a activos y añadir cuenta de acceso a FolderMapping
- Asociar entornos locales con activos y conservar su trazabilidad.
- Registrar la cuenta de acceso del mapeo con historial de cambios.
- Calcular cobertura documental específica sin alterar la cobertura general.
- Añadir migraciones, validaciones, endpoints protegidos, pruebas y documentación.
2026-10-21 11:25:23 +02:00

16 KiB
Raw Blame History

Identidades locales y cuentas de carpetas v1 — paso 3.4

Modelo y alcance

Companies conserva AccessWorkspace como propietario de sistemas, entornos, cuentas, grupos, pertenencias, concesiones, necesidades y cobertura. Se incorporan dos capacidades acotadas:

  1. Una asociación estructurada y clasificada AccessItem(environment) → Asset, para ámbitos locales de un dispositivo.
  2. La cuenta documental opcional de cada FolderMapping, con una evaluación de cobertura específica para esa cuenta.

No se crean nuevas cuentas, grupos ni entidades principales. LocalIdentityEnvironment es una entidad interna de asociación con UUID v7 propio y mapper; no sustituye ni duplica el entorno. Su discriminador estable es identityScope: "local-device". No depende de nombres como «Local», «NAS» o «Windows» ni de la etiqueta del activo.

No se implementan autenticación SMB, secretos, sincronización LDAP, descubrimiento de cuentas ni comprobación de permisos efectivos SMB/NTFS. Los modelos SMB y de publicación de los pasos 3.1–3.3 mantienen su significado.

Entornos locales

access_local_identity_environments pertenece a Companies. Cada fila conserva UUID, compañía, entorno, activo propietario, sistema, ámbito local, actor/fecha de creación y actor/fecha de retirada.

  • Un entorno tiene como máximo un vínculo propietario, incluso histórico. No puede trasladarse a otro activo, cambiar silenciosamente de sistema ni reutilizarse como otro ámbito.
  • Un activo admite varios entornos. No se añade unicidad activo/sistema: AccessWorkspace ya distingue los entornos por nombre dentro de un sistema y no existe un contrato que limite todos esos contextos a uno por dispositivo. Se conserva esa posibilidad de separación explícita.
  • La asociación inicial exige activo existente, operativo y de la misma compañía. No exige AD, sistema operativo ni categoría; admite NAS, sobremesa y portátil.
  • Se puede crear un entorno asociado o clasificar/asociar uno existente compatible. Sus cuentas, grupos y revisiones se conservan.
  • Los entornos AD registrados y cualquier entorno del sistema estructurado de AD se rechazan como locales. La comprobación utiliza las clasificaciones del paso 2.1, nunca nombres. El preparador AD solo reutiliza entornos clasificados como AD; no adopta entornos locales.
  • Un sistema visible del catálogo global puede utilizarse conforme al contrato existente de AccessWorkspace. El entorno siempre reside en el espacio de la compañía; no se aceptan sistemas de otras compañías.
  • Renombrar el activo no renombra el entorno ni las cuentas. La asociación conserva el UUID del activo.

Retirar el vínculo conserva la clasificación y el propietario históricos, con cierre idempotente. No desactiva el entorno ni sus cuentas/grupos: el ciclo de vida de esos registros sigue gestionándose en AccessWorkspace. Tampoco permite reasignar ese entorno a otro activo o reabrir el vínculo; para un nuevo ámbito se crea otro entorno. Desactivar el activo no retira el vínculo automáticamente. Los reintentos de asociación devuelven el mismo vínculo vigente cuando el activo sigue siendo elegible; los vínculos retirados permanecen inmutables.

Persistencia, transacciones y borrado

Las FK de entorno y sistema apuntan a access_items(id,workspaceId,kind), identidades estables. Nunca apuntan a access_environment_values, access_system_values ni otras filas de valores que se eliminan y reconstruyen durante una actualización. Las FK y CHECK aseguran:

  • Tipo environment / system y discriminador local-device.
  • Espacio y compañía del entorno.
  • Pareja (companyId,assetId) válida en Assets.
  • Unicidad permanente de environmentId.
  • Coherencia entre cierre, actor y fechas; actores existentes.

La compatibilidad entre sistema y entorno, visibilidad del sistema y exclusión de AD se comprueban transaccionalmente. El modelo de AccessItem ya hace inmutables el tipo y el sistema de un entorno existente; las FK impiden modificar lateralmente el tipo o el espacio de una identidad referenciada. No se añade un segundo mecanismo de edición de AccessItem.

La migración tiene dos etapas por el orden de composición existente: migrateAccess crea la tabla con sus referencias a Access e Identity; initializeLocalIdentities completa la FK de Assets una vez inicializado ese módulo, antes de registrar sus comandos. Ambas etapas son repetibles; no hay sync({ alter: true }), adopciones ni conversión de entornos existentes.

Las mutaciones bloquean catálogo global de Access y compañía siguiendo el orden existente. El lector mínimo público de Assets comprueba compañía/estado y bloquea la fila dentro de la misma transacción. Crear entorno, su revisión y asociación es atómico: si falla insertar el vínculo, se revierte también AccessWorkspace. La unicidad SQL actúa como garantía final ante concurrencia.

Las referencias presentes e históricas impiden el borrado físico de Asset mediante el mecanismo existente que inspecciona FK externas. No hay cascadas destructivas, borrado de cuentas ni eliminación pública del historial de asociación.

Cuenta de FolderMapping

FolderMapping.accessAccountId documenta la cuenta usada para acceder a la carpeta desde un perfil. Es independiente de WorkProfile.accountId, que sigue representando la cuenta asociada al uso del ordenador. Ambas pueden coincidir, diferir o no estar indicadas. No se deducen cuentas del perfil, login de la persona, dominio, hostname UNC ni cobertura.

El schema de entrada admite:

Entrada al editar Efecto
Campo ausente Conserva la cuenta anterior.
accessAccountId: null Retira la cuenta indicada.
UUID Establece o cambia la cuenta tras validarla.

En un alta sin cuenta y al leer registros anteriores se devuelve null. Todos los cambios reales se comparan semánticamente y generan las revisiones existentes; un cambio idéntico conserva la revisión. La cuenta de cada revisión se mantiene al cambiar o retirar la actual. La migración no rellena historiales con cuentas inferidas.

Una selección nueva debe ser un AccessItem de tipo account, visible en la compañía, activo, no caducado y con sistema/entorno activos. Debe tener una relación account_person vigente con la persona del perfil y configuración pending, configured o NULL cuando el contrato la exige para una cuenta técnica. Se utiliza la identidad de la relación, no la coincidencia de login. Un grupo, sistema, cuenta ajena, cuenta inexistente o asociada solo a otra persona produce FOLDER_ACCOUNT_INCOMPATIBLE.

Las cuentas compartidas son válidas si están explícitamente asociadas a esa persona; pueden seguir asociadas a otras personas según las reglas actuales. Las cuentas técnicas pueden documentarse si existe su relación personal explícita compatible; el modelo actual prohíbe utilizarlas como fundamento de Coverage, por lo que su señal específica será not-covered. No se amplía ni se elude esa política para atribuir permisos. Una cuenta técnica sin relación personal no se selecciona arbitrariamente.

Las ediciones que conservan una cuenta cuya disponibilidad cambió no la borran ni impiden corregir notas o retirar la configuración. La cobertura derivada refleja la pérdida de disponibilidad. Una nueva selección/cambio vuelve a validar la cuenta.

Restricciones SQL y registros previos

migrateFolderMappingAccounts, llamada por migrateFolderMappings, añade a la configuración y a sus revisiones accessAccountId nullable. Añade los metadatos técnicos accessAccountWorkspaceId nullable y accessAccountKind='account' para construir FK compuestas hacia las raíces estables de AccessItem y AccessWorkspace. Se exige que cuenta y espacio sean ambos NULL o ambos estén indicados.

Las revisiones incorporan companyId, derivado únicamente de su FolderMapping, para reforzar compañía mediante FK compuestas. Ese dato técnico no modifica los valores históricos ni afirma que hubiera una cuenta conocida. La FK (mappingId,companyId) mantiene cada revisión dentro de su configuración. Las restricciones impiden cuentas de otra compañía y subtipos distintos de account tanto en el estado actual como en el historial, sin referenciar valores reconstruibles.

Cobertura documental específica

Se conserva permissionsCovered con su semántica previa, cobertura general de la necesidad para la persona. Sus consumidores existentes son el listado de FolderMapping y el resumen de entorno de trabajo; no se reinterpreta ese booleano ni se cambia su presentación en este paso.

La nueva propiedad accessAccountCoverage tiene tres valores:

  • unknown: no se ha indicado cuenta.
  • covered: las vías de Coverage válidas de esa cuenta cubren todas las autorizaciones de la necesidad.
  • not-covered: la cuenta indicada no dispone de esa cobertura documental conforme a las reglas actuales.

folderAccountCoverage reutiliza projectAccess, sin crear otro motor de concesiones. Restringe las vías de Coverage por la relación account_person exacta de cuenta y persona, necesidad y recurso. Verifica que la concesión directa apunta a esa cuenta o que la pertenencia directa corresponde a esa cuenta y al grupo de la concesión. No acepta vías de otra cuenta de la misma persona ni grupos anidados. Conserva el requisito de Coverage explícito del modelo actual: la mera existencia de un Grant no crea automáticamente Coverage.

Se excluyen entradas cuyo inicio todavía no llegó y se respetan los estados de cierre/configuración de la proyección de Access existente. La necesidad debe corresponder al recurso y persona del mapeo; su recurso y demás referencias deben seguir disponibles y la UNC debe coincidir con la vigente.

La señal se calcula al consultar, no se guarda como autorización persistida:

Cambio Efecto en la siguiente lectura
La cuenta abandona el grupo La vía por esa pertenencia deja de cubrir.
Concesión o Coverage finalizados Se pierde esa vía; solo otras vías válidas de la misma cuenta pueden cubrir.
Cuenta desactivada/caducada not-covered; se conserva su referencia documental.
Necesidad finalizada not-covered.
Relación con la persona finalizada not-covered, aunque la cuenta permanezca activa.
Recurso SMB desactivado o UNC cambiada not-covered; la revisión del mapeo conserva su información previa.
Cuenta retirada del mapeo unknown; no se sustituye por la del perfil.

covered no confirma acceso efectivo, autenticación, disponibilidad ni conectividad. Tampoco cambia concesiones, necesidades, Coverage, dominios o publicación del recurso.

Contratos y HTTP

La composición de Companies expone initializeLocalIdentities, que recibe una factoría transaccional del lector público mínimo de Assets. ILocalIdentityAssetReader solo maneja UniqueID y disponibilidad, sin importar repositorios privados ni ORM en Application. Assets expone services.localIdentities(transaction). Se mantiene la dirección Companies → API pública de Assets y la composición explícita del servidor, sin dependencias circulares, Service Locator ni nuevo registry.

Rutas de identidad local, autenticadas y con validación estricta:

  • POST /api/local-identity-environments/:companyId:
    • { action: "create", assetId, systemId, name }.
    • { action: "associate", assetId, environmentId }.
    • { action: "retire", environmentId }.
    • Respuesta { environment } con identidad y trazabilidad del vínculo.
  • GET /api/local-identity-environments/:companyId: filtros assetId?, environmentId?, includeEnded=true|false (false por defecto), pageSize (1–100; 50 por defecto), afterId?. Respuesta { environments, nextAfterId }. El keyset SQL ordena por UUID y limita antes de devolver datos. El filtro por entorno consulta su propietario; el filtro por activo lista sus ámbitos locales. afterId es una continuación UUID, siempre acotada por los filtros y compañía actuales.

Las rutas actuales GET /api/folder-mappings/:companyId y POST /api/folder-mappings/:companyId[/:id/update] leen y escriben la cuenta y devuelven su señal específica mediante sus contratos existentes. No hay API genérica de relaciones.

El actor se toma de sesión. Las escrituras están sujetas al control global de origen y CSRF de Identity. Se reutiliza la autorización de operadores autenticados existente, con comprobaciones de compañía y pertenencia en aplicación/persistencia. No se acepta operatorId del navegador ni se exponen datos de otra compañía. Los comandos locales reutilizan los errores tipados de Access: 400 para entrada inválida, 404 para compañía/identidad no encontrada, 409 para incompatibilidad, estado o conflicto; errores inesperados siguen el middleware HTTP central.

Archivos principales y preparación para 3.5

  • Contratos: modules/companies/src/common/local-identity-environments.ts y folder-mappings.ts.
  • Modelo y planificación: local-identity-environment.entity.ts, local-identity-plan.ts, local-identity.use-cases.ts.
  • Persistencia: local-identity-migration.ts, local-identity.mapper.ts, sequelize-local-identity.repository.ts, folder-mapping-account-migration.ts y ampliación de snapshots/repositorio de FolderMapping.
  • Cobertura y validación: folder-mapping-account-policy.ts, folder-mapping-input.mapper.ts, folder-mapping-response.builder.ts y folder-mapping-values.vo.ts.
  • Lector Assets: modules/assets/src/api/infrastructure/persistence/local-identity-asset-reader.ts.
  • Composición/rutas: módulos Companies/Assets, identity-runtime.ts y local-identity.routes.ts.
  • Pruebas: local-identity-plan.test.ts, folder-mapping-account.test.ts y apps/server/src/local-identities-folder-mapping.integration.test.ts.

No se añade administración visual de identidades locales ni selector de cuenta en esta fase. El formulario actual conserva la cuenta recibida aunque todavía no la muestre; las ediciones que omiten el campo la preservan también en servidor. Los DTO y endpoints preparan el selector contextual y las vistas de Activo/Recurso/Persona/Perfil del paso 3.5.

Verificación

Las integraciones usan exclusivamente metis_desk_test, con datos temporales identificados por UUID y limpieza propia, sin desactivar FK. La suite nueva comprueba NAS, PC, portátil, cuentas homónimas, independencia de AD, exclusión de AD, compañía/subtipo en SQL, renombrado, retirada histórica, rollback, concurrencia, cuenta desconocida/alternativa/compartida/técnica, conservación y retirada, revisiones, cobertura directa y por grupo, estados derivados y HTTP con sesión, origen y CSRF.

Comando específico: pnpm --filter @metis/server test:local-identities-folder-mapping. Se ejecuta también la regresión de los pasos 2.1–3.3 y las suites afectadas de Companies, Assets y Resources.

Incidencias previas que se mantienen fuera del alcance: dos expectativas de texto en companies-page.test.tsx (líneas 180 y 210) y TS2375 en packages/shadcn-ui/src/components/calendar.tsx:165. No se modifica vendor ni esas expectativas para conseguir resultados verdes.

Resultados de esta entrega

  • Typecheck de Companies, Assets, Resources, servidor y web: correcto (pnpm --filter … typecheck).
  • Companies: 180/182 pruebas; las únicas dos fallidas son las expectativas de texto previas indicadas arriba. Se incluyen nueve pruebas nuevas de planificación local, valores de FolderMapping y cobertura por cuenta.
  • Assets: 131/131. Resources: 80/80. Los scripts se ejecutaron con --test-isolation=none para comprobar todos los casos.
  • MariaDB: 11/11 casos de la suite nueva y 45/45 casos de regresión de los pasos 2.1–3.3. La ejecución conjunta inicial dio 55/55 y se repitió la suite nueva completa tras añadir el caso de concurrencia de asociación, con 11/11.
  • Biome y ESLint sobre los ficheros nuevos/modificados del trabajo acumulado: correctos, sin avisos. Build del servidor y git diff --check: correctos.
  • Grafo de dependencias de producción: 13 paquetes sin ciclos. No hay nuevas dependencias.

No se han ejecutado migraciones ni comandos de negocio contra compañías reales. La integración visual contextual y la selección de cuenta se mantienen para el paso 3.5.