erp-core-database-v2/ddl/11-crm.sql

367 lines
11 KiB
SQL

-- =====================================================
-- SCHEMA: crm
-- PROPOSITO: Customer Relationship Management
-- MODULOS: MGN-CRM (CRM)
-- FECHA: 2025-11-24
-- =====================================================
-- Crear schema
CREATE SCHEMA IF NOT EXISTS crm;
-- =====================================================
-- TYPES (ENUMs)
-- =====================================================
CREATE TYPE crm.lead_status AS ENUM (
'new',
'contacted',
'qualified',
'converted',
'lost'
);
CREATE TYPE crm.opportunity_status AS ENUM (
'open',
'won',
'lost'
);
CREATE TYPE crm.activity_type AS ENUM (
'call',
'email',
'meeting',
'task',
'note'
);
CREATE TYPE crm.lead_source AS ENUM (
'website',
'phone',
'email',
'referral',
'social_media',
'advertising',
'event',
'other'
);
-- =====================================================
-- TABLES
-- =====================================================
-- Tabla: lead_stages (Etapas del pipeline de leads)
CREATE TABLE crm.lead_stages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
sequence INTEGER NOT NULL DEFAULT 10,
is_won BOOLEAN DEFAULT FALSE,
probability DECIMAL(5, 2) DEFAULT 0,
requirements TEXT,
active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
UNIQUE(tenant_id, name)
);
-- Tabla: opportunity_stages (Etapas del pipeline de oportunidades)
CREATE TABLE crm.opportunity_stages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
sequence INTEGER NOT NULL DEFAULT 10,
is_won BOOLEAN DEFAULT FALSE,
probability DECIMAL(5, 2) DEFAULT 0,
requirements TEXT,
active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
UNIQUE(tenant_id, name)
);
-- Tabla: lost_reasons (Razones de perdida)
CREATE TABLE crm.lost_reasons (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
UNIQUE(tenant_id, name)
);
-- Tabla: leads (Prospectos/Leads)
CREATE TABLE crm.leads (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,
-- Numeracion
name VARCHAR(255) NOT NULL,
ref VARCHAR(100),
-- Contacto
contact_name VARCHAR(255),
email VARCHAR(255),
phone VARCHAR(50),
mobile VARCHAR(50),
website VARCHAR(255),
-- Empresa del prospecto
company_name VARCHAR(255),
job_position VARCHAR(100),
industry VARCHAR(100),
employee_count VARCHAR(50),
annual_revenue DECIMAL(15, 2),
-- Direccion
street VARCHAR(255),
city VARCHAR(100),
state VARCHAR(100),
zip VARCHAR(20),
country VARCHAR(100),
-- Pipeline
stage_id UUID REFERENCES crm.lead_stages(id),
status crm.lead_status NOT NULL DEFAULT 'new',
-- Asignacion
user_id UUID REFERENCES auth.users(id),
sales_team_id UUID REFERENCES sales.sales_teams(id),
-- Origen
source crm.lead_source,
campaign_id UUID, -- Para futuro modulo marketing
medium VARCHAR(100),
-- Valoracion
priority INTEGER DEFAULT 0 CHECK (priority >= 0 AND priority <= 3),
probability DECIMAL(5, 2) DEFAULT 0,
expected_revenue DECIMAL(15, 2),
-- Fechas
date_open TIMESTAMP WITH TIME ZONE,
date_closed TIMESTAMP WITH TIME ZONE,
date_deadline DATE,
date_last_activity TIMESTAMP WITH TIME ZONE,
-- Conversion
partner_id UUID REFERENCES core.partners(id),
opportunity_id UUID, -- Se llena al convertir
-- Perdida
lost_reason_id UUID REFERENCES crm.lost_reasons(id),
lost_notes TEXT,
-- Notas
description TEXT,
notes TEXT,
tags VARCHAR(255)[],
-- Auditoria
created_by UUID REFERENCES auth.users(id),
updated_by UUID REFERENCES auth.users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Tabla: opportunities (Oportunidades de venta)
CREATE TABLE crm.opportunities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
company_id UUID NOT NULL REFERENCES auth.companies(id) ON DELETE CASCADE,
-- Numeracion
name VARCHAR(255) NOT NULL,
ref VARCHAR(100),
-- Cliente
partner_id UUID NOT NULL REFERENCES core.partners(id),
contact_name VARCHAR(255),
email VARCHAR(255),
phone VARCHAR(50),
-- Pipeline
stage_id UUID REFERENCES crm.opportunity_stages(id),
status crm.opportunity_status NOT NULL DEFAULT 'open',
-- Asignacion
user_id UUID REFERENCES auth.users(id),
sales_team_id UUID REFERENCES sales.sales_teams(id),
-- Valoracion
priority INTEGER DEFAULT 0 CHECK (priority >= 0 AND priority <= 3),
probability DECIMAL(5, 2) DEFAULT 0,
expected_revenue DECIMAL(15, 2),
recurring_revenue DECIMAL(15, 2),
recurring_plan VARCHAR(50),
-- Fechas
date_deadline DATE,
date_closed TIMESTAMP WITH TIME ZONE,
date_last_activity TIMESTAMP WITH TIME ZONE,
-- Origen (si viene de lead)
lead_id UUID REFERENCES crm.leads(id),
source crm.lead_source,
campaign_id UUID,
medium VARCHAR(100),
-- Cierre
lost_reason_id UUID REFERENCES crm.lost_reasons(id),
lost_notes TEXT,
-- Relaciones
quotation_id UUID REFERENCES sales.quotations(id),
order_id UUID REFERENCES sales.sales_orders(id),
-- Notas
description TEXT,
notes TEXT,
tags VARCHAR(255)[],
-- Auditoria
created_by UUID REFERENCES auth.users(id),
updated_by UUID REFERENCES auth.users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Actualizar referencia circular en leads
ALTER TABLE crm.leads ADD CONSTRAINT fk_leads_opportunity
FOREIGN KEY (opportunity_id) REFERENCES crm.opportunities(id);
-- Tabla: crm_activities (Actividades CRM)
CREATE TABLE crm.activities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
-- Referencia polimorfica
res_model VARCHAR(100) NOT NULL,
res_id UUID NOT NULL,
-- Actividad
activity_type crm.activity_type NOT NULL,
summary VARCHAR(255),
description TEXT,
-- Fechas
date_deadline DATE,
date_done TIMESTAMP WITH TIME ZONE,
-- Asignacion
user_id UUID REFERENCES auth.users(id),
assigned_to UUID REFERENCES auth.users(id),
-- Estado
done BOOLEAN DEFAULT FALSE,
-- Auditoria
created_by UUID REFERENCES auth.users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- =====================================================
-- INDEXES
-- =====================================================
CREATE INDEX idx_lead_stages_tenant ON crm.lead_stages(tenant_id);
CREATE INDEX idx_opportunity_stages_tenant ON crm.opportunity_stages(tenant_id);
CREATE INDEX idx_lost_reasons_tenant ON crm.lost_reasons(tenant_id);
CREATE INDEX idx_leads_tenant ON crm.leads(tenant_id);
CREATE INDEX idx_leads_company ON crm.leads(company_id);
CREATE INDEX idx_leads_status ON crm.leads(status);
CREATE INDEX idx_leads_stage ON crm.leads(stage_id);
CREATE INDEX idx_leads_user ON crm.leads(user_id);
CREATE INDEX idx_leads_partner ON crm.leads(partner_id);
CREATE INDEX idx_leads_email ON crm.leads(email);
CREATE INDEX idx_opportunities_tenant ON crm.opportunities(tenant_id);
CREATE INDEX idx_opportunities_company ON crm.opportunities(company_id);
CREATE INDEX idx_opportunities_status ON crm.opportunities(status);
CREATE INDEX idx_opportunities_stage ON crm.opportunities(stage_id);
CREATE INDEX idx_opportunities_user ON crm.opportunities(user_id);
CREATE INDEX idx_opportunities_partner ON crm.opportunities(partner_id);
CREATE INDEX idx_crm_activities_tenant ON crm.activities(tenant_id);
CREATE INDEX idx_crm_activities_model ON crm.activities(res_model, res_id);
CREATE INDEX idx_crm_activities_user ON crm.activities(assigned_to);
CREATE INDEX idx_crm_activities_deadline ON crm.activities(date_deadline);
-- =====================================================
-- TRIGGERS
-- =====================================================
CREATE TRIGGER update_lead_stages_timestamp
BEFORE UPDATE ON crm.lead_stages
FOR EACH ROW EXECUTE FUNCTION core.update_timestamp();
CREATE TRIGGER update_opportunity_stages_timestamp
BEFORE UPDATE ON crm.opportunity_stages
FOR EACH ROW EXECUTE FUNCTION core.update_timestamp();
CREATE TRIGGER update_leads_timestamp
BEFORE UPDATE ON crm.leads
FOR EACH ROW EXECUTE FUNCTION core.update_timestamp();
CREATE TRIGGER update_opportunities_timestamp
BEFORE UPDATE ON crm.opportunities
FOR EACH ROW EXECUTE FUNCTION core.update_timestamp();
CREATE TRIGGER update_crm_activities_timestamp
BEFORE UPDATE ON crm.activities
FOR EACH ROW EXECUTE FUNCTION core.update_timestamp();
-- =====================================================
-- ROW LEVEL SECURITY
-- =====================================================
-- Habilitar RLS
ALTER TABLE crm.lead_stages ENABLE ROW LEVEL SECURITY;
ALTER TABLE crm.opportunity_stages ENABLE ROW LEVEL SECURITY;
ALTER TABLE crm.lost_reasons ENABLE ROW LEVEL SECURITY;
ALTER TABLE crm.leads ENABLE ROW LEVEL SECURITY;
ALTER TABLE crm.opportunities ENABLE ROW LEVEL SECURITY;
ALTER TABLE crm.activities ENABLE ROW LEVEL SECURITY;
-- Políticas de aislamiento por tenant
CREATE POLICY tenant_isolation_lead_stages ON crm.lead_stages
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
CREATE POLICY tenant_isolation_opportunity_stages ON crm.opportunity_stages
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
CREATE POLICY tenant_isolation_lost_reasons ON crm.lost_reasons
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
CREATE POLICY tenant_isolation_leads ON crm.leads
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
CREATE POLICY tenant_isolation_opportunities ON crm.opportunities
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
CREATE POLICY tenant_isolation_crm_activities ON crm.activities
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
-- =====================================================
-- COMMENTS
-- =====================================================
COMMENT ON TABLE crm.lead_stages IS 'Etapas del pipeline de leads';
COMMENT ON TABLE crm.opportunity_stages IS 'Etapas del pipeline de oportunidades';
COMMENT ON TABLE crm.lost_reasons IS 'Razones de perdida de leads/oportunidades';
COMMENT ON TABLE crm.leads IS 'Prospectos/leads de ventas';
COMMENT ON TABLE crm.opportunities IS 'Oportunidades de venta';
COMMENT ON TABLE crm.activities IS 'Actividades CRM (llamadas, reuniones, etc.)';