Skip to main content
Padrão para sincronização bidirecional — quando um lead muda no CRM reflete na Cordialy, e vice-versa.
// crm-sync.ts
import { CordialyClient } from './cordialy';

const cordialy = new CordialyClient(process.env.CORDIALY_API_KEY!);

// ── CRM → Cordialy ─────────────────────────────────────────────────────────

// Chamado quando um contato é criado/atualizado no CRM
export async function syncParaCordialy(contato: CRMContact) {
  try {
    // Tenta criar — se já existir, retorna o existente
    const lead = await cordialy.leads.create({
      customer_phone: contato.phone,
      name: contato.name,
      status: mapearStatus(contato.stage),
    });

    // Salva o ID da Cordialy no CRM para referência futura
    await crm.updateContact(contato.id, { cordialy_lead_id: lead.id });

    // Atribui o vendedor responsável se houver
    if (contato.owner_id) {
      const seller = await buscarSellerPorEmail(contato.owner_email);
      if (seller) {
        await cordialy.leads.update(lead.id, { seller_id: seller.id });
      }
    }
  } catch (err) {
    console.error(`Erro ao sincronizar contato ${contato.id}:`, err);
  }
}

// Chamado quando o status muda no CRM
export async function syncStatusParaCordialy(contato: CRMContact) {
  if (!contato.cordialy_lead_id) return;

  await cordialy.leads.update(contato.cordialy_lead_id, {
    status: mapearStatus(contato.stage),
  });

  // Cancela follow-ups pendentes se convertido ou perdido
  if (['converted', 'lost'].includes(mapearStatus(contato.stage))) {
    const followups = await cordialy.followups.scheduled(contato.cordialy_lead_id);
    const pendentes = followups.filter((f: any) => f.status === 'pending');
    await Promise.all(pendentes.map((f: any) => cordialy.followups.cancel(f.id)));
  }
}

function mapearStatus(stage: string): string {
  const mapa: Record<string, string> = {
    'new': 'pending',
    'contacted': 'in_progress',
    'qualified': 'in_progress',
    'won': 'converted',
    'lost': 'lost',
  };
  return mapa[stage] ?? 'pending';
}

// ── Cordialy → CRM (via webhook) ───────────────────────────────────────────

// Recebe eventos da Cordialy e atualiza o CRM
export async function processarWebhookCordialy(evento: any) {
  switch (evento.event) {
    case 'lead.created': {
      // Lead criou-se pelo WhatsApp — cria no CRM também
      await crm.createContact({
        phone: evento.data.customer_phone,
        name: evento.data.name,
        source: 'whatsapp',
        cordialy_lead_id: evento.data.lead_id,
      });
      break;
    }

    case 'lead.status_changed': {
      const contato = await crm.findByExternal(evento.data.lead_id);
      if (contato) {
        await crm.updateStage(contato.id, mapearStage(evento.data.new_status));
      }
      break;
    }

    case 'session.ended': {
      const contato = await crm.findByExternal(evento.data.lead_id);
      if (contato) {
        await crm.addNote(contato.id, {
          type: 'whatsapp_session',
          content: `Sessão encerrada com ${evento.data.message_count} mensagens. Status: ${evento.data.status}`,
          date: evento.data.ended_at,
        });
      }
      break;
    }
  }
}

function mapearStage(status: string): string {
  const mapa: Record<string, string> = {
    'pending': 'new',
    'in_progress': 'contacted',
    'converted': 'won',
    'lost': 'lost',
  };
  return mapa[status] ?? 'new';
}

// ── Sincronização inicial em lote ───────────────────────────────────────────

import pLimit from 'p-limit';

export async function syncInicial() {
  const contatos = await crm.listAllContacts();
  const limit = pLimit(10); // 10 simultâneas para respeitar rate limit

  console.log(`Sincronizando ${contatos.length} contatos...`);

  await Promise.all(
    contatos.map(c => limit(() => syncParaCordialy(c)))
  );

  console.log('Sincronização concluída.');
}