|
11 | 11 |
|
12 | 12 | | Agregado | Raiz | Descripcion | |
13 | 13 | |---------|------|-------------| |
14 | | -| [Tenant](#aggregate-tenant) | `Tenant` | Nodo organizacional jerarquico | |
15 | | -| [UserAccount](#aggregate-useraccount) | `UserAccount` | Principal autenticable | |
16 | | -| [Branch](#aggregate-branch) | `Branch` | Sub-unidad fisica del tenant | |
| 14 | +| [Tenant](#aggregate-tenant) | `Tenant` | Nodo organizacional jerárquico y sus branches/proveedores de identidad | |
| 15 | +| [UserAccount](#aggregate-useraccount) | `UserAccount` | Principal autenticable con sus métodos de autenticación | |
17 | 16 |
|
18 | 17 | --- |
19 | 18 |
|
|
25 | 24 |
|
26 | 25 | | Entidad | Descripcion | |
27 | 26 | |---------|-------------| |
28 | | -| `Tenant` (AR) | Nodo organizacional en la jerarquia; equivale a Organization en el glosario | |
| 27 | +| `Tenant` (AR) | Nodo organizacional en la jerarquía; equivale a Organization en el glosario | |
| 28 | +| `Branch` | Sub-unidad física del tenant; ciclo de vida gobernado por el Tenant AR | |
| 29 | +| `IdentityProvider` | Proveedor de identidad federada registrado para el tenant | |
| 30 | +| `Branding` | Configuración visual y de DNS para el hosted login del tenant | |
29 | 31 |
|
30 | 32 | ### Value Objects |
31 | 33 |
|
32 | 34 | | Value Object | Tipo base | Regla | |
33 | 35 | |-------------|-----------|-------| |
34 | 36 | | `TenantType` | enum | `ROOT / ENTERPRISE / SUBSIDIARY / DIVISION / BRANCH / DEPARTMENT` | |
35 | | -| `TaxonomyRank` | int | Rango jerarquico; determina quien puede ser padre/hijo | |
| 37 | +| `TaxonomyRank` | int | Rango jerárquico; determina quién puede ser padre/hijo | |
36 | 38 | | `TenantStatus` | enum | `ACTIVE / SUSPENDED / ARCHIVED` | |
37 | 39 | | `OrganizationType` | enum | `INTERNAL / CLIENT / SUPPLIER / PARTNER` | |
38 | | -| `CompanyReference` | string | Codigo ERP externo (SAP); inmutable post-creacion | |
| 40 | +| `CompanyReference` | string | Código ERP externo (SAP); inmutable post-creación | |
39 | 41 | | `IdpStrategyHint` | enum | `INTERNAL_BCRYPT / ZITADEL / AZURE_AD / OKTA / SAML2 / GENERIC_OIDC` | |
40 | 42 | | `TenantMetadata` | JSON | Payload libre validado como JSON | |
| 43 | +| `BranchCode` | string | Único dentro del tenant; slug alfanumérico | |
| 44 | +| `GeofencingMetadata` | JSON | Nullable; requiere `radius_km`, `center_lat`, `center_lng` si presente | |
41 | 45 |
|
42 | 46 | ### Invariantes |
43 | 47 |
|
44 | 48 | | ID | Regla | Fuente | |
45 | 49 | |----|-------|--------| |
46 | 50 | | INV-T1 | `child.TaxonomyRank > parent.TaxonomyRank` — tipo hijo debe tener rango estrictamente mayor | ADR-0048 | |
47 | 51 | | INV-T2 | `BRANCH` y `DEPARTMENT` no pueden tener hijos (`can_have_children = false`) | ADR-0048 | |
48 | | -| INV-T3 | `ROOT` es su propio `root_tenant_id`; todos los demas deben diferir | ADR-0048 | |
49 | | -| INV-T4 | `CompanyReference` debe ser unico dentro del tipo `CLIENT/SUPPLIER/PARTNER` del mismo parent | FS-03 | |
50 | | -| INV-T5 | `ARCHIVED` es terminal; no retorna a `ACTIVE` sin proceso explicito | FS-03 | |
| 52 | +| INV-T3 | `ROOT` es su propio `root_tenant_id`; todos los demás deben diferir | ADR-0048 | |
| 53 | +| INV-T4 | `CompanyReference` debe ser único dentro del tipo `CLIENT/SUPPLIER/PARTNER` del mismo parent | FS-03 | |
| 54 | +| INV-T5 | `ARCHIVED` es terminal; no retorna a `ACTIVE` sin proceso explícito | FS-03 | |
51 | 55 | | INV-T6 | No puede crearse `Branch` ni `UserAccount` bajo un Tenant `SUSPENDED` o `ARCHIVED` | FS-03 | |
| 56 | +| INV-B1 | `BranchCode` único dentro del `TenantId` | FS-03 | |
| 57 | +| INV-B2 | `GeofencingMetadata` válido JSON con claves requeridas si presente | conceptual-data-model.md | |
| 58 | +| INV-B3 | Branch inactiva o suspendida no puede recibir nuevos Profiles | FS-03 | |
| 59 | +| INV-B4 | Branch debe pertenecer a un Tenant `ACTIVE` | FS-03 | |
52 | 60 |
|
53 | 61 | ### Comandos |
54 | 62 |
|
55 | 63 | | Comando | Descripcion | |
56 | 64 | |---------|-------------| |
57 | 65 | | `RegisterTenantCommand` | Registra un nuevo tenant con tipo, referencia externa y estrategia IdP | |
58 | 66 | | `SuspendTenantCommand` | Suspende el tenant; bloquea acceso a todos sus usuarios | |
59 | | -| `ArchiveTenantCommand` | Archiva el tenant; estado terminal | |
60 | | -| `UpdateIdpStrategyCommand` | Cambia la estrategia de autenticacion del tenant | |
61 | | -| `ConfigureBrandingCommand` | Configura branding del hosted login (FS-08) | |
| 67 | +| `ActivateTenantCommand` | Activa un tenant suspendido | |
| 68 | +| `AddBranchCommand` | Agrega una sub-unidad física (Branch) al tenant | |
| 69 | +| `RemoveBranchCommand` | Remueve una branch inactiva del tenant | |
| 70 | +| `DeactivateBranchCommand` | Suspende/desactiva temporalmente una branch activa | |
| 71 | +| `ReactivateBranchCommand` | Reactiva una branch suspendida | |
| 72 | +| `RegisterIdentityProviderCommand` | Registra un nuevo proveedor de identidad federada para el tenant | |
| 73 | +| `ActivateIdentityProviderCommand` | Activa un IdP y desactiva los demás del tenant | |
| 74 | +| `DeactivateIdentityProviderCommand` | Desactiva un IdP activo del tenant | |
| 75 | +| `RemoveIdentityProviderCommand` | Remueve un IdP registrado e inactivo del tenant | |
| 76 | +| `SetBrandingCommand` | Configura branding visual por primera vez | |
| 77 | +| `UpdateBrandingCommand` | Actualiza la configuración visual de branding existente | |
| 78 | +| `VerifyBrandingDnsCommand` | Marca el DNS de branding como verificado con éxito | |
| 79 | +| `FailBrandingDnsCommand` | Registra fallo en la verificación DNS del branding | |
| 80 | +| `RemoveBrandingCommand` | Remueve la configuración de branding del tenant | |
62 | 81 |
|
63 | 82 | ### Eventos de Dominio |
64 | 83 |
|
65 | 84 | ``` |
66 | | -TenantRegisteredEvent { tenantId, parentTenantId?, typeCode, companyReference? } |
67 | | -TenantSuspendedEvent { tenantId, reason } |
68 | | -TenantArchivedEvent { tenantId } |
69 | | -TenantIdpStrategyUpdatedEvent { tenantId, newStrategy, version } |
| 85 | +TenantCreatedEvent { tenantId, code, name } |
| 86 | +TenantActivatedEvent { tenantId } |
| 87 | +TenantSuspendedEvent { tenantId } |
| 88 | +BranchCreatedEvent { tenantId, branchId, code } |
| 89 | +BranchRemovedEvent { tenantId, branchId } |
| 90 | +BranchDeactivatedEvent { tenantId, branchId } |
| 91 | +BranchReactivatedEvent { tenantId, branchId } |
| 92 | +IdentityProviderRegisteredEvent { tenantId, identityProviderId, code, strategyName } |
| 93 | +IdentityProviderActivatedEvent { tenantId, identityProviderId, code } |
| 94 | +IdentityProviderDeactivatedEvent { tenantId, identityProviderId, code } |
| 95 | +IdentityProviderRemovedEvent { tenantId, identityProviderId } |
| 96 | +BrandingCreatedEvent { tenantId, brandingId } |
| 97 | +BrandingUpdatedEvent { tenantId, brandingId } |
| 98 | +BrandingDnsVerifiedEvent { tenantId, brandingId } |
| 99 | +BrandingDnsFailedEvent { tenantId, brandingId } |
| 100 | +BrandingRemovedEvent { tenantId, brandingId } |
70 | 101 | ``` |
71 | 102 |
|
72 | 103 | ### Repositorio |
73 | 104 |
|
74 | 105 | ```csharp |
75 | | -ITenantRepository { |
76 | | - FindByIdAsync(tenantId, rootTenantId) |
77 | | - FindBySlugAsync(slug) |
78 | | - FindDescendantsAsync(ancestorId, maxDepth?) |
79 | | - FindAncestorsAsync(descendantId) // via TenantClosure |
80 | | - IsDescendantOfAsync(candidateId, ancestorId) // 1 index lookup |
81 | | - FindByCompanyReferenceAsync(reference, type) |
82 | | - AddAsync(tenant) |
83 | | - UpdateAsync(tenant) |
| 106 | +ITenantRepository : IAggregateRepository<Tenant> { |
| 107 | + GetByCodeAsync(code, cancellationToken) |
84 | 108 | } |
85 | 109 | ``` |
86 | 110 |
|
@@ -178,35 +202,4 @@ IUserAccountRepository { |
178 | 202 |
|
179 | 203 | --- |
180 | 204 |
|
181 | | -## Aggregate: Branch |
182 | | - |
183 | | -**Aggregate Root:** `Branch` |
184 | | -**FS:** FS-03 |
185 | | - |
186 | | -### Value Objects |
187 | | - |
188 | | -| Value Object | Tipo | Regla | |
189 | | -|-------------|------|-------| |
190 | | -| `BranchCode` | string | Unico dentro del tenant; slug alfanumerico | |
191 | | -| `GeofencingMetadata` | JSON | Nullable; requiere `radius_km`, `center_lat`, `center_lng` si presente | |
192 | | -| `BranchStatus` | enum | `ACTIVE / SUSPENDED` | |
193 | | - |
194 | | -### Invariantes |
195 | | - |
196 | | -| ID | Regla | Fuente | |
197 | | -|----|-------|--------| |
198 | | -| INV-B1 | `BranchCode` unico dentro del `TenantId` | FS-03 | |
199 | | -| INV-B2 | `GeofencingMetadata` valido JSON con claves requeridas si presente | conceptual-data-model.md | |
200 | | -| INV-B3 | Branch `SUSPENDED` no puede recibir nuevos Profiles | FS-03 | |
201 | | -| INV-B4 | Branch debe pertenecer a un Tenant `ACTIVE` | FS-03 | |
202 | | - |
203 | | -### Comandos y Eventos |
204 | | - |
205 | | -``` |
206 | | -CreateBranchCommand -> BranchCreatedEvent { branchId, tenantId, code } |
207 | | -SuspendBranchCommand -> BranchSuspendedEvent { branchId, tenantId } |
208 | | -``` |
209 | | - |
210 | | ---- |
211 | | - |
212 | 205 | **[Anterior: Lenguaje Ubicuo](./02-ubiquitous-language.md)** | **[Indice DDD](./index.md)** | **[Siguiente: Authorization Context](./04-authorization-context.md)** |
0 commit comments