148 lines
4.7 KiB
PL/PgSQL
148 lines
4.7 KiB
PL/PgSQL
-- ============================================================================
|
|
-- SCHEMA: financial
|
|
-- TABLE: wallets
|
|
-- DESCRIPTION: Sistema de Wallet Virtual con creditos USD equivalentes
|
|
-- VERSION: 1.0.0
|
|
-- CREATED: 2026-01-10
|
|
-- ============================================================================
|
|
|
|
-- Crear schema si no existe
|
|
CREATE SCHEMA IF NOT EXISTS financial;
|
|
|
|
-- Enum para estado de wallet
|
|
DO $$ BEGIN
|
|
CREATE TYPE financial.wallet_status AS ENUM (
|
|
'active', -- Wallet activo y operativo
|
|
'frozen', -- Congelado temporalmente (investigacion)
|
|
'suspended', -- Suspendido por violacion de terminos
|
|
'closed' -- Cerrado permanentemente
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN null;
|
|
END $$;
|
|
|
|
-- Tabla principal de Wallets
|
|
CREATE TABLE IF NOT EXISTS financial.wallets (
|
|
-- Identificadores
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL,
|
|
user_id UUID NOT NULL,
|
|
|
|
-- Saldos en creditos (equivalente USD, no dinero real)
|
|
balance DECIMAL(15, 4) NOT NULL DEFAULT 0
|
|
CHECK (balance >= 0),
|
|
reserved DECIMAL(15, 4) NOT NULL DEFAULT 0
|
|
CHECK (reserved >= 0),
|
|
|
|
-- Totales acumulados (para auditoria)
|
|
total_credited DECIMAL(15, 4) NOT NULL DEFAULT 0,
|
|
total_debited DECIMAL(15, 4) NOT NULL DEFAULT 0,
|
|
|
|
-- Creditos promocionales (separados del balance principal)
|
|
promo_balance DECIMAL(15, 4) NOT NULL DEFAULT 0
|
|
CHECK (promo_balance >= 0),
|
|
promo_expiry TIMESTAMPTZ,
|
|
|
|
-- Configuracion
|
|
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
|
|
status financial.wallet_status NOT NULL DEFAULT 'active',
|
|
|
|
-- Limites operacionales
|
|
daily_spend_limit DECIMAL(15, 4) DEFAULT 1000.0000,
|
|
monthly_spend_limit DECIMAL(15, 4) DEFAULT 10000.0000,
|
|
single_transaction_limit DECIMAL(15, 4) DEFAULT 500.0000,
|
|
|
|
-- Tracking de uso
|
|
daily_spent DECIMAL(15, 4) NOT NULL DEFAULT 0,
|
|
monthly_spent DECIMAL(15, 4) NOT NULL DEFAULT 0,
|
|
last_daily_reset DATE DEFAULT CURRENT_DATE,
|
|
last_monthly_reset DATE DEFAULT DATE_TRUNC('month', CURRENT_DATE)::DATE,
|
|
|
|
-- Timestamps
|
|
last_transaction_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
-- Constraints
|
|
CONSTRAINT wallets_total_balance_check CHECK (balance + reserved >= 0),
|
|
CONSTRAINT wallets_unique_user UNIQUE (tenant_id, user_id)
|
|
);
|
|
|
|
-- Columna calculada para balance total disponible
|
|
COMMENT ON TABLE financial.wallets IS
|
|
'Wallet virtual con creditos USD equivalentes. No es dinero real.';
|
|
|
|
COMMENT ON COLUMN financial.wallets.balance IS
|
|
'Saldo disponible en creditos (1 credito = 1 USD equivalente)';
|
|
|
|
COMMENT ON COLUMN financial.wallets.reserved IS
|
|
'Creditos reservados para operaciones pendientes (ej: fondeo de agentes)';
|
|
|
|
COMMENT ON COLUMN financial.wallets.promo_balance IS
|
|
'Creditos promocionales separados del balance principal';
|
|
|
|
-- Indices
|
|
CREATE INDEX IF NOT EXISTS idx_wallets_tenant_id
|
|
ON financial.wallets(tenant_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_wallets_user_id
|
|
ON financial.wallets(user_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_wallets_status
|
|
ON financial.wallets(status);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_wallets_created_at
|
|
ON financial.wallets(created_at DESC);
|
|
|
|
-- Trigger para updated_at
|
|
CREATE OR REPLACE FUNCTION financial.update_wallet_timestamp()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS wallet_updated_at ON financial.wallets;
|
|
CREATE TRIGGER wallet_updated_at
|
|
BEFORE UPDATE ON financial.wallets
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION financial.update_wallet_timestamp();
|
|
|
|
-- Funcion para resetear limites diarios/mensuales
|
|
CREATE OR REPLACE FUNCTION financial.reset_wallet_limits()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
-- Reset diario
|
|
IF NEW.last_daily_reset < CURRENT_DATE THEN
|
|
NEW.daily_spent := 0;
|
|
NEW.last_daily_reset := CURRENT_DATE;
|
|
END IF;
|
|
|
|
-- Reset mensual
|
|
IF NEW.last_monthly_reset < DATE_TRUNC('month', CURRENT_DATE)::DATE THEN
|
|
NEW.monthly_spent := 0;
|
|
NEW.last_monthly_reset := DATE_TRUNC('month', CURRENT_DATE)::DATE;
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS wallet_reset_limits ON financial.wallets;
|
|
CREATE TRIGGER wallet_reset_limits
|
|
BEFORE UPDATE ON financial.wallets
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION financial.reset_wallet_limits();
|
|
|
|
-- RLS Policy para multi-tenancy
|
|
ALTER TABLE financial.wallets ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY wallets_tenant_isolation ON financial.wallets
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Grants
|
|
GRANT SELECT, INSERT, UPDATE ON financial.wallets TO trading_app;
|
|
GRANT SELECT ON financial.wallets TO trading_readonly;
|