From 1c026e3dcdeec5a9e662d876cba4f27d75a6328e Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Sat, 14 Mar 2026 16:35:42 -0300 Subject: [PATCH] feat: add CRM templates --- .../attendance.gbdialog/create-ticket.bas | 40 ++++++ .../attendance.gbdialog/resolve-chat.bas | 63 ++++++++++ .../attendance.gbdialog/start.bas | 48 +++++++ .../attendance.gbdialog/take-next.bas | 27 ++++ .../attendance.gbdialog/transfer-chat.bas | 66 ++++++++++ .../attendance.gbdialog/view-queue.bas | 43 +++++++ crm/crm.gbai/crm.gbdialog/close-deal.bas | 77 ++++++++++++ crm/crm.gbai/crm.gbdialog/create-deal.bas | 106 ++++++++++++++++ crm/crm.gbai/crm.gbdialog/find-deal.bas | 50 ++++++++ crm/crm.gbai/crm.gbdialog/list-deals.bas | 60 +++++++++ crm/crm.gbai/crm.gbdialog/log-activity.bas | 54 ++++++++ .../crm.gbdialog/pipeline-summary.bas | 21 ++++ crm/crm.gbai/crm.gbdialog/start.bas | 52 ++++++++ crm/crm.gbai/crm.gbdialog/update-deal.bas | 118 ++++++++++++++++++ .../marketing.gbdialog/create-campaign.bas | 52 ++++++++ .../marketing.gbdialog/list-campaigns.bas | 44 +++++++ .../marketing.gbdialog/send-campaign.bas | 59 +++++++++ .../marketing.gbdialog/start.bas | 42 +++++++ 18 files changed, 1022 insertions(+) create mode 100644 crm/attendance.gbai/attendance.gbdialog/create-ticket.bas create mode 100644 crm/attendance.gbai/attendance.gbdialog/resolve-chat.bas create mode 100644 crm/attendance.gbai/attendance.gbdialog/start.bas create mode 100644 crm/attendance.gbai/attendance.gbdialog/take-next.bas create mode 100644 crm/attendance.gbai/attendance.gbdialog/transfer-chat.bas create mode 100644 crm/attendance.gbai/attendance.gbdialog/view-queue.bas create mode 100644 crm/crm.gbai/crm.gbdialog/close-deal.bas create mode 100644 crm/crm.gbai/crm.gbdialog/create-deal.bas create mode 100644 crm/crm.gbai/crm.gbdialog/find-deal.bas create mode 100644 crm/crm.gbai/crm.gbdialog/list-deals.bas create mode 100644 crm/crm.gbai/crm.gbdialog/log-activity.bas create mode 100644 crm/crm.gbai/crm.gbdialog/pipeline-summary.bas create mode 100644 crm/crm.gbai/crm.gbdialog/start.bas create mode 100644 crm/crm.gbai/crm.gbdialog/update-deal.bas create mode 100644 crm/marketing.gbai/marketing.gbdialog/create-campaign.bas create mode 100644 crm/marketing.gbai/marketing.gbdialog/list-campaigns.bas create mode 100644 crm/marketing.gbai/marketing.gbdialog/send-campaign.bas create mode 100644 crm/marketing.gbai/marketing.gbdialog/start.bas diff --git a/crm/attendance.gbai/attendance.gbdialog/create-ticket.bas b/crm/attendance.gbai/attendance.gbdialog/create-ticket.bas new file mode 100644 index 0000000..d3b63f0 --- /dev/null +++ b/crm/attendance.gbai/attendance.gbdialog/create-ticket.bas @@ -0,0 +1,40 @@ +PARAM title AS STRING LIKE "Equipamento com defeito" DESCRIPTION "Título do problema" +PARAM category AS STRING LIKE "support" DESCRIPTION "Categoria: support, billing, sales, bug" OPTIONAL +PARAM priority AS STRING LIKE "high" DESCRIPTION "Nível: low, normal, high, urgent" OPTIONAL +PARAM contact_email AS EMAIL LIKE "usuario@dominio.com" DESCRIPTION "Email do reclamante" OPTIONAL +PARAM description AS STRING LIKE "O mouse parou" DESCRIPTION "Detalhes da solicitação" + +DESCRIPTION "Cria um Ticket de suporte de longo prazo no sistema, fora da fila de atendimento tempo-real." + +IF NOT category THEN + category = "support" +END IF + +IF NOT priority THEN + priority = "normal" +END IF + +contact_id = "" +IF contact_email THEN + existing = GET "/api/crm/contacts?search=" + contact_email + IF UBOUND(existing) > 0 THEN + contact_id = FIRST(existing).id + END IF +END IF + +new_ticket = POST "/api/tickets", #{ + title: title, + description: description, + category: category, + priority: priority, + status: "open", + contact_id: contact_id +} + +TALK "🎫 **Ticket Criado com Sucesso!**" +TALK "ID: " + new_ticket.id +TALK "Título: " + title +TALK "Prioridade: " + priority +TALK "A equipe será notificada para escalonamento conforme SLA." + +RETURN new_ticket.id diff --git a/crm/attendance.gbai/attendance.gbdialog/resolve-chat.bas b/crm/attendance.gbai/attendance.gbdialog/resolve-chat.bas new file mode 100644 index 0000000..a2e12fd --- /dev/null +++ b/crm/attendance.gbai/attendance.gbdialog/resolve-chat.bas @@ -0,0 +1,63 @@ +PARAM session_id AS STRING LIKE "uuid" DESCRIPTION "ID da sessão atendida" OPTIONAL +PARAM status AS STRING LIKE "resolved" DESCRIPTION "Finalizar o chat ou aguardar (resolved, pending_customer)" OPTIONAL +PARAM summary AS STRING LIKE "Dúvida resolvida com sucesso" DESCRIPTION "Breve resumo da solução" OPTIONAL + +DESCRIPTION "Resolve a conversa, registrando o final no CRM (atividade) ou deixa pendente pelo cliente." + +IF NOT status THEN + status = "resolved" +END IF + +IF status = "resolved" THEN + IF NOT summary THEN + TALK "A sessão foi totalmente concluída? Qual seria o resumo?" + HEAR summary AS STRING + END IF +END IF + +' Tratar ID da sessão conversacionalmente +IF NOT session_id THEN + my_sessions = GET "/api/attendance/queue?status=active" + IF UBOUND(my_sessions) = 0 THEN + TALK "Você não tem nenhuma sessão de atendimento ativa no momento." + RETURN + END IF + IF UBOUND(my_sessions) = 1 THEN + session_id = FIRST(my_sessions).session_id + ELSE + TALK "Você tem várias sessões ativas. Qual delas quer resolver? Diga o nome do cliente." + FOR EACH s IN my_sessions + TALK "- **" + s.user_name + "**" + NEXT s + HEAR query AS STRING + FOR EACH s IN my_sessions + IF INSTR(LCASE(s.user_name), LCASE(query)) > 0 THEN + session_id = s.session_id + END IF + NEXT s + IF NOT session_id THEN + TALK "Sessão não identificada." + RETURN + END IF + END IF +END IF + +resolution_payload = #{ + status: status, + resolution_summary: summary, + resolved_by: GET "session.user_id", + resolved_at: FORMAT(NOW(), "YYYY-MM-DD HH:mm:ss") +} + +PUT "/api/attendance/sessions/" + session_id + "/resolve", resolution_payload + +IF status = "resolved" THEN + TALK "✅ **Atendimento Encerrado!**" + TALK "A conversa foi marcada como **Resolvida**." + TALK "Resumo: " + summary +ELSE + TALK "⏳ **Aguardando Cliente**" + TALK "O atendimento ficará pausado aguardando retorno do cliente." +END IF + +RETURN session_id diff --git a/crm/attendance.gbai/attendance.gbdialog/start.bas b/crm/attendance.gbai/attendance.gbdialog/start.bas new file mode 100644 index 0000000..84c5ff2 --- /dev/null +++ b/crm/attendance.gbai/attendance.gbdialog/start.bas @@ -0,0 +1,48 @@ +ADD TOOL "view-queue" +ADD TOOL "take-next" +ADD TOOL "transfer-chat" +ADD TOOL "resolve-chat" +ADD TOOL "queue-stats" +ADD TOOL "create-ticket" + +SET CONTEXT "attendance" AS "You are a customer service assistant for General Bots. You help attendants manage the support queue: view waiting customers, take conversations, transfer between attendants, resolve cases, and create tickets. All actions affect real sessions in the database." + +CLEAR SUGGESTIONS + +ADD SUGGESTION "queue" AS "Ver fila de espera" +ADD SUGGESTION "take" AS "Atender próximo" +ADD SUGGESTION "transfer" AS "Transferir conversa" +ADD SUGGESTION "resolve" AS "Resolver conversa" + +BEGIN TALK +**Atendimento — Gestão de Fila** + +Posso ajudar com: +• Ver a fila de clientes aguardando +• Atender o próximo da fila +• Transferir conversa para outro atendente +• Resolver/encerrar conversa +• Criar ticket de suporte +• Ver estatísticas do atendimento + +O que deseja? +END TALK + +BEGIN SYSTEM PROMPT +You are a customer service queue manager. + +Queue statuses: +- new: Just arrived, not yet in queue +- waiting: In queue, waiting for an attendant +- active: Being handled by an attendant +- pending_customer: Waiting for customer reply +- resolved: Completed + +Key rules: +- Always show the customer's name and channel (WhatsApp, Web, Telegram, etc.) +- Show how long the customer has been waiting +- When transferring, always ask for the reason +- When resolving, ask if the customer's issue was fully resolved +- Prioritize customers who have been waiting the longest +- If a customer is from a known CRM contact, show their company and deal info +END SYSTEM PROMPT diff --git a/crm/attendance.gbai/attendance.gbdialog/take-next.bas b/crm/attendance.gbai/attendance.gbdialog/take-next.bas new file mode 100644 index 0000000..0f936a1 --- /dev/null +++ b/crm/attendance.gbai/attendance.gbdialog/take-next.bas @@ -0,0 +1,27 @@ +PARAM session_id AS STRING LIKE "uuid" DESCRIPTION "ID da sessão (opcional). Se vazio, pega o próximo da fila." OPTIONAL + +DESCRIPTION "Atender o próximo cliente da fila ou um cliente específico." + +IF NOT session_id THEN + ' Tentar pegar o mais antigo da fila ("waiting", sem assignee) + queue = GET "/api/attendance/queue?status=waiting" + IF UBOUND(queue) = 0 THEN + TALK "A fila está vazia no momento. Bom trabalho!" + RETURN + END IF + + ' Pega o primeiro (mais antigo, se a API ordenar por tempo de espera) + first_item = FIRST(queue) + session_id = first_item.session_id +END IF + +' Endpoint hipotético para "tomar posse" da sessão +assign_result = POST "/api/attendance/sessions/" + session_id + "/assign", #{ + attendant_id: GET "session.user_id" +} + +TALK "✅ **Sessão atribuída a você!**" +TALK "Você agora está atendendo: " + assign_result.user_name + " via " + assign_result.channel +TALK "Use as ferramentas de chat para conversar com o cliente." + +RETURN session_id diff --git a/crm/attendance.gbai/attendance.gbdialog/transfer-chat.bas b/crm/attendance.gbai/attendance.gbdialog/transfer-chat.bas new file mode 100644 index 0000000..3c9e415 --- /dev/null +++ b/crm/attendance.gbai/attendance.gbdialog/transfer-chat.bas @@ -0,0 +1,66 @@ +PARAM session_id AS STRING LIKE "uuid" DESCRIPTION "ID da sessão" OPTIONAL +PARAM skill AS STRING LIKE "suporte" DESCRIPTION "Habilidade ou fila destino (ex: financeiro, vendas)" OPTIONAL +PARAM priority AS STRING LIKE "high" DESCRIPTION "Nova prioridade (urgent, high, normal, low)" OPTIONAL +PARAM reason AS STRING LIKE "Dúvida sobre boleto" DESCRIPTION "Motivo da transferência (obrigatório)" + +DESCRIPTION "Transfere o chat para outro nível de suporte ou fila usando Habilidades, ajustando prioridade." + +IF NOT reason THEN + TALK "Qual é o motivo da transferência para o próximo atendente?" + HEAR reason AS STRING +END IF + +IF NOT priority THEN + priority = "normal" +END IF + +' Tratar ID da sessão conversacionalmente +IF NOT session_id THEN + ' Busca sessões ativas do atendente + my_sessions = GET "/api/attendance/queue?status=active" + IF UBOUND(my_sessions) = 0 THEN + TALK "Você não tem nenhuma sessão de atendimento ativa no momento para transferir." + RETURN + END IF + IF UBOUND(my_sessions) = 1 THEN + session_id = FIRST(my_sessions).session_id + ELSE + TALK "Você tem " + UBOUND(my_sessions) + " atendimentos ativos. Vou mostrar os nomes, por favor diga qual deseja transferir:" + FOR EACH s IN my_sessions + TALK "- **" + s.user_name + "** via " + s.channel + NEXT s + HEAR query AS STRING + ' Busca simplificada + FOR EACH s IN my_sessions + IF INSTR(LCASE(s.user_name), LCASE(query)) > 0 THEN + session_id = s.session_id + END IF + NEXT s + IF NOT session_id THEN + TALK "Não consegui identificar a sessão." + RETURN + END IF + END IF +END IF + +' TRANSFER TO HUMAN envia para a roleta novamente +' No BASIC real, seria executado ou POSTado na API de transferência + +transfer_payload = #{ + required_skill: skill, + priority: priority, + transfer_reason: reason, + transferred_by: GET "session.user_id" +} + +POST "/api/attendance/sessions/" + session_id + "/transfer", transfer_payload + +TALK "✅ **Sessão Transferida!**" +TALK "Motivo: " + reason +IF skill THEN + TALK "Aguardando um especialista em **" + skill + "**" +ELSE + TALK "Aguardando o próximo atendente livre." +END IF + +RETURN session_id diff --git a/crm/attendance.gbai/attendance.gbdialog/view-queue.bas b/crm/attendance.gbai/attendance.gbdialog/view-queue.bas new file mode 100644 index 0000000..67c182a --- /dev/null +++ b/crm/attendance.gbai/attendance.gbdialog/view-queue.bas @@ -0,0 +1,43 @@ +PARAM status AS STRING LIKE "waiting" DESCRIPTION "Filtrar por status: waiting, active, all" OPTIONAL + +DESCRIPTION "Ver a fila de atendimento: quem está aguardando, há quanto tempo, e por qual canal." + +IF NOT status THEN + status = "waiting" +END IF + +queue = GET "/api/attendance/queue?status=" + status + +item_count = UBOUND(queue) + +IF item_count = 0 THEN + TALK "✅ Fila vazia! Nenhum cliente aguardando." + RETURN +END IF + +TALK "📋 **Fila de Atendimento — " + item_count + " cliente(s)**" +TALK "" + +FOR EACH item IN queue + channel_icon = "💬" + IF item.channel = "whatsapp" THEN + channel_icon = "📱" + ELSE IF item.channel = "telegram" THEN + channel_icon = "✈️" + ELSE IF item.channel = "email" THEN + channel_icon = "📧" + END IF + + TALK "---" + TALK channel_icon + " **" + item.user_name + "** via " + item.channel + TALK "⏱️ Aguardando há " + item.wait_time + TALK "💬 Última mensagem: " + item.last_message + + IF item.assigned_to THEN + TALK "👤 Atendente: " + item.assigned_to + END IF + + TALK "🔗 Session: " + item.session_id +NEXT item + +RETURN queue diff --git a/crm/crm.gbai/crm.gbdialog/close-deal.bas b/crm/crm.gbai/crm.gbdialog/close-deal.bas new file mode 100644 index 0000000..a6aa3e2 --- /dev/null +++ b/crm/crm.gbai/crm.gbdialog/close-deal.bas @@ -0,0 +1,77 @@ +PARAM deal_id AS STRING LIKE "uuid" DESCRIPTION "ID do deal a fechar" OPTIONAL +PARAM won AS BOOLEAN LIKE TRUE DESCRIPTION "TRUE se ganhou, FALSE se perdeu" +PARAM lost_reason AS STRING LIKE "Preço acima do mercado" DESCRIPTION "Motivo da perda (obrigatório se won=FALSE)" OPTIONAL + +DESCRIPTION "Fechar um deal como ganho (won) ou perdido (lost). Se perdido, informe o motivo." + +IF NOT deal_id OR deal_id = "uuid" THEN + TALK "Qual deal você deseja fechar? (Diga o nome da empresa ou título)" + HEAR query AS STRING + deals = GET "/api/crm/deals?search=" + query + "&limit=5" + IF UBOUND(deals) = 0 THEN + TALK "Não encontrei nenhum deal relacionado a '" + query + "'." + RETURN + END IF + IF UBOUND(deals) = 1 THEN + deal_id = FIRST(deals).id + ELSE + TALK "Encontrei várias opções. Qual delas quer fechar? Especifique melhor, por favor." + FOR EACH d IN deals + TALK "- **" + d.title + "** (" + d.stage + ")" + NEXT d + RETURN + END IF +END IF + +deal = GET "/api/crm/deals/" + deal_id +IF NOT deal THEN + TALK "Deal não encontrado: " + deal_id + RETURN +END IF + +IF won THEN + PUT "/api/crm/deals/" + deal_id, #{ + stage: "won", + won: TRUE, + probability: 100, + actual_close_date: FORMAT(TODAY(), "YYYY-MM-DD") + } + + POST "/api/crm/activities", #{ + activity_type: "deal_won", + subject: "Deal ganho: " + deal.title, + contact_id: deal.contact_id, + account_id: deal.account_id + } + + TALK "🎉 **Deal ganho!**" + TALK "**" + deal.title + "** — R$ " + FORMAT(deal.value, "#,##0") + TALK "Parabéns pela conquista!" +ELSE + IF NOT lost_reason THEN + TALK "Qual foi o motivo da perda?" + HEAR lost_reason AS STRING + END IF + + PUT "/api/crm/deals/" + deal_id, #{ + stage: "lost", + won: FALSE, + probability: 0, + lost_reason: lost_reason, + actual_close_date: FORMAT(TODAY(), "YYYY-MM-DD") + } + + POST "/api/crm/activities", #{ + activity_type: "deal_lost", + subject: "Deal perdido: " + deal.title, + description: "Motivo: " + lost_reason, + contact_id: deal.contact_id, + account_id: deal.account_id + } + + TALK "📋 **Deal perdido**" + TALK "**" + deal.title + "** — Motivo: " + lost_reason + TALK "Use essa experiência para os próximos negócios." +END IF + +RETURN deal_id diff --git a/crm/crm.gbai/crm.gbdialog/create-deal.bas b/crm/crm.gbai/crm.gbdialog/create-deal.bas new file mode 100644 index 0000000..a093522 --- /dev/null +++ b/crm/crm.gbai/crm.gbdialog/create-deal.bas @@ -0,0 +1,106 @@ +PARAM title AS STRING LIKE "Contrato Empresa ABC" DESCRIPTION "Título do deal ou oportunidade" +PARAM contact_email AS EMAIL LIKE "joao@empresa.com" DESCRIPTION "Email do contato principal" +PARAM contact_name AS STRING LIKE "João Silva" DESCRIPTION "Nome do contato" OPTIONAL +PARAM company AS STRING LIKE "Empresa ABC Ltda" DESCRIPTION "Nome da empresa/conta" OPTIONAL +PARAM value AS MONEY LIKE 50000 DESCRIPTION "Valor estimado do deal" +PARAM stage AS STRING LIKE "new" DESCRIPTION "Estágio inicial: new, qualified, proposal, negotiation" OPTIONAL +PARAM source AS STRING LIKE "WHATSAPP" DESCRIPTION "Origem: WHATSAPP, EMAIL, CALL, WEBSITE, REFERAL, PARTNER, CHAT" OPTIONAL +PARAM close_date AS DATE LIKE "2026-06-30" DESCRIPTION "Data prevista de fechamento" OPTIONAL +PARAM notes AS STRING LIKE "Conheceu na feira" DESCRIPTION "Observações sobre o deal" OPTIONAL + +DESCRIPTION "Criar um novo deal no pipeline de vendas. Registra o negócio com valor, estágio, contato e empresa." + +IF NOT stage THEN + stage = "new" +END IF + +IF NOT source THEN + source = "CHAT" +END IF + +IF NOT close_date THEN + close_date = DATEADD(TODAY(), 30, "day") +END IF + +' Determine probability based on stage +probability = 10 +IF stage = "qualified" THEN + probability = 30 +ELSE IF stage = "proposal" THEN + probability = 50 +ELSE IF stage = "negotiation" THEN + probability = 70 +END IF + +' Check if contact exists, create if not +contact_id = "" +IF contact_email THEN + existing = GET "/api/crm/contacts?search=" + contact_email + IF UBOUND(existing) > 0 THEN + contact_id = FIRST(existing).id + TALK "Contato encontrado: " + FIRST(existing).first_name + " " + FIRST(existing).last_name + ELSE + ' Create contact + IF NOT contact_name THEN + contact_name = "Contato" + END IF + parts = SPLIT(contact_name, " ") + first_name = FIRST(parts) + last_name = LAST(parts) + + new_contact = POST "/api/crm/contacts", #{ + first_name: first_name, + last_name: last_name, + email: contact_email, + company: company, + source: source + } + contact_id = new_contact.id + TALK "Novo contato criado: " + contact_name + END IF +END IF + +' Check if account exists, create if not +account_id = "" +IF company THEN + existing_acc = GET "/api/crm/accounts?search=" + company + IF UBOUND(existing_acc) > 0 THEN + account_id = FIRST(existing_acc).id + ELSE + new_account = POST "/api/crm/accounts", #{ + name: company + } + account_id = new_account.id + TALK "Nova conta criada: " + company + END IF +END IF + +' Create the deal +deal = POST "/api/crm/deals", #{ + title: title, + value: value, + stage: stage, + probability: probability, + source: source, + expected_close_date: close_date, + contact_id: contact_id, + account_id: account_id, + notes: notes +} + +TALK "✅ **Deal criado com sucesso!**" +TALK "" +TALK "**" + title + "**" +TALK "💰 Valor: R$ " + FORMAT(value, "#,##0") +TALK "📊 Estágio: " + stage + " (" + probability + "% probabilidade)" +TALK "📅 Previsão de fechamento: " + close_date + +IF company THEN + TALK "🏢 Empresa: " + company +END IF + +IF contact_email THEN + TALK "👤 Contato: " + contact_email +END IF + +RETURN deal.id diff --git a/crm/crm.gbai/crm.gbdialog/find-deal.bas b/crm/crm.gbai/crm.gbdialog/find-deal.bas new file mode 100644 index 0000000..7dc5af9 --- /dev/null +++ b/crm/crm.gbai/crm.gbdialog/find-deal.bas @@ -0,0 +1,50 @@ +PARAM query AS STRING LIKE "Acme" DESCRIPTION "Nome da empresa, pessoa ou título do deal" +PARAM return_id_only AS BOOLEAN LIKE TRUE DESCRIPTION "Se TRUE retorna apenas o ID. Se FALSE, descreve as opções ao usuário." OPTIONAL + +DESCRIPTION "Procura por um deal pelo nome da empresa, título ou pessoa. Usado por outros tools para não obrigar o humano a saber UUIDs." + +IF NOT return_id_only THEN return_id_only = TRUE + +' Search API (assuming ?search= looks at title, contact email/name, and account name) +deals = GET "/api/crm/deals?search=" + query + "&limit=5" +count = UBOUND(deals) + +IF count = 0 THEN + TALK "Não encontrei nenhum deal relacionado a '" + query + "'." + RETURN "" +END IF + +IF count = 1 THEN + found_deal = FIRST(deals) + IF NOT return_id_only THEN + TALK "Encontrei: **" + found_deal.title + "** (R$ " + FORMAT(found_deal.value, "#,##0") + ", " + found_deal.stage + ")" + END IF + RETURN found_deal.id +END IF + +' If multiple matches found, ask user to disambiguate +TALK "Encontrei " + count + " opções para '" + query + "'. Qual delas?" +idx = 1 +FOR EACH d IN deals + TALK idx + ". **" + d.title + "** (" + d.stage + ") - R$ " + FORMAT(d.value, "#,##0") + idx = idx + 1 +NEXT d + +HEAR choice + +' Simple index selection logic (assuming user types "1", "2") +choice_num = TO_NUMBER(choice) +IF choice_num > 0 AND choice_num <= count THEN + selected = deals[choice_num - 1] + RETURN selected.id +ELSE + ' Fallback to string matching on title + FOR EACH d IN deals + IF INSTR(LCASE(d.title), LCASE(choice)) > 0 THEN + RETURN d.id + END IF + NEXT d +END IF + +TALK "Desculpe, não consegui identificar qual você escolheu." +RETURN "" diff --git a/crm/crm.gbai/crm.gbdialog/list-deals.bas b/crm/crm.gbai/crm.gbdialog/list-deals.bas new file mode 100644 index 0000000..7ee8e1d --- /dev/null +++ b/crm/crm.gbai/crm.gbdialog/list-deals.bas @@ -0,0 +1,60 @@ +PARAM stage AS STRING LIKE "proposal" DESCRIPTION "Filtrar por estágio: new, qualified, proposal, negotiation, won, lost" OPTIONAL +PARAM owner AS STRING LIKE "joao@empresa.com" DESCRIPTION "Filtrar por dono do deal" OPTIONAL + +DESCRIPTION "Listar deals do pipeline de vendas. Pode filtrar por estágio e por dono." + +url = "/api/crm/deals?" + +IF stage THEN + url = url + "stage=" + stage + "&" +END IF + +IF owner THEN + url = url + "search=" + owner + "&" +END IF + +url = url + "limit=20" + +deals = GET url + +deal_count = UBOUND(deals) + +IF deal_count = 0 THEN + TALK "Nenhum deal encontrado com os filtros informados." + RETURN +END IF + +TALK "📊 **Pipeline — " + deal_count + " deal(s) encontrado(s)**" +TALK "" + +total_value = 0 +total_weighted = 0 + +FOR EACH deal IN deals + prob = deal.probability + weighted = deal.value * prob / 100 + total_value = total_value + deal.value + total_weighted = total_weighted + weighted + + TALK "---" + TALK "**" + deal.title + "**" + TALK "💰 R$ " + FORMAT(deal.value, "#,##0") + " | " + deal.stage + " (" + prob + "%)" + + IF deal.expected_close_date THEN + TALK "📅 Previsão: " + deal.expected_close_date + END IF + + IF deal.contact_id THEN + contact = GET "/api/crm/contacts/" + deal.contact_id + IF contact THEN + TALK "👤 " + contact.first_name + " " + contact.last_name + END IF + END IF +NEXT deal + +TALK "" +TALK "**Totais:**" +TALK "💰 Valor total: R$ " + FORMAT(total_value, "#,##0") +TALK "📊 Valor ponderado: R$ " + FORMAT(total_weighted, "#,##0") + +RETURN deals diff --git a/crm/crm.gbai/crm.gbdialog/log-activity.bas b/crm/crm.gbai/crm.gbdialog/log-activity.bas new file mode 100644 index 0000000..f7d26d0 --- /dev/null +++ b/crm/crm.gbai/crm.gbdialog/log-activity.bas @@ -0,0 +1,54 @@ +PARAM activity_type AS STRING LIKE "call" DESCRIPTION "Tipo: call, email, meeting, note, follow_up" +PARAM subject AS STRING LIKE "Ligação de follow-up" DESCRIPTION "Assunto da atividade" +PARAM contact_email AS EMAIL LIKE "joao@empresa.com" DESCRIPTION "Email do contato relacionado" OPTIONAL +PARAM deal_id AS STRING LIKE "uuid" DESCRIPTION "ID do deal relacionado" OPTIONAL +PARAM description AS STRING LIKE "Discutimos termos do contrato" DESCRIPTION "Detalhes da atividade" OPTIONAL +PARAM due_date AS DATE LIKE "2026-04-01" DESCRIPTION "Data de follow-up" OPTIONAL + +DESCRIPTION "Registrar uma atividade de CRM: ligação, email, reunião, nota ou follow-up. Pode vincular a contato e/ou deal." + +' Resolve contact_id from email +contact_id = "" +IF contact_email THEN + contacts = GET "/api/crm/contacts?search=" + contact_email + IF UBOUND(contacts) > 0 THEN + contact_id = FIRST(contacts).id + END IF +END IF + +' Resolve deal_id if empty or uuid but we need it +IF NOT deal_id OR deal_id = "uuid" THEN + TALK "A qual deal esta atividade se refere? (Opcional: Diga 'nenhum' ou o nome da empresa)" + HEAR query AS STRING + IF LCASE(query) != "nenhum" AND LCASE(query) != "none" THEN + deals = GET "/api/crm/deals?search=" + query + "&limit=5" + IF UBOUND(deals) > 0 THEN + deal_id = FIRST(deals).id + TALK "Atividade será vinculada ao deal: " + FIRST(deals).title + END IF + ELSE + deal_id = "" + END IF +END IF + +' Create activity +activity = POST "/api/crm/activities", #{ + activity_type: activity_type, + subject: subject, + description: description, + contact_id: contact_id, + due_date: due_date +} + +TALK "✅ **Atividade registrada!**" +TALK "📋 " + activity_type + ": " + subject + +IF contact_email THEN + TALK "👤 Contato: " + contact_email +END IF + +IF due_date THEN + TALK "📅 Follow-up: " + due_date +END IF + +RETURN activity.id diff --git a/crm/crm.gbai/crm.gbdialog/pipeline-summary.bas b/crm/crm.gbai/crm.gbdialog/pipeline-summary.bas new file mode 100644 index 0000000..a3b8409 --- /dev/null +++ b/crm/crm.gbai/crm.gbdialog/pipeline-summary.bas @@ -0,0 +1,21 @@ +DESCRIPTION "Exibe resumo do pipeline de vendas: total por estágio, valor, taxa de conversão." + +stats = GET "/api/crm/stats" +stages = GET "/api/crm/pipeline" + +TALK "📊 **Resumo do Pipeline de Vendas**" +TALK "" +TALK "💰 Valor total no pipeline: R$ " + FORMAT(stats.total_value, "#,##0") +TALK "🏆 Valor ganho: R$ " + FORMAT(stats.won_value, "#,##0") +TALK "📈 Taxa de conversão: " + FORMAT(stats.conversion_rate, "#0.0") + "%" +TALK "📐 Ticket médio: R$ " + FORMAT(stats.avg_deal_size, "#,##0") +TALK "" + +IF stats.stages THEN + TALK "**Por estágio:**" + FOR EACH sg IN stats.stages + TALK " " + sg.name + ": " + sg.count + " deals — R$ " + FORMAT(sg.total_value, "#,##0") + NEXT sg +END IF + +RETURN stats diff --git a/crm/crm.gbai/crm.gbdialog/start.bas b/crm/crm.gbai/crm.gbdialog/start.bas new file mode 100644 index 0000000..0ca3ddd --- /dev/null +++ b/crm/crm.gbai/crm.gbdialog/start.bas @@ -0,0 +1,52 @@ +ADD TOOL "create-deal" +ADD TOOL "update-deal" +ADD TOOL "list-deals" +ADD TOOL "close-deal" +ADD TOOL "add-contact" +ADD TOOL "search-contact" +ADD TOOL "add-account" +ADD TOOL "log-activity" +ADD TOOL "pipeline-summary" + +SET CONTEXT "crm" AS "You are a CRM sales assistant for General Bots. You help salespeople create and manage deals, contacts, and accounts. All data is stored in PostgreSQL via the API. Deals follow a unified pipeline: new → qualified → proposal → negotiation → won/lost. There are no separate 'leads' or 'opportunities' — everything is a Deal with a stage. Business units are Departments from people_departments." + +CLEAR SUGGESTIONS + +ADD SUGGESTION "newdeal" AS "Criar um novo deal" +ADD SUGGESTION "pipeline" AS "Ver meu pipeline" +ADD SUGGESTION "contacts" AS "Buscar contato" +ADD SUGGESTION "report" AS "Relatório de vendas" + +BEGIN TALK +**CRM — Gestão de Vendas** + +Posso ajudar com: +• Criar e gerenciar deals (negócios) +• Buscar e cadastrar contatos +• Cadastrar contas (empresas) +• Atualizar estágios do pipeline +• Relatórios e previsões de vendas +• Registrar atividades (ligações, emails, reuniões) + +O que deseja fazer? +END TALK + +BEGIN SYSTEM PROMPT +You are a CRM sales assistant. All entities are managed via the General Bots REST API. + +Pipeline stages (in order): +- new: Initial contact, just entered the funnel +- qualified: Budget, authority, need, timeline confirmed (BANT) +- proposal: Quote or proposal sent to the customer +- negotiation: Active discussions on terms +- won: Deal successfully closed +- lost: Deal lost (always ask for lost_reason) + +Key rules: +- Always confirm information BEFORE saving +- Use Brazilian Real (BRL) as default currency unless the user specifies otherwise +- When creating a deal, always try to link to an existing contact or create one +- When closing a deal as lost, always ask for the reason +- Encourage the salesperson and suggest next actions based on the new stage +END SYSTEM PROMPT +ADD TOOL "find-deal" diff --git a/crm/crm.gbai/crm.gbdialog/update-deal.bas b/crm/crm.gbai/crm.gbdialog/update-deal.bas new file mode 100644 index 0000000..dbf9113 --- /dev/null +++ b/crm/crm.gbai/crm.gbdialog/update-deal.bas @@ -0,0 +1,118 @@ +PARAM deal_id AS STRING LIKE "uuid" DESCRIPTION "ID do deal a atualizar" OPTIONAL +PARAM stage AS STRING LIKE "qualified" DESCRIPTION "Novo estágio: new, qualified, proposal, negotiation, won, lost" OPTIONAL +PARAM value AS MONEY LIKE 75000 DESCRIPTION "Novo valor do deal" OPTIONAL +PARAM lost_reason AS STRING LIKE "Preço" DESCRIPTION "Motivo da perda (obrigatório se stage=lost)" OPTIONAL +PARAM notes AS STRING LIKE "Reunião positiva" DESCRIPTION "Observações adicionais" OPTIONAL + +DESCRIPTION "Atualizar um deal existente: mudar estágio, valor, adicionar notas. Se mudar para 'won' ou 'lost', o deal é fechado." + +' Validate deal exists +IF NOT deal_id OR deal_id = "uuid" THEN + TALK "Qual deal você deseja atualizar? (Pode dizer o nome da empresa ou o título)" + HEAR query AS STRING + ' Usar a ferramenta recém-criada (ou lógica similar) para buscar + deals = GET "/api/crm/deals?search=" + query + "&limit=5" + IF UBOUND(deals) = 0 THEN + TALK "Não encontrei nenhum deal relacionado a '" + query + "'." + RETURN + END IF + IF UBOUND(deals) = 1 THEN + deal_id = FIRST(deals).id + ELSE + TALK "Encontrei várias opções. Seguem as 3 primeiras:" + count = 1 + FOR EACH d IN deals + IF count <= 3 THEN + TALK count + ". **" + d.title + "** (R$ " + FORMAT(d.value, "#,##0") + ", " + d.stage + ")" + END IF + count = count + 1 + NEXT d + TALK "Por favor, especifique melhor informando o título exato ou o e-mail do contato." + RETURN + END IF +END IF + +deal = GET "/api/crm/deals/" + deal_id +IF NOT deal THEN + TALK "Deal não encontrado com ID: " + deal_id + RETURN +END IF + +TALK "Deal atual: **" + deal.title + "** — " + deal.stage + " — R$ " + FORMAT(deal.value, "#,##0") + +' Build update payload +update = #{} + +IF stage THEN + ' Validate stage + valid_stages = "new,qualified,proposal,negotiation,won,lost" + IF INSTR(valid_stages, stage) = 0 THEN + TALK "Estágio inválido. Use: new, qualified, proposal, negotiation, won, lost" + RETURN + END IF + + update.stage = stage + + ' Set probability + IF stage = "new" THEN + update.probability = 10 + ELSE IF stage = "qualified" THEN + update.probability = 30 + ELSE IF stage = "proposal" THEN + update.probability = 50 + ELSE IF stage = "negotiation" THEN + update.probability = 70 + ELSE IF stage = "won" THEN + update.probability = 100 + update.won = TRUE + update.actual_close_date = FORMAT(TODAY(), "YYYY-MM-DD") + ELSE IF stage = "lost" THEN + update.probability = 0 + update.won = FALSE + update.actual_close_date = FORMAT(TODAY(), "YYYY-MM-DD") + IF NOT lost_reason THEN + TALK "Por que o deal foi perdido?" + HEAR lost_reason AS STRING + END IF + update.lost_reason = lost_reason + END IF +END IF + +IF value THEN + update.value = value +END IF + +IF notes THEN + update.notes = notes +END IF + +' Execute update +PUT "/api/crm/deals/" + deal_id, update + +' Log activity +POST "/api/crm/activities", #{ + activity_type: "stage_change", + subject: "Deal atualizado para " + stage, + description: notes, + contact_id: deal.contact_id +} + +' Response by stage +IF stage = "won" THEN + TALK "🎉 **Parabéns! Deal ganho!**" + TALK "**" + deal.title + "** fechado com sucesso!" + TALK "💰 Valor: R$ " + FORMAT(COALESCE(value, deal.value), "#,##0") +ELSE IF stage = "lost" THEN + TALK "📋 **Deal perdido**" + TALK "**" + deal.title + "** marcado como perdido." + TALK "📝 Motivo: " + lost_reason + TALK "Analise o que aconteceu para melhorar nas próximas!" +ELSE IF stage THEN + TALK "✅ **Deal atualizado!**" + TALK "**" + deal.title + "** → **" + stage + "**" + TALK "📊 Probabilidade: " + update.probability + "%" +ELSE + TALK "✅ Deal atualizado com sucesso." +END IF + +RETURN deal_id diff --git a/crm/marketing.gbai/marketing.gbdialog/create-campaign.bas b/crm/marketing.gbai/marketing.gbdialog/create-campaign.bas new file mode 100644 index 0000000..cb928c0 --- /dev/null +++ b/crm/marketing.gbai/marketing.gbdialog/create-campaign.bas @@ -0,0 +1,52 @@ +PARAM name AS STRING LIKE "Promoção Dia das Mães" DESCRIPTION "Nome descritivo da campanha" +PARAM channel AS STRING LIKE "email" DESCRIPTION "Canal de envio: whatsapp, email, sms, telegram" +PARAM scheduled_date AS DATE LIKE "2026-05-01 09:00:00" DESCRIPTION "Data/Hora de agendamento do envio (opcional)" OPTIONAL +PARAM list_id AS STRING LIKE "uuid_da_lista" DESCRIPTION "ID da Lista de Contatos alvo" OPTIONAL +PARAM template_id AS STRING LIKE "uuid_do_template" DESCRIPTION "ID do Template de Conteúdo" OPTIONAL +PARAM ai_generate_template AS STRING LIKE "Crie um email rápido de 3 parágrafos sobre sapatos com desconto de 15%" DESCRIPTION "Prompt para IA caso queira gerar um template agora" OPTIONAL + +DESCRIPTION "Cria uma Campanha de Marketing e agenda seu envio. Pode integrar/vincular a listas e templates existentes." + +IF NOT list_id THEN + TALK "Qual lista de contatos você deseja usar para esta campanha?" + ' Aqui seria uma busca na db ou listagem interativa + HEAR list_id AS STRING +END IF + +// Create template if an AI prompt was given but no template_id +IF ai_generate_template AND NOT template_id THEN + TALK "🤖 Gerando e salvando um template de " + channel + " baseado no seu pedido..." + new_template = POST "/api/marketing/templates", #{ + name: name + " Template", + channel: channel, + ai_prompt: ai_generate_template + } + template_id = new_template.id + TALK "Template ID retornado: " + template_id +ELSE IF NOT template_id THEN + TALK "Tem o ID do template guardado? Diga-me por favor." + HEAR template_id AS STRING +END IF + +new_campaign = POST "/api/marketing/campaigns", #{ + name: name, + channel: channel, + status: "draft", + scheduled_at: scheduled_date, + template_id: template_id, + list_id: list_id +} + +TALK "📣 **Campanha Criada!**" +TALK "Nome: " + name +TALK "Canal: " + UCASE(channel) +TALK "Status: Draft" +TALK "ID: " + new_campaign.id + +IF scheduled_date THEN + TALK "Agendada para: " + scheduled_date +ELSE + TALK "Pendente de envio. Use 'send-campaign' para dispará-la agora." +END IF + +RETURN new_campaign.id diff --git a/crm/marketing.gbai/marketing.gbdialog/list-campaigns.bas b/crm/marketing.gbai/marketing.gbdialog/list-campaigns.bas new file mode 100644 index 0000000..84055df --- /dev/null +++ b/crm/marketing.gbai/marketing.gbdialog/list-campaigns.bas @@ -0,0 +1,44 @@ +PARAM status AS STRING LIKE "draft" DESCRIPTION "Filtro de status (draft, scheduled, sending, sent, completed)" OPTIONAL +PARAM limit AS INTEGER LIKE 10 DESCRIPTION "Número máximo de campanhas a exibir" OPTIONAL + +DESCRIPTION "Lista as campanhas de marketing." + +IF NOT limit THEN + limit = 10 +END IF + +url = "/api/marketing/campaigns?limit=" + limit + +IF status THEN + url = url + "&status=" + status +END IF + +campaigns = GET url +count = UBOUND(campaigns) + +IF count = 0 THEN + TALK "Nenhuma campanha encontrada." + RETURN +END IF + +TALK "📊 **Campanhas — " + count + " resultado(s)**" + +FOR EACH c IN campaigns + TALK "---" + TALK "**" + c.name + "** (" + c.status + ")" + TALK "📣 Canal: " + UCASE(c.channel) + TALK "🔑 ID: " + c.id + + IF c.scheduled_at THEN + TALK "🕒 Agendada: " + c.scheduled_at + END IF + + IF c.metrics THEN + TALK "📈 Enviados: " + c.metrics.sent + " | Erros: " + c.metrics.failed + IF c.channel = "email" THEN + TALK " Aberturas: " + c.metrics.opened + " | Clicks: " + c.metrics.clicked + END IF + END IF +NEXT c + +RETURN campaigns diff --git a/crm/marketing.gbai/marketing.gbdialog/send-campaign.bas b/crm/marketing.gbai/marketing.gbdialog/send-campaign.bas new file mode 100644 index 0000000..41c625a --- /dev/null +++ b/crm/marketing.gbai/marketing.gbdialog/send-campaign.bas @@ -0,0 +1,59 @@ +PARAM campaign_id AS STRING LIKE "uuid" DESCRIPTION "ID da Campanha" OPTIONAL +PARAM allow_over_budget AS BOOLEAN LIKE FALSE DESCRIPTION "Permitir envio mesmo se ultrapassar o orçamento do canal?" OPTIONAL + +DESCRIPTION "Confirma e inicia o disparo (envio) imediato de uma campanha." + +IF NOT campaign_id OR campaign_id = "uuid" THEN + TALK "Qual campanha você deseja enviar? (Diga o nome)" + HEAR query AS STRING + camps = GET "/api/marketing/campaigns?search=" + query + "&limit=5" + IF UBOUND(camps) = 0 THEN + TALK "Não encontrei nenhuma campanha com o termo '" + query + "'." + RETURN + END IF + IF UBOUND(camps) = 1 THEN + campaign_id = FIRST(camps).id + ELSE + TALK "Encontrei várias campanhas. Qual delas?" + FOR EACH c IN camps + TALK "- **" + c.name + "** (" + c.status + ")" + NEXT c + RETURN + END IF +END IF + +camp = GET "/api/marketing/campaigns/" + campaign_id +IF NOT camp THEN + TALK "Campanha não encontrada: " + campaign_id + RETURN +END IF + +IF camp.status = "sent" OR camp.status = "completed" THEN + TALK "A campanha '" + camp.name + "' já foi enviada." + RETURN +END IF + +IF NOT camp.list_id THEN + TALK "Esta campanha não tem uma lista de contatos vinculada. Não é possível enviar." + RETURN +END IF + +list_info = GET "/api/marketing/lists/" + camp.list_id +TALK "🔍 **Análise Pré-Envio:**" +TALK "• Campanha: " + camp.name +TALK "• Canal: " + UCASE(camp.channel) +TALK "• Segmentação/Lista: " + list_info.name + " (" + list_info.contact_count + " contatos)" + +TALK "Deseja realmente iniciar o envio AGORA para " + list_info.contact_count + " pessoas?" +HEAR confirm AS BOOLEAN + +IF confirm THEN + POST "/api/marketing/campaigns/" + campaign_id + "/send" + TALK "🚀 **Envio Iniciado!**" + TALK "As automações do " + UCASE(camp.channel) + " foram engatilhadas." + TALK "Você pode acompanhar as métricas em 'list-campaigns' depois de alguns minutos." +ELSE + TALK "Envio cancelado. A campanha continua no status '" + camp.status + "'." +END IF + +RETURN camp_id diff --git a/crm/marketing.gbai/marketing.gbdialog/start.bas b/crm/marketing.gbai/marketing.gbdialog/start.bas new file mode 100644 index 0000000..4bdb3f0 --- /dev/null +++ b/crm/marketing.gbai/marketing.gbdialog/start.bas @@ -0,0 +1,42 @@ +ADD TOOL "create-campaign" +ADD TOOL "list-campaigns" +ADD TOOL "send-campaign" +ADD TOOL "create-template" +ADD TOOL "list-templates" +ADD TOOL "create-dynamic-list" + +SET CONTEXT "marketing" AS "You are a marketing automation assistant for General Bots. You help marketers create campaigns, design templates (email/WhatsApp), build contact lists (static or dynamic), and schedule or send messages. All features interact with the CRM contacts via the General Bots marketing REST API." + +CLEAR SUGGESTIONS + +ADD SUGGESTION "newcamp" AS "Criar nova campanha" +ADD SUGGESTION "viewcamps" AS "Ver minhas campanhas" +ADD SUGGESTION "lists" AS "Criar lista de contatos" +ADD SUGGESTION "send" AS "Enviar uma campanha" + +BEGIN TALK +**Marketing — Campanhas e Envio** + +Posso ajudar com: +• Criar Campanhas (WhatsApp, Email, SMS) +• Gerenciar Templates (com IA ou predefinidos) +• Construir Listas Dinâmicas (ex: VIPs, Clientes do Bairro X) +• Enviar campanhas em massa (Broadcast) +• Ver métricas e estatísticas de aberturas + +O que quer divulgar hoje? +END TALK + +BEGIN SYSTEM PROMPT +You are a Marketing Automation assistant. + +Key workflows: +1. Contact List -> Select *who* receives it. +2. Template -> Define *what* is sent. If it's WhatsApp, it usually needs a "meta_template_id" or approval. +3. Campaign -> Groups List + Template + Schedule + Channel. We can "send" or "schedule" it. + +Key rules: +- Provide marketing advice: suggest A/B testing or better copy when writing templates. +- Ensure the user confirms the number of recipients before sending a campaign. +- AI prompt in templates allows personalizing messages per recipient (e.g. "Escreva de forma amigável e ofereça 10%"). +END SYSTEM PROMPT