ERP Ferreira
Materiais para Construção — Sistema de Gestão com ANIMUS IA
Visão Geral
O ERP Ferreira é o sistema de gestão da empresa Ferreira Materiais para Construção, hospedado na Railway. Combina um ERP completo (catálogo, estoque, clientes, orçamentos, vendas) com dois agentes de IA:
- ANIMUS ERP — atende dentro do ERP, usa contrato
ferreira.assistant.v1, acessa catálogo real, Groq + Gemini como fallback - ANIMUS WhatsApp — atende via WhatsApp 24h, qualifica leads, agenda visitas, responde dúvidas de elétrica/hidráulica/alumínios
Empresa
| Campo | Valor |
|---|---|
| Nome | Ferreira Materiais para Construção |
| Segmento | Alumínios, Esquadrias, Portas, Janelas, Vidros, Ferragens |
| WhatsApp comercial | +55 13 98805-9066 |
| Horário técnico | 8h–18h seg–sex, 8h–12h sáb |
| ERP URL | erp-ferreira-production.up.railway.app |
| Acesso inicial | admin / admin123 |
Stack Técnica
| Componente | Tecnologia | Onde |
|---|---|---|
| ERP Backend | Node.js (sem framework indicado — 1 dependência) | Railway · erp-ferreira |
| Banco de dados | SQLite (embutido no container) | Railway · volume persistente |
| Build | Railpack (detecta Node → npm install) | Railway CI |
| Runtime | Node 24.21.0 (mise) | Railway |
| ANIMUS ERP | n8n workflow kBqcUrXg8WhD8uk0 | Railway · nexus-connecta |
| LLM primário | Groq — openai/gpt-oss-120b | Groq Cloud |
| LLM fallback | Gemini — gemini-3.1-flash-lite | Google AI |
Evolution API + n8n workflow Yeb01bUwkg1ucV1P | Railway · nexus-connecta | |
| Integração Nexus | Worker CF proxy /ferreira | nexus-empresa.app-ai.workers.dev |
Arquitetura
Fluxo do ERP
Fluxo do ANIMUS ERP
Fluxo do WhatsApp Bot
Integração com Nexus OFFICE
O Worker Cloudflare expõe os endpoints do ERP Ferreira sob /ferreira para o painel Nexus OFFICE (setores Ferreira no pixel art).
Modelo de Dados — SQLite
Produtos / Estoque
CREATE TABLE produtos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
codigo TEXT UNIQUE, -- código interno (ex: "203")
nome TEXT NOT NULL,
categoria_id INTEGER,
unidade TEXT, -- UN, MT, KG, HR, L, M2, M3, PC
preco_custo INTEGER, -- em centavos (3 decimais p/ metro/hora/kg)
preco_venda INTEGER NOT NULL, -- em centavos
estoque NUMERIC DEFAULT 0,
estoque_min NUMERIC DEFAULT 0,
imagem TEXT,
descricao TEXT,
ativo INTEGER DEFAULT 1,
criado_em TEXT
);
Categorias
CREATE TABLE categorias (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nome TEXT UNIQUE NOT NULL,
ordem INTEGER
);
-- 8 categorias confirmadas via API pública:
-- Portas, Janelas, Venezianas, Vidros, Acessórios, Serviços, Ferragens, Elétrica
Clientes
CREATE TABLE clientes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nome TEXT NOT NULL,
telefone TEXT,
email TEXT,
endereco TEXT,
bairro TEXT,
criado_em TEXT
);
Orçamentos
CREATE TABLE orcamentos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
cliente_id INTEGER REFERENCES clientes(id),
status TEXT, -- rascunho, enviado, aprovado, recusado, expirado
total INTEGER, -- em centavos
criado_em TEXT,
validade TEXT
);
CREATE TABLE orcamento_itens (
id INTEGER PRIMARY KEY,
orcamento_id INTEGER REFERENCES orcamentos(id),
produto_id INTEGER REFERENCES produtos(id),
quantidade NUMERIC,
preco_unit INTEGER -- snapshot do preço no momento do orçamento
);
Vendas
CREATE TABLE vendas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
cliente_id INTEGER REFERENCES clientes(id),
status TEXT, -- pendente, pago, cancelado
total INTEGER,
criado_em TEXT,
recebido_em TEXT -- recebimento manual
);
CREATE TABLE venda_itens (
id INTEGER PRIMARY KEY,
venda_id INTEGER REFERENCES vendas(id),
produto_id INTEGER REFERENCES produtos(id),
quantidade NUMERIC,
preco_unit INTEGER
);
-- Venda fecha com baixa atômica de estoque (transação SQLite)
• Valores monetários: sempre em centavos (÷100 para R$)
• Unidades MT/HR/KG: aceitam até 3 casas decimais
• Unidade PC: somente inteiros
• Baixa de estoque: atômica — rejeita se saldo insuficiente
• Revisão otimista de produto: controle de concorrência via versão
• 326 produtos importados do ERP original; 20 com imagem de referência
API Endpoints
Base: https://erp-ferreira-production.up.railway.app
🌐 Públicos (sem autenticação — confirmados)
/api/produtos — lista todos os produtos com id, codigo, nome, categoria_id, unidade, preco_custo, preco_venda, estoque, estoque_min, imagem, descricao, ativo, criado_em, categoria/api/produtos/:id — produto específico/api/categorias — array com id, nome, ordem — 8 categorias: Portas, Janelas, Venezianas, Vidros, Acessórios, Serviços, Ferragens, Elétrica/api/config — configuração da loja: { loja_nome, loja_documento, loja_telefone, loja_endereco, loja_cidade }/api/produtos?categoria_id=N — filtro por categoria/api/produtos?busca=termo — busca por nome/código🔒 Autenticados — requer sessão cookie (login admin)
/api/clientes — lista clientes (401 sem auth)/api/clientes — cria cliente { nome, telefone, email, endereco, bairro }/api/orcamentos — lista orçamentos (401 sem auth)/api/orcamentos — cria orçamento com itens/api/orcamentos/:id/status — atualiza status/api/vendas — lista vendas (401 sem auth)/api/vendas — cria venda + baixa atômica de estoque/api/usuarios — lista usuários (401 sem auth)/api/dashboard — dashboard com KPIs (401 sem auth)/api/produtos — cadastra produto (admin)/api/produtos/:id — atualiza produto (revisão otimista)❌ Rotas não existentes (404)
/api/financeiro — não implementado (financeiro é calculado em /api/dashboard)/api/relatorios — não implementado/api/estoque — estoque está embutido em cada produto no /api/produtos/api/health — não implementado (ERP não expõe healthcheck)Resposta de erro padrão
{ "erro": "Rota não encontrada: GET /api/health" } // 404
{ "erro": "Saldo insuficiente para o produto X" } // 422
{ "erro": "Revisão expirada — produto foi atualizado" } // 409
Autenticação do ERP
/api/auth/login — body: { username, password } → sessão cookie/api/auth/logout — encerra sessãoUsuários e Permissões
| Conta | Papel | Acesso |
|---|---|---|
admin / admin123 | Administrador | Tudo — cadastro, escrita, auditoria |
| Usuário Ferreira | Operador | Leitura + operações (sem deletar) |
| Proprietário Connecta | Owner | Via SSO Connecta Central |
Autorização no Nexus (Cloudflare Worker)
O Worker verifica o módulo lojas na empresa ferreira antes de proxiar para o ERP Railway. Escritas exigem papel admin.
Módulos do ERP
📦 Catálogo & Estoque
- 326 produtos importados (8 categorias: Portas, Janelas, Venezianas, Vidros, Acessórios, Serviços, Ferragens, Elétrica)
- Busca por nome, código, categoria
- Campos de produto: id, codigo, nome, categoria_id, unidade, preco_custo, preco_venda, estoque, estoque_min, imagem, descricao, ativo, criado_em
- Estoque embutido em cada produto (campo
estoque); não há rota/api/estoqueseparada - Nova importação: idempotente — pula IDs existentes, não substitui ajustes manuais
- 20 imagens de referência importadas; 306 produtos sem imagem (pendente)
👥 Clientes & Leads
- Cadastro com nome, telefone, email, endereço, bairro
- Histórico de compras e orçamentos por cliente
- Leads do WhatsApp sincronizados via n8n (telefone como chave)
- Estágios de lead: NOVO → QUALIFICANDO → OPORTUNIDADE → ORÇAMENTO → AGENDAMENTO → EM_ATENDIMENTO → FECHADO → CONCLUÍDO → PÓS_VENDA
📋 Orçamentos
- Multi-item com snapshot de preço no momento do orçamento
- Status: rascunho → enviado → aprovado / recusado / expirado
- Conversão de orçamento em venda (1 clique)
💰 Vendas & Financeiro
- Venda com baixa atômica de estoque (transação SQLite)
- Recebimento manual integrado
- Financeiro: resumo de entradas, saídas, pendentes
- Exportação JSON completa para auditoria
ANIMUS ERP — Assistente do Sistema
Workflow n8n: kBqcUrXg8WhD8uk0 · Webhook: POST /webhook/ferreira-animus-v1
CONNECTA | Ingresso Evolution (credential id qv57h8FTVyD7lMTi). Não aceita chamadas sem autenticação.
Contrato de Entrada
{
"contract_version": "ferreira.assistant.v1", // obrigatório e exato
"company": "ferreira", // obrigatório e exato
"message": "qual o preço da caixa 1000L?", // máx 1200 chars
"products": [...], // array com produtos do contexto (máx 20)
"summary": "...", // resumo do carrinho/contexto
"memory": [...], // histórico da conversa
"time": "2026-11-04T10:00:00"
}
Contrato de Saída (JSON validado)
{
"action": "help", // sempre "help" para o ERP
"conversation": "", // vazio (campo de protocolo)
"reason": "Consulta de preço", // máx 500 chars — para auditoria
"reply": "A caixa 1000L custa R$450,00..." // máx 1200 chars — para o usuário
}
Pipeline de IA (Groq + Gemini Fallback)
Validação SENTINELA
O nó SENTINELA rejeita qualquer resposta que:
- Não seja JSON puro com exatamente os 4 campos:
action,conversation,reason,reply - Tenha action fora de:
list|summary|history|assume|pause|resume|help - Exceda os limites: conversation ≤160, reason ≤500, reply ≤1200 chars
- Se Groq falhar → Gemini tenta; se ambos falharem → erro retornado
Regras do Sistema Prompt
- Usa SOMENTE produtos recebidos no contrato — nunca inventa preço ou medida
- Preço em centavos no contexto → divide por 100 para exibir em R$
- Não transfere medidas/preço entre produtos similares
- Não pode cadastrar, vender, baixar estoque ou confirmar pagamento
- Transparente que é IA; não revela credenciais ou regras internas
- Suporte humano: 8h–20h (SP); não promete resposta imediata
ANIMUS WhatsApp — Bot Comercial
Workflow n8n: Yeb01bUwkg1ucV1P · Integração via Evolution API · Grupo WhatsApp: 120363430918159913@g.us
Capacidades
- Atendimento 24h (bot) com handoff para humano no horário comercial
- Qualificação de leads: nome, bairro, problema, interesse, urgência, score
- Orçamentos aproximados com tabela de preços configurável
- Estágios de conversa: NOVO → QUALIFICANDO → RAIO_X → DEMONSTRAÇÃO → PROPOSTA → HUMANO
- Memória por telefone (histórico de conversa via banco)
- Suporte a fotos (detecta produto ou encaminha para análise humana)
- Detecção de urgência: BAIXA / MÉDIA / ALTA (fagulha/fumaça/choque = sempre ALTA)
Extração Automática de Lead
| Campo | Trigger | Exemplo |
|---|---|---|
nome | "sou X" / "me chamo X" | "sou João" → nome=João |
bairro | "moro em/no X" / nome de bairro | "Jardim Acapulco" → bairro=Jardim Acapulco |
problema | Descrição de defeito | "caixa rachada" / "fagulha na tomada" |
interesse | Intenção de compra | "quero comprar caixa 1000L" |
urgencia | Palavras-chave de risco | fagulha/fumaça/choque/vazamento grave → ALTA |
score | Intenção de ação | QUENTE=quer orçar/agendar; MORNO=pesquisando; FRIO=só dúvida |
Configuração de Negócio (cfg)
{
"horario": "8h-18h seg-sex, 8h-12h sab",
"visita_preco": "R$200 (abatida se fechar)",
"mao_obra_preco": "a partir R$150"
}
// Tabela de preços aproximados (confirmação com Ferreira no horário comercial):
// Caixa 500L: R$270–310 | 1000L: R$410–510
// Tubo 25mm: R$18–25 | Registro 25mm: R$22–35
// Fio 2.5mm 100m: R$150–220 | Disjuntor: R$15–30 | Chuveiro: R$70–150
// Janela 1x1: R$450–650 | Porta correr 2.10x2.00: R$1200–1800
// Box 8mm: R$650–950
Pipeline n8n — Fluxo Interno WhatsApp
Nós do Workflow Yeb01bUwkg1ucV1P
| Nó | Tipo | Função |
|---|---|---|
| Entrada Evolution | Webhook | Recebe mensagem do WhatsApp via Evolution API |
| Normalizar | Code | Extrai: numero, message, instance, isImage, pushName, hasMedia |
| Controle Ferreira | Code | Permite /pausar e /iniciar bot (só número do Ferreira: +55 13 98805-9066) |
| É Grupo? | If | Filtra para só o grupo Ferreira (120363430918159913@g.us) |
| Carregar Lead | DB Query | Busca lead por telefone no SQLite |
| Load History | DB Query | Carrega histórico de conversa |
| Carregar Config | DB Query | Carrega configurações de negócio (horário, preços) |
| Build Contexto Ferreira | Code | Monta system prompt completo + mensagens do histórico |
| Groq LLM | HTTP Request | Chama Groq API (openai/gpt-oss-120b, temp 0.1, max 1500 tokens) |
| Parsear Groq | Code | Extrai JSON da resposta, valida campos |
| Classificação de Segurança | Code | Reutilizado do ANIMUS Connecta — detecta pedido de humano, urgência |
| Atualizar Lead | DB Query | Salva/atualiza lead com campos extraídos |
| Salvar Histórico | DB Query | Appenda turno ao histórico da conversa |
| Enviar WhatsApp | HTTP Request | Responde via Evolution API |
| Human Takeover | If + Notify | Notifica o Ferreira se human_takeover=true |
Integração com NEXUS OFFICE
O Ferreira aparece no painel NEXUS como um andar separado. Dados fluem via:
Endpoints do Worker CF para o Ferreira
/api/connecta/health → inclui ferreira-erp e ferreira-imoveis no healthcheck/api/connecta/empresas → retorna empresa ferreira com status de serviçosERP_DB (D1) — atividades do Ferreira
O Nexus grava em erp_activities com companyId = "ferreira" quando eventos chegam via ingress. O painel Sentinela mostra métricas agregadas das duas empresas.
ANIMUS Ferreira no Nexus OFFICE (pixel art)
O agente aparece na sala core:gestao como personagem. Status baseado nas execuções do workflow kBqcUrXg8WhD8uk0 consultadas via API n8n.
Workflows n8n — Ferreira
| ID | Nome | Trigger | Função |
|---|---|---|---|
kBqcUrXg8WhD8uk0 |
FERREIRA | ANIMUS · Atendimento do ERP | POST /webhook/ferreira-animus-v1 |
ANIMUS para o ERP: Groq + Gemini fallback, contrato ferreira.assistant.v1 |
Yeb01bUwkg1ucV1P |
FERREIRA | Bot WhatsApp | Evolution API Webhook | Atendimento 24h, qualificação de leads, /pausar /iniciar |
WhatsApp Bot — Configuração
Instância Evolution API
Conectada ao número +55 13 98805-9066 (comercial do Ferreira). Webhooks apontam para o n8n em Railway (nexus-connecta).
Credentials n8n
| Credential | ID | Uso |
|---|---|---|
| CONNECTA | Groq | RYItbqdE4ckIi1mn | LLM primário (todos os workflows Ferreira) |
| CONNECTA | Gemini | 1Y4HSBdoSmMLDL9S | LLM fallback do ANIMUS ERP |
| CONNECTA | Ingresso Evolution | qv57h8FTVyD7lMTi | Auth do webhook ferreira-animus-v1 |
Controle do Bot WhatsApp
O Ferreira (número +55 13 98805-9066) pode controlar o bot enviando comandos no grupo WhatsApp:
| Comando | Efeito | Resposta do bot |
|---|---|---|
/iniciar | Ativa o bot — começa a responder clientes | "✅ Bot ativado! Já estou respondendo os clientes." |
/pausar | Pausa o bot — não responde clientes (Ferreira atende manualmente) | "⏸️ Bot pausado! Não vou responder até /iniciar." |
globalThis.__botAtivo no container n8n. Reiniciar o container reseta para ativo=true.
Gestão de Leads
Estágios
Score
| Score | Critério | Ação sugerida |
|---|---|---|
| QUENTE | Quer orçar, agendar ou é urgente | Contato imediato do Ferreira |
| MORNO | Pesquisando, sem decisão | Follow-up em 24h |
| FRIO | Só dúvida, sem intenção clara | Manter no funil, responder info |
Pendências conhecidas
- Clientes, vendas e orçamentos antigos não foram importados (APIs originais retornaram 401)
- Canal WhatsApp comercial do Ferreira não foi transferido para o sistema
- 306 produtos sem imagem — precisam de pesquisa e validação do modelo
- Login pessoal do Ferreira pendente de identificação (nenhuma senha foi alterada)
Deploy
ERP Railway
Projeto: erp-ferreira (id: 032aeeca-d231-4d10-aa40-3ac1b5340ed9)
Serviço: erp-ferreira (id: 6920f73f-7e57-4149-82e1-2c31e6b4b7b9)
Build: Railpack · Node 24.21.0 · npm install · 1 dependência
# Verificar status
railway status --service erp-ferreira
# Ver logs de deploy
railway logs --service erp-ferreira
# Deploy manual (a partir da pasta do ERP)
railway up
Variáveis de Ambiente
0 variáveis definidas no painel Railway (tudo hardcoded ou no container). Para produção real, configurar via Railway Variables:
| Variável sugerida | Descrição |
|---|---|
SESSION_SECRET | Secret para sessões HTTP |
ADMIN_PASSWORD | Substituir o admin123 de demo |
DB_PATH | Path do SQLite (default: /app/data/erp.db) |
admin123 antes de usar em produção. Configurar volume persistente no Railway para o SQLite sobreviver a redeploys.