feat: versão 1.5 - CRM Beta com leads, funis, campanhas e portal do cliente

This commit is contained in:
Erik Silva
2025-12-24 17:36:52 -03:00
parent 99d828869a
commit dfb91c8ba5
98 changed files with 18255 additions and 1465 deletions

View File

@@ -0,0 +1,70 @@
-- Tabela de leads do CRM (multi-tenant)
CREATE TABLE IF NOT EXISTS crm_leads (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
-- Dados básicos
name VARCHAR(255),
email VARCHAR(255),
phone VARCHAR(50),
-- Origem do lead
source VARCHAR(50) DEFAULT 'import',
source_meta JSONB DEFAULT '{}'::jsonb,
-- Status
status VARCHAR(50) DEFAULT 'novo',
-- Informações adicionais
notes TEXT,
tags TEXT[],
-- Controle
is_active BOOLEAN DEFAULT true,
created_by UUID REFERENCES users(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- Constraint: email deve ser único por agência (pode repetir entre agências)
CONSTRAINT unique_lead_email_per_tenant UNIQUE (tenant_id, email)
);
-- Relacionamento N:N entre leads e listas
CREATE TABLE IF NOT EXISTS crm_lead_lists (
lead_id UUID REFERENCES crm_leads(id) ON DELETE CASCADE,
list_id UUID REFERENCES crm_lists(id) ON DELETE CASCADE,
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
added_by UUID REFERENCES users(id),
PRIMARY KEY (lead_id, list_id)
);
-- Índices para performance
CREATE INDEX IF NOT EXISTS idx_crm_leads_tenant_id ON crm_leads(tenant_id);
CREATE INDEX IF NOT EXISTS idx_crm_leads_email ON crm_leads(email);
CREATE INDEX IF NOT EXISTS idx_crm_leads_phone ON crm_leads(phone);
CREATE INDEX IF NOT EXISTS idx_crm_leads_status ON crm_leads(status);
CREATE INDEX IF NOT EXISTS idx_crm_leads_is_active ON crm_leads(is_active);
CREATE INDEX IF NOT EXISTS idx_crm_leads_tags ON crm_leads USING GIN(tags);
CREATE INDEX IF NOT EXISTS idx_crm_leads_source ON crm_leads(source);
CREATE INDEX IF NOT EXISTS idx_crm_lead_lists_lead_id ON crm_lead_lists(lead_id);
CREATE INDEX IF NOT EXISTS idx_crm_lead_lists_list_id ON crm_lead_lists(list_id);
-- Trigger para atualizar updated_at (usa a função update_updated_at_column criada em 014_create_crm_tables.sql)
DO $$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_proc
WHERE proname = 'update_updated_at_column'
) THEN
IF NOT EXISTS (
SELECT 1
FROM pg_trigger
WHERE tgname = 'update_crm_leads_updated_at'
) THEN
CREATE TRIGGER update_crm_leads_updated_at BEFORE UPDATE ON crm_leads
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
END IF;
END IF;
END $$;

View File

@@ -0,0 +1,11 @@
-- Adicionar relacionamento entre leads e customers (clientes)
-- Um lead pertence a um cliente da agência
ALTER TABLE crm_leads
ADD COLUMN customer_id UUID REFERENCES crm_customers(id) ON DELETE SET NULL;
-- Índice para performance
CREATE INDEX IF NOT EXISTS idx_crm_leads_customer_id ON crm_leads(customer_id);
-- Comentário explicativo
COMMENT ON COLUMN crm_leads.customer_id IS 'Cliente (customer) ao qual este lead pertence. Permite que agências gerenciem leads de seus clientes.';

View File

@@ -0,0 +1,20 @@
-- Migration: Create CRM share tokens table
-- Description: Allows generating secure shareable links for customers to view their leads
CREATE TABLE IF NOT EXISTS crm_share_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
customer_id UUID NOT NULL REFERENCES crm_customers(id) ON DELETE CASCADE,
token VARCHAR(64) NOT NULL UNIQUE,
expires_at TIMESTAMP,
created_by UUID NOT NULL REFERENCES users(id),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_crm_share_tokens_token ON crm_share_tokens(token);
CREATE INDEX idx_crm_share_tokens_customer_id ON crm_share_tokens(customer_id);
CREATE INDEX idx_crm_share_tokens_tenant_id ON crm_share_tokens(tenant_id);
COMMENT ON TABLE crm_share_tokens IS 'Tokens for sharing customer lead data externally';
COMMENT ON COLUMN crm_share_tokens.token IS 'Unique secure token for accessing shared data';
COMMENT ON COLUMN crm_share_tokens.expires_at IS 'Optional expiration date (NULL = never expires)';

View File

@@ -0,0 +1,21 @@
-- Migration 018: Adicionar autenticação para clientes (Portal do Cliente)
-- Permite que clientes (CRMCustomer) façam login e vejam seus próprios leads
-- Adicionar coluna de senha para clientes
ALTER TABLE crm_customers
ADD COLUMN IF NOT EXISTS password_hash VARCHAR(255),
ADD COLUMN IF NOT EXISTS has_portal_access BOOLEAN DEFAULT false,
ADD COLUMN IF NOT EXISTS portal_last_login TIMESTAMP,
ADD COLUMN IF NOT EXISTS portal_created_at TIMESTAMP DEFAULT NOW();
-- Criar índice para busca por email (login)
CREATE INDEX IF NOT EXISTS idx_crm_customers_email ON crm_customers(email);
-- Criar índice para clientes com acesso ao portal
CREATE INDEX IF NOT EXISTS idx_crm_customers_portal_access ON crm_customers(has_portal_access) WHERE has_portal_access = true;
-- Comentários
COMMENT ON COLUMN crm_customers.password_hash IS 'Hash bcrypt da senha para acesso ao portal do cliente';
COMMENT ON COLUMN crm_customers.has_portal_access IS 'Define se o cliente tem acesso ao portal';
COMMENT ON COLUMN crm_customers.portal_last_login IS 'Último login do cliente no portal';
COMMENT ON COLUMN crm_customers.portal_created_at IS 'Data de criação do acesso ao portal';

View File

@@ -0,0 +1,9 @@
-- Adicionar relacionamento entre campanhas (crm_lists) e clientes (crm_customers)
ALTER TABLE crm_lists
ADD COLUMN customer_id UUID REFERENCES crm_customers(id) ON DELETE CASCADE;
-- Índice para performance
CREATE INDEX IF NOT EXISTS idx_crm_lists_customer_id ON crm_lists(customer_id);
-- Comentário explicativo
COMMENT ON COLUMN crm_lists.customer_id IS 'Cliente ao qual esta campanha pertence.';

View File

@@ -0,0 +1,65 @@
-- Migration: Create CRM funnels and stages
-- Description: Allows agencies to create custom lead monitoring pipelines
CREATE TABLE IF NOT EXISTS crm_funnels (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
is_default BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique_funnel_per_tenant UNIQUE (tenant_id, name)
);
CREATE TABLE IF NOT EXISTS crm_funnel_stages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
funnel_id UUID NOT NULL REFERENCES crm_funnels(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
color VARCHAR(7) DEFAULT '#3b82f6',
order_index INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Add funnel and stage to leads
ALTER TABLE crm_leads
ADD COLUMN funnel_id UUID REFERENCES crm_funnels(id) ON DELETE SET NULL,
ADD COLUMN stage_id UUID REFERENCES crm_funnel_stages(id) ON DELETE SET NULL;
-- Indices
CREATE INDEX idx_crm_funnels_tenant_id ON crm_funnels(tenant_id);
CREATE INDEX idx_crm_funnel_stages_funnel_id ON crm_funnel_stages(funnel_id);
CREATE INDEX idx_crm_leads_funnel_id ON crm_leads(funnel_id);
CREATE INDEX idx_crm_leads_stage_id ON crm_leads(stage_id);
-- Triggers for updated_at
CREATE TRIGGER update_crm_funnels_updated_at BEFORE UPDATE ON crm_funnels
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_crm_funnel_stages_updated_at BEFORE UPDATE ON crm_funnel_stages
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Function to create default funnel for a tenant
CREATE OR REPLACE FUNCTION create_default_crm_funnel(t_id UUID)
RETURNS UUID AS $$
DECLARE
f_id UUID;
BEGIN
INSERT INTO crm_funnels (tenant_id, name, description, is_default)
VALUES (t_id, 'Funil de Vendas Padrão', 'Monitoramento básico de leads', true)
RETURNING id INTO f_id;
INSERT INTO crm_funnel_stages (funnel_id, name, color, order_index) VALUES
(f_id, 'Novo', '#3b82f6', 0),
(f_id, 'Qualificado', '#10b981', 1),
(f_id, 'Proposta', '#f59e0b', 2),
(f_id, 'Negociação', '#8b5cf6', 3),
(f_id, 'Fechado', '#10b981', 4),
(f_id, 'Perdido', '#ef4444', 5);
RETURN f_id;
END;
$$ LANGUAGE plpgsql;

View File

@@ -0,0 +1,8 @@
-- Migration: Link funnels to campaigns (lists)
-- Description: Allows associating a campaign with a specific sales funnel
ALTER TABLE crm_lists
ADD COLUMN funnel_id UUID REFERENCES crm_funnels(id) ON DELETE SET NULL;
-- Index for performance
CREATE INDEX idx_crm_lists_funnel_id ON crm_lists(funnel_id);