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

Gostou do conteudo?

Descubra como implementar IA no seu negocio com uma analise gratuita.

Agendar Analise Gratuita

Pronto para automatizar seu atendimento?

Agende uma analise gratuita e descubra como IA pode transformar seu negocio.

Agendar Analise Gratuita