Apuração Oficial de Sorteios no Manager
Workflow guiado em 4 etapas, modos bidirectional e upward, reservas de aproximação e exportação de planilha via S3/CDN.
✅ Implementado entre 02/08 e 20/08/2026. Feature completa no Manager, dentro da aba "Sorteios Exclusivos Oficiais" de /sorteios — visível para [oglobo, xamers, estadao, baitaBeneficios, baitaHomolog]. A ação (canManageOfficialDraw) é liberada para manager.managerType === 'admin' ou qualquer e-mail @urbis.cc.
Workflow guiado em 4 etapas
- 1Resultado da Loteria Federal: campo "Número do concurso" → POST /winner/official-draw/{lotteryId}/federal-result/contest (consulta automática à Caixa). Se a integração falhar (timeout/502/503/504), abre automaticamente um fallback manual: link para o site da Caixa + 5 campos (1º ao 5º prêmio, 6 dígitos cada), com revisão antes de confirmar (POST .../federal-result/manual).
- 2Simulação obrigatória (dry run): POST {endpoint} com { isDryRun: true } — nenhum vencedor é persistido. Mostra total de vencedores, número sorteável, hash do resultado, se está completa e a lista detalhada. O botão "Exportar planilha" baixa um .xlsx da simulação.
- 3Persistência definitiva: bloqueada se não houve dry run, se o dry run foi invalidado (mudança de concurso/loteria/whitelabel) ou se não retornou isComplete: true. Confirma com { isDryRun: false }; o front exige que a API confirme isDryRun === false na resposta.
- 4Comunicação aos vencedores: POST /winner/official-draw/{lotteryId}/send-emails, separado deliberadamente da persistência.
Dois modos de apuração (OfficialDrawProximityMode)
- bidirectional (padrão, POST /winner/official-draw/:lotteryId/process): aproximação circular +1/-1/+2/-2... Não é mais exclusivo do Baita desde 65e4ce8a (19/08) — liberado também para outros clubes com sorteio exclusivo próprio.
- upward ("sorteio regulamentado", POST /winner/manager/official-draw/:lotteryId): aproximação só para números imediatamente superiores. Exclusivo do Baita — se um sorteio pertence só ao Baita e alguém tentar rodar em modo bidirectional, a API rejeita com BadRequestException.
- Reservas de aproximação: o modo upward extrai 7 registros extras (REGULATED_DRAW_BACKUP_COUNT) marcados como reserva — não persistidos como Winner, servem para substituição manual em caso de desclassificação.
- Sorteios lotteryType === 'whitelabelOfficial' não podem ter vencedor reatribuído manualmente — a apuração oficial é auditável e imutável.
🆕 Exportação de planilha via S3/CDN
Commits db4da6c3 (21/08) e ec4cd0ea (24/08): antes salvava em disco local (logs/draws/), problemático com múltiplas réplicas. Agora OfficialDrawReportStorageService grava no bucket S3 BUCKET_NAME_ONE_SIGNAL (reaproveitado, não é dedicado), prefixo lottery/official-draw/reports/, com ServerSideEncryption: AES256. O download valida que a URL pertence à CDN oficial (cdn.media.urbis.cc/lottery/official-draw/reports/) antes de abrir — proteção contra SSRF. A extração da URL de CDN foi unificada em getUrlToCdn.util.ts.
Tratamento de erros (presentOfficialDrawError)
Mensagens específicas por tipo (401/403 autorização, 409 conflito "já processada", 400 validação). Timeout/rede na etapa de registro automático abre o fallback manual; timeout/rede na persistência mostra aviso explícito para não reenviar sem verificar o estado, evitando duplo processamento.