Acesso PRO nunca expirava

O que aconteceu

Terapeutas mantinham o acesso ao TRG Club Pro depois que a filiação CITRG expirava.

A verificação de elegibilidade já rodava em todo sign-in de terapeuta (AutoSubscribeJob → CreateFlow → ValidateEligibility lê valid_until_timestamp do CITRG), mas o resultado era descartado para usuários que já tinham assinatura PRO: o ValidateExistingSubscription falhava o flow com “Você já possui um plano PRO ativo” antes que qualquer coisa pudesse reagir a pro_eligible == false. Somado ao fato de assinaturas PRO serem criadas com expires_at = nil (o scope non_expired trata nil como “nunca expira”), o acesso PRO era efetivamente permanente.

Causa raiz

Validação calculada mas não aplicada. Um fluxo que busca estado externo (o valid_until do CITRG) precisa ter um passo que reconcilia o estado local com ele — não apenas uma guarda para o caminho de criação. Procure por valores context.<flag> que nenhum passo downstream consome.

Correção

Um novo passo Subscriptions::Pro::ExpireWhenIneligible foi inserido logo após o ValidateEligibility, expirando o PRO e reativando o plano regular. Detalhes em 20260707151623_expire_pro_when_citrg_ineligible; a regra resultante é a R-003.

Como evitar

  1. Validação calculada tem que ser aplicada. Se o flow busca estado externo, algum passo tem que reconciliar o estado local com ele.
  2. expires_at = nil significa “nunca expira” em Subscription#non_expired. Qualquer assinatura criada sem expiração é permanente a menos que algum passo a expire explicitamente.
  3. A ordem do flow esconde bugs. fail! num flow Ucasy interrompe os passos restantes; uma validação que falha cedo (“já tem plano”) pode pular silenciosamente passos de reconciliação colocados depois. Passos de reconciliação têm que vir antes das validações de saída antecipada.
  4. A citrg-api nunca empurra a expiração. Lá a expiração de filiação é computada na leitura (valid_until < today) — sem virada de status, sem webhook, sem job avisando o TRG Club. Qualquer garantia de frescor tem que vir de polling do TRG Club (fluxo de login ou job recorrente).

Follow-ups

  • Job recorrente para rebaixar usuários PRO que nunca mais fazem sign-in (tokens do DeviseTokenAuth se auto-renovam, então usuários sempre online escapam da verificação no login). Parcialmente endereçado pelo webhook de expiração — ver pro_downgrade_on_citrg_expiry.
  • Janela de 12 meses de acesso à plataforma de aulas (Apolo) para Anjo/Tutelado — nenhum sistema guarda essa data hoje; a fonte da verdade precisa ser definida antes de poder ser aplicada.
  • Na citrg-api, apolo_access_permitted? aprova usuários com carteirinha emitida mesmo com a documentação atual não aprovada (membership.rb) — causa candidata para “documentação não aprovada mas ainda tem acesso”. Endereçado do lado do TRG Club pela R-004.