trading-platform-database/schemas/05_financial_schema.sql

501 lines
16 KiB
SQL

-- ============================================================================
-- OrbiQuant IA - Esquema FINANCIAL
-- ============================================================================
-- Archivo: 05_financial_schema.sql
-- Descripción: Pagos, suscripciones, wallets y transacciones
-- Fecha: 2025-12-05
-- ============================================================================
SET search_path TO financial;
-- ============================================================================
-- TIPOS ENUMERADOS
-- ============================================================================
CREATE TYPE subscription_status_enum AS ENUM (
'trialing', -- Período de prueba
'active', -- Activa y pagando
'past_due', -- Pago vencido
'cancelled', -- Cancelada
'unpaid', -- Sin pago
'paused' -- Pausada
);
CREATE TYPE payment_status_enum AS ENUM (
'pending',
'processing',
'succeeded',
'failed',
'refunded',
'cancelled'
);
CREATE TYPE payment_method_enum AS ENUM (
'card',
'bank_transfer',
'paypal',
'crypto',
'wallet_balance'
);
-- ============================================================================
-- TABLA: subscription_plans
-- Descripción: Planes de suscripción disponibles
-- ============================================================================
CREATE TABLE IF NOT EXISTS subscription_plans (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
-- Identificación
name VARCHAR(50) NOT NULL,
slug VARCHAR(50) NOT NULL UNIQUE,
description TEXT,
-- Pricing
price_monthly DECIMAL(10,2) NOT NULL,
price_yearly DECIMAL(10,2),
currency CHAR(3) DEFAULT 'USD',
-- Stripe
stripe_price_id_monthly VARCHAR(100),
stripe_price_id_yearly VARCHAR(100),
stripe_product_id VARCHAR(100),
-- Features (JSON array)
features JSONB DEFAULT '[]', -- [{name, description, included: boolean}]
-- Límites
max_watchlists INT,
max_alerts INT,
ml_predictions_access BOOLEAN DEFAULT FALSE,
signals_access BOOLEAN DEFAULT FALSE,
backtesting_access BOOLEAN DEFAULT FALSE,
api_access BOOLEAN DEFAULT FALSE,
priority_support BOOLEAN DEFAULT FALSE,
-- Educación
courses_access VARCHAR(20) DEFAULT 'none', -- 'none', 'free_only', 'basic', 'all'
-- Orden de display
sort_order INT DEFAULT 0,
is_featured BOOLEAN DEFAULT FALSE,
-- Estado
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_plans_slug ON subscription_plans(slug);
-- ============================================================================
-- TABLA: stripe_customers
-- Descripción: Clientes de Stripe vinculados a usuarios
-- ============================================================================
CREATE TABLE IF NOT EXISTS stripe_customers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL UNIQUE REFERENCES auth.users(id) ON DELETE CASCADE,
stripe_customer_id VARCHAR(100) NOT NULL UNIQUE,
email VARCHAR(255),
-- Método de pago por defecto
default_payment_method_id VARCHAR(100),
-- Metadata
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_stripe_customers_user ON stripe_customers(user_id);
CREATE INDEX idx_stripe_customers_stripe ON stripe_customers(stripe_customer_id);
-- ============================================================================
-- TABLA: subscriptions
-- Descripción: Suscripciones activas de usuarios
-- ============================================================================
CREATE TABLE IF NOT EXISTS subscriptions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE RESTRICT,
plan_id UUID NOT NULL REFERENCES subscription_plans(id),
-- Stripe
stripe_subscription_id VARCHAR(100) UNIQUE,
stripe_customer_id VARCHAR(100) REFERENCES stripe_customers(stripe_customer_id),
-- Estado
status subscription_status_enum DEFAULT 'active',
-- Período de facturación
billing_cycle VARCHAR(20) DEFAULT 'monthly', -- 'monthly', 'yearly'
current_period_start TIMESTAMPTZ,
current_period_end TIMESTAMPTZ,
-- Trial
trial_start TIMESTAMPTZ,
trial_end TIMESTAMPTZ,
-- Cancelación
cancel_at_period_end BOOLEAN DEFAULT FALSE,
cancelled_at TIMESTAMPTZ,
cancellation_reason TEXT,
-- Precio actual (puede diferir del plan por descuentos)
current_price DECIMAL(10,2),
currency CHAR(3) DEFAULT 'USD',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_subscriptions_user ON subscriptions(user_id);
CREATE INDEX idx_subscriptions_plan ON subscriptions(plan_id);
CREATE INDEX idx_subscriptions_status ON subscriptions(status);
CREATE INDEX idx_subscriptions_stripe ON subscriptions(stripe_subscription_id);
-- ============================================================================
-- TABLA: wallets
-- Descripción: Wallets internos de usuarios
-- ============================================================================
CREATE TABLE IF NOT EXISTS wallets (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE RESTRICT,
-- Balance
currency CHAR(3) NOT NULL DEFAULT 'USD',
balance DECIMAL(15,2) NOT NULL DEFAULT 0,
available_balance DECIMAL(15,2) NOT NULL DEFAULT 0,
pending_balance DECIMAL(15,2) NOT NULL DEFAULT 0,
-- Estado
is_active BOOLEAN DEFAULT TRUE,
-- Límites
daily_withdrawal_limit DECIMAL(15,2) DEFAULT 10000,
monthly_withdrawal_limit DECIMAL(15,2) DEFAULT 50000,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, currency)
);
CREATE INDEX idx_wallets_user ON wallets(user_id);
-- ============================================================================
-- TABLA: wallet_transactions
-- Descripción: Transacciones en wallets
-- ============================================================================
CREATE TABLE IF NOT EXISTS wallet_transactions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
wallet_id UUID NOT NULL REFERENCES wallets(id) ON DELETE RESTRICT,
user_id UUID NOT NULL REFERENCES auth.users(id),
-- Tipo y monto
transaction_type public.transaction_type_enum NOT NULL,
amount DECIMAL(15,2) NOT NULL,
currency CHAR(3) DEFAULT 'USD',
-- Balance
balance_before DECIMAL(15,2) NOT NULL,
balance_after DECIMAL(15,2) NOT NULL,
-- Referencia
reference_type VARCHAR(50), -- 'payment', 'subscription', 'investment_account', 'refund'
reference_id UUID,
external_reference VARCHAR(100), -- ID externo (Stripe, etc.)
-- Descripción
description TEXT,
-- Estado
status payment_status_enum DEFAULT 'succeeded',
-- Metadata
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_wallet_tx_wallet ON wallet_transactions(wallet_id);
CREATE INDEX idx_wallet_tx_user ON wallet_transactions(user_id);
CREATE INDEX idx_wallet_tx_type ON wallet_transactions(transaction_type);
CREATE INDEX idx_wallet_tx_created ON wallet_transactions(created_at DESC);
-- ============================================================================
-- TABLA: payments
-- Descripción: Pagos procesados
-- ============================================================================
CREATE TABLE IF NOT EXISTS payments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES auth.users(id),
-- Tipo
payment_type VARCHAR(50) NOT NULL, -- 'subscription', 'course', 'deposit', 'one_time'
-- Monto
amount DECIMAL(10,2) NOT NULL,
currency CHAR(3) DEFAULT 'USD',
fee DECIMAL(10,2) DEFAULT 0,
net_amount DECIMAL(10,2),
-- Método
payment_method payment_method_enum,
payment_method_details JSONB, -- Últimos 4 dígitos, tipo de tarjeta, etc.
-- Stripe
stripe_payment_intent_id VARCHAR(100),
stripe_charge_id VARCHAR(100),
stripe_invoice_id VARCHAR(100),
-- Estado
status payment_status_enum DEFAULT 'pending',
failure_reason TEXT,
-- Referencia
reference_type VARCHAR(50), -- 'subscription', 'course', 'wallet_deposit'
reference_id UUID,
-- Descripción
description TEXT,
-- Metadata
metadata JSONB,
ip_address INET,
-- Facturación
invoice_url TEXT,
receipt_url TEXT,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_payments_user ON payments(user_id);
CREATE INDEX idx_payments_status ON payments(status);
CREATE INDEX idx_payments_type ON payments(payment_type);
CREATE INDEX idx_payments_stripe ON payments(stripe_payment_intent_id);
CREATE INDEX idx_payments_created ON payments(created_at DESC);
-- ============================================================================
-- TABLA: refunds
-- Descripción: Reembolsos procesados
-- ============================================================================
CREATE TABLE IF NOT EXISTS refunds (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
payment_id UUID NOT NULL REFERENCES payments(id),
user_id UUID NOT NULL REFERENCES auth.users(id),
-- Monto
amount DECIMAL(10,2) NOT NULL,
currency CHAR(3) DEFAULT 'USD',
-- Stripe
stripe_refund_id VARCHAR(100),
-- Razón
reason VARCHAR(100),
notes TEXT,
-- Estado
status payment_status_enum DEFAULT 'pending',
-- Procesado por
processed_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_refunds_payment ON refunds(payment_id);
CREATE INDEX idx_refunds_user ON refunds(user_id);
-- ============================================================================
-- TABLA: invoices
-- Descripción: Facturas generadas
-- ============================================================================
CREATE TABLE IF NOT EXISTS invoices (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES auth.users(id),
-- Número de factura
invoice_number VARCHAR(50) NOT NULL UNIQUE,
-- Stripe
stripe_invoice_id VARCHAR(100) UNIQUE,
-- Montos
subtotal DECIMAL(10,2) NOT NULL,
tax DECIMAL(10,2) DEFAULT 0,
total DECIMAL(10,2) NOT NULL,
amount_paid DECIMAL(10,2) DEFAULT 0,
amount_due DECIMAL(10,2),
currency CHAR(3) DEFAULT 'USD',
-- Estado
status VARCHAR(20) DEFAULT 'draft', -- 'draft', 'open', 'paid', 'void', 'uncollectible'
-- Fechas
due_date DATE,
paid_at TIMESTAMPTZ,
-- Items
line_items JSONB DEFAULT '[]', -- [{description, quantity, unit_price, amount}]
-- PDFs
pdf_url TEXT,
hosted_invoice_url TEXT,
-- Datos fiscales del cliente
billing_details JSONB,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_invoices_user ON invoices(user_id);
CREATE INDEX idx_invoices_status ON invoices(status);
CREATE INDEX idx_invoices_number ON invoices(invoice_number);
-- ============================================================================
-- TABLA: payout_requests
-- Descripción: Solicitudes de retiro a cuenta bancaria/externa
-- ============================================================================
CREATE TABLE IF NOT EXISTS payout_requests (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES auth.users(id),
wallet_id UUID NOT NULL REFERENCES wallets(id),
-- Monto
amount DECIMAL(15,2) NOT NULL,
currency CHAR(3) DEFAULT 'USD',
fee DECIMAL(10,2) DEFAULT 0,
net_amount DECIMAL(15,2),
-- Destino
payout_method VARCHAR(50) NOT NULL, -- 'bank_transfer', 'paypal', 'crypto'
destination_details JSONB, -- Cuenta bancaria, dirección, etc.
-- Estado
status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'processing', 'completed', 'rejected', 'cancelled'
processed_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
-- Procesamiento
processed_by UUID REFERENCES auth.users(id),
rejection_reason TEXT,
external_reference VARCHAR(100),
-- Transacción de wallet
wallet_transaction_id UUID REFERENCES wallet_transactions(id),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_payouts_user ON payout_requests(user_id);
CREATE INDEX idx_payouts_wallet ON payout_requests(wallet_id);
CREATE INDEX idx_payouts_status ON payout_requests(status);
-- ============================================================================
-- TABLA: promo_codes
-- Descripción: Códigos promocionales y descuentos
-- ============================================================================
CREATE TABLE IF NOT EXISTS promo_codes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(50) NOT NULL UNIQUE,
description TEXT,
-- Tipo de descuento
discount_type VARCHAR(20) NOT NULL, -- 'percentage', 'fixed_amount'
discount_value DECIMAL(10,2) NOT NULL,
currency CHAR(3) DEFAULT 'USD',
-- Aplicabilidad
applies_to VARCHAR(50) DEFAULT 'all', -- 'all', 'subscription', 'course'
applicable_plan_ids UUID[],
applicable_course_ids UUID[],
-- Límites
max_uses INT,
current_uses INT DEFAULT 0,
max_uses_per_user INT DEFAULT 1,
-- Validez
valid_from TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
valid_until TIMESTAMPTZ,
-- Requisitos
min_purchase_amount DECIMAL(10,2),
first_time_only BOOLEAN DEFAULT FALSE,
-- Estado
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_promo_codes_code ON promo_codes(code);
CREATE INDEX idx_promo_codes_active ON promo_codes(is_active) WHERE is_active = TRUE;
-- ============================================================================
-- TABLA: promo_code_uses
-- Descripción: Uso de códigos promocionales
-- ============================================================================
CREATE TABLE IF NOT EXISTS promo_code_uses (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
promo_code_id UUID NOT NULL REFERENCES promo_codes(id),
user_id UUID NOT NULL REFERENCES auth.users(id),
payment_id UUID REFERENCES payments(id),
discount_applied DECIMAL(10,2) NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_promo_uses_code ON promo_code_uses(promo_code_id);
CREATE INDEX idx_promo_uses_user ON promo_code_uses(user_id);
-- ============================================================================
-- TRIGGERS
-- ============================================================================
CREATE TRIGGER update_plans_updated_at
BEFORE UPDATE ON subscription_plans
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER update_stripe_customers_updated_at
BEFORE UPDATE ON stripe_customers
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER update_subscriptions_updated_at
BEFORE UPDATE ON subscriptions
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER update_wallets_updated_at
BEFORE UPDATE ON wallets
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER update_payments_updated_at
BEFORE UPDATE ON payments
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER update_invoices_updated_at
BEFORE UPDATE ON invoices
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
CREATE TRIGGER update_payouts_updated_at
BEFORE UPDATE ON payout_requests
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();