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