257 lines
7.0 KiB
PL/PgSQL
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;
|