-- ============================================================================ -- 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;