## 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>
129 lines
4.7 KiB
PL/PgSQL
129 lines
4.7 KiB
PL/PgSQL
-- ============================================================================
|
|
-- SCHEMA: users
|
|
-- TABLE: profiles
|
|
-- DESCRIPTION: Informacion extendida del perfil de usuario
|
|
-- VERSION: 1.0.0
|
|
-- CREATED: 2026-01-16
|
|
-- SPRINT: Sprint 1 - DDL Implementation Roadmap Q1-2026
|
|
-- ============================================================================
|
|
|
|
-- Tabla de Perfiles extendidos
|
|
CREATE TABLE IF NOT EXISTS users.profiles (
|
|
-- Identificadores
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL UNIQUE REFERENCES users.users(id) ON DELETE CASCADE,
|
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
|
|
|
-- Informacion personal
|
|
bio TEXT,
|
|
date_of_birth DATE,
|
|
gender VARCHAR(20),
|
|
nationality VARCHAR(100),
|
|
country_of_residence VARCHAR(100),
|
|
city VARCHAR(100),
|
|
address TEXT,
|
|
postal_code VARCHAR(20),
|
|
|
|
-- Informacion profesional
|
|
occupation VARCHAR(100),
|
|
company_name VARCHAR(200),
|
|
annual_income_range VARCHAR(50), -- '0-25k', '25k-50k', '50k-100k', '100k-250k', '250k+'
|
|
source_of_funds VARCHAR(100), -- 'salary', 'business', 'investments', 'inheritance', 'other'
|
|
|
|
-- Documentacion
|
|
id_document_type VARCHAR(50), -- 'passport', 'national_id', 'drivers_license'
|
|
id_document_number VARCHAR(100),
|
|
id_document_expiry DATE,
|
|
id_document_country VARCHAR(100),
|
|
|
|
-- Redes sociales
|
|
social_links JSONB DEFAULT '{}'::JSONB, -- { "twitter": "@user", "linkedin": "..." }
|
|
|
|
-- Preferencias de comunicacion
|
|
preferred_contact_method VARCHAR(20) DEFAULT 'email', -- 'email', 'phone', 'sms'
|
|
timezone VARCHAR(50) DEFAULT 'America/New_York',
|
|
locale VARCHAR(10) DEFAULT 'es-MX',
|
|
|
|
-- Completitud del perfil
|
|
completion_percentage INTEGER NOT NULL DEFAULT 0 CHECK (completion_percentage BETWEEN 0 AND 100),
|
|
last_profile_update TIMESTAMPTZ,
|
|
|
|
-- Metadata
|
|
metadata JSONB DEFAULT '{}'::JSONB,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
COMMENT ON TABLE users.profiles IS
|
|
'Informacion extendida del perfil de usuario, separada de credenciales';
|
|
|
|
COMMENT ON COLUMN users.profiles.completion_percentage IS
|
|
'Porcentaje de completitud del perfil (0-100)';
|
|
|
|
COMMENT ON COLUMN users.profiles.annual_income_range IS
|
|
'Rango de ingresos anuales para compliance KYC';
|
|
|
|
-- Indices
|
|
CREATE INDEX IF NOT EXISTS idx_profiles_user_id
|
|
ON users.profiles(user_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_profiles_tenant_id
|
|
ON users.profiles(tenant_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_profiles_completion
|
|
ON users.profiles(completion_percentage);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_profiles_country
|
|
ON users.profiles(country_of_residence);
|
|
|
|
-- Trigger para updated_at
|
|
DROP TRIGGER IF EXISTS profile_updated_at ON users.profiles;
|
|
CREATE TRIGGER profile_updated_at
|
|
BEFORE UPDATE ON users.profiles
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION users.update_user_timestamp();
|
|
|
|
-- Trigger para calcular completion_percentage
|
|
CREATE OR REPLACE FUNCTION users.calculate_profile_completion()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
v_total_fields INTEGER := 10;
|
|
v_filled_fields INTEGER := 0;
|
|
BEGIN
|
|
IF NEW.bio IS NOT NULL THEN v_filled_fields := v_filled_fields + 1; END IF;
|
|
IF NEW.date_of_birth IS NOT NULL THEN v_filled_fields := v_filled_fields + 1; END IF;
|
|
IF NEW.nationality IS NOT NULL THEN v_filled_fields := v_filled_fields + 1; END IF;
|
|
IF NEW.country_of_residence IS NOT NULL THEN v_filled_fields := v_filled_fields + 1; END IF;
|
|
IF NEW.city IS NOT NULL THEN v_filled_fields := v_filled_fields + 1; END IF;
|
|
IF NEW.occupation IS NOT NULL THEN v_filled_fields := v_filled_fields + 1; END IF;
|
|
IF NEW.annual_income_range IS NOT NULL THEN v_filled_fields := v_filled_fields + 1; END IF;
|
|
IF NEW.source_of_funds IS NOT NULL THEN v_filled_fields := v_filled_fields + 1; END IF;
|
|
IF NEW.id_document_type IS NOT NULL THEN v_filled_fields := v_filled_fields + 1; END IF;
|
|
IF NEW.timezone IS NOT NULL THEN v_filled_fields := v_filled_fields + 1; END IF;
|
|
|
|
NEW.completion_percentage := (v_filled_fields * 100) / v_total_fields;
|
|
NEW.last_profile_update := NOW();
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS profile_completion_calc ON users.profiles;
|
|
CREATE TRIGGER profile_completion_calc
|
|
BEFORE INSERT OR UPDATE ON users.profiles
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION users.calculate_profile_completion();
|
|
|
|
-- RLS Policy para multi-tenancy
|
|
ALTER TABLE users.profiles ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY profiles_tenant_isolation ON users.profiles
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Grants
|
|
GRANT SELECT, INSERT, UPDATE ON users.profiles TO trading_app;
|
|
GRANT SELECT ON users.profiles TO trading_readonly;
|