Sequência de implementação da jornada de áudios
TLDR: Sete passos para implementar a jornada de áudios diários, de models e migrations até o endpoint
GET /audios/dailye os signals de avanço.
Nota: este plano descreve o desenho na época da implementação, em que
UserJourneyrastreavacurrent_positioneunlocked_from. O model evoluiu paracurrent_journey_audio/previous_journey_audio/current_audio_available_from— o estado atual está em reference/audios/daily_audio_journey.md e nas regras R-002 a R-009.
Fases
Passo 1 — Criar os models Journey e JourneyAudio
- Criar
apps/audios/models/journey.pycomJourneyKind,JourneyManager,Journey,JourneyAudioeUserJourney - Campos de
Journey:name,event(CharField, unique, nullable),launch_date(DateField),is_active - Exportar em
apps/audios/models/__init__.py - Gerar e rodar a migration
- Teste: a migration aplica sem erro
Passo 2 — Criar o model UserJourney
- Adicionar
UserJourneyaapps/audios/models/journey.pycom os campos:user(OneToOneField→settings.AUTH_USER_MODEL)journey(FK →Journey)current_position(PositiveIntegerField, default=1)unlocked_from(DateField, default=date.today)started_at(DateTimeField,auto_now_add)updated_at(DateTimeField,auto_now)
- Gerar e rodar a migration
- Teste: a migration aplica sem erro
Passo 3 — Seed da Journey padrão (data migration)
Journey.launch_datedeve sernull=True, blank=True— o admin preenche após o deploy- Criar a data migration
audios/XXXX_seed_default_journey.py - Cria uma
Journeycomname="Jornada Padrão"elaunch_date=None - Associa os 30 áudios existentes (ordenados por
published_at,created_at,id) comoJourneyAudionas posições 1–30 - Teste:
Journey.objects.count() == 1,JourneyAudio.objects.count() == 30
Passo 4 — Registro no admin
- Registrar
Journeyemapps/audios/admin.pyexpondoname,launch_date,is_active - Adicionar
JourneyAudiocomoTabularInlinedentro do admin daJourney - Teste: o admin carrega, permite criar/editar a jornada e associar áudios
Passo 5 — JourneyAudioService (R-002 a R-008)
Escrever os testes primeiro (TDD), depois implementar:
| Teste | Regra |
|---|---|
Usuário sem UserJourney recebe o áudio mais recente fora da jornada |
R-007 / R-008 |
| Usuário na posição 1 com 0% de progresso recebe o áudio da posição 1 | R-002 / R-003 |
| Usuário na posição 1 com progresso concluído recebe o áudio da posição 2 | R-004 |
| Usuário na posição 1 que re-escuta áudio já concluído recebe a posição 2 | R-005 |
| Usuário além da última posição recebe o áudio mais recente fora da jornada | R-006 |
- Criar
JourneyAudioServiceemapps/audios/services.py get_daily_audio(user)implementa toda a lógica das regras da jornada- Reutiliza
AudioProgressdeapps/engagements/models/progress.py - Conclusão determinada por
AudioProgress.completed_at is not None
Cache:
| Cenário | Key | TTL | Invalidação |
|---|---|---|---|
| Usuário na jornada | audio_journey_{user_id} |
5 min (300s) | post_save de UserJourney |
| Áudio livre | audio_daily_free |
até o próximo áudio disponível | post_save de Audio |
Passo 6 — Signal: criar UserJourney na aceitação dos termos
- Adicionar o signal em
apps/audios/signals.py(não emaccounts— evita dependência invertida) - No
post_savedeUser, seaccept_terms_atfoi definido:- Busca
journey = Journey.objects.filter(kind=JourneyKind.ONBOARDING, is_active=True, launch_date__lte=user.accept_terms_at.date()).order_by('-launch_date').first() - Se encontrar, cria
UserJourneyviaget_or_create(idempotente)
- Busca
- Teste: aceitar os termos cria
UserJourneyquando existe jornada ativa comlaunch_date <= accept_terms_at - Teste: não cria
UserJourneyquando não há jornada ativa
Passo 7 — Atualizar o endpoint GET /audios/daily
AudioService.get_daily_audio()passa a receberusercomo parâmetro- Se o usuário tem
UserJourney→ delega paraJourneyAudioService.get_daily_audio(user) - Caso contrário → executa o código atual (lógica de janela de 24h, cache
audio_daily) - Para o caso “usuário além da última posição”, o
JourneyAudioServicechamaAudioService._get_current_daily_audio() - Atualizar
apps/audios/views.pypara passarrequest.user - Adicionar signal
post_savedeAudioProgressemapps/audios/signals.py: quandocompleted_até setado, avança a posição doUserJourneye libera o próximo áudio para o dia seguinte (idempotente) - Adicionar signal
post_savedeUserJourneypara invalidar o cacheaudio_journey_{user_id} GET /audios/dailyé puramente read-only — sem escrita no banco
Verificação
- Teste de integração do endpoint cobrindo todas as regras da jornada (R-002 a R-008)
- Migrations aplicam sem erro em cada passo
- Admin permite montar a jornada de ponta a ponta