Voltar ao blog

Como Integrar Chatbot com ERP e CRM: Guia Tecnico

Tutorial para conectar seu chatbot com sistemas legados. APIs, webhooks, bancos de dados e solucoes quando nao ha API disponivel.

RS
Richard Sakaguchi Solution Architect
Como Integrar Chatbot com ERP e CRM: Guia Tecnico

Chatbot que nao acessa seus sistemas e apenas um FAQ glorificado. Este guia mostra como conectar seu bot ao ERP, CRM e outros sistemas da empresa.

Tipos de Integracao

TipoQuando UsarComplexidade
API RESTSistema tem API documentadaBaixa
WebhookSistema envia eventosBaixa
Banco DiretoSistema sem APIMedia
RPA/AutomacaoSistema so tem interfaceAlta
Arquivo/FTPSistemas muito antigosMedia

1. Integracao via API REST

A forma mais limpa quando o sistema tem API.

Exemplo: Consultar Cliente no CRM

const axios = require('axios');

class CRMClient {
  constructor(baseUrl, apiKey) {
    this.client = axios.create({
      baseURL: baseUrl,
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      }
    });
  }

  async getCustomer(phone) {
    try {
      const response = await this.client.get('/customers', {
        params: { phone: this.normalizePhone(phone) }
      });
      return response.data[0] || null;
    } catch (error) {
      console.error('Erro ao buscar cliente:', error.message);
      return null;
    }
  }

  async createCustomer(data) {
    const response = await this.client.post('/customers', {
      name: data.name,
      phone: this.normalizePhone(data.phone),
      email: data.email
    });
    return response.data;
  }

  async addInteraction(customerId, message) {
    return this.client.post(`/customers/${customerId}/interactions`, {
      type: 'whatsapp',
      content: message,
      timestamp: new Date().toISOString()
    });
  }

  normalizePhone(phone) {
    return phone.replace(/\D/g, '');
  }
}

// Uso no chatbot
const crm = new CRMClient('https://api.seucrm.com', process.env.CRM_API_KEY);

async function handleMessage(from, message) {
  // Buscar ou criar cliente
  let customer = await crm.getCustomer(from);

  if (!customer) {
    customer = await crm.createCustomer({
      phone: from,
      name: 'WhatsApp Lead'
    });
  }

  // Registrar interacao
  await crm.addInteraction(customer.id, message);

  // Continuar fluxo com dados do cliente
  return processWithCustomerData(customer, message);
}

Exemplo: Consultar Estoque no ERP

class ERPClient {
  constructor(config) {
    this.baseUrl = config.baseUrl;
    this.client = axios.create({
      baseURL: config.baseUrl,
      auth: {
        username: config.user,
        password: config.password
      }
    });
  }

  async getProductStock(sku) {
    const response = await this.client.get(`/products/${sku}/stock`);
    return {
      available: response.data.quantity_available,
      reserved: response.data.quantity_reserved,
      warehouse: response.data.warehouse_id
    };
  }

  async getProductPrice(sku, customerId = null) {
    const params = customerId ? { customer_id: customerId } : {};
    const response = await this.client.get(`/products/${sku}/price`, { params });
    return {
      price: response.data.unit_price,
      discount: response.data.discount_percentage,
      finalPrice: response.data.final_price
    };
  }

  async createOrder(customerId, items) {
    const response = await this.client.post('/orders', {
      customer_id: customerId,
      items: items.map(item => ({
        sku: item.sku,
        quantity: item.quantity
      })),
      source: 'whatsapp_bot'
    });
    return response.data;
  }
}

// Uso
const erp = new ERPClient({
  baseUrl: 'https://erp.empresa.com/api/v1',
  user: process.env.ERP_USER,
  password: process.env.ERP_PASS
});

async function checkAvailability(productCode) {
  const stock = await erp.getProductStock(productCode);

  if (stock.available > 0) {
    return `Temos ${stock.available} unidades disponiveis!`;
  } else {
    return 'Produto sem estoque no momento.';
  }
}

2. Integracao via Webhook

Sistema envia eventos para seu chatbot.

Recebendo Eventos do CRM

// Endpoint para receber webhooks do CRM
app.post('/webhooks/crm', async (req, res) => {
  const { event, data } = req.body;

  // Validar assinatura
  if (!validateSignature(req)) {
    return res.status(401).send('Invalid signature');
  }

  res.sendStatus(200);  // Responder rapido

  // Processar evento
  switch (event) {
    case 'deal.won':
      await notifyCustomerDealWon(data.customer_phone, data.deal_name);
      break;

    case 'ticket.created':
      await notifyCustomerTicketCreated(data.customer_phone, data.ticket_id);
      break;

    case 'invoice.overdue':
      await remindCustomerPayment(data.customer_phone, data.invoice);
      break;
  }
});

async function notifyCustomerDealWon(phone, dealName) {
  await sendWhatsAppMessage(phone,
    `Parabens! Sua compra de ${dealName} foi confirmada. ` +
    `Em breve voce recebera mais detalhes.`
  );
}

Enviando Webhooks Para Outros Sistemas

async function notifyExternalSystems(event, data) {
  const webhooks = [
    'https://erp.empresa.com/webhooks/whatsapp',
    'https://crm.empresa.com/webhooks/chat',
    'https://analytics.empresa.com/events'
  ];

  const payload = {
    event: event,
    data: data,
    timestamp: new Date().toISOString(),
    source: 'whatsapp_bot'
  };

  // Enviar para todos em paralelo
  await Promise.allSettled(
    webhooks.map(url =>
      axios.post(url, payload, {
        timeout: 5000,
        headers: {
          'X-Webhook-Secret': process.env.WEBHOOK_SECRET
        }
      })
    )
  );
}

// Uso
await notifyExternalSystems('message.received', {
  from: customerPhone,
  message: messageText,
  intent: detectedIntent
});

3. Integracao Direta com Banco de Dados

Quando o sistema nao tem API, acesse o banco diretamente.

Cuidados

  • Use usuario com permissoes READ-ONLY
  • Nunca altere dados diretamente (use stored procedures)
  • Considere criar views para abstrair complexidade
  • Monitore impacto de performance

Exemplo: MySQL/MariaDB

const mysql = require('mysql2/promise');

class ERPDirect {
  constructor(config) {
    this.pool = mysql.createPool({
      host: config.host,
      user: config.user,
      password: config.password,
      database: config.database,
      waitForConnections: true,
      connectionLimit: 10
    });
  }

  async getCustomerByPhone(phone) {
    const [rows] = await this.pool.execute(
      `SELECT
        c.id,
        c.nome,
        c.email,
        c.limite_credito,
        COALESCE(SUM(p.valor_pendente), 0) as saldo_devedor
      FROM clientes c
      LEFT JOIN pedidos p ON p.cliente_id = c.id AND p.status = 'pendente'
      WHERE c.telefone = ?
      GROUP BY c.id`,
      [phone]
    );
    return rows[0] || null;
  }

  async getLastOrders(customerId, limit = 5) {
    const [rows] = await this.pool.execute(
      `SELECT
        p.id,
        p.data_pedido,
        p.valor_total,
        p.status,
        GROUP_CONCAT(i.nome_produto) as produtos
      FROM pedidos p
      JOIN itens_pedido i ON i.pedido_id = p.id
      WHERE p.cliente_id = ?
      GROUP BY p.id
      ORDER BY p.data_pedido DESC
      LIMIT ?`,
      [customerId, limit]
    );
    return rows;
  }

  async checkProductAvailability(sku) {
    const [rows] = await this.pool.execute(
      `SELECT
        p.nome,
        e.quantidade_disponivel,
        e.quantidade_reservada,
        p.preco_venda
      FROM produtos p
      JOIN estoque e ON e.produto_id = p.id
      WHERE p.sku = ?`,
      [sku]
    );
    return rows[0] || null;
  }
}

Exemplo: SQL Server

const sql = require('mssql');

class ERPSqlServer {
  constructor(config) {
    this.config = {
      user: config.user,
      password: config.password,
      server: config.host,
      database: config.database,
      options: {
        encrypt: true,
        trustServerCertificate: true
      }
    };
    this.pool = null;
  }

  async connect() {
    this.pool = await sql.connect(this.config);
  }

  async getOpenOrders(customerId) {
    const result = await this.pool.request()
      .input('customerId', sql.Int, customerId)
      .query(`
        SELECT
          NumeroPedido,
          DataPedido,
          ValorTotal,
          StatusPedido
        FROM Pedidos
        WHERE ClienteId = @customerId
          AND StatusPedido IN ('Aberto', 'EmProcessamento')
        ORDER BY DataPedido DESC
      `);
    return result.recordset;
  }

  // Usar stored procedure para operacoes de escrita
  async createServiceTicket(customerId, description) {
    const result = await this.pool.request()
      .input('customerId', sql.Int, customerId)
      .input('description', sql.NVarChar(sql.MAX), description)
      .input('source', sql.VarChar(50), 'WhatsApp')
      .execute('sp_CriarChamado');

    return result.recordset[0];
  }
}

4. Integracao via Arquivos (FTP/SFTP)

Para sistemas que so exportam/importam arquivos.

Exemplo: Importar Lista de Precos

const Client = require('ssh2-sftp-client');
const csv = require('csv-parser');

class ERPFileIntegration {
  constructor(config) {
    this.sftpConfig = config;
    this.priceCache = new Map();
  }

  async syncPrices() {
    const sftp = new Client();

    try {
      await sftp.connect(this.sftpConfig);

      // Baixar arquivo de precos
      const remotePath = '/export/precos_vigentes.csv';
      const localPath = '/tmp/precos.csv';

      await sftp.get(remotePath, localPath);
      await sftp.end();

      // Processar CSV
      return new Promise((resolve, reject) => {
        const prices = [];

        fs.createReadStream(localPath)
          .pipe(csv())
          .on('data', (row) => {
            this.priceCache.set(row.sku, {
              price: parseFloat(row.preco),
              stock: parseInt(row.estoque),
              updated: new Date()
            });
            prices.push(row);
          })
          .on('end', () => resolve(prices))
          .on('error', reject);
      });

    } finally {
      if (sftp.sftp) await sftp.end();
    }
  }

  getPrice(sku) {
    return this.priceCache.get(sku);
  }

  // Agendar sync periodico
  startSync(intervalMs = 3600000) {  // 1 hora
    this.syncPrices();  // Sync inicial
    setInterval(() => this.syncPrices(), intervalMs);
  }
}

Exportar Pedidos Para ERP

async function exportOrdersToERP(orders) {
  const sftp = new Client();

  // Gerar arquivo CSV
  const csvContent = orders.map(order =>
    [
      order.id,
      order.customer_id,
      order.items.map(i => `${i.sku}:${i.qty}`).join(';'),
      order.total,
      order.created_at
    ].join(',')
  ).join('\n');

  const header = 'pedido_id,cliente_id,itens,valor_total,data\n';
  const fileName = `pedidos_whatsapp_${Date.now()}.csv`;

  fs.writeFileSync(`/tmp/${fileName}`, header + csvContent);

  // Upload para ERP
  await sftp.connect(sftpConfig);
  await sftp.put(`/tmp/${fileName}`, `/import/${fileName}`);
  await sftp.end();

  return fileName;
}

5. Integracao via RPA (Automacao de Interface)

Ultimo recurso quando sistema so tem interface grafica.

Exemplo com Puppeteer

const puppeteer = require('puppeteer');

class ERPScraper {
  constructor(config) {
    this.config = config;
    this.browser = null;
    this.page = null;
  }

  async init() {
    this.browser = await puppeteer.launch({
      headless: 'new',
      args: ['--no-sandbox']
    });
    this.page = await this.browser.newPage();
    await this.login();
  }

  async login() {
    await this.page.goto(this.config.loginUrl);
    await this.page.type('#username', this.config.user);
    await this.page.type('#password', this.config.password);
    await this.page.click('#login-button');
    await this.page.waitForNavigation();
  }

  async searchCustomer(phone) {
    await this.page.goto(`${this.config.baseUrl}/clientes`);

    // Preencher busca
    await this.page.type('#search-phone', phone);
    await this.page.click('#search-button');
    await this.page.waitForSelector('.customer-result', { timeout: 5000 });

    // Extrair dados
    const customer = await this.page.evaluate(() => {
      const row = document.querySelector('.customer-result');
      if (!row) return null;

      return {
        name: row.querySelector('.customer-name').textContent,
        email: row.querySelector('.customer-email').textContent,
        balance: row.querySelector('.customer-balance').textContent
      };
    });

    return customer;
  }

  async createTicket(customerId, description) {
    await this.page.goto(`${this.config.baseUrl}/chamados/novo`);

    await this.page.select('#customer-select', customerId);
    await this.page.type('#description', description);
    await this.page.click('#submit-ticket');

    await this.page.waitForSelector('.ticket-created');

    const ticketId = await this.page.$eval(
      '.ticket-number',
      el => el.textContent
    );

    return ticketId;
  }

  async close() {
    if (this.browser) await this.browser.close();
  }
}

Problemas do RPA:

  • Fragil (quebra se interface muda)
  • Lento
  • Dificil de debugar
  • Nao escala bem

Use apenas quando nao ha alternativa.

Padrao de Integracao Recomendado

Camada de Abstracao

// interfaces/erp.interface.js
class ERPInterface {
  async getCustomer(phone) { throw new Error('Not implemented'); }
  async getStock(sku) { throw new Error('Not implemented'); }
  async createOrder(data) { throw new Error('Not implemented'); }
}

// adapters/totvs.adapter.js
class TotvsAdapter extends ERPInterface {
  async getCustomer(phone) {
    // Implementacao especifica TOTVS
  }
}

// adapters/sap.adapter.js
class SAPAdapter extends ERPInterface {
  async getCustomer(phone) {
    // Implementacao especifica SAP
  }
}

// factory
function createERPClient(type, config) {
  switch (type) {
    case 'totvs': return new TotvsAdapter(config);
    case 'sap': return new SAPAdapter(config);
    case 'bling': return new BlingAdapter(config);
    default: throw new Error(`Unknown ERP: ${type}`);
  }
}

// Uso no chatbot - nao sabe qual ERP e
const erp = createERPClient(process.env.ERP_TYPE, config);
const customer = await erp.getCustomer(phone);

Cache de Dados

const NodeCache = require('node-cache');

class CachedERPClient {
  constructor(erpClient, ttlSeconds = 300) {
    this.erp = erpClient;
    this.cache = new NodeCache({ stdTTL: ttlSeconds });
  }

  async getCustomer(phone) {
    const cacheKey = `customer:${phone}`;
    let customer = this.cache.get(cacheKey);

    if (!customer) {
      customer = await this.erp.getCustomer(phone);
      if (customer) {
        this.cache.set(cacheKey, customer);
      }
    }

    return customer;
  }

  async getStock(sku) {
    const cacheKey = `stock:${sku}`;
    let stock = this.cache.get(cacheKey);

    if (!stock) {
      stock = await this.erp.getStock(sku);
      this.cache.set(cacheKey, stock, 60);  // TTL menor para estoque
    }

    return stock;
  }

  invalidateCustomer(phone) {
    this.cache.del(`customer:${phone}`);
  }
}

Filas Para Operacoes de Escrita

const Queue = require('bull');

const orderQueue = new Queue('orders', 'redis://localhost:6379');

// Chatbot adiciona na fila
async function createOrderAsync(orderData) {
  const job = await orderQueue.add(orderData, {
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 1000
    }
  });

  return job.id;
}

// Worker processa
orderQueue.process(async (job) => {
  const erp = createERPClient(process.env.ERP_TYPE, config);
  const order = await erp.createOrder(job.data);

  // Notificar cliente
  await sendWhatsAppMessage(
    job.data.customerPhone,
    `Pedido #${order.id} criado com sucesso!`
  );

  return order;
});

// Tratar falhas
orderQueue.on('failed', async (job, err) => {
  console.error(`Pedido falhou: ${err.message}`);

  await sendWhatsAppMessage(
    job.data.customerPhone,
    'Houve um problema ao processar seu pedido. Nossa equipe vai entrar em contato.'
  );
});

Monitoramento

const prometheus = require('prom-client');

// Metricas
const erpRequestDuration = new prometheus.Histogram({
  name: 'erp_request_duration_seconds',
  help: 'ERP request duration in seconds',
  labelNames: ['method', 'status']
});

const erpRequestErrors = new prometheus.Counter({
  name: 'erp_request_errors_total',
  help: 'Total ERP request errors',
  labelNames: ['method', 'error_type']
});

// Wrapper com metricas
async function withMetrics(method, fn) {
  const end = erpRequestDuration.startTimer({ method });

  try {
    const result = await fn();
    end({ status: 'success' });
    return result;
  } catch (error) {
    end({ status: 'error' });
    erpRequestErrors.inc({ method, error_type: error.code || 'unknown' });
    throw error;
  }
}

// Uso
async function getCustomer(phone) {
  return withMetrics('getCustomer', () => erp.getCustomer(phone));
}

Checklist de Integracao

  • Credenciais em variaveis de ambiente
  • Rate limiting configurado
  • Retry com backoff implementado
  • Cache para dados que nao mudam frequentemente
  • Fila para operacoes de escrita
  • Logs estruturados
  • Metricas de performance
  • Alertas para falhas
  • Documentacao da API/integracao
  • Teste de integracao automatizado

Sakaguchi IA - Inteligencia Artificial para Empresas Brasileiras

Sakaguchi IA

Precisa colocar isso em produção?

Engenharia de software, IA aplicada e cibersegurança para empresas que operam de verdade. Fale com nosso time.

Falar com a equipe