ML Engine Updates: - Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records - Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence) - Backtest results: +176.71R profit with aggressive_filter strategy Documentation Consolidation: - Created docs/99-analisis/_MAP.md index with 13 new analysis documents - Consolidated inventories: removed duplicates from orchestration/inventarios/ - Updated ML_INVENTORY.yml with BTCUSD metrics and training results - Added execution reports: FASE11-BTCUSD, correction issues, alignment validation Architecture & Integration: - Updated all module documentation with NEXUS v3.4 frontmatter - Fixed _MAP.md indexes across all folders - Updated orchestration plans and traces Files: 229 changed, 5064 insertions(+), 1872 deletions(-) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
703 lines
20 KiB
Markdown
703 lines
20 KiB
Markdown
---
|
|
id: "ET-AUTH-003"
|
|
title: "Database Schema for Auth"
|
|
type: "Specification"
|
|
status: "Done"
|
|
rf_parent: "RF-AUTH-002"
|
|
epic: "OQI-001"
|
|
version: "1.0"
|
|
created_date: "2025-12-05"
|
|
updated_date: "2026-01-04"
|
|
---
|
|
|
|
# ET-AUTH-003: Especificación Técnica - Esquema de Base de Datos
|
|
|
|
**Version:** 1.0.0
|
|
**Fecha:** 2025-12-05
|
|
**Estado:** ✅ Implementado
|
|
**Épica:** [OQI-001](../_MAP.md)
|
|
|
|
---
|
|
|
|
## Resumen
|
|
|
|
Esta especificación detalla el esquema de base de datos para el sistema de autenticación de Trading Platform.
|
|
|
|
---
|
|
|
|
## Diagrama ER
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ AUTHENTICATION SCHEMA │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────────────┐ ┌─────────────────────┐
|
|
│ users │ │ user_profiles │
|
|
├─────────────────────┤ ├─────────────────────┤
|
|
│ id (PK) │──1:1──│ id (PK) │
|
|
│ email │ │ user_id (FK) │
|
|
│ password_hash │ │ display_name │
|
|
│ phone │ │ bio │
|
|
│ first_name │ │ avatar_url │
|
|
│ last_name │ │ preferred_language │
|
|
│ role │ │ timezone │
|
|
│ status │ │ date_format │
|
|
│ email_verified │ │ notification_prefs │
|
|
│ phone_verified │ │ created_at │
|
|
│ two_factor_enabled │ │ updated_at │
|
|
│ two_factor_secret │ └─────────────────────┘
|
|
│ backup_codes │
|
|
│ created_at │ ┌─────────────────────┐
|
|
│ updated_at │ │ oauth_accounts │
|
|
│ deleted_at │ ├─────────────────────┤
|
|
└─────────────────────┘ │ id (PK) │
|
|
│ │ user_id (FK) │──┐
|
|
│ │ provider │ │
|
|
├──────1:N──────────▶│ provider_user_id │ │
|
|
│ │ email │ │
|
|
│ │ access_token │ │
|
|
│ │ refresh_token │ │
|
|
│ │ token_expires_at │ │
|
|
│ │ raw_profile │ │
|
|
│ │ created_at │ │
|
|
│ │ updated_at │ │
|
|
│ └─────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────┐ │
|
|
│ │ sessions │ │
|
|
│ ├─────────────────────┤ │
|
|
├──────1:N──────────▶│ id (PK) │ │
|
|
│ │ user_id (FK) │──┘
|
|
│ │ refresh_token_hash │
|
|
│ │ device_info │
|
|
│ │ ip_address │
|
|
│ │ location │
|
|
│ │ user_agent │
|
|
│ │ is_active │
|
|
│ │ created_at │
|
|
│ │ last_activity │
|
|
│ │ expires_at │
|
|
│ │ revoked_at │
|
|
│ │ revoked_reason │
|
|
│ └─────────────────────┘
|
|
│
|
|
│ ┌─────────────────────┐
|
|
│ │email_verifications │
|
|
│ ├─────────────────────┤
|
|
├──────1:N──────────▶│ id (PK) │
|
|
│ │ user_id (FK) │
|
|
│ │ token_hash │
|
|
│ │ expires_at │
|
|
│ │ verified_at │
|
|
│ │ created_at │
|
|
│ └─────────────────────┘
|
|
│
|
|
│ ┌─────────────────────┐
|
|
│ │phone_verifications │
|
|
│ ├─────────────────────┤
|
|
├──────1:N──────────▶│ id (PK) │
|
|
│ │ phone │
|
|
│ │ user_id (FK) │
|
|
│ │ code_hash │
|
|
│ │ channel │
|
|
│ │ attempts │
|
|
│ │ expires_at │
|
|
│ │ verified_at │
|
|
│ │ created_at │
|
|
│ └─────────────────────┘
|
|
│
|
|
│ ┌─────────────────────┐
|
|
│ │password_reset_tokens│
|
|
│ ├─────────────────────┤
|
|
└──────1:N──────────▶│ id (PK) │
|
|
│ user_id (FK) │
|
|
│ token_hash │
|
|
│ expires_at │
|
|
│ used_at │
|
|
│ created_at │
|
|
└─────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## ENUMs
|
|
|
|
```sql
|
|
-- Roles de usuario
|
|
CREATE TYPE user_role_enum AS ENUM (
|
|
'investor', -- Usuario inversionista básico
|
|
'trader', -- Trader activo con más permisos
|
|
'student', -- Solo acceso a educación
|
|
'admin', -- Administrador
|
|
'superadmin' -- Super administrador
|
|
);
|
|
|
|
-- Estados de usuario
|
|
CREATE TYPE user_status_enum AS ENUM (
|
|
'pending_verification', -- Email no verificado
|
|
'active', -- Cuenta activa
|
|
'suspended', -- Suspendida temporalmente
|
|
'banned', -- Baneada permanentemente
|
|
'deleted' -- Eliminada (soft delete)
|
|
);
|
|
|
|
-- Proveedores OAuth
|
|
CREATE TYPE auth_provider_enum AS ENUM (
|
|
'google',
|
|
'facebook',
|
|
'twitter',
|
|
'apple',
|
|
'github'
|
|
);
|
|
|
|
-- Canales de verificación telefónica
|
|
CREATE TYPE phone_channel_enum AS ENUM (
|
|
'sms',
|
|
'whatsapp'
|
|
);
|
|
|
|
-- Eventos de autenticación (audit)
|
|
CREATE TYPE auth_event_enum AS ENUM (
|
|
'login_success',
|
|
'login_failed',
|
|
'logout',
|
|
'register',
|
|
'email_verified',
|
|
'phone_verified',
|
|
'password_changed',
|
|
'password_reset_requested',
|
|
'password_reset_completed',
|
|
'2fa_enabled',
|
|
'2fa_disabled',
|
|
'2fa_verified',
|
|
'backup_code_used',
|
|
'oauth_linked',
|
|
'oauth_unlinked',
|
|
'session_revoked',
|
|
'account_suspended',
|
|
'account_reactivated'
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Tablas
|
|
|
|
### users
|
|
|
|
```sql
|
|
CREATE TABLE users (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
|
|
-- Identificadores
|
|
email VARCHAR(255) UNIQUE,
|
|
phone VARCHAR(20) UNIQUE,
|
|
|
|
-- Credenciales
|
|
password_hash VARCHAR(255),
|
|
|
|
-- Datos personales
|
|
first_name VARCHAR(100) NOT NULL,
|
|
last_name VARCHAR(100) NOT NULL,
|
|
|
|
-- Permisos
|
|
role user_role_enum NOT NULL DEFAULT 'investor',
|
|
status user_status_enum NOT NULL DEFAULT 'pending_verification',
|
|
|
|
-- Verificación
|
|
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
|
phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
|
email_verified_at TIMESTAMPTZ,
|
|
phone_verified_at TIMESTAMPTZ,
|
|
|
|
-- 2FA
|
|
two_factor_enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
|
two_factor_secret VARCHAR(255), -- Encriptado
|
|
backup_codes JSONB DEFAULT '[]', -- Array de hashes
|
|
|
|
-- Metadata
|
|
last_login_at TIMESTAMPTZ,
|
|
login_count INTEGER DEFAULT 0,
|
|
failed_login_attempts INTEGER DEFAULT 0,
|
|
locked_until TIMESTAMPTZ,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
deleted_at TIMESTAMPTZ,
|
|
|
|
-- Constraints
|
|
CONSTRAINT email_or_phone_required CHECK (
|
|
email IS NOT NULL OR phone IS NOT NULL
|
|
),
|
|
CONSTRAINT password_required_for_email CHECK (
|
|
email IS NULL OR password_hash IS NOT NULL OR
|
|
EXISTS (SELECT 1 FROM oauth_accounts WHERE user_id = id)
|
|
)
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_users_email ON users(email) WHERE deleted_at IS NULL;
|
|
CREATE INDEX idx_users_phone ON users(phone) WHERE deleted_at IS NULL;
|
|
CREATE INDEX idx_users_status ON users(status) WHERE deleted_at IS NULL;
|
|
CREATE INDEX idx_users_role ON users(role) WHERE deleted_at IS NULL;
|
|
CREATE INDEX idx_users_created_at ON users(created_at);
|
|
|
|
-- Trigger para updated_at
|
|
CREATE TRIGGER update_users_updated_at
|
|
BEFORE UPDATE ON users
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
```
|
|
|
|
### user_profiles
|
|
|
|
```sql
|
|
CREATE TABLE user_profiles (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE,
|
|
|
|
-- Display
|
|
display_name VARCHAR(50),
|
|
bio TEXT,
|
|
avatar_url VARCHAR(500),
|
|
|
|
-- Preferencias regionales
|
|
preferred_language VARCHAR(5) DEFAULT 'es',
|
|
timezone VARCHAR(50) DEFAULT 'America/Mexico_City',
|
|
date_format VARCHAR(20) DEFAULT 'DD/MM/YYYY',
|
|
currency VARCHAR(3) DEFAULT 'MXN',
|
|
|
|
-- Preferencias de notificación
|
|
notification_prefs JSONB DEFAULT '{
|
|
"email": {
|
|
"marketing": true,
|
|
"security": true,
|
|
"trading": true,
|
|
"education": true
|
|
},
|
|
"push": {
|
|
"trading_signals": true,
|
|
"price_alerts": true,
|
|
"course_updates": true
|
|
},
|
|
"sms": {
|
|
"security": true,
|
|
"trading": false
|
|
}
|
|
}',
|
|
|
|
-- Trading preferences
|
|
trading_prefs JSONB DEFAULT '{
|
|
"default_timeframe": "1h",
|
|
"chart_theme": "dark",
|
|
"confirmations": true
|
|
}',
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_user_profiles_user_id ON user_profiles(user_id);
|
|
```
|
|
|
|
### oauth_accounts
|
|
|
|
```sql
|
|
CREATE TABLE oauth_accounts (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
|
|
-- Proveedor
|
|
provider auth_provider_enum NOT NULL,
|
|
provider_user_id VARCHAR(255) NOT NULL,
|
|
|
|
-- Datos del proveedor
|
|
email VARCHAR(255),
|
|
|
|
-- Tokens (encriptados)
|
|
access_token TEXT,
|
|
refresh_token TEXT,
|
|
token_expires_at TIMESTAMPTZ,
|
|
|
|
-- Perfil raw del proveedor
|
|
raw_profile JSONB,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
-- Constraints
|
|
UNIQUE(provider, provider_user_id),
|
|
UNIQUE(user_id, provider)
|
|
);
|
|
|
|
CREATE INDEX idx_oauth_accounts_user_id ON oauth_accounts(user_id);
|
|
CREATE INDEX idx_oauth_accounts_provider ON oauth_accounts(provider);
|
|
CREATE INDEX idx_oauth_accounts_email ON oauth_accounts(email);
|
|
```
|
|
|
|
### sessions
|
|
|
|
```sql
|
|
CREATE TABLE sessions (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
|
|
-- Token
|
|
refresh_token_hash VARCHAR(255) NOT NULL,
|
|
|
|
-- Device info
|
|
device_info JSONB NOT NULL DEFAULT '{}',
|
|
-- {
|
|
-- "type": "desktop",
|
|
-- "browser": "Chrome",
|
|
-- "browserVersion": "120.0.0",
|
|
-- "os": "Windows",
|
|
-- "osVersion": "11"
|
|
-- }
|
|
|
|
-- Ubicación
|
|
ip_address INET,
|
|
location JSONB,
|
|
-- {
|
|
-- "country": "México",
|
|
-- "countryCode": "MX",
|
|
-- "region": "CDMX",
|
|
-- "city": "Ciudad de México",
|
|
-- "timezone": "America/Mexico_City"
|
|
-- }
|
|
|
|
user_agent TEXT,
|
|
|
|
-- Estado
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
last_activity TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
|
|
-- Revocación
|
|
revoked_at TIMESTAMPTZ,
|
|
revoked_reason VARCHAR(100)
|
|
);
|
|
|
|
CREATE INDEX idx_sessions_user_id ON sessions(user_id);
|
|
CREATE INDEX idx_sessions_token_hash ON sessions(refresh_token_hash);
|
|
CREATE INDEX idx_sessions_active ON sessions(is_active) WHERE is_active = TRUE;
|
|
CREATE INDEX idx_sessions_expires ON sessions(expires_at) WHERE is_active = TRUE;
|
|
```
|
|
|
|
### email_verifications
|
|
|
|
```sql
|
|
CREATE TABLE email_verifications (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
|
|
-- Token
|
|
token_hash VARCHAR(255) NOT NULL,
|
|
|
|
-- Estado
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
verified_at TIMESTAMPTZ,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_email_verifications_user_id ON email_verifications(user_id);
|
|
CREATE INDEX idx_email_verifications_token ON email_verifications(token_hash);
|
|
```
|
|
|
|
### phone_verifications
|
|
|
|
```sql
|
|
CREATE TABLE phone_verifications (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
|
|
-- Teléfono (puede ser nuevo, no vinculado aún)
|
|
phone VARCHAR(20) NOT NULL,
|
|
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
|
|
|
-- OTP
|
|
code_hash VARCHAR(255) NOT NULL,
|
|
channel phone_channel_enum NOT NULL,
|
|
|
|
-- Intentos
|
|
attempts INTEGER NOT NULL DEFAULT 0,
|
|
|
|
-- Estado
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
verified_at TIMESTAMPTZ,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_phone_verifications_phone ON phone_verifications(phone);
|
|
CREATE INDEX idx_phone_verifications_user_id ON phone_verifications(user_id);
|
|
```
|
|
|
|
### password_reset_tokens
|
|
|
|
```sql
|
|
CREATE TABLE password_reset_tokens (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
|
|
-- Token
|
|
token_hash VARCHAR(255) NOT NULL,
|
|
|
|
-- Estado
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
used_at TIMESTAMPTZ,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_password_reset_tokens_user_id ON password_reset_tokens(user_id);
|
|
CREATE INDEX idx_password_reset_tokens_token ON password_reset_tokens(token_hash);
|
|
```
|
|
|
|
### auth_logs (Audit)
|
|
|
|
```sql
|
|
CREATE TABLE auth_logs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
|
|
-- Evento
|
|
event auth_event_enum NOT NULL,
|
|
|
|
-- Contexto
|
|
ip_address INET,
|
|
user_agent TEXT,
|
|
device_info JSONB,
|
|
location JSONB,
|
|
|
|
-- Detalles adicionales
|
|
metadata JSONB DEFAULT '{}',
|
|
-- Ejemplo: { "provider": "google", "reason": "invalid_password" }
|
|
|
|
-- Resultado
|
|
success BOOLEAN NOT NULL,
|
|
error_message TEXT,
|
|
|
|
-- Timestamp
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_auth_logs_user_id ON auth_logs(user_id);
|
|
CREATE INDEX idx_auth_logs_event ON auth_logs(event);
|
|
CREATE INDEX idx_auth_logs_created_at ON auth_logs(created_at);
|
|
CREATE INDEX idx_auth_logs_ip ON auth_logs(ip_address);
|
|
|
|
-- Particionar por fecha para mejor performance
|
|
-- (considerar para producción con alto volumen)
|
|
```
|
|
|
|
---
|
|
|
|
## Funciones
|
|
|
|
### update_updated_at_column
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
### log_auth_event
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION log_auth_event(
|
|
p_user_id UUID,
|
|
p_event auth_event_enum,
|
|
p_ip_address INET,
|
|
p_user_agent TEXT,
|
|
p_device_info JSONB,
|
|
p_location JSONB,
|
|
p_success BOOLEAN,
|
|
p_metadata JSONB DEFAULT '{}'
|
|
)
|
|
RETURNS UUID AS $$
|
|
DECLARE
|
|
v_log_id UUID;
|
|
BEGIN
|
|
INSERT INTO auth_logs (
|
|
user_id, event, ip_address, user_agent,
|
|
device_info, location, success, metadata
|
|
) VALUES (
|
|
p_user_id, p_event, p_ip_address, p_user_agent,
|
|
p_device_info, p_location, p_success, p_metadata
|
|
) RETURNING id INTO v_log_id;
|
|
|
|
RETURN v_log_id;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
### cleanup_expired_sessions
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION cleanup_expired_sessions()
|
|
RETURNS INTEGER AS $$
|
|
DECLARE
|
|
v_count INTEGER;
|
|
BEGIN
|
|
UPDATE sessions
|
|
SET is_active = FALSE,
|
|
revoked_at = NOW(),
|
|
revoked_reason = 'expired'
|
|
WHERE is_active = TRUE
|
|
AND expires_at < NOW();
|
|
|
|
GET DIAGNOSTICS v_count = ROW_COUNT;
|
|
RETURN v_count;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
---
|
|
|
|
## Triggers
|
|
|
|
### Crear perfil al crear usuario
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION create_user_profile()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
INSERT INTO user_profiles (user_id)
|
|
VALUES (NEW.id);
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trigger_create_user_profile
|
|
AFTER INSERT ON users
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION create_user_profile();
|
|
```
|
|
|
|
### Log de eventos de autenticación
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION log_user_status_change()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF OLD.status IS DISTINCT FROM NEW.status THEN
|
|
IF NEW.status = 'suspended' THEN
|
|
PERFORM log_auth_event(
|
|
NEW.id, 'account_suspended', NULL, NULL, NULL, NULL, TRUE
|
|
);
|
|
ELSIF NEW.status = 'active' AND OLD.status = 'suspended' THEN
|
|
PERFORM log_auth_event(
|
|
NEW.id, 'account_reactivated', NULL, NULL, NULL, NULL, TRUE
|
|
);
|
|
END IF;
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trigger_log_status_change
|
|
AFTER UPDATE ON users
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION log_user_status_change();
|
|
```
|
|
|
|
---
|
|
|
|
## Migraciones
|
|
|
|
### Archivo: `001_create_auth_schema.sql`
|
|
|
|
```sql
|
|
-- Extensiones requeridas
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
|
|
-- ENUMs
|
|
-- (copiar definiciones de ENUMs)
|
|
|
|
-- Tablas
|
|
-- (copiar definiciones de tablas en orden)
|
|
|
|
-- Funciones
|
|
-- (copiar funciones)
|
|
|
|
-- Triggers
|
|
-- (copiar triggers)
|
|
|
|
-- Datos iniciales
|
|
INSERT INTO users (
|
|
id,
|
|
email,
|
|
password_hash,
|
|
first_name,
|
|
last_name,
|
|
role,
|
|
status,
|
|
email_verified
|
|
) VALUES (
|
|
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
|
'admin@trading.com',
|
|
crypt('AdminPassword123!', gen_salt('bf', 12)),
|
|
'Admin',
|
|
'Trading Platform',
|
|
'superadmin',
|
|
'active',
|
|
TRUE
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## Consideraciones de Performance
|
|
|
|
### Índices Parciales
|
|
|
|
```sql
|
|
-- Solo usuarios activos
|
|
CREATE INDEX idx_users_active_email
|
|
ON users(email)
|
|
WHERE status = 'active' AND deleted_at IS NULL;
|
|
|
|
-- Solo sesiones activas no expiradas
|
|
CREATE INDEX idx_sessions_valid
|
|
ON sessions(user_id, expires_at)
|
|
WHERE is_active = TRUE;
|
|
```
|
|
|
|
### Particionamiento (Producción)
|
|
|
|
```sql
|
|
-- Particionar auth_logs por mes
|
|
CREATE TABLE auth_logs (
|
|
-- ... columnas ...
|
|
) PARTITION BY RANGE (created_at);
|
|
|
|
CREATE TABLE auth_logs_2025_01 PARTITION OF auth_logs
|
|
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
|
|
-- ... más particiones ...
|
|
```
|
|
|
|
---
|
|
|
|
## Referencias
|
|
|
|
- [PostgreSQL UUID](https://www.postgresql.org/docs/current/uuid-ossp.html)
|
|
- [PostgreSQL JSONB](https://www.postgresql.org/docs/current/datatype-json.html)
|
|
- [PostgreSQL Partitioning](https://www.postgresql.org/docs/current/ddl-partitioning.html)
|