Sempre mostrar terapeuta na busca, independente de availabilities_count

TLDR: Terapeutas Pro sem Availability cadastrada deixam de ser excluídos da busca; a exigência de disponibilidade some tanto da query quanto da validação que apaga o registro de SearchTherapist.

Contexto

A terapeuta Rita Valente Fialho Canudo (SearchTherapist id 10421, plan_id 1 = Pro, availabilities_count: 0) não aparece em GET /api/v1/therapists?name=canudo&page=1 apesar de passar em todos os demais critérios (pro_bono_allowed: true, price: 150.0, nome bate com canudo).

A causa raiz é o filtro base de UserProfiles::SearchTherapistsQuery#eligible_therapists, que exige, para terapeutas Pro, ao menos uma disponibilidade cadastrada (availabilities_count >= 1). Existe ainda uma regra equivalente e complementar em SearchTherapists::UpdateByProfile#valid?, que destrói o registro de um Pro assim que ele fica sem Availability — fechando a busca por um segundo caminho.

A nova regra de negócio é sempre mostrar o terapeuta (Pro ou Regular) na busca, independente de ter disponibilidade cadastrada. Fora essa regra, todos os demais filtros (plano ativo, onboarding aprovado, perfil publicado, pro bono, preço para cliente, etc.) permanecem.

O contador availabilities_count continua sendo alimentado porque ainda é exibido no dashboard Avo (app/views/avo/cards/_search_therapist_count.html.erb:14-15).

Quando e onde UpdateByProfile é chamado

SearchTherapists::UpdateByProfile.call tem um único caller: SearchTherapists::RefreshByProfile#call (app/use_cases/search_therapists/refresh_by_profile.rb:10). Portanto, todo impacto da mudança em UpdateByProfile#valid? passa pelos gatilhos de RefreshByProfile:

Gatilhos síncronos de RefreshByProfile.call

  1. Job SearchTherapists::RefreshByProfileJob — app/jobs/search_therapists/refresh_by_profile_job.rb:4. Ponto final de todo fluxo assíncrono.
  2. Cron a cada minuto — TherapistAvailableTimes::UpdateLastProfilesChangedJob → itera perfis mexidos recentemente e chama RefreshByProfile.call(profile.user_id) em app/use_cases/therapist_available_times/update_last_profiles_changed.rb:17.
  3. Cron a cada hora (minuto 0) — TherapistAvailableTimes::RefreshJob → percorre TODOS os perfis com search_therapist em app/jobs/therapist_available_times/refresh_job.rb:5.
  4. SearchTherapists::Refresh — app/use_cases/search_therapists/refresh.rb:5. Itera UserProfile.published. Sem caller no repo; uso manual via console para reindexação em massa.

Callbacks que enfileiram RefreshByProfileJob via EnqueueRefreshByProfile (com dedup e delay de 2 s)

Model Hook Arquivo:linha Condição
UserProfile after_commit user_profile.rb:241 qualquer mudança
UserProfileCertificate after_commit user_profile_certificate.rb:29 qualquer mudança
Availability after_commit availability.rb:159-161 qualquer mudança
UserAddress after_commit user_address.rb:64 qualquer mudança
FinancialSetting after_commit financial_setting.rb:58 qualquer mudança
Subscription before_update subscription.rb:47-51 (pro? OR terapeuta?) AND active?
Meeting after_commit meeting.rb:187 qualquer mudança
MeetingParticipant after_commit meeting_participant.rb:82 professional?
Rate after_commit rate.rb:38 qualquer mudança

Callback que enfileira direto (sem passar por EnqueueRefreshByProfile)

  • User#before_update em app/models/user.rb:187-193, quando name_changed?, agenda SearchTherapists::RefreshByProfileJob com delay de 2 s.

Efeito concreto da mudança em valid?

Removida a checagem user.pro? && !user.availabilities.exists?: - Nenhum Pro é destruído da tabela por perder Availability. Qualquer edit nos 10 models acima passa a recriar registros de Pros hoje ausentes. - Via cron horário, em até 1 h a tabela converge para o novo universo sem ação manual. - Para reindexar imediatamente após o deploy: SearchTherapists::Refresh.call no console. - No caso da Rita, o registro já existe; a remoção do filtro da query basta para ela aparecer. A mudança no valid? é uma salvaguarda contra remoções futuras.

Arquivos a alterar

1. app/queries/user_profiles/search_therapists_query.rb

Simplificar initialize e remover o método eligible_therapists. Resultado:

ruby def initialize @collection = SearchTherapist.distinct end

  • Remover linhas 14-16 (body atual do initialize que calcula pro_plan_id) e 18-21 (método eligible_therapists).
  • A constante DEFAULT_ORDER_DIRECTION permanece — continua em uso no default_order.

Efeito: a query passa a partir do universo completo de SearchTherapist. Os demais escopos (by_user, by_name, by_price, by_date, default_order, etc.) continuam intactos e agem em cima disso.

2. app/use_cases/search_therapists/update_by_profile.rb

Remover a linha 42:

ruby return false if user.pro? && !user.availabilities.exists?

As demais checagens do valid? (onboarding, published, is_therapist, subscription presente, slug whitelisted, não expirada, ativa) permanecem — apenas a obrigatoriedade de availability para Pro sai.

A atribuição de availabilities_count na linha 22 permanece — o campo continua sendo útil para o Avo e para debugging.

3. spec/queries/user_profiles/search_therapists_query_spec.rb

  • Contexto “when PRO therapist has no availabilities” (linhas 55-68): inverter expectativa — passa a esperar to include(search_therapist). Atualizar descrição do context e do it para refletir o novo comportamento (“include therapist in the collection”).
  • Contexto “when both PRO and regular therapists exist” (linhas 70-91): alterar linha 87 de expect(collection).not_to include(pro_without_availability) para expect(collection).to include(pro_without_availability). Atualizar a descrição do it para “include every therapist regardless of availabilities”.
  • Contextos “regular sem availabilities”, “regular com availabilities” e “PRO com availabilities” permanecem inalterados.

4. spec/use_cases/search_therapists/update_by_profile_spec.rb

  • Contexto “when user is PRO without availabilities” (linhas 22-37): inverter expectativa para be true e ajustar descrição do it para “create a SearchTherapist record”.
  • Contexto “when PRO user loses availabilities” (linhas 74-92): com a regra removida, o registro NÃO é mais destruído ao apagar a última availability. Atualizar para: descrição “when PRO user loses availabilities / keep the SearchTherapist record”, expectativa final expect(SearchTherapist.exists?(profile_id: profile.id)).to be true.

O que NÃO mexer

  • Coluna search_therapists.availabilities_count (permanece no schema).
  • Atualização do contador em UpdateByProfile (linha 22) — alimenta o dashboard Avo.
  • Callbacks de EnqueueRefreshByProfile nos 11 modelos (Availability, Subscription, etc.) — continuam essenciais para manter o restante dos campos (price, score, plan_id, name, etc.) sincronizados.
  • Filtro by_user (pro bono para terapeutas, plan_id = PRO + price > 0 para clientes).
  • Demais checagens em UpdateByProfile#valid?.
  • Specs de request em spec/requests/api/v1/search_therapists_index_as_* — o shared context when_setup_searchable_therapist.rb já cria availability para todos os perfis, então continuam verdes sem alteração.

Verificação

  1. Testes unitários afetados (rodar antes para ver falhas, depois para confirmar fix): bash make test test=spec/queries/user_profiles/search_therapists_query_spec.rb make test test=spec/use_cases/search_therapists/update_by_profile_spec.rb

  2. Testes de request (não devem quebrar, só sanity check): bash make test test=spec/requests/api/v1/search_therapists_index_as_anonymous_spec.rb make test test=spec/requests/api/v1/search_therapists_index_as_client_spec.rb make test test=spec/requests/api/v1/search_therapists_index_as_therapist_spec.rb

  3. Verificação manual no caso da Rita (make console): ruby # Antes da mudança: deve retornar 0 # Depois da mudança: deve retornar 1 UserProfiles::SearchTherapistsQuery.new .by_name("canudo") .instance_variable_get(:@collection) .pluck(:id, :name)

  4. Smoke test end-to-end — autenticar como cliente e bater na rota, confirmar que a Rita aparece: GET /api/v1/therapists?name=canudo&page=1

  5. Regressão pro bono — autenticar como terapeuta (com e sem a flag cost_reduction_phase_one) e confirmar que os filtros pro_bono_allowed e quota continuam sendo aplicados corretamente sobre o universo expandido.

Observações finais

  • Mudança de comportamento observável: terapeutas Pro recém-aprovados já aparecem na busca antes de cadastrar qualquer horário. Isso pode impactar métricas de “terapeutas ativos exibidos”; vale comunicar ao produto antes de subir.
  • O score: 0 da Rita a posiciona no final do ranking (ordenação ORDER BY score DESC). Se o produto quiser que ela apareça com destaque, é outra discussão (SearchTherapists::CalculateScore).