ums

BC-A — Identity Context

Idioma: Español Versión en inglés no disponible

Schema: [ums_identity] · [delegation] | Owner: UMS Core API .NET 8
Misión: Gestionar el ciclo de vida de principals (usuarios), estructuras organizacionales (tenants) y sub-unidades (branches). Delegar verificación de credenciales a adaptadores de IdP. Gobernar la autoridad administrativa delegada entre administradores.
FS cubiertos: FS-01, FS-03, FS-08, FS-09, FS-14
Versión: 2.1 | Fecha: 2026-05-22

Arquitectura de Agregados: Modelo completo con diagramas, secuencias, ER y API: Tenant · UserAccount · UserManagementDelegation


Agregados

Agregado Raiz Descripción
Tenant Tenant Nodo organizacional jerárquico y sus branches/proveedores de identidad
UserAccount UserAccount Principal autenticable con sus métodos de autenticación
UserManagementDelegation UserManagementDelegation Autoridad administrativa delegada con scope, acciones y vigencia temporal

Aggregate: Tenant

Aggregate Root: Tenant

Entidades

Entidad Descripción
Tenant (AR) Nodo organizacional en la jerarquía; equivale a Organization en el glosario
Branch Sub-unidad física del tenant; ciclo de vida gobernado por el Tenant AR
IdentityProvider Proveedor de identidad federada registrado para el tenant
Branding Configuración visual y de DNS para el hosted login del tenant

Value Objects

Value Object Tipo base Regla
TenantType enum ROOT / ENTERPRISE / SUBSIDIARY / DIVISION / BRANCH / DEPARTMENT
TaxonomyRank int Rango jerárquico; determina quién puede ser padre/hijo
TenantStatus enum ACTIVE / SUSPENDED / ARCHIVED
OrganizationType enum INTERNAL / CLIENT / SUPPLIER / PARTNER
CompanyReference string Código ERP externo (SAP); inmutable post-creación
IdpStrategyHint enum INTERNAL_BCRYPT / ZITADEL / AZURE_AD / OKTA / SAML2 / GENERIC_OIDC
TenantMetadata JSON Payload libre validado como JSON
BranchCode string Único dentro del tenant; slug alfanumérico
GeofencingMetadata JSON Nullable; requiere radius_km, center_lat, center_lng si presente

Invariantes

ID Regla Fuente
INV-T1 child.TaxonomyRank > parent.TaxonomyRank — tipo hijo debe tener rango estrictamente mayor ADR-0048
INV-T2 BRANCH y DEPARTMENT no pueden tener hijos (can_have_children = false) ADR-0048
INV-T3 ROOT es su propio root_tenant_id; todos los demás deben diferir ADR-0048
INV-T4 CompanyReference debe ser único dentro del tipo CLIENT/SUPPLIER/PARTNER del mismo parent FS-03
INV-T5 ARCHIVED es terminal; no retorna a ACTIVE sin proceso explícito FS-03
INV-T6 No puede crearse Branch ni UserAccount bajo un Tenant SUSPENDED o ARCHIVED FS-03
INV-B1 BranchCode único dentro del TenantId FS-03
INV-B2 GeofencingMetadata válido JSON con claves requeridas si presente conceptual-data-model.md
INV-B3 Branch inactiva o suspendida no puede recibir nuevos Profiles FS-03
INV-B4 Branch debe pertenecer a un Tenant ACTIVE FS-03

Diagrama del Agregado

classDiagram
    direction TB
    class Tenant {
        <<AggregateRoot>>
        +Guid Id
        +string Code
        +string Name
        +TenantType Type
        +TenantStatus Status
        +OrganizationType OrgType
        +string CompanyReference
        +Guid ParentTenantId
    }
    class Branch {
        <<Entity>>
        +Guid Id
        +string Code
        +string Name
        +bool IsActive
        +JSON GeofencingMetadata
    }
    class IdentityProvider {
        <<Entity>>
        +Guid Id
        +string Code
        +string Name
        +IdpStrategyHint Strategy
        +bool IsActive
    }
    class Branding {
        <<Entity>>
        +Guid Id
        +string Logo
        +LogoFormat Format
        +string PrimaryColor
        +BackgroundStyle BgStyle
        +string CustomDomain
        +DnsVerificationStatus DnsStatus
        +bool MagicLinkFallback
    }
    Tenant "1" --> "0..*" Branch : contains
    Tenant "1" --> "0..*" IdentityProvider : registers
    Tenant "1" --> "0..1" Branding : configures
    Tenant "1" --> "0..*" Tenant : parentOf

Máquina de Estado: Tenant

stateDiagram-v2
    [*] --> ACTIVE : RegisterTenant
    ACTIVE --> SUSPENDED : SuspendTenant
    SUSPENDED --> ACTIVE : ActivateTenant
    ACTIVE --> ARCHIVED : ArchiveTenant
    note right of ARCHIVED : Estado terminal — sin retorno

Máquina de Estado: Branch

stateDiagram-v2
    [*] --> ACTIVE : AddBranch
    ACTIVE --> SUSPENDED : DeactivateBranch
    SUSPENDED --> ACTIVE : ReactivateBranch
    ACTIVE --> [*] : RemoveBranch (solo cuando inactiva)

Comandos

Comando Descripción
RegisterTenantCommand Registra un nuevo tenant con tipo, referencia externa y estrategia IdP
SuspendTenantCommand Suspende el tenant; bloquea acceso a todos sus usuarios
ActivateTenantCommand Activa un tenant suspendido
AddBranchCommand Agrega una sub-unidad física (Branch) al tenant
RemoveBranchCommand Remueve una branch inactiva del tenant
DeactivateBranchCommand Suspende/desactiva temporalmente una branch activa
ReactivateBranchCommand Reactiva una branch suspendida
RegisterIdentityProviderCommand Registra un nuevo proveedor de identidad federada para el tenant
ActivateIdentityProviderCommand Activa un IdP y desactiva los demás del tenant
DeactivateIdentityProviderCommand Desactiva un IdP activo del tenant
RemoveIdentityProviderCommand Remueve un IdP registrado e inactivo del tenant
SetBrandingCommand Configura branding visual por primera vez
UpdateBrandingCommand Actualiza la configuración visual de branding existente
VerifyBrandingDnsCommand Marca el DNS de branding como verificado con éxito
FailBrandingDnsCommand Registra fallo en la verificación DNS del branding
RemoveBrandingCommand Remueve la configuración de branding del tenant

Eventos de Dominio

TenantCreatedEvent                 { tenantId, code, name }
TenantActivatedEvent               { tenantId }
TenantSuspendedEvent               { tenantId }
BranchCreatedEvent                 { tenantId, branchId, code }
BranchRemovedEvent                 { tenantId, branchId }
BranchDeactivatedEvent             { tenantId, branchId }
BranchReactivatedEvent             { tenantId, branchId }
IdentityProviderRegisteredEvent    { tenantId, identityProviderId, code, strategyName }
IdentityProviderActivatedEvent     { tenantId, identityProviderId, code }
IdentityProviderDeactivatedEvent   { tenantId, identityProviderId, code }
IdentityProviderRemovedEvent       { tenantId, identityProviderId }
BrandingCreatedEvent               { tenantId, brandingId }
BrandingUpdatedEvent               { tenantId, brandingId }
BrandingDnsVerifiedEvent           { tenantId, brandingId }
BrandingDnsFailedEvent             { tenantId, brandingId }
BrandingRemovedEvent               { tenantId, brandingId }

Repositorio

ITenantRepository : IAggregateRepository<Tenant> {
    GetByCodeAsync(code, cancellationToken)
}

Aggregate: UserAccount

Aggregate Root: UserAccount
FS: FS-01, FS-03, FS-09, FS-10

Entidades

Entidad Descripción
UserAccount (AR) Principal autenticable en la plataforma
MfaEnrollment Metodo MFA o passwordless enrolado por el usuario (FS-09); ver gap V2

Value Objects

Value Object Tipo base Regla
Email string Formato RFC 5321; único dentro del tenant
UserCategory enum INTERNAL / EXTERNAL / B2B / PARTNER / SERVICE_ACCOUNT
UserStatus enum PENDING / ACTIVE / BLOCKED
IdentityReference string Referencia externa al sistema origen (HR/ERP/vendor)
IdentityReferenceType enum HR_ID / VENDOR_CODE / GOVERNMENT_ID / PARTNER_REF
PasswordHash string Nullable; null cuando autenticación delegada a IdP externo
MfaMethod enum TOTP / WEBAUTHN / SMS_OTP / EMAIL_OTP
MfaEnrollmentStatus enum NOT_ENROLLED / ENROLLED / VERIFIED

Invariantes

ID Regla Fuente
INV-U1 Email único dentro del mismo TenantId FS-03
INV-U2 PasswordHash solo NOT NULL cuando IdpStrategy == INTERNAL_BCRYPT ADR-0031
INV-U3 INTERNAL users deben tener IdentityReferenceType == HR_ID ADR-0031
INV-U4 UserAccount BLOCKED no puede recibir asignacion de nuevos Profiles ADR-0044
INV-U5 PENDING -> ACTIVE solo tras ApprovalRequest ONBOARDING aprobado para EXTERNAL/B2B/PARTNER ADR-0044, FS-10
INV-U6 SERVICE_ACCOUNT creado directamente en ACTIVE por admin; sin flujo de aprobacion FS-01
INV-U7 Sesion no puede iniciarse si UserStatus != ACTIVE FS-01
INV-U8 Fallo de IdP no concede acceso silencioso FS-01
INV-U9 MfaEnrollment solo aplica cuando el tenant tiene MFA habilitado o elevacíon de riesgo detectada FS-09

Diagrama del Agregado

classDiagram
    direction TB
    class UserAccount {
        <<AggregateRoot>>
        +Guid Id
        +Guid TenantId
        +Guid BranchId
        +Email Email
        +UserCategory Category
        +UserStatus Status
        +string IdentityReference
        +IdentityReferenceType RefType
    }
    class MfaEnrollment {
        <<Entity>>
        +Guid Id
        +MfaMethod Method
        +MfaEnrollmentStatus Status
    }
    class PasswordCredential {
        <<Entity>>
        +Guid Id
        +string PasswordHash
        +bool IsActive
    }
    UserAccount "1" --> "0..*" MfaEnrollment : enrolls
    UserAccount "1" --> "0..1" PasswordCredential : secures

Máquina de Estado: UserAccount

Visualización: interactive-ddd-viewer.html — sección “UserAccount”

stateDiagram-v2
    [*] --> PENDING : RegisterUser
    PENDING --> ACTIVE : ActivateUser (ONBOARDING aprobado o INTERNAL directo)
    ACTIVE --> BLOCKED : BlockUser (DocumentExpired CRITICAL / bloqueo manual)
    BLOCKED --> ACTIVE : RestoreUser (renovacion documental + desbloqueo)
    PENDING --> [*] : ApprovalRequest REJECTED

Comandos

Comando Descripción
RegisterUserCommand Registra usuario con categoria, email, referencia externa
ActivateUserCommand Activa el usuario post-aprobacion o directamente si es INTERNAL
BlockUserCommand Bloquea el usuario (expiración documental o manual)
RestoreUserCommand Desbloquea y reactiva el usuario
UpdateCredentialsCommand Actualiza PasswordHash (solo INTERNAL_BCRYPT)
EnrollMfaCommand Registra metodo MFA/passwordless (FS-09)
VerifyMfaChallengeCommand Verifica challenge MFA en el flujo de autenticación

Eventos de Dominio

UserRegisteredEvent          { userId, tenantId, branchId?, userCategory, identityReference }
UserActivatedEvent           { userId, tenantId }
UserBlockedEvent             { userId, tenantId, reason, enforcementAction }
UserRestoredEvent            { userId, tenantId }
MfaEnrolledEvent             { userId, tenantId, method }
MfaVerifiedEvent             { userId, tenantId, method, riskScore }
AuthenticationAttemptedEvent { userId?, tenantId, outcome, reason, ipAddress }

Repositorio

IUserAccountRepository {
    FindByIdAsync(userId, rootTenantId)
    FindByEmailAsync(email, tenantId)
    FindByTenantAsync(tenantId, status?)
    FindPendingApprovalAsync(tenantId)
    FindByIdentityReferenceAsync(reference, referenceType, tenantId)
    AddAsync(user)
    UpdateAsync(user)
}

Aggregate: UserManagementDelegation

Aggregate Root: UserManagementDelegation
Schema: [delegation]
FS: FS-14

Entidades

Entidad Descripción
UserManagementDelegation (AR) Registro de autoridad delegada con scope, acciones y vigencia; sin entidades hijas

Value Objects

Value Object Tipo base Regla
DelegationScopeType enum TENANT / ORGANIZATION / DEPARTMENT / SYSTEM / TEAM
AllowedActions list Subconjunto de CREATE_USER · BLOCK_USER · ASSIGN_PROFILE · RESET_PASSWORD · REVOKE_MFA
DelegationStatus enum DRAFT / PENDING_APPROVAL / ACTIVE / REVOKED / EXPIRED / COMPLETED / REJECTED / ARCHIVED
ValidFrom DateTimeOffset ValidUntil
ValidUntil DateTimeOffset > ValidFrom
RevocationReason string? Requerido cuando Status → REVOKED

Invariantes

ID Regla Fuente
INV-DEL1 DelegatingAdmin no puede otorgar acciones que no posee (no-elevation) FS-14 §6.1
INV-DEL2 DelegatingAdmin ≠ DelegatedAdmin FS-14 §6
INV-DEL3 ValidUntil > ValidFrom FS-14 §3
INV-DEL4 AllowedActions debe ser subconjunto no vacío de la autoridad real del delegador FS-14 §6.2
INV-DEL5 Delegación circular prohibida: si A delegó en B, B no puede delegar en A FS-14 §5.B
INV-DEL6 Una delegación DRAFT no es visible para el admin delegado hasta ACTIVE EP-06 §2.2
INV-DEL7 REVOKED o EXPIRED no pueden reactivarse; crear nueva EP-06 §2.2
INV-DEL8 MaxDurationDays si definido limita ValidUntil − ValidFrom EP-06 §2.1
INV-DEL9 Si RequiresApproval=true, la activación requiere ApprovalRequestId aprobado EP-06 §2.1
INV-DEL10 ScopeId debe estar presente si ScopeType ≠ TENANT diseño

Diagrama del Agregado

classDiagram
    direction TB
    class UserManagementDelegation {
        <<AggregateRoot>>
        +Guid Id
        +Guid TenantId
        +Guid DelegatingAdminId
        +Guid DelegatedAdminId
        +DelegationScopeType ScopeType
        +Guid ScopeId
        +AllowedActions AllowedActions
        +DateTimeOffset ValidFrom
        +DateTimeOffset ValidUntil
        +int MaxDurationDays
        +bool RequiresApproval
        +Guid ApprovalRequestId
        +DelegationStatus Status
        +DateTimeOffset RevokedAt
        +Guid RevokedBy
        +string RevocationReason
    }
    class UserAccount {
        <<AggregateRoot>>
        +Guid Id
    }
    UserManagementDelegation "many" --> "1" UserAccount : delegatingAdmin
    UserManagementDelegation "many" --> "1" UserAccount : delegatedAdmin

Máquina de Estado: UserManagementDelegation

stateDiagram-v2
    [*] --> DRAFT : CreateDelegation
    DRAFT --> PENDING_APPROVAL : SubmitForApproval (RequiresApproval=true)
    DRAFT --> ACTIVE : Activate (RequiresApproval=false)
    PENDING_APPROVAL --> ACTIVE : ApproveDelegation
    PENDING_APPROVAL --> REJECTED : RejectDelegation
    ACTIVE --> REVOKED : RevokeDelegation
    ACTIVE --> EXPIRED : valid_until alcanzado (Background Worker)
    ACTIVE --> COMPLETED : Periodo concluye naturalmente
    REVOKED --> ARCHIVED : ArchiveDelegation
    EXPIRED --> ARCHIVED : ArchiveDelegation
    COMPLETED --> ARCHIVED : ArchiveDelegation
    REJECTED --> ARCHIVED : ArchiveDelegation
    note right of ACTIVE : IDelegationScopeValidator\nsolo resuelve delegaciones ACTIVE
    note right of ARCHIVED : Estado terminal — sin retorno

Comandos

Comando Descripción
CreateDelegationCommand Crea borrador de delegación con scope y acciones permitidas
SubmitDelegationForApprovalCommand Envía a flujo de aprobación si RequiresApproval=true
ActivateDelegationCommand Activa directamente o tras aprobación
RevokeDelegationCommand Revocación manual con razón obligatoria
ExpireDelegationCommand Background Worker — expira por valid_until
CompleteDelegationCommand Background Worker — conclusión natural del período
ArchiveDelegationCommand Background Worker — archiva estados terminales

Eventos de Dominio

DelegationCreatedEvent              { delegationId, delegatingAdminId, delegatedAdminId, scopeType, allowedActions }
DelegationSubmittedForApprovalEvent { delegationId, approvalRequestId }
DelegationActivatedEvent            { delegationId, validFrom, validUntil }
DelegationRevokedEvent              { delegationId, revokedBy, reason }
DelegationExpiredEvent              { delegationId, expiredAt }
DelegationRejectedEvent             { delegationId, rejectionReason }
DelegationArchivedEvent             { delegationId, previousStatus }

Repositorio

IUserManagementDelegationRepository {
    FindByIdAsync(delegationId, rootTenantId)
    FindActiveDelegationsForAdminAsync(delegatedAdminId, tenantId)
    FindExpiredActiveAsync(asOf)                       // Background Worker
    FindGrantedByAsync(delegatingAdminId, tenantId)
    AddAsync(delegation)
    UpdateAsync(delegation)
}

Anterior: Lenguaje Ubicuo Índice DDD Siguiente: Authorization Context