- Replace old DDL structure with new numbered files (01-24) - Update migrations and seeds for new schema - Clean up deprecated files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
545 lines
21 KiB
PL/PgSQL
545 lines
21 KiB
PL/PgSQL
-- =============================================================
|
|
-- ARCHIVO: 08-plans.sql
|
|
-- DESCRIPCION: Extensiones de planes SaaS (features, limits, Stripe)
|
|
-- VERSION: 1.0.0
|
|
-- PROYECTO: ERP-Core V2
|
|
-- FECHA: 2026-01-10
|
|
-- EPIC: SAAS-BILLING (EPIC-SAAS-002)
|
|
-- HISTORIAS: US-007, US-010, US-011, US-012
|
|
-- =============================================================
|
|
|
|
-- =====================
|
|
-- MODIFICACIONES A TABLAS EXISTENTES
|
|
-- =====================
|
|
|
|
-- Agregar columnas Stripe a tenant_subscriptions (US-007, US-008)
|
|
ALTER TABLE billing.tenant_subscriptions
|
|
ADD COLUMN IF NOT EXISTS stripe_customer_id VARCHAR(255),
|
|
ADD COLUMN IF NOT EXISTS stripe_subscription_id VARCHAR(255),
|
|
ADD COLUMN IF NOT EXISTS stripe_payment_method_id VARCHAR(255),
|
|
ADD COLUMN IF NOT EXISTS stripe_price_id VARCHAR(255),
|
|
ADD COLUMN IF NOT EXISTS trial_ends_at TIMESTAMPTZ,
|
|
ADD COLUMN IF NOT EXISTS canceled_at TIMESTAMPTZ,
|
|
ADD COLUMN IF NOT EXISTS cancel_at TIMESTAMPTZ;
|
|
|
|
-- Agregar columnas Stripe a subscription_plans
|
|
ALTER TABLE billing.subscription_plans
|
|
ADD COLUMN IF NOT EXISTS stripe_product_id VARCHAR(255),
|
|
ADD COLUMN IF NOT EXISTS stripe_price_id_monthly VARCHAR(255),
|
|
ADD COLUMN IF NOT EXISTS stripe_price_id_annual VARCHAR(255);
|
|
|
|
-- Agregar columnas Stripe a payment_methods
|
|
ALTER TABLE billing.payment_methods
|
|
ADD COLUMN IF NOT EXISTS stripe_payment_method_id VARCHAR(255);
|
|
|
|
-- Indices para campos Stripe
|
|
CREATE INDEX IF NOT EXISTS idx_subscriptions_stripe_customer ON billing.tenant_subscriptions(stripe_customer_id) WHERE stripe_customer_id IS NOT NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_subscriptions_stripe_sub ON billing.tenant_subscriptions(stripe_subscription_id) WHERE stripe_subscription_id IS NOT NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_plans_stripe_product ON billing.subscription_plans(stripe_product_id) WHERE stripe_product_id IS NOT NULL;
|
|
|
|
-- =====================
|
|
-- TABLA: billing.plan_features
|
|
-- Features habilitadas por plan (US-010, US-011)
|
|
-- =====================
|
|
CREATE TABLE IF NOT EXISTS billing.plan_features (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
plan_id UUID NOT NULL REFERENCES billing.subscription_plans(id) ON DELETE CASCADE,
|
|
|
|
-- Identificacion
|
|
feature_key VARCHAR(100) NOT NULL,
|
|
feature_name VARCHAR(255) NOT NULL,
|
|
category VARCHAR(100),
|
|
|
|
-- Estado
|
|
enabled BOOLEAN DEFAULT TRUE,
|
|
|
|
-- Configuracion
|
|
configuration JSONB DEFAULT '{}',
|
|
|
|
-- Metadata
|
|
description TEXT,
|
|
metadata JSONB DEFAULT '{}',
|
|
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(plan_id, feature_key)
|
|
);
|
|
|
|
-- Indices para plan_features
|
|
CREATE INDEX IF NOT EXISTS idx_plan_features_plan ON billing.plan_features(plan_id);
|
|
CREATE INDEX IF NOT EXISTS idx_plan_features_key ON billing.plan_features(feature_key);
|
|
CREATE INDEX IF NOT EXISTS idx_plan_features_enabled ON billing.plan_features(plan_id, enabled) WHERE enabled = TRUE;
|
|
|
|
-- =====================
|
|
-- TABLA: billing.plan_limits
|
|
-- Limites cuantificables por plan (US-010, US-012)
|
|
-- =====================
|
|
CREATE TABLE IF NOT EXISTS billing.plan_limits (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
plan_id UUID NOT NULL REFERENCES billing.subscription_plans(id) ON DELETE CASCADE,
|
|
|
|
-- Identificacion
|
|
limit_key VARCHAR(100) NOT NULL,
|
|
limit_name VARCHAR(255) NOT NULL,
|
|
|
|
-- Valor
|
|
limit_value INTEGER NOT NULL,
|
|
limit_type VARCHAR(50) DEFAULT 'monthly', -- monthly, daily, total, per_user
|
|
|
|
-- Overage (si se permite exceder)
|
|
allow_overage BOOLEAN DEFAULT FALSE,
|
|
overage_unit_price DECIMAL(10,4) DEFAULT 0,
|
|
overage_currency VARCHAR(3) DEFAULT 'MXN',
|
|
|
|
-- Alertas
|
|
alert_threshold_percent INTEGER DEFAULT 80,
|
|
hard_limit BOOLEAN DEFAULT TRUE, -- Si true, bloquea; si false, solo alerta
|
|
|
|
-- Metadata
|
|
description TEXT,
|
|
metadata JSONB DEFAULT '{}',
|
|
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(plan_id, limit_key)
|
|
);
|
|
|
|
-- Indices para plan_limits
|
|
CREATE INDEX IF NOT EXISTS idx_plan_limits_plan ON billing.plan_limits(plan_id);
|
|
CREATE INDEX IF NOT EXISTS idx_plan_limits_key ON billing.plan_limits(limit_key);
|
|
|
|
-- =====================
|
|
-- TABLA: billing.coupons
|
|
-- Cupones de descuento
|
|
-- =====================
|
|
CREATE TABLE IF NOT EXISTS billing.coupons (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Identificacion
|
|
code VARCHAR(50) NOT NULL UNIQUE,
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
|
|
-- Descuento
|
|
discount_type VARCHAR(20) NOT NULL CHECK (discount_type IN ('percentage', 'fixed')),
|
|
discount_value DECIMAL(10,2) NOT NULL,
|
|
currency VARCHAR(3) DEFAULT 'MXN',
|
|
|
|
-- Aplicabilidad
|
|
applicable_plans UUID[] DEFAULT '{}', -- Vacio = todos
|
|
min_amount DECIMAL(10,2) DEFAULT 0,
|
|
|
|
-- Limites
|
|
max_redemptions INTEGER,
|
|
times_redeemed INTEGER DEFAULT 0,
|
|
max_redemptions_per_customer INTEGER DEFAULT 1,
|
|
|
|
-- Vigencia
|
|
valid_from TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
valid_until TIMESTAMPTZ,
|
|
|
|
-- Duracion del descuento
|
|
duration VARCHAR(20) DEFAULT 'once', -- once, forever, repeating
|
|
duration_months INTEGER, -- Si duration = repeating
|
|
|
|
-- Stripe
|
|
stripe_coupon_id VARCHAR(255),
|
|
|
|
-- Estado
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Indices para coupons
|
|
CREATE INDEX IF NOT EXISTS idx_coupons_code ON billing.coupons(code);
|
|
CREATE INDEX IF NOT EXISTS idx_coupons_active ON billing.coupons(is_active, valid_until);
|
|
|
|
-- =====================
|
|
-- TABLA: billing.coupon_redemptions
|
|
-- Uso de cupones
|
|
-- =====================
|
|
CREATE TABLE IF NOT EXISTS billing.coupon_redemptions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
coupon_id UUID NOT NULL REFERENCES billing.coupons(id),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
subscription_id UUID REFERENCES billing.tenant_subscriptions(id),
|
|
|
|
-- Descuento aplicado
|
|
discount_amount DECIMAL(10,2) NOT NULL,
|
|
|
|
-- Timestamps
|
|
redeemed_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
expires_at TIMESTAMPTZ,
|
|
|
|
UNIQUE(coupon_id, tenant_id)
|
|
);
|
|
|
|
-- Indices para coupon_redemptions
|
|
CREATE INDEX IF NOT EXISTS idx_coupon_redemptions_coupon ON billing.coupon_redemptions(coupon_id);
|
|
CREATE INDEX IF NOT EXISTS idx_coupon_redemptions_tenant ON billing.coupon_redemptions(tenant_id);
|
|
|
|
-- =====================
|
|
-- TABLA: billing.stripe_events
|
|
-- Log de eventos de Stripe (US-008)
|
|
-- =====================
|
|
CREATE TABLE IF NOT EXISTS billing.stripe_events (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Evento de Stripe
|
|
stripe_event_id VARCHAR(255) NOT NULL UNIQUE,
|
|
event_type VARCHAR(100) NOT NULL,
|
|
api_version VARCHAR(20),
|
|
|
|
-- Datos
|
|
data JSONB NOT NULL,
|
|
|
|
-- Procesamiento
|
|
processed BOOLEAN DEFAULT FALSE,
|
|
processed_at TIMESTAMPTZ,
|
|
error_message TEXT,
|
|
retry_count INTEGER DEFAULT 0,
|
|
|
|
-- Tenant relacionado (si aplica)
|
|
tenant_id UUID REFERENCES auth.tenants(id),
|
|
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Indices para stripe_events
|
|
CREATE INDEX IF NOT EXISTS idx_stripe_events_type ON billing.stripe_events(event_type);
|
|
CREATE INDEX IF NOT EXISTS idx_stripe_events_processed ON billing.stripe_events(processed) WHERE processed = FALSE;
|
|
CREATE INDEX IF NOT EXISTS idx_stripe_events_tenant ON billing.stripe_events(tenant_id);
|
|
|
|
-- =====================
|
|
-- RLS POLICIES
|
|
-- =====================
|
|
ALTER TABLE billing.plan_features ENABLE ROW LEVEL SECURITY;
|
|
-- Plan features son globales, no requieren isolation
|
|
CREATE POLICY public_read_plan_features ON billing.plan_features
|
|
FOR SELECT USING (true);
|
|
|
|
ALTER TABLE billing.plan_limits ENABLE ROW LEVEL SECURITY;
|
|
-- Plan limits son globales, no requieren isolation
|
|
CREATE POLICY public_read_plan_limits ON billing.plan_limits
|
|
FOR SELECT USING (true);
|
|
|
|
ALTER TABLE billing.coupons ENABLE ROW LEVEL SECURITY;
|
|
-- Cupones son globales pero solo admins pueden modificar
|
|
CREATE POLICY public_read_coupons ON billing.coupons
|
|
FOR SELECT USING (is_active = TRUE);
|
|
|
|
ALTER TABLE billing.coupon_redemptions ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation_coupon_redemptions ON billing.coupon_redemptions
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
|
|
|
ALTER TABLE billing.stripe_events ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation_stripe_events ON billing.stripe_events
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid OR tenant_id IS NULL);
|
|
|
|
-- =====================
|
|
-- FUNCIONES
|
|
-- =====================
|
|
|
|
-- Funcion para verificar si un tenant tiene una feature habilitada
|
|
CREATE OR REPLACE FUNCTION billing.has_feature(
|
|
p_tenant_id UUID,
|
|
p_feature_key VARCHAR(100)
|
|
)
|
|
RETURNS BOOLEAN AS $$
|
|
DECLARE
|
|
v_enabled BOOLEAN;
|
|
BEGIN
|
|
SELECT pf.enabled INTO v_enabled
|
|
FROM billing.tenant_subscriptions ts
|
|
JOIN billing.plan_features pf ON pf.plan_id = ts.plan_id
|
|
WHERE ts.tenant_id = p_tenant_id
|
|
AND ts.status = 'active'
|
|
AND pf.feature_key = p_feature_key;
|
|
|
|
RETURN COALESCE(v_enabled, FALSE);
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
-- Funcion para obtener limite de un tenant
|
|
CREATE OR REPLACE FUNCTION billing.get_limit(
|
|
p_tenant_id UUID,
|
|
p_limit_key VARCHAR(100)
|
|
)
|
|
RETURNS INTEGER AS $$
|
|
DECLARE
|
|
v_limit INTEGER;
|
|
BEGIN
|
|
SELECT pl.limit_value INTO v_limit
|
|
FROM billing.tenant_subscriptions ts
|
|
JOIN billing.plan_limits pl ON pl.plan_id = ts.plan_id
|
|
WHERE ts.tenant_id = p_tenant_id
|
|
AND ts.status = 'active'
|
|
AND pl.limit_key = p_limit_key;
|
|
|
|
RETURN COALESCE(v_limit, 0);
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
-- Funcion para verificar si se puede usar una feature (con limite)
|
|
CREATE OR REPLACE FUNCTION billing.can_use_feature(
|
|
p_tenant_id UUID,
|
|
p_limit_key VARCHAR(100),
|
|
p_current_usage INTEGER DEFAULT 0
|
|
)
|
|
RETURNS TABLE (
|
|
allowed BOOLEAN,
|
|
limit_value INTEGER,
|
|
current_usage INTEGER,
|
|
remaining INTEGER,
|
|
allow_overage BOOLEAN,
|
|
overage_price DECIMAL
|
|
) AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
CASE
|
|
WHEN pl.hard_limit THEN p_current_usage < pl.limit_value
|
|
ELSE TRUE
|
|
END as allowed,
|
|
pl.limit_value,
|
|
p_current_usage as current_usage,
|
|
GREATEST(0, pl.limit_value - p_current_usage) as remaining,
|
|
pl.allow_overage,
|
|
pl.overage_unit_price
|
|
FROM billing.tenant_subscriptions ts
|
|
JOIN billing.plan_limits pl ON pl.plan_id = ts.plan_id
|
|
WHERE ts.tenant_id = p_tenant_id
|
|
AND ts.status = 'active'
|
|
AND pl.limit_key = p_limit_key;
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
-- Funcion para obtener todas las features de un tenant
|
|
CREATE OR REPLACE FUNCTION billing.get_tenant_features(p_tenant_id UUID)
|
|
RETURNS TABLE (
|
|
feature_key VARCHAR(100),
|
|
feature_name VARCHAR(255),
|
|
enabled BOOLEAN,
|
|
configuration JSONB
|
|
) AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
pf.feature_key,
|
|
pf.feature_name,
|
|
pf.enabled,
|
|
pf.configuration
|
|
FROM billing.tenant_subscriptions ts
|
|
JOIN billing.plan_features pf ON pf.plan_id = ts.plan_id
|
|
WHERE ts.tenant_id = p_tenant_id
|
|
AND ts.status IN ('active', 'trial');
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
-- Funcion para obtener todos los limites de un tenant
|
|
CREATE OR REPLACE FUNCTION billing.get_tenant_limits(p_tenant_id UUID)
|
|
RETURNS TABLE (
|
|
limit_key VARCHAR(100),
|
|
limit_name VARCHAR(255),
|
|
limit_value INTEGER,
|
|
limit_type VARCHAR(50),
|
|
allow_overage BOOLEAN,
|
|
overage_unit_price DECIMAL
|
|
) AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
pl.limit_key,
|
|
pl.limit_name,
|
|
pl.limit_value,
|
|
pl.limit_type,
|
|
pl.allow_overage,
|
|
pl.overage_unit_price
|
|
FROM billing.tenant_subscriptions ts
|
|
JOIN billing.plan_limits pl ON pl.plan_id = ts.plan_id
|
|
WHERE ts.tenant_id = p_tenant_id
|
|
AND ts.status IN ('active', 'trial');
|
|
END;
|
|
$$ LANGUAGE plpgsql STABLE;
|
|
|
|
-- Funcion para aplicar cupon
|
|
CREATE OR REPLACE FUNCTION billing.apply_coupon(
|
|
p_tenant_id UUID,
|
|
p_coupon_code VARCHAR(50)
|
|
)
|
|
RETURNS TABLE (
|
|
success BOOLEAN,
|
|
message TEXT,
|
|
discount_amount DECIMAL
|
|
) AS $$
|
|
DECLARE
|
|
v_coupon RECORD;
|
|
v_subscription RECORD;
|
|
v_discount DECIMAL;
|
|
BEGIN
|
|
-- Obtener cupon
|
|
SELECT * INTO v_coupon
|
|
FROM billing.coupons
|
|
WHERE code = p_coupon_code
|
|
AND is_active = TRUE
|
|
AND (valid_until IS NULL OR valid_until > CURRENT_TIMESTAMP);
|
|
|
|
IF NOT FOUND THEN
|
|
RETURN QUERY SELECT FALSE, 'Cupon no valido o expirado'::TEXT, 0::DECIMAL;
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- Verificar max redemptions
|
|
IF v_coupon.max_redemptions IS NOT NULL AND v_coupon.times_redeemed >= v_coupon.max_redemptions THEN
|
|
RETURN QUERY SELECT FALSE, 'Cupon agotado'::TEXT, 0::DECIMAL;
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- Verificar si ya fue usado por este tenant
|
|
IF EXISTS (SELECT 1 FROM billing.coupon_redemptions WHERE coupon_id = v_coupon.id AND tenant_id = p_tenant_id) THEN
|
|
RETURN QUERY SELECT FALSE, 'Cupon ya utilizado'::TEXT, 0::DECIMAL;
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- Obtener suscripcion
|
|
SELECT * INTO v_subscription
|
|
FROM billing.tenant_subscriptions
|
|
WHERE tenant_id = p_tenant_id
|
|
AND status = 'active';
|
|
|
|
IF NOT FOUND THEN
|
|
RETURN QUERY SELECT FALSE, 'No hay suscripcion activa'::TEXT, 0::DECIMAL;
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- Calcular descuento
|
|
IF v_coupon.discount_type = 'percentage' THEN
|
|
v_discount := v_subscription.current_price * (v_coupon.discount_value / 100);
|
|
ELSE
|
|
v_discount := v_coupon.discount_value;
|
|
END IF;
|
|
|
|
-- Registrar uso
|
|
INSERT INTO billing.coupon_redemptions (coupon_id, tenant_id, subscription_id, discount_amount)
|
|
VALUES (v_coupon.id, p_tenant_id, v_subscription.id, v_discount);
|
|
|
|
-- Actualizar contador
|
|
UPDATE billing.coupons SET times_redeemed = times_redeemed + 1 WHERE id = v_coupon.id;
|
|
|
|
RETURN QUERY SELECT TRUE, 'Cupon aplicado correctamente'::TEXT, v_discount;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- =====================
|
|
-- TRIGGERS
|
|
-- =====================
|
|
|
|
-- Trigger para updated_at en plan_features
|
|
CREATE OR REPLACE FUNCTION billing.update_plan_features_timestamp()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = CURRENT_TIMESTAMP;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trg_plan_features_updated_at
|
|
BEFORE UPDATE ON billing.plan_features
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION billing.update_plan_features_timestamp();
|
|
|
|
CREATE TRIGGER trg_plan_limits_updated_at
|
|
BEFORE UPDATE ON billing.plan_limits
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION billing.update_plan_features_timestamp();
|
|
|
|
-- =====================
|
|
-- SEED DATA: Features por Plan
|
|
-- =====================
|
|
|
|
-- Features para plan Starter
|
|
INSERT INTO billing.plan_features (plan_id, feature_key, feature_name, category, enabled) VALUES
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'starter'), 'pos', 'Punto de Venta', 'sales', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'starter'), 'inventory_basic', 'Inventario Basico', 'inventory', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'starter'), 'reports_basic', 'Reportes Basicos', 'reports', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'starter'), 'email_support', 'Soporte por Email', 'support', TRUE)
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- Features para plan Professional
|
|
INSERT INTO billing.plan_features (plan_id, feature_key, feature_name, category, enabled) VALUES
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'professional'), 'pos', 'Punto de Venta', 'sales', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'professional'), 'mobile_app', 'App Movil', 'platform', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'professional'), 'inventory_advanced', 'Inventario Avanzado', 'inventory', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'professional'), 'reports_advanced', 'Reportes Avanzados', 'reports', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'professional'), 'multi_branch', 'Multi-Sucursal', 'core', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'professional'), 'api_access', 'Acceso a API', 'integration', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'professional'), 'chat_support', 'Soporte por Chat', 'support', TRUE)
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- Features para plan Business
|
|
INSERT INTO billing.plan_features (plan_id, feature_key, feature_name, category, enabled) VALUES
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'pos', 'Punto de Venta', 'sales', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'mobile_app', 'App Movil', 'platform', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'desktop_app', 'App Desktop', 'platform', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'inventory_advanced', 'Inventario Avanzado', 'inventory', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'manufacturing', 'Manufactura', 'production', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'hr_module', 'Modulo RRHH', 'hr', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'accounting', 'Contabilidad', 'financial', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'ai_assistant', 'Asistente IA', 'ai', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'webhooks', 'Webhooks', 'integration', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'priority_support', 'Soporte Prioritario', 'support', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'sla_99', 'SLA 99.9%', 'support', TRUE)
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- =====================
|
|
-- SEED DATA: Limits por Plan
|
|
-- =====================
|
|
|
|
-- Limits para plan Starter
|
|
INSERT INTO billing.plan_limits (plan_id, limit_key, limit_name, limit_value, limit_type, allow_overage) VALUES
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'starter'), 'users', 'Usuarios', 3, 'total', FALSE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'starter'), 'branches', 'Sucursales', 1, 'total', FALSE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'starter'), 'storage_mb', 'Storage (MB)', 5120, 'total', TRUE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'starter'), 'api_calls', 'API Calls', 5000, 'monthly', FALSE),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'starter'), 'invoices', 'Facturas', 100, 'monthly', TRUE)
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- Limits para plan Professional
|
|
INSERT INTO billing.plan_limits (plan_id, limit_key, limit_name, limit_value, limit_type, allow_overage, overage_unit_price) VALUES
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'professional'), 'users', 'Usuarios', 10, 'total', TRUE, 99),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'professional'), 'branches', 'Sucursales', 3, 'total', TRUE, 199),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'professional'), 'storage_mb', 'Storage (MB)', 20480, 'total', TRUE, 0.10),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'professional'), 'api_calls', 'API Calls', 25000, 'monthly', TRUE, 0.001),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'professional'), 'invoices', 'Facturas', 500, 'monthly', TRUE, 2)
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- Limits para plan Business
|
|
INSERT INTO billing.plan_limits (plan_id, limit_key, limit_name, limit_value, limit_type, allow_overage, overage_unit_price) VALUES
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'users', 'Usuarios', 25, 'total', TRUE, 79),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'branches', 'Sucursales', 10, 'total', TRUE, 149),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'storage_mb', 'Storage (MB)', 102400, 'total', TRUE, 0.05),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'api_calls', 'API Calls', 100000, 'monthly', TRUE, 0.0005),
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'invoices', 'Facturas', 0, 'monthly', FALSE, 0), -- Ilimitadas
|
|
((SELECT id FROM billing.subscription_plans WHERE code = 'business'), 'ai_tokens', 'Tokens IA', 100000, 'monthly', TRUE, 0.0001)
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
-- =====================
|
|
-- COMENTARIOS
|
|
-- =====================
|
|
COMMENT ON TABLE billing.plan_features IS 'Features habilitadas por plan de suscripcion';
|
|
COMMENT ON TABLE billing.plan_limits IS 'Limites cuantificables por plan';
|
|
COMMENT ON TABLE billing.coupons IS 'Cupones de descuento';
|
|
COMMENT ON TABLE billing.coupon_redemptions IS 'Registro de uso de cupones';
|
|
COMMENT ON TABLE billing.stripe_events IS 'Log de eventos recibidos de Stripe';
|
|
|
|
COMMENT ON FUNCTION billing.has_feature IS 'Verifica si un tenant tiene una feature habilitada';
|
|
COMMENT ON FUNCTION billing.get_limit IS 'Obtiene el valor de un limite para un tenant';
|
|
COMMENT ON FUNCTION billing.can_use_feature IS 'Verifica si un tenant puede usar una feature con limite';
|
|
COMMENT ON FUNCTION billing.get_tenant_features IS 'Obtiene todas las features habilitadas para un tenant';
|
|
COMMENT ON FUNCTION billing.get_tenant_limits IS 'Obtiene todos los limites de un tenant';
|
|
COMMENT ON FUNCTION billing.apply_coupon IS 'Aplica un cupon de descuento a un tenant';
|