- Update vision, architecture and technical documentation - Update module definitions (PMC-001 to PMC-008) - Update requirements documentation - Add CONTEXT-MAP.yml and ENVIRONMENT-INVENTORY.yml - Add orchestration guidelines and references 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1031 lines
34 KiB
Markdown
1031 lines
34 KiB
Markdown
---
|
|
id: "ESQUEMA-BD"
|
|
title: "Esquema de Base de Datos - Platform Marketing Content"
|
|
type: "Model"
|
|
status: "Draft"
|
|
project: "platform_marketing_content"
|
|
version: "1.0.0"
|
|
created_date: "2026-01-04"
|
|
updated_date: "2026-01-04"
|
|
---
|
|
# Esquema de Base de Datos - Platform Marketing Content
|
|
|
|
**Versión:** 1.0.0
|
|
**Fecha:** 2025-12-08
|
|
**Motor:** PostgreSQL 15+
|
|
|
|
---
|
|
|
|
## Configuración General
|
|
|
|
```sql
|
|
-- Extensiones requeridas
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
|
|
-- Schemas por módulo
|
|
CREATE SCHEMA IF NOT EXISTS auth;
|
|
CREATE SCHEMA IF NOT EXISTS crm;
|
|
CREATE SCHEMA IF NOT EXISTS projects;
|
|
CREATE SCHEMA IF NOT EXISTS generation;
|
|
CREATE SCHEMA IF NOT EXISTS assets;
|
|
CREATE SCHEMA IF NOT EXISTS automation;
|
|
CREATE SCHEMA IF NOT EXISTS analytics;
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: auth
|
|
|
|
### Tabla: plans
|
|
|
|
```sql
|
|
CREATE TABLE auth.plans (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
name VARCHAR(100) NOT NULL,
|
|
code VARCHAR(50) NOT NULL UNIQUE,
|
|
features JSONB NOT NULL DEFAULT '{}',
|
|
limits JSONB NOT NULL DEFAULT '{}',
|
|
price_monthly DECIMAL(10,2) DEFAULT 0,
|
|
price_yearly DECIMAL(10,2) DEFAULT 0,
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
COMMENT ON TABLE auth.plans IS 'Planes de suscripción con límites';
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_plans_code ON auth.plans(code);
|
|
CREATE INDEX idx_plans_active ON auth.plans(is_active);
|
|
```
|
|
|
|
### Tabla: tenants
|
|
|
|
```sql
|
|
CREATE TABLE auth.tenants (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
name VARCHAR(255) NOT NULL,
|
|
slug VARCHAR(100) NOT NULL UNIQUE,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'active'
|
|
CHECK (status IN ('active', 'suspended', 'trial', 'cancelled')),
|
|
plan_id UUID NOT NULL REFERENCES auth.plans(id),
|
|
settings JSONB NOT NULL DEFAULT '{}',
|
|
branding JSONB NOT NULL DEFAULT '{}',
|
|
limits JSONB DEFAULT NULL,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
deleted_at TIMESTAMP WITH TIME ZONE DEFAULT NULL
|
|
);
|
|
|
|
COMMENT ON TABLE auth.tenants IS 'Organizaciones/agencias que usan la plataforma';
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_tenants_slug ON auth.tenants(slug);
|
|
CREATE INDEX idx_tenants_status ON auth.tenants(status);
|
|
CREATE INDEX idx_tenants_plan ON auth.tenants(plan_id);
|
|
```
|
|
|
|
### Tabla: roles
|
|
|
|
```sql
|
|
CREATE TABLE auth.roles (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID REFERENCES auth.tenants(id),
|
|
name VARCHAR(100) NOT NULL,
|
|
description TEXT,
|
|
permissions TEXT[] NOT NULL DEFAULT '{}',
|
|
is_system BOOLEAN NOT NULL DEFAULT false,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
COMMENT ON TABLE auth.roles IS 'Roles con permisos asignados';
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_roles_tenant ON auth.roles(tenant_id);
|
|
CREATE UNIQUE INDEX idx_roles_name_tenant ON auth.roles(tenant_id, name);
|
|
```
|
|
|
|
### Tabla: users
|
|
|
|
```sql
|
|
CREATE TABLE auth.users (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
email VARCHAR(255) NOT NULL,
|
|
password_hash VARCHAR(255) NOT NULL,
|
|
first_name VARCHAR(100) NOT NULL,
|
|
last_name VARCHAR(100) NOT NULL,
|
|
avatar_url VARCHAR(500),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending'
|
|
CHECK (status IN ('pending', 'active', 'suspended', 'deactivated')),
|
|
role_id UUID NOT NULL REFERENCES auth.roles(id),
|
|
preferences JSONB NOT NULL DEFAULT '{}',
|
|
last_login_at TIMESTAMP WITH TIME ZONE,
|
|
email_verified_at TIMESTAMP WITH TIME ZONE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(tenant_id, email)
|
|
);
|
|
|
|
COMMENT ON TABLE auth.users IS 'Usuarios del sistema';
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_users_tenant ON auth.users(tenant_id);
|
|
CREATE INDEX idx_users_email ON auth.users(email);
|
|
CREATE INDEX idx_users_role ON auth.users(role_id);
|
|
CREATE INDEX idx_users_status ON auth.users(status);
|
|
```
|
|
|
|
### Tabla: invitations
|
|
|
|
```sql
|
|
CREATE TABLE auth.invitations (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
email VARCHAR(255) NOT NULL,
|
|
role_id UUID NOT NULL REFERENCES auth.roles(id),
|
|
invited_by UUID NOT NULL REFERENCES auth.users(id),
|
|
token VARCHAR(255) NOT NULL UNIQUE,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending'
|
|
CHECK (status IN ('pending', 'accepted', 'expired', 'cancelled')),
|
|
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
accepted_at TIMESTAMP WITH TIME ZONE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
COMMENT ON TABLE auth.invitations IS 'Invitaciones pendientes';
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_invitations_tenant ON auth.invitations(tenant_id);
|
|
CREATE INDEX idx_invitations_token ON auth.invitations(token);
|
|
CREATE INDEX idx_invitations_status ON auth.invitations(status);
|
|
```
|
|
|
|
### Tabla: audit_logs
|
|
|
|
```sql
|
|
CREATE TABLE auth.audit_logs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
user_id UUID REFERENCES auth.users(id),
|
|
action VARCHAR(100) NOT NULL,
|
|
entity_type VARCHAR(100),
|
|
entity_id UUID,
|
|
old_values JSONB,
|
|
new_values JSONB,
|
|
ip_address INET,
|
|
user_agent TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
COMMENT ON TABLE auth.audit_logs IS 'Registro de auditoría inmutable';
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_audit_tenant ON auth.audit_logs(tenant_id);
|
|
CREATE INDEX idx_audit_user ON auth.audit_logs(user_id);
|
|
CREATE INDEX idx_audit_action ON auth.audit_logs(action);
|
|
CREATE INDEX idx_audit_created ON auth.audit_logs(created_at);
|
|
```
|
|
|
|
### Tabla: settings
|
|
|
|
```sql
|
|
CREATE TABLE auth.settings (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID REFERENCES auth.tenants(id),
|
|
key VARCHAR(255) NOT NULL,
|
|
value JSONB NOT NULL,
|
|
type VARCHAR(20) NOT NULL DEFAULT 'string'
|
|
CHECK (type IN ('string', 'number', 'boolean', 'json')),
|
|
category VARCHAR(100),
|
|
is_secret BOOLEAN NOT NULL DEFAULT false,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(tenant_id, key)
|
|
);
|
|
|
|
COMMENT ON TABLE auth.settings IS 'Configuración del sistema';
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_settings_tenant ON auth.settings(tenant_id);
|
|
CREATE INDEX idx_settings_category ON auth.settings(category);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: crm
|
|
|
|
### Tabla: clients
|
|
|
|
```sql
|
|
CREATE TABLE crm.clients (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
legal_name VARCHAR(255),
|
|
tax_id VARCHAR(50),
|
|
industry VARCHAR(100),
|
|
size VARCHAR(20) CHECK (size IN ('micro', 'small', 'medium', 'large', 'enterprise')),
|
|
website VARCHAR(500),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'prospect'
|
|
CHECK (status IN ('prospect', 'active', 'inactive', 'churned')),
|
|
notes TEXT,
|
|
metadata JSONB DEFAULT '{}',
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_clients_tenant ON crm.clients(tenant_id);
|
|
CREATE INDEX idx_clients_status ON crm.clients(status);
|
|
CREATE INDEX idx_clients_name ON crm.clients(name);
|
|
|
|
-- RLS
|
|
ALTER TABLE crm.clients ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON crm.clients
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: contacts
|
|
|
|
```sql
|
|
CREATE TABLE crm.contacts (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
client_id UUID NOT NULL REFERENCES crm.clients(id) ON DELETE CASCADE,
|
|
first_name VARCHAR(100) NOT NULL,
|
|
last_name VARCHAR(100) NOT NULL,
|
|
email VARCHAR(255) NOT NULL,
|
|
phone VARCHAR(50),
|
|
position VARCHAR(100),
|
|
department VARCHAR(100),
|
|
is_primary BOOLEAN NOT NULL DEFAULT false,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'active'
|
|
CHECK (status IN ('active', 'inactive')),
|
|
metadata JSONB DEFAULT '{}',
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_contacts_tenant ON crm.contacts(tenant_id);
|
|
CREATE INDEX idx_contacts_client ON crm.contacts(client_id);
|
|
CREATE INDEX idx_contacts_email ON crm.contacts(email);
|
|
|
|
-- RLS
|
|
ALTER TABLE crm.contacts ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON crm.contacts
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: brands
|
|
|
|
```sql
|
|
CREATE TABLE crm.brands (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
client_id UUID NOT NULL REFERENCES crm.clients(id) ON DELETE CASCADE,
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'active'
|
|
CHECK (status IN ('active', 'inactive')),
|
|
identity JSONB NOT NULL DEFAULT '{}',
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_brands_tenant ON crm.brands(tenant_id);
|
|
CREATE INDEX idx_brands_client ON crm.brands(client_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE crm.brands ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON crm.brands
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: products
|
|
|
|
```sql
|
|
CREATE TABLE crm.products (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
brand_id UUID NOT NULL REFERENCES crm.brands(id) ON DELETE CASCADE,
|
|
name VARCHAR(255) NOT NULL,
|
|
sku VARCHAR(100),
|
|
description TEXT,
|
|
category VARCHAR(100),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'active'
|
|
CHECK (status IN ('active', 'discontinued', 'coming_soon')),
|
|
attributes JSONB DEFAULT '{}',
|
|
reference_images TEXT[] DEFAULT '{}',
|
|
lora_model_id UUID,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_products_tenant ON crm.products(tenant_id);
|
|
CREATE INDEX idx_products_brand ON crm.products(brand_id);
|
|
CREATE INDEX idx_products_sku ON crm.products(sku);
|
|
|
|
-- RLS
|
|
ALTER TABLE crm.products ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON crm.products
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: opportunities
|
|
|
|
```sql
|
|
CREATE TABLE crm.opportunities (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
client_id UUID NOT NULL REFERENCES crm.clients(id),
|
|
contact_id UUID REFERENCES crm.contacts(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
value DECIMAL(15,2),
|
|
currency VARCHAR(3) DEFAULT 'USD',
|
|
stage VARCHAR(20) NOT NULL DEFAULT 'lead'
|
|
CHECK (stage IN ('lead', 'qualified', 'proposal', 'negotiation', 'won', 'lost')),
|
|
probability INTEGER DEFAULT 0 CHECK (probability >= 0 AND probability <= 100),
|
|
expected_close_date DATE,
|
|
actual_close_date DATE,
|
|
lost_reason TEXT,
|
|
notes TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_opportunities_tenant ON crm.opportunities(tenant_id);
|
|
CREATE INDEX idx_opportunities_client ON crm.opportunities(client_id);
|
|
CREATE INDEX idx_opportunities_stage ON crm.opportunities(stage);
|
|
|
|
-- RLS
|
|
ALTER TABLE crm.opportunities ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON crm.opportunities
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: projects
|
|
|
|
### Tabla: projects
|
|
|
|
```sql
|
|
CREATE TABLE projects.projects (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
client_id UUID NOT NULL REFERENCES crm.clients(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
code VARCHAR(50) NOT NULL,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'draft'
|
|
CHECK (status IN ('draft', 'active', 'on_hold', 'completed', 'cancelled')),
|
|
start_date DATE,
|
|
end_date DATE,
|
|
budget DECIMAL(15,2),
|
|
currency VARCHAR(3) DEFAULT 'USD',
|
|
owner_id UUID NOT NULL REFERENCES auth.users(id),
|
|
team_members UUID[] DEFAULT '{}',
|
|
settings JSONB DEFAULT '{}',
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(tenant_id, code)
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_projects_tenant ON projects.projects(tenant_id);
|
|
CREATE INDEX idx_projects_client ON projects.projects(client_id);
|
|
CREATE INDEX idx_projects_status ON projects.projects(status);
|
|
CREATE INDEX idx_projects_owner ON projects.projects(owner_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE projects.projects ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON projects.projects
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: campaigns
|
|
|
|
```sql
|
|
CREATE TABLE projects.campaigns (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
project_id UUID NOT NULL REFERENCES projects.projects(id) ON DELETE CASCADE,
|
|
brand_id UUID NOT NULL REFERENCES crm.brands(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
type VARCHAR(50) NOT NULL
|
|
CHECK (type IN ('social_media', 'performance_ads', 'catalog', 'landing', 'email', 'other')),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'draft'
|
|
CHECK (status IN ('draft', 'briefing', 'in_production', 'review', 'approved', 'published', 'archived')),
|
|
brief JSONB NOT NULL DEFAULT '{}',
|
|
channels TEXT[] DEFAULT '{}',
|
|
start_date DATE,
|
|
end_date DATE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_campaigns_tenant ON projects.campaigns(tenant_id);
|
|
CREATE INDEX idx_campaigns_project ON projects.campaigns(project_id);
|
|
CREATE INDEX idx_campaigns_brand ON projects.campaigns(brand_id);
|
|
CREATE INDEX idx_campaigns_status ON projects.campaigns(status);
|
|
|
|
-- RLS
|
|
ALTER TABLE projects.campaigns ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON projects.campaigns
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: campaign_assets
|
|
|
|
```sql
|
|
CREATE TABLE projects.campaign_assets (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
campaign_id UUID NOT NULL REFERENCES projects.campaigns(id) ON DELETE CASCADE,
|
|
asset_id UUID NOT NULL REFERENCES assets.assets(id) ON DELETE CASCADE,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending'
|
|
CHECK (status IN ('pending', 'approved', 'rejected', 'revision_requested')),
|
|
feedback TEXT,
|
|
approved_by UUID REFERENCES auth.users(id),
|
|
approved_at TIMESTAMP WITH TIME ZONE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(campaign_id, asset_id)
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_campaign_assets_campaign ON projects.campaign_assets(campaign_id);
|
|
CREATE INDEX idx_campaign_assets_asset ON projects.campaign_assets(asset_id);
|
|
CREATE INDEX idx_campaign_assets_status ON projects.campaign_assets(status);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: generation
|
|
|
|
### Tabla: workflow_templates
|
|
|
|
```sql
|
|
CREATE TABLE generation.workflow_templates (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID REFERENCES auth.tenants(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
type VARCHAR(50) NOT NULL
|
|
CHECK (type IN ('product_photo', 'social_post', 'banner', 'avatar', 'variation', 'custom')),
|
|
category VARCHAR(100),
|
|
comfyui_workflow JSONB NOT NULL,
|
|
input_schema JSONB NOT NULL,
|
|
output_config JSONB NOT NULL DEFAULT '{}',
|
|
models_required TEXT[] DEFAULT '{}',
|
|
estimated_time_seconds INTEGER DEFAULT 60,
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
is_system BOOLEAN NOT NULL DEFAULT false,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_workflows_tenant ON generation.workflow_templates(tenant_id);
|
|
CREATE INDEX idx_workflows_type ON generation.workflow_templates(type);
|
|
CREATE INDEX idx_workflows_active ON generation.workflow_templates(is_active);
|
|
```
|
|
|
|
### Tabla: custom_models
|
|
|
|
```sql
|
|
CREATE TABLE generation.custom_models (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
brand_id UUID REFERENCES crm.brands(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
type VARCHAR(20) NOT NULL
|
|
CHECK (type IN ('lora', 'checkpoint', 'embedding', 'controlnet')),
|
|
purpose VARCHAR(100),
|
|
description TEXT,
|
|
file_path VARCHAR(500) NOT NULL,
|
|
file_size BIGINT NOT NULL,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'ready'
|
|
CHECK (status IN ('training', 'ready', 'failed', 'archived')),
|
|
training_params JSONB DEFAULT '{}',
|
|
trigger_word VARCHAR(100),
|
|
preview_images TEXT[] DEFAULT '{}',
|
|
metadata JSONB DEFAULT '{}',
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_models_tenant ON generation.custom_models(tenant_id);
|
|
CREATE INDEX idx_models_brand ON generation.custom_models(brand_id);
|
|
CREATE INDEX idx_models_type ON generation.custom_models(type);
|
|
CREATE INDEX idx_models_status ON generation.custom_models(status);
|
|
|
|
-- RLS
|
|
ALTER TABLE generation.custom_models ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON generation.custom_models
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: generation_jobs
|
|
|
|
```sql
|
|
CREATE TABLE generation.generation_jobs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
campaign_id UUID REFERENCES projects.campaigns(id),
|
|
product_id UUID REFERENCES crm.products(id),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id),
|
|
type VARCHAR(20) NOT NULL
|
|
CHECK (type IN ('image', 'text', 'image_batch', 'mixed')),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'queued'
|
|
CHECK (status IN ('queued', 'processing', 'completed', 'failed', 'cancelled')),
|
|
priority INTEGER NOT NULL DEFAULT 5 CHECK (priority >= 1 AND priority <= 10),
|
|
workflow_id UUID REFERENCES generation.workflow_templates(id),
|
|
input_params JSONB NOT NULL,
|
|
output_assets UUID[] DEFAULT '{}',
|
|
error_message TEXT,
|
|
progress INTEGER DEFAULT 0 CHECK (progress >= 0 AND progress <= 100),
|
|
started_at TIMESTAMP WITH TIME ZONE,
|
|
completed_at TIMESTAMP WITH TIME ZONE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_jobs_tenant ON generation.generation_jobs(tenant_id);
|
|
CREATE INDEX idx_jobs_campaign ON generation.generation_jobs(campaign_id);
|
|
CREATE INDEX idx_jobs_user ON generation.generation_jobs(user_id);
|
|
CREATE INDEX idx_jobs_status ON generation.generation_jobs(status);
|
|
CREATE INDEX idx_jobs_priority ON generation.generation_jobs(priority DESC);
|
|
CREATE INDEX idx_jobs_created ON generation.generation_jobs(created_at);
|
|
|
|
-- RLS
|
|
ALTER TABLE generation.generation_jobs ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON generation.generation_jobs
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: text_generations
|
|
|
|
```sql
|
|
CREATE TABLE generation.text_generations (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
job_id UUID REFERENCES generation.generation_jobs(id),
|
|
campaign_id UUID REFERENCES projects.campaigns(id),
|
|
type VARCHAR(20) NOT NULL
|
|
CHECK (type IN ('copy', 'title', 'description', 'hashtags', 'cta', 'full_post')),
|
|
prompt TEXT NOT NULL,
|
|
context JSONB NOT NULL DEFAULT '{}',
|
|
output TEXT,
|
|
variations TEXT[] DEFAULT '{}',
|
|
model_used VARCHAR(100),
|
|
tokens_used INTEGER,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'pending'
|
|
CHECK (status IN ('pending', 'completed', 'failed')),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_text_gen_tenant ON generation.text_generations(tenant_id);
|
|
CREATE INDEX idx_text_gen_job ON generation.text_generations(job_id);
|
|
CREATE INDEX idx_text_gen_campaign ON generation.text_generations(campaign_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE generation.text_generations ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON generation.text_generations
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: assets
|
|
|
|
### Tabla: assets
|
|
|
|
```sql
|
|
CREATE TABLE assets.assets (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
type VARCHAR(20) NOT NULL
|
|
CHECK (type IN ('image', 'copy', 'video', 'document', 'model', 'template')),
|
|
mime_type VARCHAR(100),
|
|
file_path VARCHAR(500) NOT NULL,
|
|
file_size BIGINT NOT NULL,
|
|
dimensions JSONB,
|
|
duration INTEGER,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'draft'
|
|
CHECK (status IN ('draft', 'pending_review', 'approved', 'rejected', 'archived')),
|
|
visibility VARCHAR(20) NOT NULL DEFAULT 'team'
|
|
CHECK (visibility IN ('private', 'team', 'client')),
|
|
source VARCHAR(20) NOT NULL DEFAULT 'uploaded'
|
|
CHECK (source IN ('generated', 'uploaded', 'imported')),
|
|
generation_job_id UUID REFERENCES generation.generation_jobs(id),
|
|
metadata JSONB DEFAULT '{}',
|
|
tags TEXT[] DEFAULT '{}',
|
|
created_by UUID NOT NULL REFERENCES auth.users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
deleted_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_assets_tenant ON assets.assets(tenant_id);
|
|
CREATE INDEX idx_assets_type ON assets.assets(type);
|
|
CREATE INDEX idx_assets_status ON assets.assets(status);
|
|
CREATE INDEX idx_assets_job ON assets.assets(generation_job_id);
|
|
CREATE INDEX idx_assets_created ON assets.assets(created_at);
|
|
CREATE INDEX idx_assets_tags ON assets.assets USING GIN(tags);
|
|
CREATE INDEX idx_assets_name_search ON assets.assets USING GIN(to_tsvector('spanish', name || ' ' || COALESCE(description, '')));
|
|
|
|
-- RLS
|
|
ALTER TABLE assets.assets ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON assets.assets
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: asset_versions
|
|
|
|
```sql
|
|
CREATE TABLE assets.asset_versions (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
asset_id UUID NOT NULL REFERENCES assets.assets(id) ON DELETE CASCADE,
|
|
version_number INTEGER NOT NULL,
|
|
file_path VARCHAR(500) NOT NULL,
|
|
file_size BIGINT NOT NULL,
|
|
changes_description TEXT,
|
|
created_by UUID NOT NULL REFERENCES auth.users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(asset_id, version_number)
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_versions_asset ON assets.asset_versions(asset_id);
|
|
```
|
|
|
|
### Tabla: collections
|
|
|
|
```sql
|
|
CREATE TABLE assets.collections (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
type VARCHAR(20) NOT NULL DEFAULT 'manual'
|
|
CHECK (type IN ('manual', 'smart', 'campaign', 'brand')),
|
|
smart_filters JSONB,
|
|
cover_asset_id UUID REFERENCES assets.assets(id),
|
|
is_public BOOLEAN NOT NULL DEFAULT false,
|
|
created_by UUID NOT NULL REFERENCES auth.users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_collections_tenant ON assets.collections(tenant_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE assets.collections ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON assets.collections
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: collection_assets
|
|
|
|
```sql
|
|
CREATE TABLE assets.collection_assets (
|
|
collection_id UUID NOT NULL REFERENCES assets.collections(id) ON DELETE CASCADE,
|
|
asset_id UUID NOT NULL REFERENCES assets.assets(id) ON DELETE CASCADE,
|
|
added_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
PRIMARY KEY (collection_id, asset_id)
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_coll_assets_collection ON assets.collection_assets(collection_id);
|
|
CREATE INDEX idx_coll_assets_asset ON assets.collection_assets(asset_id);
|
|
```
|
|
|
|
### Tabla: asset_comments
|
|
|
|
```sql
|
|
CREATE TABLE assets.asset_comments (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
asset_id UUID NOT NULL REFERENCES assets.assets(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL REFERENCES auth.users(id),
|
|
content TEXT NOT NULL,
|
|
position JSONB,
|
|
is_resolved BOOLEAN NOT NULL DEFAULT false,
|
|
parent_id UUID REFERENCES assets.asset_comments(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_comments_asset ON assets.asset_comments(asset_id);
|
|
CREATE INDEX idx_comments_user ON assets.asset_comments(user_id);
|
|
CREATE INDEX idx_comments_parent ON assets.asset_comments(parent_id);
|
|
```
|
|
|
|
### Tabla: downloads
|
|
|
|
```sql
|
|
CREATE TABLE assets.downloads (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
asset_id UUID REFERENCES assets.assets(id),
|
|
collection_id UUID REFERENCES assets.collections(id),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id),
|
|
download_type VARCHAR(20) NOT NULL
|
|
CHECK (download_type IN ('single', 'batch', 'collection')),
|
|
format VARCHAR(50),
|
|
ip_address INET,
|
|
user_agent TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_downloads_tenant ON assets.downloads(tenant_id);
|
|
CREATE INDEX idx_downloads_asset ON assets.downloads(asset_id);
|
|
CREATE INDEX idx_downloads_user ON assets.downloads(user_id);
|
|
CREATE INDEX idx_downloads_created ON assets.downloads(created_at);
|
|
|
|
-- RLS
|
|
ALTER TABLE assets.downloads ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON assets.downloads
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: automation
|
|
|
|
### Tabla: automation_flows
|
|
|
|
```sql
|
|
CREATE TABLE automation.automation_flows (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
type VARCHAR(20) NOT NULL
|
|
CHECK (type IN ('trigger_based', 'scheduled', 'manual')),
|
|
trigger_event VARCHAR(100),
|
|
n8n_workflow_id VARCHAR(100),
|
|
is_active BOOLEAN NOT NULL DEFAULT false,
|
|
last_run TIMESTAMP WITH TIME ZONE,
|
|
run_count INTEGER NOT NULL DEFAULT 0,
|
|
config JSONB NOT NULL DEFAULT '{}',
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_flows_tenant ON automation.automation_flows(tenant_id);
|
|
CREATE INDEX idx_flows_trigger ON automation.automation_flows(trigger_event);
|
|
CREATE INDEX idx_flows_active ON automation.automation_flows(is_active);
|
|
|
|
-- RLS
|
|
ALTER TABLE automation.automation_flows ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON automation.automation_flows
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: automation_runs
|
|
|
|
```sql
|
|
CREATE TABLE automation.automation_runs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
flow_id UUID NOT NULL REFERENCES automation.automation_flows(id),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'running'
|
|
CHECK (status IN ('running', 'completed', 'failed', 'cancelled')),
|
|
trigger_data JSONB,
|
|
output_data JSONB,
|
|
error_message TEXT,
|
|
started_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
completed_at TIMESTAMP WITH TIME ZONE,
|
|
duration_ms INTEGER
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_runs_flow ON automation.automation_runs(flow_id);
|
|
CREATE INDEX idx_runs_tenant ON automation.automation_runs(tenant_id);
|
|
CREATE INDEX idx_runs_status ON automation.automation_runs(status);
|
|
CREATE INDEX idx_runs_started ON automation.automation_runs(started_at);
|
|
|
|
-- RLS
|
|
ALTER TABLE automation.automation_runs ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON automation.automation_runs
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: webhook_endpoints
|
|
|
|
```sql
|
|
CREATE TABLE automation.webhook_endpoints (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
slug VARCHAR(100) NOT NULL,
|
|
secret_key VARCHAR(255) NOT NULL,
|
|
target_flow_id UUID NOT NULL REFERENCES automation.automation_flows(id),
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
last_called TIMESTAMP WITH TIME ZONE,
|
|
call_count INTEGER NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
UNIQUE(tenant_id, slug)
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_webhooks_tenant ON automation.webhook_endpoints(tenant_id);
|
|
CREATE INDEX idx_webhooks_slug ON automation.webhook_endpoints(slug);
|
|
|
|
-- RLS
|
|
ALTER TABLE automation.webhook_endpoints ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON automation.webhook_endpoints
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
---
|
|
|
|
## Schema: analytics
|
|
|
|
### Tabla: metrics
|
|
|
|
```sql
|
|
CREATE TABLE analytics.metrics (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
metric_type VARCHAR(100) NOT NULL,
|
|
dimension_1 VARCHAR(255),
|
|
dimension_2 VARCHAR(255),
|
|
value DECIMAL(20,4) NOT NULL,
|
|
period_type VARCHAR(10) NOT NULL
|
|
CHECK (period_type IN ('hour', 'day', 'week', 'month')),
|
|
period_start TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_metrics_tenant_type ON analytics.metrics(tenant_id, metric_type);
|
|
CREATE INDEX idx_metrics_period ON analytics.metrics(period_type, period_start);
|
|
CREATE INDEX idx_metrics_tenant_period ON analytics.metrics(tenant_id, period_type, period_start);
|
|
|
|
-- RLS
|
|
ALTER TABLE analytics.metrics ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON analytics.metrics
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: reports
|
|
|
|
```sql
|
|
CREATE TABLE analytics.reports (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
type VARCHAR(50) NOT NULL,
|
|
parameters JSONB NOT NULL DEFAULT '{}',
|
|
file_path VARCHAR(500),
|
|
file_format VARCHAR(10) NOT NULL
|
|
CHECK (file_format IN ('pdf', 'xlsx', 'csv')),
|
|
generated_by UUID NOT NULL REFERENCES auth.users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_reports_tenant ON analytics.reports(tenant_id);
|
|
CREATE INDEX idx_reports_type ON analytics.reports(type);
|
|
|
|
-- RLS
|
|
ALTER TABLE analytics.reports ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON analytics.reports
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
### Tabla: saved_views
|
|
|
|
```sql
|
|
CREATE TABLE analytics.saved_views (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
|
|
user_id UUID NOT NULL REFERENCES auth.users(id),
|
|
name VARCHAR(255) NOT NULL,
|
|
dashboard VARCHAR(50) NOT NULL,
|
|
config JSONB NOT NULL,
|
|
is_default BOOLEAN NOT NULL DEFAULT false,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_views_tenant ON analytics.saved_views(tenant_id);
|
|
CREATE INDEX idx_views_user ON analytics.saved_views(user_id);
|
|
|
|
-- RLS
|
|
ALTER TABLE analytics.saved_views ENABLE ROW LEVEL SECURITY;
|
|
CREATE POLICY tenant_isolation ON analytics.saved_views
|
|
USING (tenant_id = current_setting('app.current_tenant')::uuid);
|
|
```
|
|
|
|
---
|
|
|
|
## Tabla: brand_models (relación N:N)
|
|
|
|
```sql
|
|
CREATE TABLE generation.brand_models (
|
|
brand_id UUID NOT NULL REFERENCES crm.brands(id) ON DELETE CASCADE,
|
|
model_id UUID NOT NULL REFERENCES generation.custom_models(id) ON DELETE CASCADE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
PRIMARY KEY (brand_id, model_id)
|
|
);
|
|
|
|
-- Índices
|
|
CREATE INDEX idx_brand_models_brand ON generation.brand_models(brand_id);
|
|
CREATE INDEX idx_brand_models_model ON generation.brand_models(model_id);
|
|
```
|
|
|
|
---
|
|
|
|
## Funciones de Utilidad
|
|
|
|
```sql
|
|
-- Función para generar código de proyecto
|
|
CREATE OR REPLACE FUNCTION projects.generate_project_code(p_tenant_id UUID)
|
|
RETURNS VARCHAR AS $$
|
|
DECLARE
|
|
v_year VARCHAR(4);
|
|
v_count INTEGER;
|
|
BEGIN
|
|
v_year := TO_CHAR(CURRENT_DATE, 'YYYY');
|
|
|
|
SELECT COUNT(*) + 1 INTO v_count
|
|
FROM projects.projects
|
|
WHERE tenant_id = p_tenant_id
|
|
AND code LIKE 'PRJ-' || v_year || '-%';
|
|
|
|
RETURN 'PRJ-' || v_year || '-' || LPAD(v_count::TEXT, 3, '0');
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Trigger para auto-generar código de proyecto
|
|
CREATE OR REPLACE FUNCTION projects.set_project_code()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.code IS NULL OR NEW.code = '' THEN
|
|
NEW.code := projects.generate_project_code(NEW.tenant_id);
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER tr_set_project_code
|
|
BEFORE INSERT ON projects.projects
|
|
FOR EACH ROW EXECUTE FUNCTION projects.set_project_code();
|
|
|
|
-- Función para actualizar updated_at
|
|
CREATE OR REPLACE FUNCTION update_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = CURRENT_TIMESTAMP;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Aplicar trigger a todas las tablas con updated_at
|
|
-- (Ejemplo para una tabla, repetir para todas)
|
|
CREATE TRIGGER tr_update_timestamp
|
|
BEFORE UPDATE ON auth.tenants
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
```
|
|
|
|
---
|
|
|
|
## Referencias
|
|
|
|
- [MODELO-DOMINIO.md](./MODELO-DOMINIO.md)
|
|
- [ARQUITECTURA-TECNICA.md](../00-vision-general/ARQUITECTURA-TECNICA.md)
|
|
|
|
---
|
|
|
|
**Documento generado por:** Requirements-Analyst
|
|
**Fecha:** 2025-12-08
|