diff --git a/bin/hermes-validate b/bin/hermes-validate index 0df450a0f..e3f172943 100755 --- a/bin/hermes-validate +++ b/bin/hermes-validate @@ -59,6 +59,17 @@ DB_DUMP=$(docker exec "$CID" bundle exec rails runner " hum = asst.config['response_delay'] cats = unit&.pricing_categories&.includes(:amounts)&.to_a || [] galleria = unit&.gallery_items&.count || 0 + ci_unit_id = ci&.captain_unit_id + inter_ok = unit && unit.respond_to?(:inter_credentials_present?) ? unit.inter_credentials_present? : false + pricing_dry_run = nil + if unit && cats.any? + first_cat_key = cats.first.key + res = Captain::Mcp::PricingTables.calculate( + unit_id: unit.id, suite_category: first_cat_key, + period: 'pernoite_promo', total_guests: 2 + ) + pricing_dry_run = res[:error] ? \"ERR: #{res[:error]}\" : \"OK R$ #{res[:amount]} (#{first_cat_key}/pernoite)\" + end out = { assistant_id: asst.id, name: asst.name, @@ -81,7 +92,10 @@ DB_DUMP=$(docker exec "$CID" bundle exec rails runner " gallery_count: galleria, enabled_for: inbox ? Captain::Hermes.enabled_for?(inbox) : nil, webhook_url: inbox ? Captain::Hermes.webhook_url_for(inbox) : nil, - secret_via: inbox ? Captain::Hermes.subscription_signing_secret(inbox)&.first(8) : nil + secret_via: inbox ? Captain::Hermes.subscription_signing_secret(inbox)&.first(8) : nil, + ci_unit_id: ci_unit_id, + inter_ok: inter_ok, + pricing_dry_run: pricing_dry_run } puts out.to_json " 2>&1 | grep -v 'WARN\|RubyLLM\|ip_lookup' | tail -1) @@ -114,9 +128,16 @@ check "hermes_subscription_secret setado" "$([[ $(echo "$DB_DUMP" | jq -r '.secr check "hermes_webhook_base_url" "$([[ $(echo "$DB_DUMP" | jq -r '.base_url') =~ ^http ]] && echo PASS || echo FAIL)" check "parent_assistant_id setado" "$([[ "$PARENT_ID" != 'null' && "$PARENT_ID" != '' ]] && echo PASS || echo WARN)" "parent=$PARENT_ID" check "captain_unit_id setado" "$([[ $(echo "$DB_DUMP" | jq -r '.unit_id') != 'null' ]] && echo PASS || echo FAIL)" "$(echo "$DB_DUMP" | jq -r '.unit_name')" +ASSIST_UNIT=$(echo "$DB_DUMP" | jq -r '.unit_id // ""') +CI_UNIT=$(echo "$DB_DUMP" | jq -r '.ci_unit_id // ""') +check "CaptainInbox.unit == Assistant.unit (sem divergência)" "$([[ -n "$ASSIST_UNIT" && "$ASSIST_UNIT" == "$CI_UNIT" ]] && echo PASS || echo FAIL)" "asst=$ASSIST_UNIT ci=$CI_UNIT" check "Brand resolvida" "$([[ $(echo "$DB_DUMP" | jq -r '.brand_name') != 'null' ]] && echo PASS || echo FAIL)" "$(echo "$DB_DUMP" | jq -r '.brand_name')" check "Pricing categorias > 0" "$([[ $(echo "$DB_DUMP" | jq -r '.cats_count') -gt 0 ]] && echo PASS || echo FAIL)" "$(echo "$DB_DUMP" | jq -r '.cats_count') cats: $(echo "$DB_DUMP" | jq -r '.cats_keys | join(",")')" check "Pricing amounts > 0" "$([[ $(echo "$DB_DUMP" | jq -r '.amounts_total') -gt 0 ]] && echo PASS || echo FAIL)" "$(echo "$DB_DUMP" | jq -r '.amounts_total') amounts" +PRICING_DRY=$(echo "$DB_DUMP" | jq -r '.pricing_dry_run // ""') +check "Pricing dry-run (calcula sem erro)" "$([[ "$PRICING_DRY" == OK* ]] && echo PASS || echo FAIL)" "$PRICING_DRY" +INTER_OK=$(echo "$DB_DUMP" | jq -r '.inter_ok // false') +check "Credenciais Inter completas (cert+key+client_id)" "$([[ "$INTER_OK" == 'true' ]] && echo PASS || echo WARN)" "Sem isso generate_pix cai no fallback de link" check "CaptainInbox mapeada" "$([[ "$INBOX_ID" != 'null' && "$INBOX_ID" != '' ]] && echo PASS || echo WARN)" "inbox=$INBOX_ID" check "Inbox.typing_delay > 0 (debounce)" "$([[ $(echo "$DB_DUMP" | jq -r '.inbox_typing_delay // 0') -gt 0 ]] && echo PASS || echo WARN)" "$(echo "$DB_DUMP" | jq -r '.inbox_typing_delay // 0')s" check "config.response_delay (typing simulation)" "$([[ $(echo "$DB_DUMP" | jq -r '.response_delay.mode // ""') == 'typing_simulation' ]] && echo PASS || echo WARN)" "$(echo "$DB_DUMP" | jq -r '.response_delay.mode // "none"')" diff --git a/enterprise/app/services/captain/mcp/tools/generate_pix_tool.rb b/enterprise/app/services/captain/mcp/tools/generate_pix_tool.rb index 64c417407..514be072e 100644 --- a/enterprise/app/services/captain/mcp/tools/generate_pix_tool.rb +++ b/enterprise/app/services/captain/mcp/tools/generate_pix_tool.rb @@ -77,7 +77,7 @@ class Captain::Mcp::Tools::GeneratePixTool < Captain::Mcp::Tools::BaseTool conversation = resolve_conversation(args, context) return error_response('Conversa não encontrada. Passe conversation_id (cid do [ctx]) em arguments.') if conversation.blank? - unit = resolve_unit(conversation) + unit = resolve_unit(conversation, context) return error_response('Unidade do Captain não vinculada à inbox dessa conversa.') if unit.blank? return error_response('Unidade não tem credenciais Inter configuradas. Avise a gerência.') unless unit.inter_credentials_present? @@ -135,7 +135,20 @@ class Captain::Mcp::Tools::GeneratePixTool < Captain::Mcp::Tools::BaseTool Conversation.find_by(id: conv_id) || Conversation.find_by(display_id: conv_id) end - def resolve_unit(conversation) + # Resolve unit em 3 níveis (defesa em profundidade contra divergência + # entre Captain::Assistant.captain_unit_id e CaptainInbox.captain_unit_id): + # 1. Assistant.captain_unit (autoritativo — setado por hermes-provision + # e admin UI; não vaza entre agentes que compartilham inbox). + # 2. CaptainInbox legacy (fallback pré-engine column; só funciona se + # a inbox tem 1 agente único). + # 3. Captain::Unit.inbox_id legacy (fallback antigo, antes de CaptainInbox). + def resolve_unit(conversation, context = nil) + asst_id = context && (context[:assistant_id] || context['assistant_id']) + if asst_id + asst = Captain::Assistant.find_by(id: asst_id) + return asst.captain_unit if asst&.captain_unit_id.present? + end + captain_inbox = CaptainInbox.find_by(inbox_id: conversation.inbox_id) return captain_inbox.captain_unit if captain_inbox&.captain_unit.present?