trading-platform-database-v2/ddl/schemas/users/tables/004_kyc_verifications.sql
rckrdmrd b86dfa2e06 [DDL] feat: Sprint 1 - Add 12 tables for users, admin, notifications, market_data
## New Tables Created (Sprint 1 - DDL Roadmap Q1-2026)

### users schema (4 tables):
- profiles: Extended user profile information
- user_settings: User preferences and configurations
- kyc_verifications: KYC/AML verification records
- risk_profiles: Trading risk assessment profiles

### admin schema (3 tables):
- admin_roles: Platform administrative roles
- platform_analytics: Aggregated platform metrics
- api_keys: Programmatic API access keys

### notifications schema (1 table):
- notifications: Multi-channel notification system

### market_data schema (4 tables):
- tickers: Financial instruments catalog
- ohlcv_5m: 5-minute OHLCV price data
- technical_indicators: Pre-calculated TA indicators
- ohlcv_5m_staging: Staging table for data ingestion

## Features:
- Multi-tenancy with RLS policies
- Comprehensive indexes for query optimization
- Triggers for computed fields and timestamps
- Helper functions for common operations
- Views for dashboard and reporting
- Full GRANTS configuration

Roadmap: orchestration/planes/ROADMAP-IMPLEMENTACION-DDL-2026-Q1.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 19:41:53 -06:00

231 lines
8.1 KiB
PL/PgSQL

-- ============================================================================
-- SCHEMA: users
-- TABLE: kyc_verifications
-- DESCRIPTION: Verificacion de identidad KYC (Know Your Customer)
-- VERSION: 1.0.0
-- CREATED: 2026-01-16
-- SPRINT: Sprint 1 - DDL Implementation Roadmap Q1-2026
-- ============================================================================
-- Enum para estado de verificacion KYC
DO $$ BEGIN
CREATE TYPE users.kyc_status AS ENUM (
'not_started', -- No ha iniciado proceso
'pending', -- Documentos enviados, pendiente revision
'under_review', -- En proceso de revision manual
'approved', -- Aprobado completamente
'rejected', -- Rechazado (puede reintentar)
'expired', -- Verificacion expirada, requiere re-verificacion
'suspended' -- Suspendido por actividad sospechosa
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Enum para nivel de verificacion
DO $$ BEGIN
CREATE TYPE users.kyc_level AS ENUM (
'none', -- Sin verificacion
'basic', -- Email + telefono verificado
'standard', -- Documento ID verificado
'enhanced', -- ID + prueba de direccion
'full' -- Verificacion completa con video
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Tabla de Verificaciones KYC
CREATE TABLE IF NOT EXISTS users.kyc_verifications (
-- Identificadores
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE,
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
-- Estado actual
status users.kyc_status NOT NULL DEFAULT 'not_started',
level users.kyc_level NOT NULL DEFAULT 'none',
-- Verificacion de Email
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
email_verified_at TIMESTAMPTZ,
email_verification_method VARCHAR(50), -- 'code', 'link'
-- Verificacion de Telefono
phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
phone_verified_at TIMESTAMPTZ,
phone_verification_method VARCHAR(50), -- 'sms', 'call'
-- Verificacion de Documento ID
id_document_verified BOOLEAN NOT NULL DEFAULT FALSE,
id_document_verified_at TIMESTAMPTZ,
id_document_type VARCHAR(50), -- 'passport', 'national_id', 'drivers_license'
id_document_number_hash VARCHAR(255), -- Hash del numero para verificacion
id_document_country VARCHAR(100),
id_document_expiry DATE,
id_document_front_url TEXT, -- URL segura al documento (encriptado)
id_document_back_url TEXT,
id_selfie_url TEXT, -- Selfie con documento
-- Verificacion de Direccion
address_verified BOOLEAN NOT NULL DEFAULT FALSE,
address_verified_at TIMESTAMPTZ,
address_document_type VARCHAR(50), -- 'utility_bill', 'bank_statement', 'tax_document'
address_document_url TEXT,
address_document_date DATE, -- Fecha del documento (max 3 meses)
-- Verificacion de Video (enhanced)
video_verified BOOLEAN NOT NULL DEFAULT FALSE,
video_verified_at TIMESTAMPTZ,
video_url TEXT,
video_liveness_score DECIMAL(5, 4), -- 0.0000 - 1.0000
-- Proveedor de verificacion externo
external_provider VARCHAR(50), -- 'sumsub', 'onfido', 'jumio'
external_verification_id VARCHAR(255),
external_status VARCHAR(50),
external_risk_score DECIMAL(5, 4),
-- Informacion de revision
reviewer_id UUID,
reviewed_at TIMESTAMPTZ,
review_notes TEXT,
rejection_reason TEXT,
rejection_codes JSONB, -- Array de codigos de rechazo
-- AML/PEP checks
aml_checked BOOLEAN NOT NULL DEFAULT FALSE,
aml_checked_at TIMESTAMPTZ,
aml_result VARCHAR(50), -- 'clear', 'match', 'potential_match'
aml_details JSONB,
pep_checked BOOLEAN NOT NULL DEFAULT FALSE,
pep_checked_at TIMESTAMPTZ,
pep_result VARCHAR(50),
pep_details JSONB,
sanctions_checked BOOLEAN NOT NULL DEFAULT FALSE,
sanctions_checked_at TIMESTAMPTZ,
sanctions_result VARCHAR(50),
sanctions_details JSONB,
-- Expiracion
expires_at TIMESTAMPTZ,
reminder_sent_at TIMESTAMPTZ,
-- Intentos
verification_attempts INTEGER NOT NULL DEFAULT 0,
max_attempts INTEGER NOT NULL DEFAULT 3,
locked_until TIMESTAMPTZ,
-- Metadata
ip_address INET,
user_agent TEXT,
device_fingerprint VARCHAR(255),
metadata JSONB DEFAULT '{}'::JSONB,
-- Timestamps
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Constraints
CONSTRAINT kyc_unique_user UNIQUE (user_id)
);
COMMENT ON TABLE users.kyc_verifications IS
'Registro de verificacion KYC de usuarios para compliance regulatorio';
COMMENT ON COLUMN users.kyc_verifications.level IS
'Nivel de verificacion alcanzado: none, basic, standard, enhanced, full';
COMMENT ON COLUMN users.kyc_verifications.external_risk_score IS
'Score de riesgo del proveedor externo (0-1, menor es mejor)';
-- Indices
CREATE INDEX IF NOT EXISTS idx_kyc_user_id
ON users.kyc_verifications(user_id);
CREATE INDEX IF NOT EXISTS idx_kyc_tenant_id
ON users.kyc_verifications(tenant_id);
CREATE INDEX IF NOT EXISTS idx_kyc_status
ON users.kyc_verifications(status);
CREATE INDEX IF NOT EXISTS idx_kyc_level
ON users.kyc_verifications(level);
CREATE INDEX IF NOT EXISTS idx_kyc_expires_at
ON users.kyc_verifications(expires_at)
WHERE expires_at IS NOT NULL AND status = 'approved';
CREATE INDEX IF NOT EXISTS idx_kyc_pending_review
ON users.kyc_verifications(created_at)
WHERE status IN ('pending', 'under_review');
CREATE INDEX IF NOT EXISTS idx_kyc_aml_matches
ON users.kyc_verifications(tenant_id, aml_result)
WHERE aml_result IN ('match', 'potential_match');
-- Trigger para updated_at
DROP TRIGGER IF EXISTS kyc_updated_at ON users.kyc_verifications;
CREATE TRIGGER kyc_updated_at
BEFORE UPDATE ON users.kyc_verifications
FOR EACH ROW
EXECUTE FUNCTION users.update_user_timestamp();
-- Funcion para actualizar nivel de KYC automaticamente
CREATE OR REPLACE FUNCTION users.update_kyc_level()
RETURNS TRIGGER AS $$
BEGIN
-- Calcular nivel basado en verificaciones completadas
IF NEW.video_verified AND NEW.address_verified AND NEW.id_document_verified AND NEW.phone_verified AND NEW.email_verified THEN
NEW.level := 'full';
ELSIF NEW.address_verified AND NEW.id_document_verified AND NEW.phone_verified AND NEW.email_verified THEN
NEW.level := 'enhanced';
ELSIF NEW.id_document_verified AND (NEW.phone_verified OR NEW.email_verified) THEN
NEW.level := 'standard';
ELSIF NEW.email_verified AND NEW.phone_verified THEN
NEW.level := 'basic';
ELSE
NEW.level := 'none';
END IF;
-- Actualizar status si todas las verificaciones del nivel estan completas
IF NEW.level = 'full' AND OLD.status IN ('pending', 'under_review') THEN
NEW.status := 'approved';
NEW.completed_at := NOW();
-- Set expiration (typically 1-2 years)
NEW.expires_at := NOW() + INTERVAL '2 years';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS kyc_level_update ON users.kyc_verifications;
CREATE TRIGGER kyc_level_update
BEFORE UPDATE ON users.kyc_verifications
FOR EACH ROW
EXECUTE FUNCTION users.update_kyc_level();
-- RLS Policy para multi-tenancy
ALTER TABLE users.kyc_verifications ENABLE ROW LEVEL SECURITY;
CREATE POLICY kyc_tenant_isolation ON users.kyc_verifications
FOR ALL
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
-- Policy especial para revisores (pueden ver todos los pendientes)
CREATE POLICY kyc_reviewer_access ON users.kyc_verifications
FOR SELECT
USING (
current_setting('app.is_kyc_reviewer', true)::boolean = true
AND status IN ('pending', 'under_review')
);
-- Grants
GRANT SELECT, INSERT, UPDATE ON users.kyc_verifications TO trading_app;
GRANT SELECT ON users.kyc_verifications TO trading_readonly;