|
| 1 | +# 🗄️ Modelo Entidad-Relación (E/R) - SQL Server 2022 |
| 2 | + |
| 3 | +**Tipo de Documento:** Diseño de Base de Datos |
| 4 | +**Estatus:** Propuesto |
| 5 | +**Arquitectura:** Multi-tenancy (Esquema Compartido + RLS) |
| 6 | +**Motor:** SQL Server 2022 |
| 7 | + |
| 8 | +## 1. Introducción |
| 9 | +Este documento detalla el diseño del modelo de datos para el **User Management System (UMS)**. El diseño está optimizado para **SQL Server 2022**, utilizando tipos de datos modernos y una estructura que facilita el aislamiento de datos mediante **Row-Level Security (RLS)** y el uso de `SESSION_CONTEXT`. |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## 2. Diagrama E/R (Mermaid) |
| 14 | + |
| 15 | +```mermaid |
| 16 | +erDiagram |
| 17 | + TENANT ||--o{ USER : "pertenece_a" |
| 18 | + TENANT ||--o{ ROLE : "define" |
| 19 | + TENANT ||--o{ BRANCH : "posee" |
| 20 | + TENANT ||--o{ AUDIT_LOG : "genera" |
| 21 | +
|
| 22 | + USER ||--|| USER_CREDENTIAL : "tiene" |
| 23 | + USER ||--|| PROFILE : "tiene" |
| 24 | + USER ||--o{ USER_ROLE : "asignado_a" |
| 25 | + USER ||--o{ USER_BRANCH : "opera_en" |
| 26 | +
|
| 27 | + ROLE ||--o{ ROLE_PERMISSION : "contiene" |
| 28 | + ROLE ||--o{ USER_ROLE : "asignado_a" |
| 29 | + |
| 30 | + PERMISSION ||--o{ ROLE_PERMISSION : "asociado_a" |
| 31 | + |
| 32 | + BRANCH ||--o{ USER_BRANCH : "asociado_a" |
| 33 | +
|
| 34 | + TENANT { |
| 35 | + uniqueidentifier TenantId PK |
| 36 | + nvarchar Name "NOT NULL" |
| 37 | + nvarchar Code "UK, NOT NULL" |
| 38 | + int Status "NOT NULL" |
| 39 | + datetimeoffset CreatedAt "DEFAULT GETDATE()" |
| 40 | + } |
| 41 | +
|
| 42 | + USER { |
| 43 | + uniqueidentifier UserId PK |
| 44 | + uniqueidentifier TenantId FK "RLS" |
| 45 | + nvarchar Username "UK, NOT NULL" |
| 46 | + nvarchar Email "UK, NOT NULL" |
| 47 | + bit IsActive "DEFAULT 1" |
| 48 | + bit IsServiceAccount "DEFAULT 0" |
| 49 | + datetimeoffset CreatedAt |
| 50 | + } |
| 51 | +
|
| 52 | + USER_CREDENTIAL { |
| 53 | + uniqueidentifier CredentialId PK |
| 54 | + uniqueidentifier UserId FK "UK" |
| 55 | + nvarchar PasswordHash "NOT NULL" |
| 56 | + nvarchar SecurityStamp |
| 57 | + datetimeoffset LastChangedAt |
| 58 | + } |
| 59 | +
|
| 60 | + PROFILE { |
| 61 | + uniqueidentifier ProfileId PK |
| 62 | + uniqueidentifier UserId FK "UK" |
| 63 | + nvarchar FirstName |
| 64 | + nvarchar LastName |
| 65 | + nvarchar Attributes "JSON (ABAC)" |
| 66 | + } |
| 67 | +
|
| 68 | + ROLE { |
| 69 | + uniqueidentifier RoleId PK |
| 70 | + uniqueidentifier TenantId FK |
| 71 | + nvarchar Name "NOT NULL" |
| 72 | + nvarchar Description |
| 73 | + bit IsSystemRole "DEFAULT 0" |
| 74 | + } |
| 75 | +
|
| 76 | + PERMISSION { |
| 77 | + uniqueidentifier PermissionId PK |
| 78 | + nvarchar Code "UK, NOT NULL" |
| 79 | + nvarchar Name "NOT NULL" |
| 80 | + nvarchar Category |
| 81 | + } |
| 82 | +
|
| 83 | + ROLE_PERMISSION { |
| 84 | + uniqueidentifier RoleId PK, FK |
| 85 | + uniqueidentifier PermissionId PK, FK |
| 86 | + } |
| 87 | +
|
| 88 | + USER_ROLE { |
| 89 | + uniqueidentifier UserId PK, FK |
| 90 | + uniqueidentifier RoleId PK, FK |
| 91 | + } |
| 92 | +
|
| 93 | + BRANCH { |
| 94 | + uniqueidentifier BranchId PK |
| 95 | + uniqueidentifier TenantId FK |
| 96 | + nvarchar Name "NOT NULL" |
| 97 | + nvarchar Code "NOT NULL" |
| 98 | + } |
| 99 | +
|
| 100 | + USER_BRANCH { |
| 101 | + uniqueidentifier UserId PK, FK |
| 102 | + uniqueidentifier BranchId PK, FK |
| 103 | + } |
| 104 | +
|
| 105 | + AUDIT_LOG { |
| 106 | + bigint LogId PK "IDENTITY" |
| 107 | + uniqueidentifier TenantId FK |
| 108 | + uniqueidentifier UserId FK |
| 109 | + nvarchar Action "NOT NULL" |
| 110 | + nvarchar EntityName "NOT NULL" |
| 111 | + nvarchar EntityId |
| 112 | + nvarchar OldValue "JSON" |
| 113 | + nvarchar NewValue "JSON" |
| 114 | + datetimeoffset Timestamp "DEFAULT GETDATE()" |
| 115 | + } |
| 116 | +``` |
| 117 | + |
| 118 | +--- |
| 119 | + |
| 120 | +## 3. Diccionario de Datos y Tipos SQL Server |
| 121 | + |
| 122 | +### 3.1 Estándares de Tipos |
| 123 | +* **Identificadores (PK/FK):** `uniqueidentifier` utilizando `NEWSEQUENTIALID()` en SQL Server para evitar fragmentación de índices. |
| 124 | +* **Fechas:** `datetimeoffset` para garantizar precisión en zonas horarias globales. |
| 125 | +* **Cadenas:** `nvarchar(n)` para soporte Unicode completo. |
| 126 | +* **Metadatos/ABAC:** `nvarchar(max)` con validación `ISJSON()` para flexibilidad de atributos dinámicos. |
| 127 | + |
| 128 | +### 3.2 Tablas Principales |
| 129 | + |
| 130 | +| Tabla | Propósito | Estrategia de Índice | |
| 131 | +| :--- | :--- | :--- | |
| 132 | +| `Tenants` | Maestro de inquilinos. | Clustered en `TenantId`. Unique en `Code`. | |
| 133 | +| `Users` | Identidades de usuario. | Clustered en `UserId`. Non-clustered en `TenantId` (RLS Optimization). | |
| 134 | +| `Roles` | Definición de roles por tenant. | Filtrado por `TenantId`. | |
| 135 | +| `AuditLogs` | Trazabilidad de cambios. | Clustered en `LogId` (bigint identity). Particionado por `Timestamp` si la escala aumenta. | |
| 136 | + |
| 137 | +--- |
| 138 | + |
| 139 | +## 4. Implementación de Multi-tenancy (RLS) |
| 140 | + |
| 141 | +Para SQL Server, la propuesta de aislamiento se basa en el uso de **Security Policies** y **Inline Table-Valued Functions (iTVF)**. |
| 142 | + |
| 143 | +### Función de Filtro Predicado |
| 144 | +```sql |
| 145 | +CREATE FUNCTION Security.fn_tenantSecurityPredicate(@TenantId uniqueidentifier) |
| 146 | + RETURNS TABLE |
| 147 | + WITH SCHEMABINDING |
| 148 | +AS |
| 149 | + RETURN SELECT 1 AS fn_security_predicate_result |
| 150 | + WHERE @TenantId = CAST(SESSION_CONTEXT(N'TenantId') AS uniqueidentifier) |
| 151 | + OR CAST(SESSION_CONTEXT(N'IsSuperAdmin') AS bit) = 1; |
| 152 | +``` |
| 153 | + |
| 154 | +### Política de Seguridad |
| 155 | +```sql |
| 156 | +CREATE SECURITY POLICY Security.TenantIdFilter |
| 157 | + ADD FILTER PREDICATE Security.fn_tenantSecurityPredicate(TenantId) ON dbo.Users, |
| 158 | + ADD FILTER PREDICATE Security.fn_tenantSecurityPredicate(TenantId) ON dbo.Roles, |
| 159 | + ADD FILTER PREDICATE Security.fn_tenantSecurityPredicate(TenantId) ON dbo.AuditLogs |
| 160 | + WITH (STATE = ON); |
| 161 | +``` |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## 5. Consideraciones para el Blueprint |
| 166 | +1. **Escalabilidad:** Se recomienda el uso de **Indexes Columnstore** en la tabla `AuditLogs` si el volumen de auditoría supera los millones de registros. |
| 167 | +2. **Integridad:** Todas las relaciones N:M se manejan mediante tablas de unión con claves compuestas para optimizar la navegación. |
| 168 | +3. **Seguridad:** Los hashes de contraseña nunca deben almacenarse en la tabla `Users`, sino en `UserCredentials` para permitir la rotación de secretos y múltiples métodos de autenticación (ej. MFA). |
0 commit comments