Sempre exibir o terapeuta na busca, independente de disponibilidade
TLDR: Remove a exigência de ao menos uma disponibilidade cadastrada para que um terapeuta Pro apareça na busca — o filtro fechava a busca por dois caminhos e escondia terapeutas que passavam em todos os demais critérios.
Contexto
A terapeuta Rita Valente Fialho Canudo (SearchTherapist id 10421, plan_id 1 = Pro, availabilities_count: 0) não aparecia 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 batendo com canudo).
A causa raiz era o filtro base de UserProfiles::SearchTherapistsQuery#eligible_therapists, que exigia, para terapeutas Pro, ao menos uma disponibilidade cadastrada (availabilities_count >= 1). Havia ainda uma regra equivalente e complementar em SearchTherapists::UpdateByProfile#valid?, que destruía o registro de um Pro assim que ele ficava 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).
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
- Job
SearchTherapists::RefreshByProfileJob—app/jobs/search_therapists/refresh_by_profile_job.rb:4. Ponto final de todo fluxo assíncrono. - Cron a cada minuto —
TherapistAvailableTimes::UpdateLastProfilesChangedJob→ itera perfis mexidos recentemente e chamaRefreshByProfile.call(profile.user_id)emapp/use_cases/therapist_available_times/update_last_profiles_changed.rb:17. - Cron a cada hora (minuto 0) —
TherapistAvailableTimes::RefreshJob→ percorre TODOS os perfis comsearch_therapistemapp/jobs/therapist_available_times/refresh_job.rb:5. SearchTherapists::Refresh—app/use_cases/search_therapists/refresh.rb:5. IteraUserProfile.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 remoção da checagem user.pro? && !user.availabilities.exists?:
- Nenhum Pro é destruído da tabela por perder
Availability. Qualquer edição nos 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.callno console. - No caso da Rita, o registro já existia; a remoção do filtro da query bastava para ela aparecer. A mudança no
valid?é uma salvaguarda contra remoções futuras.
Objetivos
- Exibir terapeutas Pro e Regular na busca independentemente de terem disponibilidade cadastrada.
- Impedir que um Pro seja removido da tabela
search_therapistspor ficar semAvailability. - Preservar todos os demais filtros de elegibilidade da busca.
Fora de escopo
- Coluna
search_therapists.availabilities_count— permanece no schema. - Atualização do contador em
UpdateByProfile(linha 22) — alimenta o dashboard Avo. - Callbacks de
EnqueueRefreshByProfilenos 11 modelos (Availability,Subscription, etc.) — continuam essenciais para manter os demais campos (price,score,plan_id,name) sincronizados. - Filtro
by_user(pro bono para terapeutas,plan_id = PRO+price > 0para clientes). - Demais checagens em
UpdateByProfile#valid?. - Specs de request em
spec/requests/api/v1/search_therapists_index_as_*— o shared contextwhen_setup_searchable_therapist.rbjá cria availability para todos os perfis, então continuam verdes sem alteração. - Ordenação/score da Rita (
score: 0a coloca no fim do ranking) — outra discussão, emSearchTherapists::CalculateScore.
Mudanças
1. app/queries/user_profiles/search_therapists_query.rb
Simplificar initialize e remover o método eligible_therapists:
ruby
def initialize
@collection = SearchTherapist.distinct
end
Remover o body do initialize que calculava pro_plan_id e o método eligible_therapists. A constante DEFAULT_ORDER_DIRECTION permanece — continua em uso no default_order. A query passa a partir do universo completo de SearchTherapist; os demais escopos (by_user, by_name, by_price, by_date, default_order) continuam intactos e agem em cima disso.
2. app/use_cases/search_therapists/update_by_profile.rb
Remover a linha:
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. A atribuição de availabilities_count permanece.
3. spec/queries/user_profiles/search_therapists_query_spec.rb
- Contexto “when PRO therapist has no availabilities”: inverter a expectativa para
to include(search_therapist)e atualizar as descrições. - Contexto “when both PRO and regular therapists exist”: trocar
expect(collection).not_to include(pro_without_availability)porexpect(collection).to include(pro_without_availability). - Demais contextos permanecem inalterados.
4. spec/use_cases/search_therapists/update_by_profile_spec.rb
- Contexto “when user is PRO without availabilities”: inverter a expectativa para
be true. - Contexto “when PRO user loses availabilities”: o registro não é mais destruído — expectativa final
expect(SearchTherapist.exists?(profile_id: profile.id)).to be true.
Como verificar
-
Testes unitários afetados:
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 -
Testes de request (sanity check, não devem quebrar):
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 -
Verificação manual no caso da Rita (
make console) — antes deve retornar 0, depois 1:ruby UserProfiles::SearchTherapistsQuery.new .by_name("canudo") .instance_variable_get(:@collection) .pluck(:id, :name) -
Smoke test end-to-end — autenticar como cliente e bater em
GET /api/v1/therapists?name=canudo&page=1, confirmando que a Rita aparece. -
Regressão pro bono — autenticar como terapeuta (com e sem a flag
cost_reduction_phase_one) e confirmar que os filtrospro_bono_allowede quota continuam aplicados sobre o universo expandido.
Documentação
Mudança de comportamento observável: terapeutas Pro recém-aprovados passam a aparecer na busca antes de cadastrar qualquer horário. Isso pode impactar a métrica de “terapeutas ativos exibidos” — vale comunicar ao produto antes de subir.