diff --git a/enterprise/app/services/captain/contact_memories/extraction_service.rb b/enterprise/app/services/captain/contact_memories/extraction_service.rb index 4922ba51c..2a04171f6 100644 --- a/enterprise/app/services/captain/contact_memories/extraction_service.rb +++ b/enterprise/app/services/captain/contact_memories/extraction_service.rb @@ -54,10 +54,22 @@ class Captain::ContactMemories::ExtractionService - ✅ **CERTO**: nada. O cliente não reservou — disse que vai entrar em contato. Se amanhã ele reservar de fato, a conversa de amanhã vira memória. ### Sinais de AÇÃO CONSUMADA (pode virar `padrao_comportamental` com data): - - Bot gerou Pix / enviou link de reserva **E** cliente confirmou recebimento ou pagou. - - Cliente disse explicitamente "paguei", "confirmado", "pode confirmar", "tá feito", "perfeito, pode marcar". - - Bot respondeu confirmando suíte + data + valor **E** cliente não desdisse. - - Registro de estadia: "fiquei na Alexa em 03/02", "nos hospedamos no fim de semana". + - **Pagamento confirmado**: cliente disse literalmente "paguei o Pix", "Pix pago", "fiz o pagamento", "acabei de pagar", OU aparece mensagem automática "Pagamento confirmado" / "Pix recebido" do bot. + - Registro de estadia PASSADA: "fiquei na Alexa em 03/02", "nos hospedamos no fim de semana". + - Cliente disse "já estou no hotel", "cheguei", "fazendo check-in agora". + + ### ATENÇÃO — Pix GERADO ≠ Pix PAGO (diferença crítica) + + Quando o bot gera um Pix e o cliente **só agradece ou silencia**, isso **NÃO é reserva consumada**. Pix gerado é um convite pra pagar — a reserva só vira real quando o pagamento cai. + + | Situação na conversa | É `padrao_comportamental "Reservou X"`? | + |---|---| + | Bot gerou Pix + cliente disse "obrigado" / nada | ❌ NÃO (nem "solicitou reserva" vale — evite o tipo `padrao_comportamental` inteiro) | + | Bot gerou Pix + cliente disse "paguei" / "fiz o Pix" | ✅ SIM, pode registrar "Reservou X em DD/MM/AAAA" | + | Bot gerou Pix + aparece msg automática "Pagamento confirmado" | ✅ SIM | + | Cliente só pediu valor sem pedir pra reservar | ❌ NÃO | + + Regra prática: se você não consegue apontar uma frase LITERAL de pagamento confirmado (do cliente ou do sistema), **não extraia `padrao_comportamental` de reserva**. Melhor memória ausente do que memória mentirosa dizendo que o cliente reservou quando só gerou Pix. ### Sinais de INTENÇÃO FUTURA (NÃO MEMORIZE — retorne nada): - "Entro em contato amanhã para reservar" @@ -123,14 +135,16 @@ class Captain::ContactMemories::ExtractionService "Escolheu 4hrs em 14/03/2026" SIM: "Sempre chego tarde, entre 23h e meia-noite" (declarou hábito) SIM: "Costumo ficar só o pernoite" (declarou hábito) - SIM: "Reservou Alexa para pernoite em 23/05/2026" — APENAS se a reserva foi consumada (Pix gerado + cliente não desistiu, OU cliente disse "pode confirmar" + bot confirmou) + SIM: "Reservou Alexa para pernoite em 23/05/2026" — APENAS se há confirmação LITERAL de pagamento (cliente disse "paguei"/"Pix pago" OU mensagem automática "Pagamento confirmado"). Pix meramente gerado não conta. SIM: "Escolheu 4hrs na visita de 14/03/2026" — se efetivamente escolheu e fechou NÃO: "Costuma ficar 2 horas" (SEM DATA e SEM declaração — banido) NÃO: "Prefere permanência de 4 horas" (banido — isso seria preferencia, que exige declaração explícita) NÃO: "Vai chegar às 22h hoje" (intenção da conversa atual, não histórico) NÃO: "Reservou X" quando o cliente só disse "entro em contato amanhã para reservar" ou "quero reservar" (intenção futura — violação da REGRA DE OURO). NÃO: "Reservou X" quando o bot apenas cotou preço e o cliente não fechou explicitamente. - REGRA CRÍTICA: se você vai registrar uma escolha pontual, (a) a ação DEVE ter sido consumada, e (b) SEMPRE inclua a data no content. Memória sem data vira ruído; memória sem consumação vira mentira. + NÃO: "Reservou X em DD/MM" quando o bot GEROU Pix mas o cliente só agradeceu / ficou em silêncio / não há confirmação de pagamento na conversa. Pix gerado sem pagamento = pré-reserva, NÃO é ação consumada, NÃO vira memória. + NÃO: "Solicitou reserva de X" / "Pediu Pix para X" — evite registrar a fase de intenção/pré-reserva como `padrao_comportamental`. Se a reserva foi paga, registra "Reservou X"; se não foi, não registra nada sobre essa tentativa. + REGRA CRÍTICA: se você vai registrar uma escolha pontual, (a) a ação DEVE ter sido consumada com pagamento confirmado na conversa, e (b) SEMPRE inclua a data no content. Memória sem data vira ruído; memória sem consumação vira mentira. 5. **reclamacao** — queixa EXPLÍCITA sobre algo que desagradou/frustrou/causou problema, com sentimento negativo claro. SIM: "O ar-condicionado estava barulhento demais, não dormi direito" @@ -169,7 +183,7 @@ class Captain::ContactMemories::ExtractionService 1. **Evidência OBRIGATÓRIA**: cada fato precisa de um trecho LITERAL da conversa. Se não tem trecho claro, não extraia. 2. **Perguntas/dúvidas NÃO são reclamação nem memória**: se o cliente fez uma pergunta ("tem X?", "aceita Y?"), isso é informação que ele queria, não fato sobre ele. 3. **Cortesia genérica NÃO é feedback**: "obrigado", "tá bom", "ok" NÃO viram feedback_positivo. - 4. **Aplicar a REGRA DE OURO de ação-consumada vs intenção-futura**: "informou CPF" nunca é memória (é cadastro). "Escolheu X" ou "Reservou X em tal data" SÓ vira `padrao_comportamental` se a ação foi efetivamente CONSUMADA nesta conversa (Pix confirmado, cliente disse "pode marcar"+ bot confirmou, ou registro de estadia passada). Discussão/intenção sem fechamento = NÃO EXTRAIA. + 4. **Aplicar a REGRA DE OURO de ação-consumada vs intenção-futura**: "informou CPF" nunca é memória (é cadastro). "Escolheu X" ou "Reservou X em tal data" SÓ vira `padrao_comportamental` se o pagamento do Pix foi CONFIRMADO na conversa (cliente disse "paguei" ou apareceu msg automática "Pagamento confirmado"). Pix gerado sem confirmação de pagamento = pré-reserva, NÃO é ação consumada, NÃO vira memória. Discussão/intenção sem pagamento = NÃO EXTRAIA. 5. **Ações do atendente NÃO são memória do cliente**: se o bot "incentivou X" ou "ofereceu Y", isso descreve o atendente, não o cliente. Ignore. 6. **Máximo 5 fatos por conversa**. Se há dúvida entre extrair ou não, DESCARTE. Qualidade > quantidade. 7. **Se a conversa não tem NADA realmente memorável**, retorne `{"facts": []}`. Isso é o comportamento normal e esperado da maioria das conversas transacionais. diff --git a/enterprise/app/services/captain/tools/generate_pix_tool.rb b/enterprise/app/services/captain/tools/generate_pix_tool.rb index 28891d906..58f2c0d9f 100644 --- a/enterprise/app/services/captain/tools/generate_pix_tool.rb +++ b/enterprise/app/services/captain/tools/generate_pix_tool.rb @@ -426,12 +426,15 @@ class Captain::Tools::GeneratePixTool < Captain::Tools::BaseTool mark_conversation_as_awaiting_payment(reservation) Rails.logger.info "[GeneratePixTool] Reserva #{reservation.id} → pending_payment" - final_prefix = prefix || 'Cobrança Pix gerada com sucesso.' - response = build_pix_response(charge, reservation, amount: charge_amount, prefix: final_prefix) + response = build_pix_response(charge, reservation, amount: charge_amount, prefix: prefix) # Envia o link como mensagem direta pro cliente. Isso garante que o URL chegue # no WhatsApp mesmo que a LLM parafraseie com placeholder tipo "[Link do Pix]". - dispatch_direct_link_message(response[:payment_link], 'Link do Pix:') + # A mensagem do link é a ÚNICA com URL — a resposta da LLM não deve conter link. + dispatch_direct_link_message( + response[:payment_link], + "Pix do sinal (R$ #{format('%.2f', charge_amount)}) — sua reserva só fica garantida depois que o pagamento cair:" + ) response rescue StandardError => e @@ -772,8 +775,6 @@ class Captain::Tools::GeneratePixTool < Captain::Tools::BaseTool end def build_pix_response(charge, reservation, amount:, prefix: nil) - intro = prefix.present? ? "#{prefix}\n\n" : '' - base_url = InstallationConfig.find_by(name: 'FRONTEND_URL')&.value.presence || ENV.fetch('FRONTEND_URL', 'http://localhost:3000') base_url = base_url.gsub('0.0.0.0', '127.0.0.1') if base_url.include?('0.0.0.0') @@ -781,14 +782,6 @@ class Captain::Tools::GeneratePixTool < Captain::Tools::BaseTool token = charge.to_sgid(expires_in: 2.hours, purpose: :pix_payment).to_s link = Rails.application.routes.url_helpers.short_payment_link_url(token, host: base_url) - instructions = "Para facilitar, gerei um link de pagamento rápido com botão COPIAR.\n" \ - "1. Clique no link abaixo\n" \ - "2. Aperte 'COPIAR CÓDIGO PIX'\n" \ - "3. Cole no app do seu banco e pague.\n\n" \ - "Link Seguro: #{link}\n\n" \ - '(IMPORTANTE PARA A I.A.: Envie este link EXATAMENTE como está acima. NÃO use formatação Markdown [texto](url). ' \ - 'O WhatsApp não reconhece. Envie APENAS a URL pura, solta no texto.)' - final_code = charge.pix_copia_e_cola.to_s.strip if final_code.start_with?('/spi/') header = '00020101021226930014BR.GOV.BCB.PIX2571spi-qrcode.bancointer.com.br' @@ -797,9 +790,16 @@ class Captain::Tools::GeneratePixTool < Captain::Tools::BaseTool final_code = normalize_text(final_code) + # formatted_message vai pra LLM como tool output — deve ser CURTO, sem URL + # e sem código Pix, porque dispatch_direct_link_message já envia o link em + # mensagem separada. Se incluirmos link aqui, a LLM parafraseia/cola de + # novo e o cliente recebe 2 mensagens com a mesma URL. + # prefix opcional pra casos como "Pix ainda válido" / "Gerando novo Pix". + summary = prefix.presence || 'Pix do sinal gerado e enviado em mensagem separada.' + normalize_payload( { - formatted_message: "#{intro}#{instructions}", + formatted_message: summary, raw_payload: final_code, payment_link: link, amount: amount.to_f,