workspace-v1/projects/erp-suite/apps/saas/billing/database/ddl/00-schema.sql
rckrdmrd 66161b1566 feat: Workspace-v1 complete migration with NEXUS v3.4
Sistema NEXUS v3.4 migrado con:

Estructura principal:
- core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles)
- core/catalog: Catalogo de funcionalidades reutilizables
- shared/knowledge-base: Base de conocimiento compartida
- devtools/scripts: Herramientas de desarrollo
- control-plane/registries: Control de servicios y CI/CD
- orchestration/: Configuracion de orquestacion de agentes

Proyectos incluidos (11):
- gamilit (submodule -> GitHub)
- trading-platform (OrbiquanTIA)
- erp-suite con 5 verticales:
  - erp-core, construccion, vidrio-templado
  - mecanicas-diesel, retail, clinicas
- betting-analytics
- inmobiliaria-analytics
- platform_marketing_content
- pos-micro, erp-basico

Configuracion:
- .gitignore completo para Node.js/Python/Docker
- gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git)
- Sistema de puertos estandarizado (3005-3199)

Generated with NEXUS v3.4 Migration System
EPIC-010: Configuracion Git y Repositorios
2026-01-04 03:37:42 -06:00

677 lines
22 KiB
PL/PgSQL

-- ============================================================================
-- SAAS LAYER - BILLING SCHEMA
-- ============================================================================
-- Version: 1.0.0
-- Description: Billing, subscriptions, and payments management for SaaS
-- Target: All ERP Suite products (POS Micro, ERP Básico, Verticales)
-- ============================================================================
-- Enable required extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ============================================================================
-- SCHEMA CREATION
-- ============================================================================
CREATE SCHEMA IF NOT EXISTS saas;
SET search_path TO saas, public;
-- ============================================================================
-- ENUMS
-- ============================================================================
CREATE TYPE saas.plan_type AS ENUM (
'pos_micro', -- 100 MXN/mes
'erp_basic', -- 300 MXN/mes
'erp_pro', -- 500 MXN/mes
'vertical' -- 1000+ MXN/mes
);
CREATE TYPE saas.billing_cycle AS ENUM (
'monthly',
'yearly'
);
CREATE TYPE saas.subscription_status AS ENUM (
'trial',
'active',
'past_due',
'suspended',
'cancelled',
'expired'
);
CREATE TYPE saas.payment_status AS ENUM (
'pending',
'processing',
'completed',
'failed',
'refunded',
'cancelled'
);
CREATE TYPE saas.invoice_status AS ENUM (
'draft',
'pending',
'paid',
'overdue',
'cancelled',
'refunded'
);
CREATE TYPE saas.payment_provider AS ENUM (
'stripe',
'conekta',
'oxxo',
'transfer',
'manual'
);
-- ============================================================================
-- TABLE 1: plans (Subscription plans)
-- ============================================================================
CREATE TABLE saas.plans (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Identification
code VARCHAR(50) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
-- Type
plan_type saas.plan_type NOT NULL,
-- Pricing (MXN)
price_monthly DECIMAL(10,2) NOT NULL,
price_yearly DECIMAL(10,2), -- Usually with discount
-- Limits
max_users INTEGER NOT NULL DEFAULT 1,
max_products INTEGER DEFAULT 500,
max_sales_per_month INTEGER DEFAULT 1000,
max_storage_mb INTEGER DEFAULT 100,
-- Features (JSON for flexibility)
features JSONB NOT NULL DEFAULT '{}',
-- Status
is_active BOOLEAN NOT NULL DEFAULT TRUE,
is_public BOOLEAN NOT NULL DEFAULT TRUE, -- Show in pricing page
sort_order INTEGER DEFAULT 0,
-- Stripe/Conekta IDs
stripe_price_id_monthly VARCHAR(100),
stripe_price_id_yearly VARCHAR(100),
conekta_plan_id VARCHAR(100),
-- Audit
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP
);
-- ============================================================================
-- TABLE 2: plan_features (Feature definitions)
-- ============================================================================
CREATE TABLE saas.plan_features (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
plan_id UUID NOT NULL REFERENCES saas.plans(id) ON DELETE CASCADE,
-- Feature
feature_code VARCHAR(50) NOT NULL, -- e.g., 'accounting', 'hr', 'cfdi'
feature_name VARCHAR(100) NOT NULL,
-- Pricing (for add-ons)
is_included BOOLEAN NOT NULL DEFAULT FALSE,
addon_price_monthly DECIMAL(10,2), -- Price if add-on
-- Status
is_active BOOLEAN NOT NULL DEFAULT TRUE,
CONSTRAINT uq_plan_features UNIQUE (plan_id, feature_code)
);
-- ============================================================================
-- TABLE 3: tenants (All SaaS customers)
-- ============================================================================
CREATE TABLE saas.tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Business info
business_name VARCHAR(200) NOT NULL,
legal_name VARCHAR(200),
tax_id VARCHAR(20), -- RFC México
-- Contact
owner_name VARCHAR(200) NOT NULL,
email VARCHAR(255) NOT NULL,
phone VARCHAR(20) NOT NULL,
whatsapp VARCHAR(20),
-- Address
address TEXT,
city VARCHAR(100),
state VARCHAR(50),
zip_code VARCHAR(10),
country VARCHAR(2) DEFAULT 'MX',
-- Product reference (which product schema to use)
product_type saas.plan_type NOT NULL,
product_schema VARCHAR(50), -- e.g., 'pos_micro', 'erp_basic'
-- Settings
timezone VARCHAR(50) DEFAULT 'America/Mexico_City',
currency VARCHAR(3) DEFAULT 'MXN',
language VARCHAR(10) DEFAULT 'es',
settings JSONB DEFAULT '{}',
-- Audit
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP,
CONSTRAINT uq_tenants_email UNIQUE (email),
CONSTRAINT uq_tenants_phone UNIQUE (phone)
);
CREATE INDEX idx_tenants_email ON saas.tenants(email);
CREATE INDEX idx_tenants_phone ON saas.tenants(phone);
CREATE INDEX idx_tenants_product_type ON saas.tenants(product_type);
-- ============================================================================
-- TABLE 4: subscriptions (Active subscriptions)
-- ============================================================================
CREATE TABLE saas.subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES saas.tenants(id) ON DELETE CASCADE,
plan_id UUID NOT NULL REFERENCES saas.plans(id),
-- Billing
billing_cycle saas.billing_cycle NOT NULL DEFAULT 'monthly',
current_price DECIMAL(10,2) NOT NULL,
-- Dates
trial_starts_at TIMESTAMP,
trial_ends_at TIMESTAMP,
current_period_start TIMESTAMP NOT NULL,
current_period_end TIMESTAMP NOT NULL,
cancelled_at TIMESTAMP,
-- Status
status saas.subscription_status NOT NULL DEFAULT 'trial',
cancel_reason TEXT,
-- External IDs
stripe_subscription_id VARCHAR(100),
conekta_subscription_id VARCHAR(100),
-- Features (active add-ons)
active_features JSONB DEFAULT '[]',
-- Audit
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP,
CONSTRAINT uq_subscriptions_tenant UNIQUE (tenant_id) -- One active subscription per tenant
);
CREATE INDEX idx_subscriptions_tenant_id ON saas.subscriptions(tenant_id);
CREATE INDEX idx_subscriptions_status ON saas.subscriptions(status);
CREATE INDEX idx_subscriptions_period_end ON saas.subscriptions(current_period_end);
-- ============================================================================
-- TABLE 5: invoices (Billing invoices)
-- ============================================================================
CREATE TABLE saas.invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES saas.tenants(id) ON DELETE CASCADE,
subscription_id UUID REFERENCES saas.subscriptions(id),
-- Invoice number
invoice_number VARCHAR(50) NOT NULL,
-- Dates
invoice_date DATE NOT NULL DEFAULT CURRENT_DATE,
due_date DATE NOT NULL,
paid_at TIMESTAMP,
-- Amounts
subtotal DECIMAL(10,2) NOT NULL,
tax_amount DECIMAL(10,2) NOT NULL DEFAULT 0,
discount_amount DECIMAL(10,2) NOT NULL DEFAULT 0,
total DECIMAL(10,2) NOT NULL,
-- Currency
currency VARCHAR(3) NOT NULL DEFAULT 'MXN',
-- Status
status saas.invoice_status NOT NULL DEFAULT 'draft',
-- CFDI (Mexico)
cfdi_uuid VARCHAR(50),
cfdi_xml TEXT,
cfdi_pdf_url VARCHAR(500),
-- External IDs
stripe_invoice_id VARCHAR(100),
conekta_order_id VARCHAR(100),
-- Notes
notes TEXT,
-- Audit
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP,
CONSTRAINT uq_invoices_number UNIQUE (invoice_number)
);
CREATE INDEX idx_invoices_tenant_id ON saas.invoices(tenant_id);
CREATE INDEX idx_invoices_status ON saas.invoices(status);
CREATE INDEX idx_invoices_due_date ON saas.invoices(due_date);
-- ============================================================================
-- TABLE 6: invoice_items (Invoice line items)
-- ============================================================================
CREATE TABLE saas.invoice_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
invoice_id UUID NOT NULL REFERENCES saas.invoices(id) ON DELETE CASCADE,
-- Description
description VARCHAR(255) NOT NULL,
-- Amounts
quantity INTEGER NOT NULL DEFAULT 1,
unit_price DECIMAL(10,2) NOT NULL,
subtotal DECIMAL(10,2) NOT NULL,
-- Reference
plan_id UUID REFERENCES saas.plans(id),
feature_code VARCHAR(50),
-- Audit
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_invoice_items_invoice_id ON saas.invoice_items(invoice_id);
-- ============================================================================
-- TABLE 7: payments (Payment transactions)
-- ============================================================================
CREATE TABLE saas.payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES saas.tenants(id) ON DELETE CASCADE,
invoice_id UUID REFERENCES saas.invoices(id),
-- Amount
amount DECIMAL(10,2) NOT NULL,
currency VARCHAR(3) NOT NULL DEFAULT 'MXN',
-- Provider
provider saas.payment_provider NOT NULL,
provider_payment_id VARCHAR(100),
provider_customer_id VARCHAR(100),
-- Status
status saas.payment_status NOT NULL DEFAULT 'pending',
-- Details
payment_method_type VARCHAR(50), -- card, oxxo, spei, etc.
last_four VARCHAR(4),
brand VARCHAR(20), -- visa, mastercard, etc.
-- Error handling
failure_code VARCHAR(50),
failure_message TEXT,
-- Dates
paid_at TIMESTAMP,
refunded_at TIMESTAMP,
-- Metadata
metadata JSONB DEFAULT '{}',
-- Audit
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP
);
CREATE INDEX idx_payments_tenant_id ON saas.payments(tenant_id);
CREATE INDEX idx_payments_invoice_id ON saas.payments(invoice_id);
CREATE INDEX idx_payments_status ON saas.payments(status);
CREATE INDEX idx_payments_provider ON saas.payments(provider);
-- ============================================================================
-- TABLE 8: payment_methods (Saved payment methods)
-- ============================================================================
CREATE TABLE saas.payment_methods (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES saas.tenants(id) ON DELETE CASCADE,
-- Provider
provider saas.payment_provider NOT NULL,
provider_payment_method_id VARCHAR(100) NOT NULL,
-- Card details (if applicable)
card_type VARCHAR(20),
card_brand VARCHAR(20),
card_last_four VARCHAR(4),
card_exp_month INTEGER,
card_exp_year INTEGER,
-- Status
is_default BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
-- Audit
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP
);
CREATE INDEX idx_payment_methods_tenant_id ON saas.payment_methods(tenant_id);
-- ============================================================================
-- TABLE 9: usage_tracking (Usage metrics for billing)
-- ============================================================================
CREATE TABLE saas.usage_tracking (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES saas.tenants(id) ON DELETE CASCADE,
-- Period
period_start DATE NOT NULL,
period_end DATE NOT NULL,
-- Metrics
users_count INTEGER DEFAULT 0,
products_count INTEGER DEFAULT 0,
sales_count INTEGER DEFAULT 0,
storage_used_mb INTEGER DEFAULT 0,
api_calls INTEGER DEFAULT 0,
-- WhatsApp/AI usage (billable)
whatsapp_messages INTEGER DEFAULT 0,
ai_tokens_used INTEGER DEFAULT 0,
-- Audit
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP,
CONSTRAINT uq_usage_tracking_tenant_period UNIQUE (tenant_id, period_start, period_end)
);
CREATE INDEX idx_usage_tracking_tenant_id ON saas.usage_tracking(tenant_id);
CREATE INDEX idx_usage_tracking_period ON saas.usage_tracking(period_start, period_end);
-- ============================================================================
-- TABLE 10: support_tickets (Basic support)
-- ============================================================================
CREATE TABLE saas.support_tickets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES saas.tenants(id) ON DELETE CASCADE,
-- Ticket
ticket_number VARCHAR(20) NOT NULL,
subject VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
-- Category
category VARCHAR(50) NOT NULL DEFAULT 'general', -- billing, technical, feature, general
priority VARCHAR(20) NOT NULL DEFAULT 'normal', -- low, normal, high, urgent
-- Status
status VARCHAR(20) NOT NULL DEFAULT 'open', -- open, in_progress, waiting, resolved, closed
-- Assignment
assigned_to VARCHAR(100),
-- Resolution
resolution TEXT,
resolved_at TIMESTAMP,
-- Audit
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP,
CONSTRAINT uq_support_tickets_number UNIQUE (ticket_number)
);
CREATE INDEX idx_support_tickets_tenant_id ON saas.support_tickets(tenant_id);
CREATE INDEX idx_support_tickets_status ON saas.support_tickets(status);
-- ============================================================================
-- UTILITY FUNCTIONS
-- ============================================================================
-- Function: Update updated_at timestamp
CREATE OR REPLACE FUNCTION saas.update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Function: Generate invoice number
CREATE OR REPLACE FUNCTION saas.generate_invoice_number()
RETURNS VARCHAR AS $$
DECLARE
v_year VARCHAR;
v_seq INTEGER;
BEGIN
v_year := TO_CHAR(CURRENT_DATE, 'YYYY');
SELECT COALESCE(MAX(
CAST(SUBSTRING(invoice_number FROM 5) AS INTEGER)
), 0) + 1
INTO v_seq
FROM saas.invoices
WHERE invoice_number LIKE v_year || '%';
RETURN v_year || LPAD(v_seq::TEXT, 6, '0');
END;
$$ LANGUAGE plpgsql;
-- Function: Generate ticket number
CREATE OR REPLACE FUNCTION saas.generate_ticket_number()
RETURNS VARCHAR AS $$
DECLARE
v_date VARCHAR;
v_seq INTEGER;
BEGIN
v_date := TO_CHAR(CURRENT_DATE, 'YYMMDD');
SELECT COALESCE(MAX(
CAST(SUBSTRING(ticket_number FROM 8) AS INTEGER)
), 0) + 1
INTO v_seq
FROM saas.support_tickets
WHERE DATE(created_at) = CURRENT_DATE;
RETURN 'TK' || v_date || LPAD(v_seq::TEXT, 4, '0');
END;
$$ LANGUAGE plpgsql;
-- ============================================================================
-- TRIGGERS
-- ============================================================================
CREATE TRIGGER trg_plans_updated_at
BEFORE UPDATE ON saas.plans
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
CREATE TRIGGER trg_tenants_updated_at
BEFORE UPDATE ON saas.tenants
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
CREATE TRIGGER trg_subscriptions_updated_at
BEFORE UPDATE ON saas.subscriptions
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
CREATE TRIGGER trg_invoices_updated_at
BEFORE UPDATE ON saas.invoices
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
CREATE TRIGGER trg_payments_updated_at
BEFORE UPDATE ON saas.payments
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
CREATE TRIGGER trg_payment_methods_updated_at
BEFORE UPDATE ON saas.payment_methods
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
CREATE TRIGGER trg_usage_tracking_updated_at
BEFORE UPDATE ON saas.usage_tracking
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
CREATE TRIGGER trg_support_tickets_updated_at
BEFORE UPDATE ON saas.support_tickets
FOR EACH ROW EXECUTE FUNCTION saas.update_updated_at();
-- Auto-generate invoice number
CREATE OR REPLACE FUNCTION saas.auto_invoice_number()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.invoice_number IS NULL THEN
NEW.invoice_number := saas.generate_invoice_number();
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_invoices_auto_number
BEFORE INSERT ON saas.invoices
FOR EACH ROW EXECUTE FUNCTION saas.auto_invoice_number();
-- Auto-generate ticket number
CREATE OR REPLACE FUNCTION saas.auto_ticket_number()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.ticket_number IS NULL THEN
NEW.ticket_number := saas.generate_ticket_number();
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_tickets_auto_number
BEFORE INSERT ON saas.support_tickets
FOR EACH ROW EXECUTE FUNCTION saas.auto_ticket_number();
-- ============================================================================
-- SEED DATA: Default plans
-- ============================================================================
INSERT INTO saas.plans (code, name, description, plan_type, price_monthly, price_yearly, max_users, max_products, max_sales_per_month, features) VALUES
-- POS Micro
('pos_micro', 'POS Micro', 'Punto de venta ultra-básico para pequeños negocios', 'pos_micro', 100.00, 1000.00, 1, 500, 1000,
'{"offline_mode": true, "whatsapp_basic": true, "reports_basic": true}'),
-- ERP Básico
('erp_basic', 'ERP Básico', 'ERP completo pero austero para PyMEs', 'erp_basic', 300.00, 3000.00, 5, 10000, 5000,
'{"inventory": true, "sales": true, "purchases": true, "reports": true, "multi_warehouse": false}'),
-- ERP Pro
('erp_pro', 'ERP Pro', 'ERP completo con módulos avanzados', 'erp_pro', 500.00, 5000.00, 10, 50000, 20000,
'{"inventory": true, "sales": true, "purchases": true, "reports": true, "multi_warehouse": true, "accounting_basic": true}'),
-- Vertical (template)
('vertical_base', 'Vertical Industry', 'ERP especializado por industria', 'vertical', 1000.00, 10000.00, 20, 100000, 50000,
'{"inventory": true, "sales": true, "purchases": true, "reports": true, "multi_warehouse": true, "accounting": true, "industry_specific": true}');
-- Feature definitions
INSERT INTO saas.plan_features (plan_id, feature_code, feature_name, is_included, addon_price_monthly) VALUES
-- POS Micro add-ons
((SELECT id FROM saas.plans WHERE code = 'pos_micro'), 'cfdi', 'Facturación CFDI', FALSE, 50.00),
((SELECT id FROM saas.plans WHERE code = 'pos_micro'), 'whatsapp_pro', 'WhatsApp Avanzado', FALSE, 100.00),
-- ERP Básico add-ons
((SELECT id FROM saas.plans WHERE code = 'erp_basic'), 'accounting', 'Contabilidad', FALSE, 150.00),
((SELECT id FROM saas.plans WHERE code = 'erp_basic'), 'hr', 'Recursos Humanos', FALSE, 100.00),
((SELECT id FROM saas.plans WHERE code = 'erp_basic'), 'cfdi', 'Facturación CFDI', FALSE, 100.00),
((SELECT id FROM saas.plans WHERE code = 'erp_basic'), 'extra_user', 'Usuario Extra', FALSE, 50.00),
((SELECT id FROM saas.plans WHERE code = 'erp_basic'), 'multi_warehouse', 'Multi-Almacén', FALSE, 100.00),
-- ERP Pro included features
((SELECT id FROM saas.plans WHERE code = 'erp_pro'), 'accounting', 'Contabilidad Básica', TRUE, NULL),
((SELECT id FROM saas.plans WHERE code = 'erp_pro'), 'multi_warehouse', 'Multi-Almacén', TRUE, NULL),
((SELECT id FROM saas.plans WHERE code = 'erp_pro'), 'cfdi', 'Facturación CFDI', FALSE, 100.00),
((SELECT id FROM saas.plans WHERE code = 'erp_pro'), 'hr', 'Recursos Humanos', FALSE, 100.00),
((SELECT id FROM saas.plans WHERE code = 'erp_pro'), 'extra_user', 'Usuario Extra', FALSE, 50.00);
-- ============================================================================
-- VIEWS
-- ============================================================================
-- View: Active subscriptions with plan details
CREATE OR REPLACE VIEW saas.active_subscriptions_view AS
SELECT
s.id,
s.tenant_id,
t.business_name,
t.email,
p.code as plan_code,
p.name as plan_name,
s.billing_cycle,
s.current_price,
s.status,
s.current_period_start,
s.current_period_end,
s.trial_ends_at,
CASE
WHEN s.status = 'trial' THEN s.trial_ends_at - CURRENT_TIMESTAMP
ELSE s.current_period_end - CURRENT_TIMESTAMP
END as time_remaining
FROM saas.subscriptions s
JOIN saas.tenants t ON s.tenant_id = t.id
JOIN saas.plans p ON s.plan_id = p.id
WHERE s.status IN ('trial', 'active', 'past_due');
-- View: Revenue by plan
CREATE OR REPLACE VIEW saas.revenue_by_plan AS
SELECT
p.code as plan_code,
p.name as plan_name,
COUNT(s.id) as active_subscriptions,
SUM(s.current_price) as monthly_revenue
FROM saas.plans p
LEFT JOIN saas.subscriptions s ON p.id = s.plan_id
AND s.status IN ('active', 'trial')
GROUP BY p.id, p.code, p.name
ORDER BY monthly_revenue DESC NULLS LAST;
-- ============================================================================
-- COMMENTS
-- ============================================================================
COMMENT ON SCHEMA saas IS 'SaaS Layer - Billing, subscriptions, and multi-tenancy management';
COMMENT ON TABLE saas.plans IS 'Available subscription plans with pricing';
COMMENT ON TABLE saas.plan_features IS 'Features included or available as add-ons per plan';
COMMENT ON TABLE saas.tenants IS 'All SaaS customers/businesses';
COMMENT ON TABLE saas.subscriptions IS 'Active subscriptions per tenant';
COMMENT ON TABLE saas.invoices IS 'Generated invoices for billing';
COMMENT ON TABLE saas.payments IS 'Payment transactions';
COMMENT ON TABLE saas.payment_methods IS 'Saved payment methods per tenant';
COMMENT ON TABLE saas.usage_tracking IS 'Usage metrics for billing and limits';
COMMENT ON TABLE saas.support_tickets IS 'Customer support tickets';
-- ============================================================================
-- SCHEMA COMPLETE
-- ============================================================================
DO $$
BEGIN
RAISE NOTICE 'SaaS Billing schema created successfully!';
RAISE NOTICE 'Tables: 10 (plans, plan_features, tenants, subscriptions, invoices, invoice_items, payments, payment_methods, usage_tracking, support_tickets)';
RAISE NOTICE 'Default plans: POS Micro (100 MXN), ERP Básico (300 MXN), ERP Pro (500 MXN), Vertical (1000+ MXN)';
END $$;