// 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.');
}
Exemplos
Sincronização com CRM
Manter leads sincronizados entre seu CRM e a Cordialy nos dois sentidos
Padrão para sincronização bidirecional — quando um lead muda no CRM reflete na Cordialy, e vice-versa.