Contexto Acotado: Approvals
Raíz del Agregado: Sí
Módulo: Ums.Domain.Approvals.UserDocument
Estado: Producción
El agregado UserDocument representa una credencial digital o documento de cumplimiento cargado por un usuario (por ejemplo, verificación de identidad, certificaciones). Gestiona el ciclo de vida de verificación del documento, su estado de validez, estado de cumplimiento y el historial de notificaciones de vencimiento enviadas al usuario a través de la entidad AccessNotification. AccessNotification sirve como un registro cronológico inmutable de alertas generadas por el sistema.
AccessNotification) como entidades de propiedad exclusiva, capturando los canales de comunicación y el índice de paso.UserDocument es una raíz de agregado soberana dentro del contexto de Approvals. Controla su estado interno y garantiza que todos los hijos (como AccessNotification) sean modificados exclusivamente a través de los métodos del dominio raíz.
ExpirationDate) debe ser cronológicamente mayor que su fecha de emisión (IssueDate).PendingReview.PendingReview puede transicionar a Valid (a través de Validate) o Rejected (a través de Reject).Valid puede transicionar a Expired (a través de Expire) cuando la fecha del calendario supera la fecha de vencimiento.Expired y Rejected pueden activar la recarga (ReUpload), lo que restablece el estado a PendingReview y reinicia el contador de pasos de notificación a cero.Rejected no puede transicionar directamente a Valid sin pasar por un nuevo ciclo de carga/verificación.FileChecksum) y hacer referencia a una estructura de tipo de documento (DocumentTypeId) existente.AccessNotification, sus propiedades no pueden ser modificadas.DaysRemaining debe ser un entero positivo o cero, que represente la ventana de validez restante.Step de la notificación debe corresponder a una fase de advertencia activa configurada en las reglas del tipo de documento.| Entidad / VO | Tipo | Descripción |
|—|—|—|
| UserDocumentId | Objeto de Valor | Identificador único del agregado |
| UserId | Objeto de Valor | Referencia al propietario, vinculando con el Contexto de Identity |
| DocumentTypeId | Objeto de Valor | Referencia al agregado de definición de plantilla |
| DocumentStatus | Enumerado | PendingReview · Valid · Rejected · Expired |
| DocumentCriticity | Objeto de Valor | Clasificación de severidad de cumplimiento |
| TextValueObject | Objeto de Valor | Ruta de almacenamiento validada en el sistema de archivos |
| AccessNotification | Entidad | Entidad hija propiedad del agregado que registra el historial de alertas |
| AccessNotificationId | Objeto de Valor | Identificador único de la entidad de notificación |
| NotificationChannel | Enumerado | EMAIL · SMS · IN_APP · WEB_PUSH |
| Step | Primitivo | Contador del índice de paso |
UserDocument (Aggregate Root)
├── Props: UserDocumentProps
│ ├── Id: UserDocumentId
│ ├── UserId: UserId (Ref Externa)
│ ├── DocumentTypeId: DocumentTypeId (Ref Externa)
│ ├── IssueDate: DateTime
│ ├── ExpirationDate: DateTime
│ ├── Status: DocumentStatus
│ ├── Criticity: DocumentCriticity
│ ├── FileStoragePath: TextValueObject
│ ├── FileChecksum: string
│ ├── NotificationStep: int
│ └── Audit: AuditValueObject
└── Notifications: AccessNotification[] (Colección Hija)
└── Props: AccessNotificationProps
├── Id: AccessNotificationId
├── Step: int
├── Channel: NotificationChannel
├── DaysRemaining: int
└── SentAt: DateTime
classDiagram
direction TB
class UserDocument {
+UserDocumentProps Props
+IReadOnlyCollection~AccessNotification~ Notifications
+Upload(UserId, DocumentTypeId, IssueDate, ExpirationDate, DocumentCriticity, TextValueObject, string, ActorId) Result~UserDocument~
+Validate(ActorId) Result
+Reject(string, ActorId) Result
+Expire(ActorId) Result
+ReUpload(DateTime, DateTime, TextValueObject, string, ActorId) Result
+RecordNotificationSent(int, NotificationChannel, int, ActorId) Result
+RecordEnforcementExecuted(string, ActorId) Result
}
class UserDocumentProps {
+IdValueObject Id
+UserId UserId
+DocumentTypeId DocumentTypeId
+DateTime IssueDate
+DateTime ExpirationDate
+DocumentStatus Status
+DocumentCriticity Criticity
+TextValueObject FileStoragePath
+string FileChecksum
+int NotificationStep
+AuditValueObject Audit
}
class AccessNotification {
+Guid Id
+int Step
+NotificationChannel Channel
+int DaysRemaining
+DateTime SentAt
+Record(step, channel, daysRemaining) AccessNotification
}
class DocumentStatus {
<<enumeration>>
PendingReview
Valid
Rejected
Expired
}
class DocumentCriticity {
+string Name
+int SeverityLevel
}
class NotificationChannel {
<<enumeration>>
EMAIL
SMS
IN_APP
WEB_PUSH
}
UserDocument *-- UserDocumentProps
UserDocument "1" *-- "0..*" AccessNotification : posee
UserDocumentProps --> DocumentStatus
UserDocumentProps --> DocumentCriticity
AccessNotification --> NotificationChannel
sequenceDiagram
autonumber
actor Reviewer as Verificador
participant Portal as Cliente Web
participant App as Servicio de Aplicación
participant Doc as UserDocument [Agregado]
participant Repo as UserDocumentRepository
participant DB as SQL Server
Reviewer->>Portal: Revisa detalles del documento
Portal->>App: ValidateUserDocumentCommand(DocId)
App->>Repo: GetByIdAsync(DocId)
Repo-->>App: UserDocument
App->>Doc: Validate(ReviewerId)
note over Doc: Verificar INV-UD2<br/>(Status == PendingReview)
Doc-->>App: Success
App->>Repo: SaveAsync(UserDocument)
Repo->>DB: UPDATE USER_DOCUMENT SET Status = 'Valid'
DB-->>Repo: Acknowledge
Repo-->>App: Done
App-->>Portal: ValidateUserDocumentResponse(Success)
erDiagram
USER_DOCUMENT ||--o{ ACCESS_NOTIFICATION : "contiene / registra"
USER_DOCUMENT }o--|| DOCUMENT_TYPE : "instancia"
USER_DOCUMENT {
uniqueidentifier UserDocumentId PK
uniqueidentifier UserId FK "Contexto de Identity"
uniqueidentifier DocumentTypeId FK
datetime2 IssueDate
datetime2 ExpirationDate
nvarchar Status
nvarchar Criticity
nvarchar FileStoragePath
nvarchar FileChecksum
int NotificationStep
nvarchar CreatedBy
datetime2 CreatedAt
nvarchar UpdatedBy
datetime2 UpdatedAt
}
ACCESS_NOTIFICATION {
uniqueidentifier NotificationId PK
uniqueidentifier UserDocumentId FK
int Step
nvarchar Channel
int DaysRemaining
datetime2 SentAt
}
UserId. La seguridad para entidades hijas como AccessNotification está garantizada implícitamente por el padre.flowchart TD
subgraph IdentityContext [Contexto de Identity]
U[UserAccount]
end
subgraph ApprovalsContext [Contexto de Approvals]
DT[DocumentType]
UD[UserDocument]
AN[AccessNotification]
end
UD -.->|hace referencia a UserId| U
UD -->|instancia| DT
UD *--|posee| AN
Valid.Rejected, incorporando los motivos del rechazo para su posterior corrección.PendingReview.AccessNotification.public class UserDocumentConfiguration : IEntityTypeConfiguration<UserDocument>
{
public void Configure(EntityTypeBuilder<UserDocument> builder)
{
builder.ToTable("USER_DOCUMENT");
builder.HasKey(e => e.Id);
builder.OwnsOne(e => e.Props, props =>
{
props.Property(p => p.Id).HasColumnName("UserDocumentId");
props.Property(p => p.UserId).HasColumnName("UserId");
props.Property(p => p.DocumentTypeId).HasColumnName("DocumentTypeId");
props.Property(p => p.IssueDate).HasColumnName("IssueDate");
props.Property(p => p.ExpirationDate).HasColumnName("ExpirationDate");
props.Property(p => p.Status).HasConversion<string>().HasColumnName("Status");
props.Property(p => p.Criticity).HasConversion(c => c.Name, n => DocumentCriticity.FromName(n)).HasColumnName("Criticity");
props.Property(p => p.FileStoragePath).HasConversion(p => p.GetValue(), s => TextValueObject.Create(s).Value).HasColumnName("FileStoragePath");
props.Property(p => p.FileChecksum).HasColumnName("FileChecksum");
props.Property(p => p.NotificationStep).HasColumnName("NotificationStep");
props.OwnsOne(p => p.Audit);
});
builder.HasMany(e => e.Notifications)
.WithOne()
.HasForeignKey("UserDocumentId")
.OnDelete(DeleteBehavior.Cascade); // Cascada persistida como tabla dependiente.
}
}
Role.User pueden cargar o volver a cargar documentos. Solo Role.Reviewer puede validar o rechazar.FileStoragePath) deben residir en directorios protegidos. La validación criptográfica de FileChecksum protege contra alteraciones en el almacenamiento físico subyacente.AccessNotification como una colección anidada y persistirlos como entidades propias garantiza trazas de auditoría cronológicas consistentes. Mantener el historial dentro del documento padre proporciona verificaciones rápidas y asegura un alto rendimiento durante las comprobaciones de cumplimiento, sin requerir costosas consultas cruzadas contra logs generales de mensajería o motores externos de auditoría.