erp-core-database/ddl/10-billing.sql

639 lines
22 KiB
PL/PgSQL

-- =====================================================
-- SCHEMA: billing
-- PROPÓSITO: Suscripciones SaaS, planes, pagos, facturación
-- MÓDULOS: MGN-015 (Billing y Suscripciones)
-- FECHA: 2025-11-24
-- =====================================================
-- NOTA: Este schema permite que el sistema opere como SaaS multi-tenant
-- o como instalación single-tenant (on-premise). En modo single-tenant,
-- las tablas de este schema pueden ignorarse o tener un único plan "unlimited".
-- =====================================================
-- Crear schema
CREATE SCHEMA IF NOT EXISTS billing;
-- =====================================================
-- TYPES (ENUMs)
-- =====================================================
CREATE TYPE billing.subscription_status AS ENUM (
'trialing', -- En período de prueba
'active', -- Suscripción activa
'past_due', -- Pago atrasado
'paused', -- Suscripción pausada
'cancelled', -- Cancelada por usuario
'suspended', -- Suspendida por falta de pago
'expired' -- Expirada
);
CREATE TYPE billing.billing_cycle AS ENUM (
'monthly',
'quarterly',
'semi_annual',
'annual'
);
CREATE TYPE billing.payment_method_type AS ENUM (
'card',
'bank_transfer',
'paypal',
'oxxo', -- México
'spei', -- México
'other'
);
CREATE TYPE billing.invoice_status AS ENUM (
'draft',
'open',
'paid',
'void',
'uncollectible'
);
CREATE TYPE billing.payment_status AS ENUM (
'pending',
'processing',
'succeeded',
'failed',
'cancelled',
'refunded'
);
-- =====================================================
-- TABLES
-- =====================================================
-- Tabla: subscription_plans (Planes disponibles - global, no por tenant)
-- Esta tabla no tiene tenant_id porque los planes son globales del sistema SaaS
CREATE TABLE billing.subscription_plans (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(50) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
-- Precios
price_monthly DECIMAL(12,2) NOT NULL DEFAULT 0,
price_yearly DECIMAL(12,2) NOT NULL DEFAULT 0,
currency_code VARCHAR(3) NOT NULL DEFAULT 'MXN',
-- Límites
max_users INTEGER DEFAULT 10,
max_companies INTEGER DEFAULT 1,
max_storage_gb INTEGER DEFAULT 5,
max_api_calls_month INTEGER DEFAULT 10000,
-- Características incluidas (JSON para flexibilidad)
features JSONB DEFAULT '{}'::jsonb,
-- Ejemplo: {"inventory": true, "sales": true, "financial": true, "purchase": true, "crm": false}
-- Metadata
is_active BOOLEAN NOT NULL DEFAULT true,
is_public BOOLEAN NOT NULL DEFAULT true, -- Visible en página de precios
is_default BOOLEAN NOT NULL DEFAULT false, -- Plan por defecto para nuevos tenants
trial_days INTEGER DEFAULT 14,
sort_order INTEGER DEFAULT 0,
-- Auditoría
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID,
updated_at TIMESTAMP,
updated_by UUID,
CONSTRAINT chk_plans_price_monthly CHECK (price_monthly >= 0),
CONSTRAINT chk_plans_price_yearly CHECK (price_yearly >= 0),
CONSTRAINT chk_plans_max_users CHECK (max_users > 0 OR max_users IS NULL),
CONSTRAINT chk_plans_trial_days CHECK (trial_days >= 0)
);
-- Tabla: tenant_owners (Propietarios/Contratantes de tenant)
-- Usuario(s) que contratan y pagan por el tenant
CREATE TABLE billing.tenant_owners (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
ownership_type VARCHAR(20) NOT NULL DEFAULT 'owner',
-- owner: Propietario principal (puede haber solo 1)
-- billing_admin: Puede gestionar facturación
-- Contacto de facturación (puede diferir del usuario)
billing_email VARCHAR(255),
billing_phone VARCHAR(50),
billing_name VARCHAR(255),
-- Auditoría
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID,
CONSTRAINT uq_tenant_owners UNIQUE (tenant_id, user_id),
CONSTRAINT chk_ownership_type CHECK (ownership_type IN ('owner', 'billing_admin'))
);
-- Tabla: subscriptions (Suscripciones activas de cada tenant)
CREATE TABLE billing.subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
plan_id UUID NOT NULL REFERENCES billing.subscription_plans(id),
-- Estado
status billing.subscription_status NOT NULL DEFAULT 'trialing',
billing_cycle billing.billing_cycle NOT NULL DEFAULT 'monthly',
-- Fechas importantes
trial_start_at TIMESTAMP,
trial_end_at TIMESTAMP,
current_period_start TIMESTAMP NOT NULL,
current_period_end TIMESTAMP NOT NULL,
cancelled_at TIMESTAMP,
cancel_at_period_end BOOLEAN NOT NULL DEFAULT false,
paused_at TIMESTAMP,
-- Descuentos/Cupones
discount_percent DECIMAL(5,2) DEFAULT 0,
coupon_code VARCHAR(50),
-- Integración pasarela de pago
stripe_subscription_id VARCHAR(255),
stripe_customer_id VARCHAR(255),
-- Auditoría
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID,
updated_at TIMESTAMP,
updated_by UUID,
CONSTRAINT uq_subscriptions_tenant UNIQUE (tenant_id), -- Solo 1 suscripción activa por tenant
CONSTRAINT chk_subscriptions_discount CHECK (discount_percent >= 0 AND discount_percent <= 100),
CONSTRAINT chk_subscriptions_period CHECK (current_period_end > current_period_start)
);
-- Tabla: payment_methods (Métodos de pago por tenant)
CREATE TABLE billing.payment_methods (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
type billing.payment_method_type NOT NULL,
is_default BOOLEAN NOT NULL DEFAULT false,
-- Información de tarjeta (solo últimos 4 dígitos por seguridad)
card_last_four VARCHAR(4),
card_brand VARCHAR(20), -- visa, mastercard, amex
card_exp_month INTEGER,
card_exp_year INTEGER,
-- Dirección de facturación
billing_name VARCHAR(255),
billing_email VARCHAR(255),
billing_address_line1 VARCHAR(255),
billing_address_line2 VARCHAR(255),
billing_city VARCHAR(100),
billing_state VARCHAR(100),
billing_postal_code VARCHAR(20),
billing_country VARCHAR(2), -- ISO 3166-1 alpha-2
-- Integración pasarela
stripe_payment_method_id VARCHAR(255),
-- Auditoría
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID,
updated_at TIMESTAMP,
deleted_at TIMESTAMP, -- Soft delete
CONSTRAINT chk_payment_methods_card_exp CHECK (
(type != 'card') OR
(card_exp_month BETWEEN 1 AND 12 AND card_exp_year >= EXTRACT(YEAR FROM CURRENT_DATE))
)
);
-- Tabla: billing_invoices (Facturas de suscripción)
CREATE TABLE billing.invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
subscription_id UUID REFERENCES billing.subscriptions(id),
-- Número de factura
invoice_number VARCHAR(50) NOT NULL,
-- Estado y fechas
status billing.invoice_status NOT NULL DEFAULT 'draft',
period_start TIMESTAMP,
period_end TIMESTAMP,
due_date DATE NOT NULL,
paid_at TIMESTAMP,
voided_at TIMESTAMP,
-- Montos
subtotal DECIMAL(12,2) NOT NULL DEFAULT 0,
tax_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
discount_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
total DECIMAL(12,2) NOT NULL DEFAULT 0,
amount_paid DECIMAL(12,2) NOT NULL DEFAULT 0,
amount_due DECIMAL(12,2) NOT NULL DEFAULT 0,
currency_code VARCHAR(3) NOT NULL DEFAULT 'MXN',
-- Datos fiscales del cliente
customer_name VARCHAR(255),
customer_tax_id VARCHAR(50),
customer_email VARCHAR(255),
customer_address TEXT,
-- PDF y CFDI (México)
pdf_url VARCHAR(500),
cfdi_uuid VARCHAR(36), -- UUID del CFDI si aplica
cfdi_xml_url VARCHAR(500),
-- Integración pasarela
stripe_invoice_id VARCHAR(255),
-- Notas
notes TEXT,
-- Auditoría
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID,
updated_at TIMESTAMP,
CONSTRAINT uq_invoices_number UNIQUE (invoice_number),
CONSTRAINT chk_invoices_amounts CHECK (total >= 0 AND subtotal >= 0 AND amount_due >= 0)
);
-- Tabla: invoice_lines (Líneas de detalle de factura)
CREATE TABLE billing.invoice_lines (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
invoice_id UUID NOT NULL REFERENCES billing.invoices(id) ON DELETE CASCADE,
description VARCHAR(255) NOT NULL,
quantity DECIMAL(12,4) NOT NULL DEFAULT 1,
unit_price DECIMAL(12,2) NOT NULL,
amount DECIMAL(12,2) NOT NULL,
-- Para facturación por uso
period_start TIMESTAMP,
period_end TIMESTAMP,
-- Auditoría
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_invoice_lines_qty CHECK (quantity > 0),
CONSTRAINT chk_invoice_lines_price CHECK (unit_price >= 0)
);
-- Tabla: payments (Pagos recibidos)
CREATE TABLE billing.payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
invoice_id UUID REFERENCES billing.invoices(id),
payment_method_id UUID REFERENCES billing.payment_methods(id),
-- Monto y moneda
amount DECIMAL(12,2) NOT NULL,
currency_code VARCHAR(3) NOT NULL DEFAULT 'MXN',
-- Estado
status billing.payment_status NOT NULL DEFAULT 'pending',
-- Fechas
paid_at TIMESTAMP,
failed_at TIMESTAMP,
refunded_at TIMESTAMP,
-- Detalles del error (si falló)
failure_reason VARCHAR(255),
failure_code VARCHAR(50),
-- Referencia de transacción
transaction_id VARCHAR(255),
stripe_payment_intent_id VARCHAR(255),
-- Auditoría
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_payments_amount CHECK (amount > 0)
);
-- Tabla: usage_records (Registros de uso para billing por consumo)
CREATE TABLE billing.usage_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
subscription_id UUID REFERENCES billing.subscriptions(id),
-- Tipo de métrica
metric_type VARCHAR(50) NOT NULL,
-- Ejemplos: 'users', 'storage_gb', 'api_calls', 'invoices_sent', 'emails_sent'
quantity DECIMAL(12,4) NOT NULL,
billing_period DATE NOT NULL, -- Mes de facturación (YYYY-MM-01)
-- Auditoría
recorded_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_usage_quantity CHECK (quantity >= 0)
);
-- Tabla: coupons (Cupones de descuento)
CREATE TABLE billing.coupons (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(50) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
-- Tipo de descuento
discount_type VARCHAR(20) NOT NULL DEFAULT 'percent',
-- 'percent': Porcentaje de descuento
-- 'fixed': Monto fijo de descuento
discount_value DECIMAL(12,2) NOT NULL,
currency_code VARCHAR(3) DEFAULT 'MXN', -- Solo para tipo 'fixed'
-- Restricciones
max_redemptions INTEGER, -- Máximo de usos totales
max_redemptions_per_tenant INTEGER DEFAULT 1, -- Máximo por tenant
redemptions_count INTEGER NOT NULL DEFAULT 0,
-- Vigencia
valid_from TIMESTAMP,
valid_until TIMESTAMP,
-- Aplicable a
applicable_plans UUID[], -- Array de plan_ids, NULL = todos
-- Estado
is_active BOOLEAN NOT NULL DEFAULT true,
-- Auditoría
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID,
CONSTRAINT chk_coupons_discount CHECK (
(discount_type = 'percent' AND discount_value > 0 AND discount_value <= 100) OR
(discount_type = 'fixed' AND discount_value > 0)
),
CONSTRAINT chk_coupons_dates CHECK (valid_until IS NULL OR valid_until > valid_from)
);
-- Tabla: coupon_redemptions (Uso de cupones)
CREATE TABLE 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) ON DELETE CASCADE,
subscription_id UUID REFERENCES billing.subscriptions(id),
-- Auditoría
redeemed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
redeemed_by UUID,
CONSTRAINT uq_coupon_redemptions UNIQUE (coupon_id, tenant_id)
);
-- Tabla: subscription_history (Historial de cambios de suscripción)
CREATE TABLE billing.subscription_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
subscription_id UUID NOT NULL REFERENCES billing.subscriptions(id) ON DELETE CASCADE,
event_type VARCHAR(50) NOT NULL,
-- 'created', 'upgraded', 'downgraded', 'renewed', 'cancelled',
-- 'paused', 'resumed', 'payment_failed', 'payment_succeeded'
previous_plan_id UUID REFERENCES billing.subscription_plans(id),
new_plan_id UUID REFERENCES billing.subscription_plans(id),
previous_status billing.subscription_status,
new_status billing.subscription_status,
-- Metadata adicional
metadata JSONB DEFAULT '{}'::jsonb,
notes TEXT,
-- Auditoría
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID
);
-- =====================================================
-- ÍNDICES
-- =====================================================
-- subscription_plans
CREATE INDEX idx_plans_is_active ON billing.subscription_plans(is_active) WHERE is_active = true;
CREATE INDEX idx_plans_is_public ON billing.subscription_plans(is_public) WHERE is_public = true;
-- tenant_owners
CREATE INDEX idx_tenant_owners_tenant_id ON billing.tenant_owners(tenant_id);
CREATE INDEX idx_tenant_owners_user_id ON billing.tenant_owners(user_id);
-- subscriptions
CREATE INDEX idx_subscriptions_tenant_id ON billing.subscriptions(tenant_id);
CREATE INDEX idx_subscriptions_status ON billing.subscriptions(status);
CREATE INDEX idx_subscriptions_period_end ON billing.subscriptions(current_period_end);
-- payment_methods
CREATE INDEX idx_payment_methods_tenant_id ON billing.payment_methods(tenant_id);
CREATE INDEX idx_payment_methods_default ON billing.payment_methods(tenant_id, is_default) WHERE is_default = true;
-- invoices
CREATE INDEX idx_invoices_tenant_id ON billing.invoices(tenant_id);
CREATE INDEX idx_invoices_status ON billing.invoices(status);
CREATE INDEX idx_invoices_due_date ON billing.invoices(due_date);
CREATE INDEX idx_invoices_stripe_id ON billing.invoices(stripe_invoice_id);
-- payments
CREATE INDEX idx_payments_tenant_id ON billing.payments(tenant_id);
CREATE INDEX idx_payments_status ON billing.payments(status);
CREATE INDEX idx_payments_invoice_id ON billing.payments(invoice_id);
-- usage_records
CREATE INDEX idx_usage_records_tenant_id ON billing.usage_records(tenant_id);
CREATE INDEX idx_usage_records_period ON billing.usage_records(billing_period);
CREATE INDEX idx_usage_records_metric ON billing.usage_records(metric_type, billing_period);
-- coupons
CREATE INDEX idx_coupons_code ON billing.coupons(code);
CREATE INDEX idx_coupons_active ON billing.coupons(is_active) WHERE is_active = true;
-- subscription_history
CREATE INDEX idx_subscription_history_subscription ON billing.subscription_history(subscription_id);
CREATE INDEX idx_subscription_history_created ON billing.subscription_history(created_at);
-- =====================================================
-- TRIGGERS
-- =====================================================
-- Trigger updated_at para subscriptions
CREATE TRIGGER trg_subscriptions_updated_at
BEFORE UPDATE ON billing.subscriptions
FOR EACH ROW EXECUTE FUNCTION auth.update_updated_at_column();
-- Trigger updated_at para payment_methods
CREATE TRIGGER trg_payment_methods_updated_at
BEFORE UPDATE ON billing.payment_methods
FOR EACH ROW EXECUTE FUNCTION auth.update_updated_at_column();
-- Trigger updated_at para invoices
CREATE TRIGGER trg_invoices_updated_at
BEFORE UPDATE ON billing.invoices
FOR EACH ROW EXECUTE FUNCTION auth.update_updated_at_column();
-- Trigger updated_at para subscription_plans
CREATE TRIGGER trg_plans_updated_at
BEFORE UPDATE ON billing.subscription_plans
FOR EACH ROW EXECUTE FUNCTION auth.update_updated_at_column();
-- =====================================================
-- FUNCIONES
-- =====================================================
-- Función para obtener el plan actual de un tenant
CREATE OR REPLACE FUNCTION billing.get_tenant_plan(p_tenant_id UUID)
RETURNS TABLE(
plan_code VARCHAR,
plan_name VARCHAR,
max_users INTEGER,
max_companies INTEGER,
features JSONB,
subscription_status billing.subscription_status,
days_until_renewal INTEGER
) AS $$
BEGIN
RETURN QUERY
SELECT
sp.code,
sp.name,
sp.max_users,
sp.max_companies,
sp.features,
s.status,
EXTRACT(DAY FROM s.current_period_end - CURRENT_TIMESTAMP)::INTEGER
FROM billing.subscriptions s
JOIN billing.subscription_plans sp ON s.plan_id = sp.id
WHERE s.tenant_id = p_tenant_id;
END;
$$ LANGUAGE plpgsql;
-- Función para verificar si tenant puede agregar más usuarios
CREATE OR REPLACE FUNCTION billing.can_add_user(p_tenant_id UUID)
RETURNS BOOLEAN AS $$
DECLARE
v_max_users INTEGER;
v_current_users INTEGER;
BEGIN
-- Obtener límite del plan
SELECT sp.max_users INTO v_max_users
FROM billing.subscriptions s
JOIN billing.subscription_plans sp ON s.plan_id = sp.id
WHERE s.tenant_id = p_tenant_id AND s.status IN ('active', 'trialing');
-- Si no hay límite (NULL), permitir
IF v_max_users IS NULL THEN
RETURN true;
END IF;
-- Contar usuarios actuales
SELECT COUNT(*) INTO v_current_users
FROM auth.users
WHERE tenant_id = p_tenant_id AND deleted_at IS NULL;
RETURN v_current_users < v_max_users;
END;
$$ LANGUAGE plpgsql;
-- Función para verificar si una feature está habilitada para el tenant
CREATE OR REPLACE FUNCTION billing.has_feature(p_tenant_id UUID, p_feature VARCHAR)
RETURNS BOOLEAN AS $$
DECLARE
v_features JSONB;
BEGIN
SELECT sp.features INTO v_features
FROM billing.subscriptions s
JOIN billing.subscription_plans sp ON s.plan_id = sp.id
WHERE s.tenant_id = p_tenant_id AND s.status IN ('active', 'trialing');
-- Si no hay plan o features, denegar
IF v_features IS NULL THEN
RETURN false;
END IF;
-- Verificar feature
RETURN COALESCE((v_features ->> p_feature)::boolean, false);
END;
$$ LANGUAGE plpgsql;
-- =====================================================
-- DATOS INICIALES (Plans por defecto)
-- =====================================================
-- Plan Free/Trial
INSERT INTO billing.subscription_plans (code, name, description, price_monthly, price_yearly, max_users, max_companies, max_storage_gb, trial_days, is_default, sort_order, features)
VALUES (
'free',
'Free / Trial',
'Plan gratuito para probar el sistema',
0, 0,
3, 1, 1, 14, true, 1,
'{"inventory": true, "sales": true, "financial": false, "purchase": false, "crm": false, "projects": false, "reports_basic": true, "reports_advanced": false, "api_access": false}'::jsonb
);
-- Plan Básico
INSERT INTO billing.subscription_plans (code, name, description, price_monthly, price_yearly, max_users, max_companies, max_storage_gb, trial_days, sort_order, features)
VALUES (
'basic',
'Básico',
'Ideal para pequeños negocios',
499, 4990,
5, 1, 5, 14, 2,
'{"inventory": true, "sales": true, "financial": true, "purchase": true, "crm": false, "projects": false, "reports_basic": true, "reports_advanced": false, "api_access": false}'::jsonb
);
-- Plan Profesional
INSERT INTO billing.subscription_plans (code, name, description, price_monthly, price_yearly, max_users, max_companies, max_storage_gb, trial_days, sort_order, features)
VALUES (
'professional',
'Profesional',
'Para empresas en crecimiento',
999, 9990,
15, 3, 20, 14, 3,
'{"inventory": true, "sales": true, "financial": true, "purchase": true, "crm": true, "projects": true, "reports_basic": true, "reports_advanced": true, "api_access": true}'::jsonb
);
-- Plan Enterprise
INSERT INTO billing.subscription_plans (code, name, description, price_monthly, price_yearly, max_users, max_companies, max_storage_gb, trial_days, sort_order, features)
VALUES (
'enterprise',
'Enterprise',
'Solución completa para grandes empresas',
2499, 24990,
NULL, NULL, 100, 30, 4, -- NULL = ilimitado
'{"inventory": true, "sales": true, "financial": true, "purchase": true, "crm": true, "projects": true, "reports_basic": true, "reports_advanced": true, "api_access": true, "white_label": true, "priority_support": true, "custom_integrations": true}'::jsonb
);
-- Plan Single-Tenant (para instalaciones on-premise)
INSERT INTO billing.subscription_plans (code, name, description, price_monthly, price_yearly, max_users, max_companies, max_storage_gb, trial_days, is_public, sort_order, features)
VALUES (
'single_tenant',
'Single Tenant / On-Premise',
'Instalación dedicada sin restricciones',
0, 0,
NULL, NULL, NULL, 0, false, 99, -- No público, solo asignación manual
'{"inventory": true, "sales": true, "financial": true, "purchase": true, "crm": true, "projects": true, "reports_basic": true, "reports_advanced": true, "api_access": true, "white_label": true, "priority_support": true, "custom_integrations": true, "unlimited": true}'::jsonb
);
-- =====================================================
-- COMENTARIOS
-- =====================================================
COMMENT ON SCHEMA billing IS 'Schema para gestión de suscripciones SaaS, planes, pagos y facturación';
COMMENT ON TABLE billing.subscription_plans IS 'Planes de suscripción disponibles (global, no por tenant)';
COMMENT ON TABLE billing.tenant_owners IS 'Propietarios/administradores de facturación de cada tenant';
COMMENT ON TABLE billing.subscriptions IS 'Suscripciones activas de cada tenant';
COMMENT ON TABLE billing.payment_methods IS 'Métodos de pago registrados por tenant';
COMMENT ON TABLE billing.invoices IS 'Facturas de suscripción';
COMMENT ON TABLE billing.invoice_lines IS 'Líneas de detalle de facturas';
COMMENT ON TABLE billing.payments IS 'Pagos recibidos';
COMMENT ON TABLE billing.usage_records IS 'Registros de uso para billing por consumo';
COMMENT ON TABLE billing.coupons IS 'Cupones de descuento';
COMMENT ON TABLE billing.coupon_redemptions IS 'Registro de cupones usados';
COMMENT ON TABLE billing.subscription_history IS 'Historial de cambios de suscripción';
COMMENT ON FUNCTION billing.get_tenant_plan IS 'Obtiene información del plan actual de un tenant';
COMMENT ON FUNCTION billing.can_add_user IS 'Verifica si el tenant puede agregar más usuarios según su plan';
COMMENT ON FUNCTION billing.has_feature IS 'Verifica si una feature está habilitada para el tenant';