Estándares y Estructura de Base de Datos
1. Estrategia de Identificadores
No existe una solución única para todos los casos. La elección del tipo de identificador depende del patrón de escritura, el volumen esperado y si el ID será o no expuesto hacia el exterior. Se sigue una regla de tres:
1.1 UUIDv7 (GUID v7)
¿Cuándo usarlo?
- Tablas de altísimo tráfico donde múltiples sistemas escriben en paralelo y la velocidad de escritura es la prioridad absoluta.
- Datos expuestos hacia afuera (APIs públicas) donde no se quiere revelar el volumen de registros ni permitir adivinar el siguiente ID.
- Inserciones masivas y concurrentes desde diferentes nodos que deben generar su propio ID en memoria sin hacer fila a la base de datos.
¿Por qué UUIDv7 y no v4? UUIDv7 incluye un componente de tiempo en los bits más significativos, haciéndolo ordenable cronológicamente. Esto reduce la fragmentación del índice clustered en SQL Server y mejora el rendimiento de inserción en comparación con UUIDs aleatorios (v4).
| Tabla | Razón |
|---|---|
web_usuario_login_fallido | Bitácora de alta frecuencia: múltiples servicios pueden registrar intentos fallidos simultáneamente desde distintos nodos. El ID se genera en memoria sin coordinación. |
1.2 Sequence + Get Range
¿Cuándo usarlo?
- Tablas core del negocio donde se exige un identificador numérico legible (fácil de leer, buscar o comunicar).
- Tablas que reciben cargas masivas periódicas (procesos batch) pero también inserciones del día a día.
- El proceso batch pide un rango completo de IDs en una sola llamada a la BD y los usa en memoria para insertar miles de registros sin volver a consultar la secuencia.
| Tabla | Razón |
|---|---|
web_usuario | Tabla central de usuarios: puede recibir cargas batch e inserciones individuales. El ID numérico es conveniente para referencias internas. |
web_rol | Se relaciona con usuarios y módulos; su ID numérico simplifica joins y depuración. |
wr_email | Catálogo de emails que puede crecer en lotes al importar listas. |
1.3 Identity (Autoincremental)
¿Cuándo usarlo?
- Tablas satélite, catálogos, diccionarios o configuraciones: pequeñas, que rara vez cambian y normalmente se llenan a mano o desde pantallas de administración.
- Tablas donde la concurrencia es nula o despreciable.
- Es la opción que menos configuración requiere tanto en SQL Server como en JPA/Hibernate.
| Tabla | Razón |
|---|---|
tipo_usuario | Catálogo estático. Los tipos se crean manualmente y rarísimamente cambian. |
web_clasificacion_usuario | Diccionario de cuatro valores fijos (AGENTE, CLIENTE, ADMINISTRADOR, EXTERNO). |
web_modulo | Los módulos los define el equipo de desarrollo, no el usuario final. |
web_pagina | Se definen una vez y se mantienen estables. |
web_reporte | Catálogo gestionado por administradores. Baja frecuencia de inserción. |
web_configuracion | Tabla de configuración global con un único registro (PK = 1). |
2. Convenciones de Nomenclatura
2.1 snake_case en todo
Todos los nombres de tablas, columnas, índices, constraints y secuencias usan snake_case en minúsculas. Garantiza compatibilidad en entornos case-sensitive, legibilidad uniforme y coherencia entre SQL y el código de la aplicación.
-- ✅ Correcto
web_usuario, tipo_usuario_id, fecha_ultima_conexion
-- ❌ Incorrecto
WebUsuario, TipoUsuarioId, FechaUltimaConexion2.2 Nomenclatura de objetos
| Objeto | Patrón | Ejemplo |
|---|---|---|
| Tabla | [prefijo_]nombre | web_usuario, tipo_usuario |
| Secuencia | nombre_tabla_seq | web_usuario_seq |
| Primary Key | Implícita en PRIMARY KEY | — |
| Unique Constraint | UQ_tabla_columna | UQ_web_usuario_correo |
| Foreign Key | FK_tabla_referencia | FK_web_usuario_rol |
| Índice | IX_tabla_columna | IX_web_usuario_rol_id |
| Check Constraint | CK_tabla_descripcion | CK_web_configuracion_id |
3. Constraints
3.1 Primary Key
Toda tabla tiene una Primary Key explícita, declarada al momento de crearla.
-- Autoincremental (catálogos)
id SMALLINT PRIMARY KEY
-- Sequence (core)
id INT PRIMARY KEY DEFAULT NEXT VALUE FOR web_usuario_seq
-- UUID (alta concurrencia)
id UNIQUEIDENTIFIER DEFAULT NEWSEQUENTIALID() PRIMARY KEY
-- Compuesta (tablas de relación N:M)
PRIMARY KEY (modulo_id, pagina_id)PKs compuestas: el orden de las columnas importa. Se coloca primero la columna por la que más frecuentemente se consulta, ya que SQL Server construye el índice clustered en ese orden.
-- Se busca más por módulo que por rol → modulo_id va primero
PRIMARY KEY (modulo_id, rol_id)3.2 Unique Constraints
Para columnas que deben ser únicas pero no son la PK. Crear implícitamente un índice único que también acelera búsquedas por esa columna.
CONSTRAINT UQ_web_usuario_usuario UNIQUE (usuario),
CONSTRAINT UQ_web_usuario_correo UNIQUE (correo_electronico)Preferir CONSTRAINT con nombre explícito sobre UNIQUE inline. Permite hacer DROP CONSTRAINT por nombre sin tener que buscar el índice generado automáticamente.
3.3 Check Constraints
Para garantizar integridad de dominio en columnas con valores restringidos.
-- La tabla de configuración global solo admite un registro
id SMALLINT PRIMARY KEY CHECK(id = 1)3.4 Foreign Keys
Todas las relaciones entre tablas se declaran como FKs explícitas con nombre. Beneficios:
- El motor garantiza integridad referencial independiente de la aplicación.
- Documentan la relación de forma inequívoca.
- Permiten que herramientas de modelado generen diagramas automáticamente.
ALTER TABLE web_usuario
ADD CONSTRAINT FK_web_usuario_rol
FOREIGN KEY (rol_id) REFERENCES web_rol(id);Patrón: las FKs se declaran al final del script, después de crear todas las tablas e índices. Esto evita problemas de orden de creación y facilita la lectura del DDL.
3.5 Default Values
Se declaran inline en la definición de la columna para que el motor los aplique automáticamente sin depender de la aplicación:
activo BIT NOT NULL DEFAULT 1
created_at DATETIME2(0) NOT NULL DEFAULT SYSDATETIME()4. Índices
4.1 Índices sobre Foreign Keys
Regla: toda columna que sea FK debe tener un índice no agrupado (NONCLUSTERED). SQL Server no crea índices automáticamente para las FK (a diferencia de otros motores). Sin índice, cada validación de integridad referencial o JOIN hace un table scan.
CREATE INDEX IX_web_usuario_rol_id
ON web_usuario(rol_id);4.2 Índices sobre columnas de auditoría FK
Las columnas usuario_ingreso_id y usuario_actualizo_id son FKs presentes en múltiples tablas. Se indexan para soportar consultas del tipo “¿qué registró este usuario?“.
CREATE INDEX IX_web_rol_usuario_ingreso
ON web_rol(usuario_ingreso_id);
CREATE INDEX IX_web_rol_modulo_usuario_actualizo_id
ON web_rol_modulo(usuario_actualizo_id);4.3 Índices compuestos
En tablas de relación donde se consulta frecuentemente por más de una columna, un índice compuesto evita el key lookup:
-- Permite buscar por rol y filtrar por módulo en un solo índice
CREATE INDEX IX_web_rol_modulo_rol_modulo
ON web_rol_modulo(rol_id, modulo_id);4.4 Cuándo NO agregar índices
- Tablas pequeñas (< 1,000 filas) con baja frecuencia de consulta: el overhead de mantenimiento supera el beneficio.
- Columnas de baja cardinalidad (ej.
activo BIT): un índice sobre dos valores posibles raramente es aprovechado por el optimizador. - No duplicar cobertura: si una columna ya está cubierta por la PK compuesta, no es necesario un índice adicional para ella.
5. Columnas de Auditoría
Toda tabla que registre operaciones de usuarios incluye columnas de auditoría estándar:
| Columna | Tipo | Propósito |
|---|---|---|
usuario_ingreso_id | INT NOT NULL | ID del usuario que creó el registro |
usuario_actualizo_id | INT NULL | ID del usuario que hizo la última modificación |
created_at | DATETIME2(0) NOT NULL DEFAULT SYSDATETIME() | Timestamp de creación |
updated_at | DATETIME2(0) NULL | Timestamp de última modificación |
DATETIME2(0): precisión de segundos, suficiente para auditoría y más eficiente en espacio que DATETIME2 con mayor precisión o el tipo legado DATETIME.
Las columnas usuario_ingreso_id y usuario_actualizo_id apuntan a web_usuario(id) mediante FK, garantizando que cualquier actor quede registrado con un usuario válido del sistema.
6. Resumen de Tablas
| Tabla | Tipo de ID | Estrategia | Propósito |
|---|---|---|---|
tipo_usuario | SMALLINT | Identity | Catálogo de tipos de usuario |
web_clasificacion_usuario | SMALLINT manual | Identity | Diccionario de clasificaciones (4 valores fijos) |
web_rol | SMALLINT SEQUENCE | Sequence | Roles del sistema |
web_usuario | INT SEQUENCE | Sequence | Usuarios de la aplicación |
web_usuario_login_fallido | UNIQUEIDENTIFIER | UUIDv7 | Bitácora de intentos fallidos de login |
web_modulo | SMALLINT SEQUENCE | Identity | Catálogo de módulos |
web_rol_modulo | PK compuesta | — | Relación N:M roles-módulos |
web_modulo_pagina | PK compuesta | — | Relación N:M módulos-páginas |
web_pagina | SMALLINT SEQUENCE | Identity | Catálogo de páginas |
web_pagina_access_role | PK compuesta | — | Access roles por página |
web_reporte | INT manual | Identity | Catálogo de reportes |
wr_email | INT SEQUENCE | Sequence | Catálogo genérico de emails |
web_configuracion | SMALLINT CHECK(id=1) | Identity | Configuración global (singleton) |
web_reporte_notificacion_email | PK compuesta | — | Emails por reporte |