Datas do Trial sem horário no admin

TLDR: start_date e end_date do Trial passam a ser sempre gravados à meia-noite (horário local) e o admin exibe apenas um campo de data para eles, sem mudar o tipo da coluna (continua DateTimeField).

Contexto

Trial.start_date e Trial.end_date são DateTimeField. No admin, o widget padrão de DateTimeField (AdminSplitDateTime) mostra um campo de data e um de hora separados, mas o horário nunca teve relevância de negócio para o período do trial — só a data de início e fim importa. Isso permitia cadastrar horários arbitrários (ex.: início às 14h) e exibia um campo de hora sem uso real na tela de edição.

Trocar o tipo da coluna para DateField implicaria migração e poderia afetar comparações existentes com timezone.now() (ex.: apps/trials/services/user_trial.py), então a decisão foi manter DateTimeField e normalizar o valor para sempre ser meia-noite.

Objetivos

  • Sempre que um Trial for salvo, start_date e end_date são normalizados para 00:00:00 no timezone local (America/Sao_Paulo), independentemente da hora informada.
  • O formulário do admin (TrialAdminForm) exibe só um campo de data para start_date/end_date, sem o campo de hora do widget padrão.
  • A validação de end_date posterior a start_date continua funcionando corretamente após a normalização (evitar falso positivo/negativo por causa da hora original).

Fora de escopo

  • Não altera o tipo da coluna no banco (continua DateTimeField, sem migração).
  • Não altera UserTrial.started_at/expires_at (já são somente leitura no admin, conforme R-018 em user_trial_dates_immutable_in_admin.md) nem seu formato de exibição.
  • Não altera a exibição das colunas start_date/end_date na listagem (list_display) do admin, nem o dashboard de estatísticas (TrialDashboardMixin).
  • Não altera nenhum serializer ou endpoint de API.

Mudanças

apps/trials/models/trial.py

  • Adiciona Trial._to_midnight(value): converte um datetime para o horário local (timezone.localtime) e zera hora/minuto/segundo/microssegundo.
  • Trial.clean() normaliza start_date/end_date para meia-noite antes de validar end_date > start_date.
  • Trial.save() normaliza start_date/end_date para meia-noite antes de chamar super().save(), garantindo o comportamento mesmo fora do fluxo do admin (ex.: shell, script).

apps/trials/forms.py

  • TrialAdminForm declara start_date/end_date como forms.DateField(widget=AdminDateWidget), substituindo o widget padrão de data+hora.
  • Adiciona clean_start_date/clean_end_date, convertendo a data escolhida em um datetime timezone-aware à meia-noite local antes de popular a instância do model.

Como verificar

  1. make test (ou pytest tests/trials) — suíte de trials passa, incluindo a validação de end_date > start_date.
  2. No admin (dash.*/admin/trials/trial/), abrir um Trial para edição: os campos “Início” e “Fim” mostram apenas um seletor de data, sem campo de hora.
  3. Criar/editar um Trial informando datas no formulário e confirmar no banco (ou no shell) que start_date/end_date ficam com hora 00:00:00-03:00.
  4. Criar um Trial diretamente via Trial.objects.create(...) com um datetime com hora diferente de meia-noite e confirmar que é normalizado ao salvar.

Documentação

Nenhuma mudança de documentação necessária — não há regra de negócio documentada em .project/docs/rules/trials/ sobre o formato de start_date/end_date do Trial para atualizar.