Commit Graph

4544 Commits

Author SHA1 Message Date
Rodrigo Borba
98252e968a feat: add landing click attribution tracking and stats endpoint 2026-03-02 17:37:28 -03:00
Rodrigo Borba
a73689dce4 fix: aumenta janela de atribuição para 30min e sanitiza hostname no frontend
- attribution_matcher_service: window 10min → 30min (mais realista para jornada do lead)
- LandingHostsConfig.vue: strip automático de https://, www e trailing slash antes de salvar
2026-03-02 15:35:05 -03:00
Rodrigo Borba
118f52e239 feat: lead attribution tracking - landing page origin detection
- Cria modelo LeadClick para registrar cliques das landing pages
- Cria modelo LandingHost para mapear hostname → inbox_id
- Endpoint público POST /track/click para receber eventos de clique
- Leads::AttributionMatcherService para correlacionar clique com conversa
- Integração com IncomingMessageWuzapiService para atribuição automática
- API REST para gerenciar LandingHosts por inbox (index/create/destroy)
- UI: nova aba 'Landing Pages' nas configurações da caixa de entrada
- Dashboard API client dedicado (landingHosts.js)
- RuboCop: refatora shift_signature_name, TrackingController, AttributionMatcherService e WuzapiService
2026-03-02 14:40:35 -03:00
Rodrigo Borba
7a84cb3433 fix(captain): Move NOTIFICATIONS key settings to correct correct namespace in i18n. 2026-03-01 22:51:33 -03:00
Rodrigo Borba
162e8e15be fix: corrige filtro de inboxes na página de notificações
- Remove filtro por captain_assistant_id (campo não existe no payload)
- Mostra todas as inboxes da conta para o usuário escolher em qual
  caixa de entrada configurar os templates de notificação
2026-03-01 22:25:41 -03:00
Rodrigo Borba
203b1e3cc3 fix: adiciona 'Notificações Automáticas' no menu lateral do Captain
- Sidebar.vue: novo item Captain > Notificações Automáticas
  apontando para a rota captain_settings_notifications
- pt_BR/settings.json: CAPTAIN_NOTIFICATIONS key adicionada
- en/settings.json: CAPTAIN_REPORTS e CAPTAIN_NOTIFICATIONS adicionadas
2026-03-01 22:21:22 -03:00
Rodrigo Borba
84fff38d94 refactor: move notification templates de units para inboxes
- Arquitetura corrigida: templates agora pertencem à inbox (WhatsApp),
  não à unidade PIX (que é uma config financeira, não de mensagens)
- Migration: troca FK captain_unit_id -> inbox_id (up/down explícito)
- Model: belongs_to :inbox; scope for_inbox
- Controller: escopo via account.inboxes.find(inbox_id)
- Rotas: move de captain/units/:id → inboxes/:id/notification_templates
- Scanner job: joins(:conversation).where(conversations: {inbox_id:})
- UI: página /captain/notifications com seletor de inbox no topo
  (chips clicáveis, templates carregam por watch no selectedInboxId)
- i18n PT/EN: novas keys INBOX_LABEL, SELECT_INBOX_HINT, EMPTY
2026-03-01 22:17:27 -03:00
Rodrigo Borba
ce2904e57f Revert "fix: adiciona botão de notificações na listagem de unidades"
This reverts commit de62e7d68c.
2026-03-01 22:09:45 -03:00
Rodrigo Borba
de62e7d68c fix: adiciona botão de notificações na listagem de unidades
Sem o botão na tela de Units, não havia como chegar até a página
de templates de notificação (captain/units/:unitId/notifications).
Adiciona ícone de sino com rota correta + chave i18n PT/EN.
2026-03-01 22:04:16 -03:00
Rodrigo Borba
44908f32d1 feat: sistema de notificações de reserva com templates configuráveis
- Adiciona check_in_at/duration_hours ao schema do tool CreateReservationIntent
  para que a IA capture o horário EXATO de chegada informado pelo cliente
- Cria captain_notification_templates: label, content, timing_minutes,
  timing_direction (before/after), active, position
- Implementa SendNotificationService com interpolação de variáveis
  (guest_name, check_in_time, check_out_time, suite_name, unit_name)
- Implementa NotificationScannerJob (Sidekiq-cron a cada 5min) com
  janela de tolerância de ±5min e idempotência via metadata JSONB
- API REST: /captain/units/:unit_id/notification_templates (CRUD)
- Store Vuex captainNotificationTemplates + API client
- UI: página de gestão de templates com editor inline e botão '+'
- Configura rota captain_settings_notifications
- i18n PT/EN para todas as strings novas
- Rubocop e ESLint: zero offenses
2026-03-01 21:53:11 -03:00
Rodrigo Borba
9a7599d971 Traduz e corrige os relatórios de insights do Capitão: centraliza traduções, corrige fuso horário na data e adiciona polling automático 2026-03-01 18:28:46 -03:00
Rodrigo Borba
dc3d1bbcf7 Fix(Captain): Correção na geração de relatórios de IA e adição do status Confirmada nas Reservas 2026-03-01 15:40:10 -03:00
Rodrigo Borba
e8b51109cb feat(captain): add status field to manual reservation modal 2026-03-01 03:40:46 -03:00
Rodrigo Borba
adcadcf12c refactor(captain): remove manual unit selection from reservation modal 2026-03-01 03:37:19 -03:00
Rodrigo Borba
8896482b1d fix(captain): force dialog open modal on mounted 2026-03-01 03:19:43 -03:00
Rodrigo Borba
7108bb135e feat(captain): permite criacao manual de reserva via painel e conversa 2026-03-01 03:07:44 -03:00
Rodrigo Borba
cbb39a4db5 fix(i18n): remove unsupported liquid curly braces from pt_BR translations causing vue render syntax error 2026-03-01 02:22:30 -03:00
Rodrigo Borba
ebed1caeb4 feat: Adiciona a opção "Criar FAQ" no menu de contexto da mensagem, permitindo criar uma resposta com seleção de assistente. 2026-02-28 22:20:43 -03:00
Rodrigo Borba
cfa2dc71bd fix(media): usa URL relativa para arquivos em desenvolvimento
Substitui rails_storage_proxy_url (URL absoluta com host ngrok) por
rails_storage_proxy_path (URL relativa) em file_url e thumb_url.

Problema: ngrok mostra página de interstitial HTML para sub-recursos
carregados pelo browser (img/audio) sem cookie ngrok válido.
O browser recebia HTML em vez da mídia → imagem 'não disponível' e
áudio '00:00/00:00'.

Solução: URL relativa (/rails/active_storage/blobs/proxy/...) resolve
para o servidor atual sem passar pelo ngrok, eliminando o interstitial.
Funciona tanto em localhost:3000 quanto acessando via ngrok no browser.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 19:32:54 -03:00
Rodrigo Borba
5d3ce4e565 fix(whatsapp): corrige content-type audio/opus e extensão para OGG
- MediaHandler: adiciona sanitize_content_type que normaliza audio/opus → audio/ogg
- MediaHandler: detect_extension retorna .ogg (não .mp3) para áudios WhatsApp
- MediaHandler: final_filename força extensão .ogg em áudios que chegam com .mp3
- Attachment: normalize_opus_blob_content_type! agora verifica apenas content_type
  (remove checagem de extensão de filename que impedia normalização de blobs .mp3)
- Attachment: audio_metadata chama normalize_opus_blob_content_type! para corrigir
  blobs existentes na primeira vez que são acessados (lazy fix)

WhatsApp envia áudio como container OGG/Opus (bytes OggS = 4f 67 67 53),
mas declarava mimetype audio/opus. Browsers não conseguem reproduzir container
OGG via MIME audio/opus — precisam de audio/ogg.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 18:40:06 -03:00
Rodrigo Borba
ec6cfc317d fix(whatsapp): restaura attachment_params no PayloadParser para mídia funcionar
O refactoring c48047ba5 removeu attachment_params acidentalmente sem mover
para outro lugar, quebrando o download de áudio, imagem, vídeo e documento.
O método é chamado por incoming_message_wuzapi_service.rb#attach_files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 17:47:33 -03:00
Rodrigo Borba
e6e4c36525 fix(whatsapp): adiciona método text_content ao PayloadParser e ignora UndecryptableMessage
Corrige NoMethodError que impedia todas as mensagens de texto de chegarem
ao front. O método text_content era chamado mas não existia na classe.

Também adiciona UndecryptableMessage à lista de eventos ignorados para
evitar tentativa de processar mensagens sem conteúdo descriptografável.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 17:00:52 -03:00
Rodrigo Borba
c48047ba50 refactor(whatsapp): modulariza processamento de mídias e payloads para conformidade com RuboCop 2026-02-28 15:42:50 -03:00
Rodrigo Borba
6b214b38db feat: Adiciona configuração Active Storage proxy, refatora serviço de decriptografia WhatsApp para processar bytes diretamente e ajusta componentes de mídia. 2026-02-28 12:48:17 -03:00
Rodrigo Borba
26692bb5e2 fix(ui): ajusta layout e responsividade do text-area do prompt do orquestrador
Some checks failed
Build and Push to GHCR (multi-arch) / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push to GHCR (multi-arch) / build (linux/arm64, ubuntu-22.04-arm) (push) Has been cancelled
Build and Push to GHCR (multi-arch) / merge (push) Has been cancelled
2026-02-27 14:14:40 -03:00
Rodrigo Borba
c1b8534ea7 feat: Adiciona prompt orquestrador configurável para assistentes Captain com editor UI. 2026-02-27 11:57:59 -03:00
Rodrigo Borba
a67d164e8f fix(captain-reports): remove units filter, keep inbox-only filter
Replace unit+inbox combined dropdown with inbox-only select.
Add ALL_INBOXES i18n key in pt_BR and en. Units (Pix) are unrelated
to conversation reports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 07:31:32 -03:00
Rodrigo Borba
87bff8126c feat(captain): add AI reports page with insights generation
Implementa a página Relatórios IA com geração de análises semanais
por IA baseadas nas conversas de cada unidade/caixa de entrada.

Funcionalidades:
- Página /settings/captain/reports com dois tabs (Insights IA / Operacional)
- Botão "Gerar Análise" que enfileira job Sidekiq
- Filtro por unidade ou caixa de entrada
- Exibe insights com status (pendente/processando/concluído/falhou)
- Mostra top_topics, ai_failures e period_summary
- Estado vazio com CTA para gerar primeiro relatório

Backend:
- InsightsController com endpoints index/show/generate
- GenerateInsightsJob que processa conversas com LLM
- ConversationInsightService com chunking e merge inteligente
- Migração para adicionar inbox_id à tabela captain_conversation_insights
- Link sidebar "Relatórios IA" em /settings/captain/reports

Frontend:
- Vuex store captainReports com actions/mutations/getters
- API client CaptainReportsAPI (getInsights, generateInsight)
- i18n en e pt_BR para CAPTAIN_REPORTS.*

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 07:05:58 -03:00
Rodrigo Borba
972fc5c67b feat(captain): improve suite photo search accuracy with AI guidance
Melhorias na ferramenta send_suite_images para resolver confusão entre
categoria e número de suíte:

1. **Descrições de parâmetros mais claras**
   - suite_category: exemplos específicos (Hidromassagem, ALEXA, STILO)
   - suite_number: apenas números (101, 102, 103) - remove exemplos confusos

2. **Instruções explícitas no system prompt**
   - Seção [Galeria de Fotos] com regras claras
   - Prioriza suite_category quando ambíguo
   - Evita confirmações desnecessárias com cliente

3. **Mensagens de erro melhoradas**
   - Sugere buscar por categoria quando busca por número falha
   - Feedback mais útil para a IA

Resultado esperado:
- Cliente: "Me manda foto da suite Alexa"
- IA: busca por suite_category="Alexa" ✓ (sem pedir confirmação)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-26 23:04:28 -03:00
Rodrigo Borba
c022f4ce5d feat(units): allow one Pix unit to link to multiple inboxes (N:N) 2026-02-26 21:33:23 -03:00
Rodrigo Borba
792951e4c8 fix(ci): update health check endpoint for review apps
- Return expected payload { version, timestamp, queue_services, data_services } in /health
- Fix infinite attempt loop in deploy check Github Action
- Untrack temporary wuzapi test scripts
2026-02-26 16:35:15 -03:00
Rodrigo Borba
f2fb40afaa ajuste galeria de imagens 2026-02-26 15:27:25 -03:00
Rodrigo Borba
ccc1bdf35f Fix Wuzapi webhook handling 2026-02-26 10:49:00 -03:00
Rodrigo Borba
58f5ae6157 Fix Wuzapi webhook handling 2026-02-26 10:10:09 -03:00
Rodrigo Borba
14dbc0f423 Adicionar aba faturamento em Reserv 2026-02-26 06:51:08 -03:00
Rodrigo Borba
05d87281cd feat: sync with enterprise unlock recipe - force premium, enable all features, and force extensions 2026-02-25 19:37:52 -03:00
Rodrigo Borba
e7326e543b fix: force captain enabled for self-hosted - remove feature flag dependency 2026-02-25 19:19:11 -03:00
Rodrigo Borba
75e3dde312 fix: remove enterprise-only restriction from Captain in self-hosted 2026-02-25 18:58:57 -03:00
Rodrigo Borba
0e7dc282c4 chore(style): fix rubocop offenses and update typing indicators 2026-02-25 15:06:58 -03:00
Shivam Mishra
c026ee2fc8 fix: url endpoint
fix: spec
2026-02-25 12:28:30 -03:00
Gabriel Jablonski
72354a4459
fix: normalize audio/opus content type to audio/ogg for WhatsApp attachments (#223) 2026-02-24 22:01:18 -03:00
Gabriel Jablonski
bce4e9b3a7
fix: clear source_id when retrying message to prevent skipping (#222)
* fix: clear source_id when retrying message to prevent skipping

* fix: validate message status and type before retrying to ensure proper handling
2026-02-24 14:45:07 -03:00
Gabriel Jablonski
ce39e54308
feat: add audio transcoding support for WhatsApp Cloud API (#220)
* feat: add audio transcoding support for WhatsApp Cloud API

- Introduced `Audio::TranscodeService` to handle audio transcoding to OGG/Opus format.
- Updated `Messages::MessageBuilder` to transcode audio attachments based on `transcode_audio` parameter.
- Enhanced `WhatsappCloudService` to normalize audio content types and send voice flag for recorded audio in OGG format.
- Added utility functions for audio conversion in JavaScript.
- Updated Dockerfile to include FFmpeg for audio processing.
- Added tests for audio transcoding and WhatsApp Cloud service interactions.

* feat: enhance audio handling with transcoding support and error management

* feat: improve audio transcoding error handling and enhance audio recording features

* feat: enhance audio transcoding process and error handling for better reliability

* feat: update recorded audio handling to support boolean and array formats
2026-02-22 16:21:50 -03:00
Gabriel Jablonski
3b8a38b153
feat: Implement existing template linking for CSAT surveys (#218)
* feat: Implement existing template linking for CSAT surveys

- Added functionality to link existing CSAT templates for WhatsApp channels.
- Introduced a new component for selecting existing templates.
- Updated the dashboard settings page to support template mode switching between creating new and using existing templates.
- Enhanced the CSAT template management service to handle linking existing templates and fetching available templates.
- Updated API routes to include linking and fetching available templates.
- Added tests for the new linking functionality and template availability checks.

* feat: Enhance CSAT template handling and validation across services and components

* feat: Refactor body variable extraction for CSAT templates and update related validations

* feat: Add linked_at field to CSAT template responses and update related handling

* feat: Add tests for ConversationDrop date formatting and CSAT template body variable handling
2026-02-18 18:00:29 -03:00
Gabriel Jablonski
f2635a69ed
fix: email delivery in Email::SendOnEmailService (#217)
* fix: email delivery in Email::SendOnEmailService

* fix: handle nil response from email_reply in Email::SendOnEmailService
2026-02-18 16:23:13 -03:00
gabrieljablonski
360ad59732 feat: add enableCopilot prop to Editor and update ScheduledMessageModal to disable copilot 2026-02-18 10:47:25 -03:00
gabrieljablonski
85ec13a273 chore: update DropdownBody styles for better overflow handling 2026-02-18 10:23:14 -03:00
gabrieljablonski
248d6c23b3 feat: add external_created_at to message creation and update specs for provider callback 2026-02-17 23:32:27 -03:00
gabrieljablonski
9a4c5058f3 Merge branch 'main' into chore/merge-upstream-4.11.0 2026-02-17 23:05:26 -03:00
Muhsin Keloth
e75e8a77f6
feat(shopify): Add mandatory compliance webhooks with HMAC verification (#13549)
Fixes
https://linear.app/chatwoot/issue/CW-6494/add-shopify-mandatory-compliance-webhooks-for-app-store-listing

Shopify requires all public apps to handle three GDPR compliance
webhooks before they can be listed on the App Store. Their automated
review checks for these endpoints and verifies that apps validate HMAC
signatures on incoming requests. We were failing both checks.

This PR adds a single webhook endpoint at `POST /webhooks/shopify` that
receives all three compliance events. When Shopify sends a webhook, it
signs the payload with our app's client secret and includes the
signature in the `X-Shopify-Hmac-SHA256` header. Our controller reads
the raw body, computes the expected HMAC-SHA256 digest, and rejects
mismatched requests with a 401.

Shopify identifies the event type through the `X-Shopify-Topic` header.
For `customers/data_request` and `customers/redact`, we simply
acknowledge with a 200—Chatwoot doesn't persist any Shopify customer
data. All order lookups happen as live API calls at query time. For
`shop/redact`, which Shopify sends after a merchant uninstalls the app,
we delete the integration hook for that shop domain and remove the
stored access token and configuration.


### How to test via Rails console
```
secret = GlobalConfigService.load('SHOPIFY_CLIENT_SECRET', nil)
body = '{"shop_domain":"test.myshopify.com"}'
valid_hmac = Base64.strict_encode64(OpenSSL::HMAC.digest('SHA256', secret, body))
```

  #### Test 1: No HMAC → 401
```
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Topic' => 'customers/data_request' }
app.response.code  # => "401"
```
  ####  Test 2: Invalid HMAC → 401
```
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => 'invalid', 'X-Shopify-Topic' => 'customers/data_request' }
app.response.code  # => "401"
```
  ####  Test 3: Valid HMAC, customers/data_request → 200
```
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'customers/data_request' }
app.response.code  # => "200"
```

####  Test 4: Valid HMAC, customers/redact → 200
```
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'customers/redact' }
app.response.code  # => "200"
```

#### Test 5: Valid HMAC, shop/redact → 200 (deletes hook)
```  
# First check if a hook exists for this domain:
Integrations::Hook.where(app_id: 'shopify', reference_id: 'test.myshopify.com').count
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'shop/redact' }
app.response.code  # => "200"
```

---------

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2026-02-17 16:52:13 +05:30