Guia de Engenharia: Módulo Representante
Bem-vindo ao manual técnico de arquitetura do Módulo de Representante Comercial (crm_rep) do CRM Brasil Componentes.
Este documento foi escrito de desenvolvedor para desenvolvedor para você entender cada decisão de design, modelo de dados híbrido, pegadinhas de framework e regras de segurança sem perder tempo descobrindo na tentativa e erro.
O Padrão Dual Database (ERP Legado + CRM Local)
A espinha dorsal deste projeto baseia-se em um modelo híbrido. A empresa possui um ERP robusto em PostgreSQL rodando em produção há anos (banco external).
Entretanto, o CRM precisa de recursos adicionais que o ERP não possui (Kanban, dados de compradores da Receita Federal, flags de fixação de cartões, logs de auditoria).
external) deve ser tratado como estritamente READ-ONLY para o CRM.
Qualquer escrita, enriquecimento cadastral ou alteração de rotas deve ser gravada no Banco Local do CRM (default).
Se você tentar rodar um INSERT ou UPDATE na tabela cliente ou pedidovenda, você colocará a operação fabril em risco!
•
cliente, cidade, classe•
pedidovenda, pedidovendaitem•
valida_pedido, validacao•
transportadora, produto
•
crm_client_info (Aniversário, OBS, CNPJ)•
crm_kanban_tasks (Quadro de tarefas)•
crm_user_seller_mapping (Vínculos)•
crm_system_logs (Auditoria)
No código, o acesso aos bancos é centralizado na classe App\Connection\DatabaseConnection:
// Conexão com o Banco Local do CRM (Tabelas crm_*)
$localDb = (new DatabaseConnection())->getPrimaryConnection(); // ou getConnection('default')
// Conexão com o ERP Externo (Apenas Leitura de Clientes, Pedidos e Validações)
$erpDb = (new DatabaseConnection())->getConnection('external');
A Regra Estrita: Zero SQL no Controller
Nosso padrão arquitetural exige que nenhum comando SQL (SELECT, INSERT, UPDATE, DELETE) nem conexão PDO exista dentro de Controllers.
Os Controllers devem ser camadas finas responsáveis apenas por:
- Validar os parâmetros da requisição HTTP (JSON, query params, headers);
- Validar permissões e RBAC (papéis JWT);
- Invocar métodos de negócio dos respectivos Models;
- Formatar a resposta em JSON (
{ success: true, data: ... }).
// ❌ ERRADO: Fazer query direta no Controller
$stmt = $db->prepare("SELECT area_code FROM crm_user_seller_mapping WHERE email = :e");
$stmt->execute([':e' => $userEmail]);
// ✅ CORRETO: Chamar método especializado no Model
$userArea = $this->model->getUserAreaCodeByEmail($userEmail);
Autenticação JWT, Middleware e Comunicação Frontend
O backend Slim 4 protege todos os endpoints via App\Security\AuthMiddleware.
O token JWT trafega no cabeçalho Authorization: Bearer <token>.
No frontend, as chamadas são unificadas na função crmApiRequest() em frontend/assets/javascript/rep_modules.js.
| Papel (Role) | Identificador JWT | Permissões de Acesso | Regra de Área Comercial |
|---|---|---|---|
| Representante | crm_rep |
Acesso ao Painel, Clientes da Área, Kanban próprio e Pedidos da Área | Travado estritamente na sua própria área (ex: Área 9) |
| Supervisor | crm_sup |
Acesso à gestão de equipes, relatórios consolidados e metas | Livre para filtrar qualquer área ou vendedor |
| Gerente | crm_ger |
Visão executiva, transferência de carteira e metas anuais | Livre para filtrar qualquer área ou vendedor |
| Administrador | crm_admin |
Controle total, mapeamentos, logs de sistema e testes | Superusuário sem restrições |
Meus Clientes & Enriquecimento Local (clients.php)
O módulo de clientes une os dados cadastrais do ERP com o enriquecimento local do CRM:
- Busca Paginada no ERP:
GET /api/v1/clients/{cd_area}?limit=50&status=ATIVO&search=termo(traz nome, CNPJ, cidade, classe e código da empresac.i_cdcliente AS cd_cliente). - Enriquecimento de Dados no CRM Local: Ao abrir o modal de detalhes, o CRM consulta
crm_client_infopor CNPJ. Caso não exista, o usuário pode clicar em "Buscar Dados CNPJ" (consulta API da Receita Federal) e salvar anotações, comprador e aniversário viaPOST /api/v1/clients/info. - Histórico de Pedidos & PDF com Fotos:
GET /api/v1/clients/{cd_cliente}/ordersbusca os pedidos anteriores. Clicando em "📄 Visualizar PDF", o sistema monta o espelho oficial com fotos dos produtos via CDNhttps://brasilcompautomotivos.com.br/service/imagem/{codigo_item}?size=p.
Rota de Prioridades da Semana (Kanban - tasks.php)
O Kanban estilo Trello permite ao representante organizar sua esteira comercial em 4 colunas:
a_atender ➔ em_atendimento ➔ aguardando_retorno ➔ concluido.
PATCH e PUT enviadas como formulário comum (application/x-www-form-urlencoded) NÃO preenchem a superglobal $_POST!Como o frontend enviava
PATCH /api/v1/kanban/tasks/1/status sem cabeçalho JSON explícito, o json_decode($body) no backend retornava null.O backend retornava erro 400 (
Coluna não informada), e o frontend disparava o rollback automático (loadTasks()), fazendo o cartão voltar magicamente para onde estava!
Como resolvemos definitivamente para que nunca mais quebre:
// Garante Content-Type e JSON.stringify para todos os métodos com payload
const upperMethod = (method || 'GET').toUpperCase();
const hasPayload = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(upperMethod) && data !== null;
if (hasPayload) {
headers['Content-Type'] = 'application/json';
}
$.ajax({
url: baseUrl + endpoint,
type: upperMethod,
headers: headers,
data: hasPayload ? JSON.stringify(data) : data, // <-- Crucial!
dataType: 'json',
...
});
// Extrai o payload de forma resiliente em qualquer cenário
private function getRequestData(Request $request): array
{
$parsed = $request->getParsedBody();
if (!empty($parsed) && is_array($parsed)) {
return $parsed;
}
$raw = (string)$request->getBody();
$decoded = json_decode($raw, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
return $decoded;
}
// Fallback se alguém enviar form-encoded em PATCH
$form = [];
parse_str($raw, $form);
return $form;
}
listEl.addEventListener('drop', ...) dentro de renderColumns()!
Se você renderizar as colunas 5 vezes, haverá 5 listeners executando no mesmo drop, disparando 5 requisições simultâneas.
No nosso código, os listeners dos dropzones são registrados apenas uma vez em initKanbanModule()!
Pedidos Enviados & Validações do ERP (orders.php)
Permite acompanhar todos os pedidos da área nos últimos 60 dias (janela padrão), com as 5 etapas da esteira interna do ERP:
| Etapa | Código Validação | Permissão Autorização | Descrição no ERP | Situações Possíveis |
|---|---|---|---|---|
| Etapa 1 | i_cdvalidacao = 9 |
i_cdpermautorizacao = 20 |
LIBERAÇÃO DO PEDIDO | Autorizado (A) / Liberado (L) / Aguardando (N) |
| Etapa 2 | i_cdvalidacao = 13 |
i_cdpermautorizacao = 17 |
SEPARAÇÃO DE ITENS | Autorizado (A) / Liberado (L) / Aguardando (N) |
| Etapa 3 | i_cdvalidacao = 10 |
i_cdpermautorizacao = 25 |
PENDÊNCIAS DE ESTOQUE | Liberado (L) / Em Análise / Pendente (N) |
| Etapa 4 | i_cdvalidacao = 4 |
i_cdpermautorizacao = 9 |
FATURAMENTO | Autorizado (A) / Faturado (L) |
| Etapa 5 | - (Logística) |
pedidovenda.d_despacho |
DESPACHO LOGÍSTICO & TRANSPORTE | Despachado (Data gravada) / Aguardando Coleta |
- Na Interface: O campo de área é renderizado como uma trava fixa com cadeado (
Área 9 🔒) e não como um input editável. - No Backend: O método
PedidosController::getAreaOrdersinspeciona o token JWT. Se o usuário forcrm_rep, a rota ignora qualquer código de área enviado na URL e força estritamente o código do login (viaPedidosModel::getUserAreaCodeByEmail). - Supervisores e Gerentes: Se o token possuir
crm_sup,crm_geroucrm_admin, o filtro de área é desbloqueado.
Guia de Testes Automatizados & Troubleshooting
Sempre que alterar qualquer model, controller ou rota, você DEVE rodar a suíte de testes de integração para garantir que nenhuma regressão ocorra:
# Executa todos os 15 testes de integração ponta a ponta
php backend/src/test/test_backend.php
# Saída esperada (100% de sucesso):
[PASS] 1. Swagger JSON endpoint (200 OK, valid OpenAPI)
[PASS] 2. Swagger UI endpoint (200 OK)
[PASS] 3. Security: Unauthorized without token (401)
[PASS] 4. Security: Authorized crm_sup accessed /teams (200 OK)
[PASS] 5. RBAC: Forbidden for crm_rep on /admin/logs (403)
[PASS] 6. RBAC: Allowed for crm_admin on /admin/logs (200 OK)
[PASS] 7. Client Enrichment: POST /api/v1/clients/info saved to local database (200 OK)
[PASS] 8. Client Enrichment: GET /api/v1/clients/{cnpj}/info retrieved from local DB (200 OK)
[PASS] 9. Client Pagination & Filter: GET /api/v1/clients/9 (200 OK)
[PASS] 10. Client Orders: GET /api/v1/clients/21869/orders (200 OK)
[PASS] 11. Order Details & Modern PDF: GET /api/v1/orders/9695 and /pdf (200 OK)
[PASS] 12. Kanban Tasks: Full CRUD, Drag & Drop status update, and Summary (200/201 OK)
[PASS] 13. Kanban Client Autocomplete: GET /api/v1/kanban/clients from ERP (200 OK)
[PASS] 14. Area Orders: GET /api/v1/orders/area/349 retrieved real orders with KPIs (200 OK)
[PASS] 15. Order Validations: GET /api/v1/orders/34900227/validations retrieved 5 steps (200 OK)
ALL BACKEND TESTS PASSED SUCCESSFULLY!
vendor/bin/phinx migrate para garantir todas as colunas locais criadas.2. Variáveis de Ambiente: Conferir
.env com credenciais do banco local e ERP.3. Swagger OpenAPI: Acessar
http://10.1.1.133:8001/docs para testar qualquer rota interativamente.4. Logs: Em caso de exceções, checar a tabela local
crm_system_logs.