diff --git a/enterprise/app/models/captain/reservation.rb b/enterprise/app/models/captain/reservation.rb index c84bf9fea..fcbe37cf3 100644 --- a/enterprise/app/models/captain/reservation.rb +++ b/enterprise/app/models/captain/reservation.rb @@ -88,6 +88,7 @@ class Captain::Reservation < ApplicationRecord before_validation :set_captain_unit_id, on: :create after_commit :sync_conversation_marker_snapshot + after_create_commit :update_contact_reservation_metadata def ui_status Captain::Reservations::MarkerBuilder.ui_status(status) @@ -125,4 +126,23 @@ class Captain::Reservation < ApplicationRecord rescue StandardError => e Rails.logger.error("[Captain::Reservation] failed to sync conversation marker: #{e.class} - #{e.message}") end + + # Atualiza campos visiveis no painel lateral do Chatwoot (custom_attributes) + # pra que a recepcionista veja num relance: + # ultima_suite, ultima_permanencia, ultima_reserva_em, total_reservas + def update_contact_reservation_metadata # rubocop:disable Metrics/AbcSize + return if contact.blank? + + meta = metadata.to_h + current = contact.custom_attributes.to_h + + current['ultima_suite'] = meta['category'].presence || suite_identifier.to_s.split('·').first.to_s.strip + current['ultima_permanencia'] = meta['stay_type'].presence || suite_identifier.to_s.split('·').last.to_s.strip + current['ultima_reserva_em'] = created_at.iso8601 + current['total_reservas'] = (current['total_reservas'].to_i + 1) + + contact.update_columns(custom_attributes: current) # rubocop:disable Rails/SkipsModelValidations + rescue StandardError => e + Rails.logger.warn("[Captain::Reservation] failed to update contact metadata: #{e.class} - #{e.message}") + end end diff --git a/enterprise/app/services/captain/tools/generate_pix_tool.rb b/enterprise/app/services/captain/tools/generate_pix_tool.rb index 4808b9415..2912538b8 100644 --- a/enterprise/app/services/captain/tools/generate_pix_tool.rb +++ b/enterprise/app/services/captain/tools/generate_pix_tool.rb @@ -2,6 +2,7 @@ class Captain::Tools::GeneratePixTool < Captain::Tools::BaseTool CPF_WITH_LABEL_REGEX = /cpf[^\d]*(\d{3}\.?\d{3}\.?\d{3}-?\d{2}|\d{11})/i CPF_FALLBACK_REGEX = /\b\d{11}\b/ NAME_WITH_LABEL_REGEX = /nome\s*[:\-]\s*([^\n\r,;]+)/i + EMAIL_REGEX = /\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b/ SUITE_REGEX = /su[ií]te\s+([^\n\r,.!?]+)/i DDMMYYYY_REGEX = %r{\b(\d{1,2}/\d{1,2}/\d{2,4})\b} CURRENCY_REGEX = /r\$\s*([\d.,]+)/i @@ -116,6 +117,8 @@ class Captain::Tools::GeneratePixTool < Captain::Tools::BaseTool updates[:name] = extracted[:name] if valid_contact_name?(contact.name).blank? && extracted[:name].present? + updates[:email] = extracted[:email] if contact.email.blank? && extracted[:email].present? + return if updates.blank? contact.update!(updates) @@ -132,6 +135,7 @@ class Captain::Tools::GeneratePixTool < Captain::Tools::BaseTool cpf = nil name = nil + email = nil recent_messages.each do |message| content = normalize_text(message.content) @@ -139,15 +143,23 @@ class Captain::Tools::GeneratePixTool < Captain::Tools::BaseTool cpf ||= extract_cpf_from_text(content) name ||= extract_name_from_text(content) - break if cpf.present? && name.present? + email ||= extract_email_from_text(content) + break if cpf.present? && name.present? && email.present? end { cpf: cpf, - name: name + name: name, + email: email }.compact end + def extract_email_from_text(text) + text = normalize_text(text) + candidate = text[EMAIL_REGEX] + candidate.to_s.strip.downcase.presence + end + def extract_cpf_from_text(text) text = normalize_text(text) candidate = text[CPF_WITH_LABEL_REGEX, 1] @@ -317,6 +329,18 @@ class Captain::Tools::GeneratePixTool < Captain::Tools::BaseTool normalize_payload({ formatted_message: msg, success: true }) end + def dispatch_direct_link_message(link, label) + return if @conversation.blank? || link.to_s.strip.empty? + + content = "#{label}\n#{link}".strip + Messages::MessageBuilder.new(@assistant, @conversation, { + content: content, + message_type: 'outgoing' + }).perform + rescue StandardError => e + Rails.logger.warn("[GeneratePixTool] failed to dispatch direct link: #{e.class} - #{e.message}") + end + def current_pix_charge_for(reservation) return nil unless reservation @@ -333,7 +357,13 @@ class Captain::Tools::GeneratePixTool < Captain::Tools::BaseTool Rails.logger.info "[GeneratePixTool] Reserva #{reservation.id} → pending_payment" final_prefix = prefix || 'Cobrança Pix gerada com sucesso.' - build_pix_response(charge, reservation, amount: charge_amount, prefix: final_prefix) + response = build_pix_response(charge, reservation, amount: charge_amount, prefix: final_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:') + + response rescue StandardError => e safe_error_message = normalize_text(e.message) Rails.logger.error("[GeneratePixTool] Falha ao gerar Pix: #{e.class} - #{safe_error_message}") diff --git a/enterprise/app/services/captain/tools/generate_reservation_link_tool.rb b/enterprise/app/services/captain/tools/generate_reservation_link_tool.rb index c1a8c0dfb..8df1ecea4 100644 --- a/enterprise/app/services/captain/tools/generate_reservation_link_tool.rb +++ b/enterprise/app/services/captain/tools/generate_reservation_link_tool.rb @@ -81,6 +81,10 @@ class Captain::Tools::GenerateReservationLinkTool < Captain::Tools::BaseTool query = build_query(enriched_params) url = "#{base}/?#{query}" + # Envia o link como mensagem direta pra garantir que chegue no WhatsApp + # mesmo que a LLM parafraseie com placeholder [Link da reserva] etc. + dispatch_direct_link_message(conversation, url) + { formatted_message: url, raw_payload: url, @@ -117,6 +121,18 @@ class Captain::Tools::GenerateReservationLinkTool < Captain::Tools::BaseTool }.compact end + def dispatch_direct_link_message(conversation, url) + return if conversation.blank? || url.to_s.strip.empty? + + content = "Link da sua reserva (tudo ja preenchido):\n#{url}" + Messages::MessageBuilder.new(@assistant, conversation, { + content: content, + message_type: 'outgoing' + }).perform + rescue StandardError => e + Rails.logger.warn("[GenerateReservationLinkTool] failed to dispatch link: #{e.class} - #{e.message}") + end + def build_query(actual_params) mapping = { marca: actual_params[:marca],