trading-platform-database-v2/ddl/schemas/vip/001_vip_system.sql
rckrdmrd e520268348 Migración desde trading-platform/apps/database - Estándar multi-repo v2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 08:32:52 -06:00

257 lines
7.0 KiB
PL/PgSQL

-- ============================================================================
-- SCHEMA: vip
-- TABLES: tiers, subscriptions, model_access
-- DESCRIPTION: Sistema VIP con tiers y modelos exclusivos
-- VERSION: 1.0.0
-- CREATED: 2026-01-10
-- ============================================================================
-- Crear schema
CREATE SCHEMA IF NOT EXISTS vip;
-- Enums
DO $$ BEGIN
CREATE TYPE vip.tier_type AS ENUM (
'GOLD', -- Tier Gold - $199/mes
'PLATINUM', -- Tier Platinum - $399/mes
'DIAMOND' -- Tier Diamond - $999/mes
);
EXCEPTION WHEN duplicate_object THEN null; END $$;
DO $$ BEGIN
CREATE TYPE vip.subscription_status AS ENUM (
'trialing', -- En periodo de prueba
'active', -- Activa y pagando
'past_due', -- Pago atrasado
'cancelled', -- Cancelada
'expired' -- Expirada
);
EXCEPTION WHEN duplicate_object THEN null; END $$;
-- Tabla de definicion de Tiers
CREATE TABLE IF NOT EXISTS vip.tiers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Identificacion
tier vip.tier_type UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
tagline VARCHAR(200),
-- Precios
price_monthly DECIMAL(10, 2) NOT NULL,
price_yearly DECIMAL(10, 2),
currency VARCHAR(3) DEFAULT 'USD',
-- Stripe
stripe_product_id VARCHAR(255),
stripe_price_monthly_id VARCHAR(255),
stripe_price_yearly_id VARCHAR(255),
-- Beneficios (JSONB array)
benefits JSONB NOT NULL DEFAULT '[]',
-- Ejemplo:
-- [
-- {"name": "Modelo Ensemble Pro", "included": true},
-- {"name": "Predicciones VIP", "value": "50/mes"},
-- {"name": "Soporte prioritario", "included": true}
-- ]
-- Limites
limits JSONB NOT NULL DEFAULT '{}',
-- Ejemplo:
-- {
-- "vip_predictions_per_month": 50,
-- "api_calls_per_minute": 30,
-- "exclusive_models": ["ensemble_pro"]
-- }
-- Modelos exclusivos incluidos
included_models VARCHAR[] DEFAULT '{}',
-- Display
color VARCHAR(7),
icon VARCHAR(50),
sort_order INT DEFAULT 0,
is_popular BOOLEAN DEFAULT FALSE,
-- Estado
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
COMMENT ON TABLE vip.tiers IS
'Definicion de los tiers VIP disponibles (Gold, Platinum, Diamond)';
-- Tabla de suscripciones VIP
CREATE TABLE IF NOT EXISTS vip.subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
user_id UUID NOT NULL,
tier_id UUID NOT NULL REFERENCES vip.tiers(id),
-- Stripe
stripe_subscription_id VARCHAR(255) UNIQUE,
stripe_customer_id VARCHAR(255),
-- Estado
status vip.subscription_status NOT NULL DEFAULT 'trialing',
-- Periodo
interval VARCHAR(10) DEFAULT 'month',
current_period_start TIMESTAMPTZ,
current_period_end TIMESTAMPTZ,
-- Trial
trial_start TIMESTAMPTZ,
trial_end TIMESTAMPTZ,
-- Cancelacion
cancel_at TIMESTAMPTZ,
cancelled_at TIMESTAMPTZ,
cancel_reason VARCHAR(500),
-- Uso del periodo actual
vip_predictions_used INT DEFAULT 0,
api_calls_this_period INT DEFAULT 0,
last_usage_reset TIMESTAMPTZ DEFAULT NOW(),
-- Metadata
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
-- Solo una suscripcion activa por usuario
CONSTRAINT unique_active_vip_subscription
UNIQUE (tenant_id, user_id)
);
COMMENT ON TABLE vip.subscriptions IS
'Suscripciones VIP activas de usuarios';
-- Tabla de acceso a modelos exclusivos
CREATE TABLE IF NOT EXISTS vip.model_access (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
subscription_id UUID NOT NULL REFERENCES vip.subscriptions(id) ON DELETE CASCADE,
-- Modelo
model_id VARCHAR(100) NOT NULL,
model_name VARCHAR(200),
-- Uso
predictions_generated INT DEFAULT 0,
last_used_at TIMESTAMPTZ,
-- Limites especificos del modelo
daily_limit INT,
monthly_limit INT,
daily_used INT DEFAULT 0,
monthly_used INT DEFAULT 0,
-- Acceso
granted_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ,
UNIQUE(subscription_id, model_id)
);
COMMENT ON TABLE vip.model_access IS
'Registro de acceso a modelos exclusivos por suscripcion VIP';
-- Indices
CREATE INDEX IF NOT EXISTS idx_vip_tiers_active
ON vip.tiers(is_active) WHERE is_active = TRUE;
CREATE INDEX IF NOT EXISTS idx_vip_subs_tenant
ON vip.subscriptions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_vip_subs_user
ON vip.subscriptions(user_id);
CREATE INDEX IF NOT EXISTS idx_vip_subs_status
ON vip.subscriptions(status);
CREATE INDEX IF NOT EXISTS idx_vip_subs_stripe
ON vip.subscriptions(stripe_subscription_id);
CREATE INDEX IF NOT EXISTS idx_vip_access_sub
ON vip.model_access(subscription_id);
CREATE INDEX IF NOT EXISTS idx_vip_access_model
ON vip.model_access(model_id);
-- Trigger updated_at
CREATE OR REPLACE FUNCTION vip.update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS vip_tiers_updated ON vip.tiers;
CREATE TRIGGER vip_tiers_updated
BEFORE UPDATE ON vip.tiers
FOR EACH ROW
EXECUTE FUNCTION vip.update_timestamp();
DROP TRIGGER IF EXISTS vip_subs_updated ON vip.subscriptions;
CREATE TRIGGER vip_subs_updated
BEFORE UPDATE ON vip.subscriptions
FOR EACH ROW
EXECUTE FUNCTION vip.update_timestamp();
-- Funcion para verificar acceso VIP
CREATE OR REPLACE FUNCTION vip.check_vip_access(
p_user_id UUID,
p_model_id VARCHAR(100)
)
RETURNS BOOLEAN AS $$
DECLARE
v_has_access BOOLEAN;
BEGIN
SELECT EXISTS (
SELECT 1
FROM vip.subscriptions s
JOIN vip.model_access ma ON ma.subscription_id = s.id
WHERE s.user_id = p_user_id
AND s.status = 'active'
AND ma.model_id = p_model_id
AND (ma.expires_at IS NULL OR ma.expires_at > NOW())
) INTO v_has_access;
RETURN v_has_access;
END;
$$ LANGUAGE plpgsql;
-- RLS
ALTER TABLE vip.tiers ENABLE ROW LEVEL SECURITY;
ALTER TABLE vip.subscriptions ENABLE ROW LEVEL SECURITY;
ALTER TABLE vip.model_access ENABLE ROW LEVEL SECURITY;
CREATE POLICY tiers_read_all ON vip.tiers
FOR SELECT USING (TRUE);
CREATE POLICY subs_tenant_isolation ON vip.subscriptions
FOR ALL
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
CREATE POLICY access_via_subscription ON vip.model_access
FOR ALL
USING (
subscription_id IN (
SELECT id FROM vip.subscriptions
WHERE tenant_id = current_setting('app.current_tenant_id', true)::UUID
)
);
-- Grants
GRANT SELECT ON vip.tiers TO trading_app;
GRANT SELECT, INSERT, UPDATE ON vip.subscriptions TO trading_app;
GRANT SELECT, INSERT, UPDATE ON vip.model_access TO trading_app;
GRANT SELECT ON vip.tiers TO trading_readonly;
GRANT SELECT ON vip.subscriptions TO trading_readonly;