trading-platform-database-v2/ddl/schemas/tenants/tables/001_tenants.sql
rckrdmrd e520268348 Migración desde trading-platform/apps/database - Estándar multi-repo v2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 08:32:52 -06:00

166 lines
5.3 KiB
PL/PgSQL

-- ============================================================================
-- SCHEMA: tenants
-- TABLE: tenants, tenant_settings
-- DESCRIPTION: Sistema de Multi-Tenancy para organizaciones/empresas
-- VERSION: 1.0.0
-- CREATED: 2026-01-10
-- ============================================================================
-- Crear schema si no existe
CREATE SCHEMA IF NOT EXISTS tenants;
-- Enum para estado de tenant
DO $$ BEGIN
CREATE TYPE tenants.tenant_status AS ENUM (
'pending', -- Pendiente de activacion
'active', -- Activo y operativo
'suspended', -- Suspendido (impago o violacion)
'deleted' -- Eliminado (soft delete)
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Enum para estado de subscription
DO $$ BEGIN
CREATE TYPE tenants.subscription_status AS ENUM (
'trialing', -- En periodo de prueba
'active', -- Activo con pagos al dia
'past_due', -- Pago vencido
'canceled', -- Cancelado
'incomplete' -- Configuracion incompleta
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Tabla principal de Tenants (Organizaciones)
CREATE TABLE IF NOT EXISTS tenants.tenants (
-- Identificadores
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Informacion basica
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
domain VARCHAR(255),
logo_url TEXT,
-- Estado
status tenants.tenant_status NOT NULL DEFAULT 'pending',
-- Subscription info (referencia a billing)
plan_id UUID, -- FK a plans.plans (se agrega despues)
subscription_status tenants.subscription_status DEFAULT 'trialing',
trial_ends_at TIMESTAMPTZ DEFAULT (NOW() + INTERVAL '14 days'),
-- Stripe integration
stripe_customer_id VARCHAR(255),
stripe_subscription_id VARCHAR(255),
-- Settings (JSONB flexible)
settings JSONB NOT NULL DEFAULT '{
"timezone": "America/Mexico_City",
"locale": "es-MX",
"currency": "MXN",
"date_format": "DD/MM/YYYY"
}'::JSONB,
-- Metadata adicional
metadata JSONB DEFAULT '{}'::JSONB,
-- Soft delete
deleted_at TIMESTAMPTZ,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE tenants.tenants IS
'Organizaciones/empresas que usan la plataforma. Base del sistema multi-tenant.';
COMMENT ON COLUMN tenants.tenants.slug IS
'Identificador URL-safe unico para el tenant (ej: acme-corp)';
COMMENT ON COLUMN tenants.tenants.settings IS
'Configuracion del tenant: timezone, locale, currency, etc.';
-- Indices
CREATE INDEX IF NOT EXISTS idx_tenants_slug
ON tenants.tenants(slug);
CREATE INDEX IF NOT EXISTS idx_tenants_status
ON tenants.tenants(status) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_tenants_stripe_customer
ON tenants.tenants(stripe_customer_id) WHERE stripe_customer_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_tenants_subscription_status
ON tenants.tenants(subscription_status);
-- Trigger para updated_at
CREATE OR REPLACE FUNCTION tenants.update_tenant_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS tenant_updated_at ON tenants.tenants;
CREATE TRIGGER tenant_updated_at
BEFORE UPDATE ON tenants.tenants
FOR EACH ROW
EXECUTE FUNCTION tenants.update_tenant_timestamp();
-- ============================================================================
-- TABLA: tenant_settings (configuracion extendida)
-- ============================================================================
CREATE TABLE IF NOT EXISTS tenants.tenant_settings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
-- Configuracion por categoria
category VARCHAR(50) NOT NULL, -- 'branding', 'notifications', 'security', etc.
key VARCHAR(100) NOT NULL,
value JSONB NOT NULL,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Unique por tenant + category + key
UNIQUE (tenant_id, category, key)
);
COMMENT ON TABLE tenants.tenant_settings IS
'Configuraciones extendidas por tenant, organizadas por categoria';
-- Indices
CREATE INDEX IF NOT EXISTS idx_tenant_settings_tenant
ON tenants.tenant_settings(tenant_id);
CREATE INDEX IF NOT EXISTS idx_tenant_settings_category
ON tenants.tenant_settings(tenant_id, category);
-- Trigger para updated_at
DROP TRIGGER IF EXISTS tenant_settings_updated_at ON tenants.tenant_settings;
CREATE TRIGGER tenant_settings_updated_at
BEFORE UPDATE ON tenants.tenant_settings
FOR EACH ROW
EXECUTE FUNCTION tenants.update_tenant_timestamp();
-- RLS Policy (tenant_settings aislado por tenant)
ALTER TABLE tenants.tenant_settings ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_settings_isolation ON tenants.tenant_settings
FOR ALL
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
-- Grants
GRANT SELECT, INSERT, UPDATE ON tenants.tenants TO trading_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON tenants.tenant_settings TO trading_app;
GRANT SELECT ON tenants.tenants TO trading_readonly;
GRANT SELECT ON tenants.tenant_settings TO trading_readonly;