Regras de Negócio¶
🎯 Propósito: este é o documento de consulta do suporte interno. Reúne as regras que governam o sistema "por baixo dos panos" — justamente as que a gente esquece em features pouco usadas. Quando alguém perguntar "por que o sistema fez X?", a resposta provavelmente está aqui.
1. Pessoas, Usuários e Perfis de acesso¶
- Usuário ≠ Pessoa. Usuário faz login (e-mail/senha); Pessoa salta/trabalha e tem carteira. Um usuário pode estar ligado a uma pessoa, mas não precisa.
- Perfil de acesso ≠ classificação da pessoa. Os perfis de acesso (o que o usuário pode fazer) são um conjunto fechado: Administrador, Operacional, Financeiro, Staff de campo, Dobrador, Atleta. A classificação operacional da pessoa (instrutor, piloto, tandem, videomaker, rigger…) vive nas flags da Pessoa, derivadas de licença, e serve para filtrar/escalar — não dá acesso. Acesso é validado por capability (ação): GET exige
*.view, alteração exige*.manage/ação específica. O mapa role→capability é editável pelo system admin em/system/rbac(vale para todas as DZs); o código é o seed + fallback, e o perfil Administrador sempre tem tudo (não dá para se trancar fora). - Modo "somente leitura" automático nas telas. Quando o usuário tem o
*.viewmas não o*.managede uma área (ex.: financial em Locais, ou staff no Manifesto), a UI esconde/desabilita as ações de gerência: o manifesto entra em modo travado (sem arrastar, sem check-in), Reservas esconde criar/editar/sync, Pessoas esconde criar/editar/convite/excluir, Configurações da DZ desabilita o form e mostra um selo "Somente leitura", e cada catálogo esconde criar/editar/excluir. Assim o usuário não recebe 403 ao clicar em algo que o perfil não pode. - Comparador de perfis na tela de Equipe. Ao convidar/editar um membro, o ícone ⓘ ao lado de "Funções" abre um diálogo com a matriz role × área (Gerencia/Visualiza/Aprova) — é a fonte rápida pro admin entender o que vai conceder.
- Adicionar um Dobrador cria/vincula uma Pessoa automaticamente e marca a flag
é_dobrador. Os demais perfis de acesso não marcam flags — a classificação vem das licenças. - Só Administrador concede o perfil Administrador. Operacional gerencia a equipe (criar/editar/resetar senha) mas não pode atribuir/alterar/remover o perfil Administrador (sem autopromoção).
- As funções da Pessoa são derivadas das licenças, não marcadas à mão.
é_atleta,é_instrutor,é_rigger,é_piloto,é_rtasão atualizadas automaticamente quando a licença/habilitação muda: é_atleta← licença de salto válida (A/B/C/D/AI)é_instrutor← habilitação real de instrutor válida. No cadastro via CBPq, só contam habilitações de instrutor de verdade: Instrutor AFF/ASL, Treinador BBF, Piloto Tandem, Examinador. A habilitação "Aluno em Instrução" e o "CIS" genérico (que o CBPq joga na mesma categoria CIS) não marcam a pessoa como instrutor.é_rigger← certificação de dobra de reservaé_piloto← habilitação de piloto válida (PP/PC/PLA)é_default_rta: no máximo uma pessoa por DZ pode ser o RTA padrão (auto-selecionado ao criar decolagem).
Validações no cadastro/edição de Pessoa¶
- E-mail obrigatório. Toda Pessoa precisa de e-mail; o sistema rejeita criação sem ele.
- E-mail único por DZ. Não dá pra criar duas Pessoas com o mesmo e-mail no mesmo tenant (constraint
unique_people_email_per_tenant). Conflito retorna 409. - Documento único por DZ. Mesmo CPF/Passaporte não pode aparecer em duas Pessoas (
unique_person_documentportenant_id + tipo + número). - CPF é validado. Quando o tipo de documento é CPF, o número passa por validação de dígitos verificadores. Acentos/máscara são strip-ados — armazenamos só dígitos.
- Tipos de documento aceitos: apenas CPF e Passaporte. Pessoas legadas com
RG/CNHcontinuam funcionando, mas não dá pra cadastrar novos. - Deduplicação proativa por nome+nascimento. No fluxo de criação manual, antes de gravar o sistema busca Pessoas com mesmo nome (normalizado) e data de nascimento compatível; se houver candidatos, abre um diálogo de confirmação. O fluxo de lookup CBPq já tem dedup próprio por nome.
- Normalização automática. Nome e apelido entram com
trim+ colapso de espaços; e-mail entra em lowercase. Evita duplicatas por digitação inconsistente.
2. Acesso aos Portais¶
- Portal do atleta tem dois interruptores em série: o da DZ (
portal_enabled) e o da pessoa (portal_access_enabled). Ambos precisam estar ligados. - O toggle individual da pessoa (
portal_access_enabled) é o que efetivamente dirige o papel de atleta + auto-cadastro/OAuth/vínculo no login. (O antigo toggle por equipe foi removido.) - Modo de registro (
invite_onlyvsaberto) decide se o atleta entra só por convite ou por auto-cadastro. - Portal do dobrador exige
packer_portal_enabledna DZ e a função de dobrador na pessoa.
3. Decolagem (Load) — Máquina de Estados¶
Transições válidas (qualquer outra é rejeitada):
rascunho → agendada, cancelada
agendada → embarque, rascunho, cancelada
embarque → decolou, agendada, cancelada
decolou → pousou
pousou → (final, sem transição)
cancelada → (final, sem transição)
- Agendar exige aeronave + piloto licenciado. Sem piloto com habilitação válida, não passa de rascunho.
- Piloto padrão da aeronave. Cada aeronave pode ter um piloto padrão (opcional; pessoa com
é_piloto). O preenchimento é do backend (igual ao RTA padrão), então vale em todos os caminhos de criação: decolagem avulsa, loads placeholder/rascunho intermediários e criação em lote. Regra: ao criar uma decolagem sem piloto informado, usa o piloto padrão da aeronave; ao trocar a aeronave de uma decolagem sem informar piloto no mesmo request, o piloto passa a ser o padrão da nova aeronave. Um piloto informado explicitamente sempre prevalece. O piloto segue obrigatório para agendar e editável a qualquer momento. - Não dá pra alocar mais slots que a capacidade da aeronave.
pousoué o gatilho da cobrança. Veja seção 5.- Decolar recalcula o agendado das próximas. Ao marcar decolou, o sistema recalcula o horário agendado das decolagens seguintes da mesma aeronave a partir do horário real de decolagem: cada uma vira decolou + tempo de voo da aeronave (
EstimatedFlightTime, + abastecimento quando a decolagem está marcada com combustível). Só mexe nas decolagens ainda não decoladas (rascunho/agendada/embarque) — as que já decolaram/pousaram mantêm os horários. Editar manualmente o agendado de uma decolagem também propaga para as seguintes da mesma aeronave, do mesmo jeito. - RTA obrigatório para decolar/pousar. Se algum produto da decolagem tem item que paga o RTA e nenhum RTA está atribuído, o sistema bloqueia marcar decolou ou pousou — evita pousar uma carga e só depois perceber que o repasse do RTA não foi gerado.
- Atribuir RTA numa decolagem já pousada re-gera o repasse. Ao corrigir o RTA (ou a aeronave) de uma decolagem que já pousou, a cobrança roda de novo de forma idempotente: cria o repasse do RTA que faltava sem duplicar as cobranças já feitas.
- Modo de edição permite editar uma decolagem já pousada (exceção controlada).
4. Manifesto¶
- Um manifesto por dia + local.
GetOrCreateretorna o existente ou cria. - Manifesto pode ser fechado e reaberto.
- Categoria mínima pode ser definida por manifesto (filtra quem pode saltar).
- Aeronave do dia. Cada manifesto pode ter uma aeronave padrão do dia, independente da aeronave padrão da DZ. Ao criar uma decolagem nova, a aeronave pré-selecionada segue a prioridade: (1) aeronave do dia do manifesto, se definida e ativa; (2) senão, aeronave marcada como padrão da DZ (
is_default). É conveniência de UI — só afeta decolagens novas, nunca as já criadas, e a aeronave continua editável. Definir/limpar a aeronave do dia exigemanifest.managee o manifesto aberto. - Validação de licença/reserva no manifesto segue o modo configurado na DZ:
- Estrito → bloqueia.
- Aviso → alerta e deixa seguir.
- Não checar (só reserva) → ignora.
- Licença pendente de validação bloqueia o embarque (espelha o equipamento). Uma licença só é considerada válida para saltos que exigem licença quando está validada pela DZ (ou auto-validada via CBPq), além de estar regular e dentro da validade. Licença
pendente de validaçãocai no modo estrito/aviso acima, com a mensagem específica "licença pendente de validação" (distinta de expirada/inativa).
Retorno a Bordo (embarcou, decolou e voltou sem saltar)¶
Quando alguém embarca, o avião decola e a pessoa volta sem saltar, marque a vaga como Retorno a Bordo. Por ser uma edição de uma decolagem já pousada, entre no Modo de edição da decolagem e use o botão ⤶ Retorno a Bordo na vaga (abre um diálogo de confirmação, com a opção de já não cobrar). É diferente de No-show (que nem embarcou) e de cancelar a vaga:
- É uma marca física do salto. O tipo de salto não muda (um tandem continua tandem) — é o que garante que os relatórios saibam a origem.
- Logbook: aparece com o selo Retorno a Bordo, mas não conta no total de saltos da pessoa.
- Relatórios: vira categoria própria "Retorno a Bordo" no Relatório RTA e no Operacional-Financeiro (sai das contagens de duplo/aluno/esportivo). A decolagem continua contando.
- Cobrança (padrão): continua igual. O atleta segue pagando o produto (mesma receita) — útil quando o tandem embarcou e voltou mas é cobrado do mesmo jeito.
- Não cobrar (opcional): a opção "Não cobrar (Retorno a Bordo)" troca apenas o produto da vaga por um RETORNO A BORDO custo-zero (a DZ cria esse produto/tipo de salto no catálogo). O motor reprocessa a cobrança e estorna o débito; o produto original fica guardado, e desmarcar o Retorno a Bordo restaura o produto e volta a cobrar.
5. Cobrança Automática (Billing) — pousou¶
Quando a decolagem pousa, ProcessLoadBilling(load, data_do_manifesto) roda nesta ordem:
- Filtra slots cobráveis — exclui
no_showecancelada. - Carrega produtos + itens de todos os slots.
- Resolve preços versionados pela data do manifesto (não a data de hoje).
- Slots sem grupo → débito direto na pessoa.
- Para cada grupo:
- identifica pagantes (
paid_by_group = falso) e pagos (paid_by_group = verdadeiro); - cobra cada pagante pelo próprio produto;
- rateia o custo de cada slot pago igualmente entre os pagantes (o último absorve o arredondamento);
- comissões add-on: produto próprio do staff → credita o staff (itens
performersem tipo de salto); - comissões de pacote: produto do pagante → credita o staff correspondente (itens
performercom tipo de salto → acha o slot de staff daquele tipo); - comissões estáticas: credita o destinatário fixo (
static_party,rta,agency,equipment). - roteamento de repasse (org/CNPJ): antes de creditar, o resolver verifica se há uma rota
(pessoa, template)definida. Se houver, o crédito vai pra carteira da organização vinculada em vez da conta pessoal — só vale para repasses baseados em pessoa (rtaeperformer). Ver §6.1. - Repasse de aeronave: depende do modo de repasse da aeronave (
payback_mode): - por vaga (padrão): se o produto tem
repassa_aeronave, credita o dono o preço de vaga vigente, por vaga paga (override por produto → preço padrão da aeronave); - por decolagem: credita o dono um valor fixo por decolagem (vigente na data do manifesto) quando o load pousa com ≥1 pessoa — independente de assentos e da flag
repassa_aeronave. Load vazio não gera repasse. A descrição na carteira mostraDecolagem {ocupados}/{capacidade}. Ambos emitem uma única transaçãoaircraft_paybackpor load (mesmoreference_type), então a reconciliação delta-aware vale igual. - Repasse de equipamento: se o slot tem rig, credita o dono do rig via item de equipamento.
Reconciliação delta-aware (não duplica e corrige)¶
O motor é delta-aware: a cada reprocesso ele recalcula o valor desejado de cada cobrança e lança
só a diferença como ajuste (transações são imutáveis). Buckets:
- Cobrança de slot → reference_type = load_billing + slot ID.
- Rateio → reference_type = load_billing_split + source_slot_id no metadata.
- Comissão → reference_type = load_commission + product_item_id.
- Cobrança de complemento → reference_type = load_billing_extra + extra ID.
➡️ Reland / Reprocessar não gera cobrança em dobro (diferença = 0 → nada é criado).
- Trocar a vaga/produto de um atleta e reprocessar ajusta a conta dele. Se o novo produto é mais barato, gera estorno da diferença; se é mais caro, cobra o complemento. Vale ao editar o produto da vaga numa decolagem já pousada (recálculo imediato) e ao voltar para Agendado e pousar de novo.
- Estorno automático do que saiu. Vaga/complemento removido, marcado
no_show/cancelado, ou atleta trocado de vaga: o motor estorna a cobrança que não vale mais (estorna o antigo, cobra o novo) no próximo reprocesso. - Voltar para Agendado não estorna sozinho — a reconciliação acontece quando a decolagem pousa de novo.
Extras (complementos) em grupos¶
- Extras pertencem a um slot específico, não ao grupo. Cobrados sempre do
slot.person_id, mesmo se o slot estiver marcadopaid_by_group. - Em tandem completo, os extras digitados no diálogo de alocação ficam no slot do passageiro (leader). Antes desse fix, o fluxo de criação de grupo ignorava silenciosamente o array de extras.
- No resumo do grupo (mesmo em loads finalizadas) e no detalhamento por produto do dia, os extras aparecem como linhas próprias.
6. Itens de Produto e Templates¶
- Sem itens, toda a receita vai pra DZ.
- Comissão
performerCOM tipo de salto = modelo pacote (acha o staff pelo tipo de salto no grupo). - Comissão
performerSEM tipo de salto = modelo add-on (o staff já está no slot do produto). - Quando o item está ligado a um Template, o Template manda. Os campos
recipient_type/amount/jump_typeefetivos vêm do Template (resolva via template, não pelos campos do item). Os campos no item ficam só por compatibilidade com linhas legadas. - Modos de cálculo do template:
valor fixo,por vaga paga,% da receita. calculation_modedo template é imutável. Depois de criado, o backend rejeita a troca (ErrCalculationModeImmutable, HTTP 400). Pra trocar o modo, crie um novo template. Pra trocar o valor, use o endpoint de preços (/prices) que cria uma nova versão vigente — o valor não é atualizado pelo PUT do template.bills_aircraftsó é relevante em produtosjump. Outros tipos (rental/service/merchandise) são forçados afalseno save — mesmo se o frontend enviartrue.- Operador no extra é exigido só quando o produto tem item performer dinâmico. Produtos service sem performer dinâmico (taxa de parcelamento, p.ex.) viram receita pura sem precisar de operador.
Licenças¶
Emissor(CBPq/USPA/ABPq/Outro) é separado deFederação: emissor é a confederação que emite a licença; federação é a associação regional (livre texto). Lookup CBPq setaEmissor = CBPqautomaticamente.(tenant_id, license_number)é único empeople_license(sem duplicar nº de licença na DZ).(tenant_id, cis_number)também é único.- Validação da licença (
validation_status:pending→validated). Espelha a aprovação de reserva do equipamento. Licença criada/editada pela DZ no admin já nasce validada (a DZ é a autoridade). Licença auto-cadastrada pelo atleta nasce pendente: a CBPq é auto-validada pela integração (quando a DZ deixa "validar licença automaticamente" ligado); USPA/ABPq/Outro ficam pendentes até alguém validar manualmente emPessoa → Licença → Validar(capabilitylicense.validate). Validar registra quem, quando e a fonte (cbpq_auto/manual). Uma licença só vale no manifesto se estiver validada (ver seção Manifesto).
6.1. Roteamento de Repasses (pessoa → org/CNPJ)¶
Uma pessoa pode acumular vários papéis (RTA, instrutor, câmera) e querer receber cada repasse numa conta diferente — ex.: o pagamento de RTA no CNPJ da empresa dela, mas a comissão de instrutor na conta pessoal. O roteamento resolve isso.
- Vínculo pessoa↔org. Na edição da pessoa, a seção "Empresas (CNPJ)" vincula a pessoa a organizações. Só vincula Party do tipo
ORGANIZATION(party que não é org → 422). - Granularidade por template. A chave da rota é
(pessoa, template de item). Isso separa câmera de instrutor naturalmente (templates diferentes). Configurado na seção "Roteamento de repasses", com um seletor por template → "Conta pessoal" (padrão) ou uma das empresas vinculadas. - Ausência de rota = conta pessoal. Não existe linha "pessoal"; só há registro quando há redirecionamento. O
PUTsubstitui o conjunto inteiro (array vazio = tudo pessoal). - Só repasses baseados em pessoa. Roteia
rtaeperformer. Os demais (agency,static_party,equipment,company) já apontam para um party específico — não roteiam.packeré pago no fluxo de confirmação de dobra, fora do template engine. - Atribuição preservada. O crédito vai pra carteira da organização, mas a transação grava quem gerou (
earned_by_person_id/_name) — relatórios de produção por pessoa continuam corretos. A descrição ganha sufixo "(via Nome)" pra carteira da org mostrar de quem veio. - Defina a rota ANTES de cobrar o load. Trocar a rota depois que o load já foi cobrado não move créditos já liquidados (exige reconciliação manual). Não precisa reprocessar nem zerar o billing existente: o motor casa por earner, com fallback legado para transações antigas.
- RBAC: gerenciar vínculos/rotas usa
people.manage(mutação) epeople.view(leitura) — é parte de editar a pessoa. - Portal: a pessoa vê as carteiras das orgs vinculadas a ela (seção "Minhas empresas"), com guard de posse — só vê a carteira de org à qual está vinculada (não-membro → 403 plano).
7. Preços Versionados¶
- Produtos, templates e preço de vaga de aeronave usam intervalos
vigente_de/vigente_até(vigente_até = NULL→ ativo). - A cobrança usa o preço vigente na data do manifesto. Manifesto retroativo cobra o preço da época.
- O banco impede sobreposição de períodos.
- Valores de item definidos direto (sem template) NÃO são versionados — são atualizados no lugar. Como o billing roda no mesmo dia, a transação na carteira guarda o valor real como registro histórico.
8. Carteira e Transações¶
- Toda Party (pessoa ou organização) tem uma conta por DZ.
- Transações são imutáveis: nunca são editadas nem apagadas (hooks bloqueiam update/delete). Para corrigir, use ajuste ou estorno (transação relacionada).
- Cada transação guarda
balance_after, referência (load_slot/depósito/ajuste) e metadata — é o registro de auditoria.
9. Dobra (Packer)¶
- Dobra só pode ser criada com o manifesto aberto / no mesmo dia.
- Uma dobra só pode ser desfeita se ainda NÃO foi paga.
- Origens:
auto(motor, ao pousar/decolar),manual_load(reivindicada num slot),manual_independent(avulsa). - Status:
pendente→pago(atleta pagou) /dz_paid(a DZ banca, veio embutido no produto) /waived(cortesia). - O gatilho automático (
pousouvsdecolou) é configurável na DZ (packer_auto_entry_trigger). - Dobrador pode ter preço próprio por serviço (override do preço padrão do catálogo).
- Pagamento atleta→dobrador passa por confirmação:
pendente_confirmação→confirmado/contestado. - Precedência do item de dobra: quando o slot tem complemento de aluguel com item
packer, a DZ paga (modelo de aluguel da DZ). O complemento vence sobre qualquer item de dobra no produto principal. - Confirmação de dobra sem equipamento no manifesto (tandem). O equipamento é opcional para manifestar, mas o item pode exigir confirmação de dobra. Quando nenhum rig foi marcado no grupo, o Confirmar equipamento aparece na linha do piloto do tandem (a função marcada como quem veste o equipamento atribuído) — não no passageiro. O dobrador escolhe o rig que dobrou e o repasse é gerado ao dono; aluguéis (complementos) confirmam no próprio complemento.
- Reorganizar decolagem já pousada não perde nem duplica dobras. Ao mover um grupo para outra decolagem, as dobras (e serviços) acompanham a pessoa para o novo slot — sem virar registro "solto" e sem a DZ pagar de novo. Ao remover um slot/pessoa, a dobra pendente ligada é estornada (as já pagas não são tocadas).
9.1. Repasses de Serviço (Rádio e futuros)¶
Genéricos pra repasses similares à dobra mas executados por outros profissionais. Hoje cobre rádio AFF; futuros tipos (vídeo, organizador, …) seguem o mesmo padrão.
- Quem reivindica? Staff de campo (capability
radio.claim). No portal do atleta, o staff vê todos os loads do dia e os slots com item de rádio ganham botão "Fiz o rádio". - Detecção do item segue a mesma cadeia da dobra: complemento → produto principal → siblings de grupo. Aluguel/complemento de rádio sempre vence.
- DZ paga. Entry criada com
status=dz_paid, vai pra carteira do staff. Atleta NÃO é cobrado por rádio (o repasse é da DZ ao staff). - Sem auto-criação ao pousar. Diferente da dobra, nunca cria automaticamente — quem fez o trabalho reivindica via portal. Evita atribuir errado.
- Janela de undo: enquanto o manifesto estiver aberto, o próprio staff (ou admin) pode desfazer.
- Conflito de claim: dois staff que clicam quase ao mesmo tempo → o segundo recebe erro de duplicidade (UNIQUE por
load_slot_id + service_type). Frontend mostra "já reivindicado por X". - Persistência: tabela própria
service_entries(não mistura compacker_entries). Permite expandir com video/cameraman sem refactor.
10. Equipamento (Rig) e Reserva¶
- Validade da reserva = data da dobra +
rig_reserve_pack_expire_months(config da DZ, padrão 6). - Re-dobra (
repack) deixa a reserva pendente de aprovação → precisa ser aprovada. - Validação de reserva vencida no manifesto segue
manifest_reserve_validation_mode(estrito/aviso/não checar). - A reserva validada é a do equipamento que a pessoa veste. O manifesto checa a reserva do rig atribuído ao slot (o que ela realmente veste) quando há um; só cai no equipamento padrão dela quando não há rig atribuído. No tandem, o rig fica no slot do piloto — então a reserva validada é a do rig de tandem designado, e não a do equipamento esportivo pessoal do piloto (que ele não usa no salto).
- Flag do tipo de salto
wears_assigned_rig("Veste equipamento designado"). Para papéis que sempre saltam com equipamento designado/da casa (ex.: Tandem Pilot), marque essa flag no tipo de salto. Com ela ligada e sem rig atribuído ao slot, o manifesto não valida o equipamento pessoal da pessoa (apenas libera) — evita o bloqueio falso quando o operador não designou o rig. Com rig atribuído, a reserva dele continua sendo validada normalmente. - Equipamento pendente de validação pela DZ (reserva na validade, ainda não aprovada — típico do gear auto-cadastrado pelo atleta) segue uma key separada:
manifest_pending_gear_mode(bloquear/alerta/permitir, padrão bloquear). Só tem efeito quandomanifest_reserve_validation_mode != não checar.bloquear/alertadisparam o erroerror_reserve_pending; emalertao front oferece "Continuar mesmo assim";permitirignora o pendente. - Equipamento com dono pessoa (
OwnerType=person) precisa ter oPersonIDsetado para aparecer no perfil da pessoa e emPessoas → Equipamentos; dono organização/DZ é equipamento da casa. manifest_allow_without_geardecide se dá pra manifestar sem rig vinculado.
Equipamento próprio cadastrado pelo atleta (portal)¶
- Pelo portal (
Perfil → Meus Equipamentos) o atleta cadastra um equipamento próprio (os cadastrados pela DZ não contam para esse limite). Pode editar o seu, mas não excluir (só a DZ exclui). - A categoria é sempre Profissional e o
is_rentableé forçado false (não dá pra marcar como alugável pelo portal). - O equipamento entra com reserva pendente (
ReserveStatus=pending→ selo "Pendente de validação pela DZ") e vira o equipamento padrão da pessoa automaticamente. - Guard de posse: o atleta só altera/lê o rig que é dele (
rig.PersonID == caller→ senão 403).
11. Integração Bookeo¶
- Mapeie os produtos do Bookeo antes de sincronizar — sem mapeamento, a reserva chega sem produto.
- A sincronização cria/atualiza reservas e participantes (passageiros viram Pessoas).
- Importa o depósito pago online.
- Requer acesso à internet de saída (e o login Google/OIDC também). Em deploy serverless, isso tem implicações de infra — ver
CLAUDE.md.
12. Multi-tenant¶
- A DZ = Tenant. Todo dado tem
tenant_ide é isolado por tenant. - As requisições da área administrativa carregam o tenant via header (
X-Tenant-ID) e validam o acesso do usuário àquele tenant.
13. Cadastro Público de Atletas¶
Página pública por DZ (/r/{slug}) onde atletas se cadastram sozinhos. Opt-in — a DZ habilita em Configurações → Dropzone → Portal. Detalhes operacionais em Cadastro Público de Atletas.
Máquina de estados da verificação¶
Toda Pessoa tem verification_status com 3 valores:
verified → criada pela DZ ou aprovada após cadastro público (default)
pending_verification → veio do cadastro público, aguarda DZ revisar
rejected → DZ rejeitou (motivo registrado, atleta não pode re-tentar)
Transições válidas:
- DZ-created →
verified(sempre, default) - Cadastro público + CBPq encontrado →
verified - Cadastro público + CBPq não encontrado / USPA / ABPq / Outro →
pending_verification pending_verification→verified(DZ clicou Verificar)pending_verification→rejected(DZ clicou Rejeitar com motivo)rejectedé terminal — não volta nem é retentável pelo formulário
O
verification_statusé apenas uma etiqueta de revisão da Pessoa — não controla acesso ao portal (que é sempre imediato) nem embarque (que é controlado pela validação da licença). Ver seção Licenças.
Match (bloqueio de duplicata)¶
A página pública não atualiza Person existente — bloqueia. As chaves de match:
email(case-insensitive)cpf(sem máscara)passport_numbername + birth_date(nome case-insensitive, data exata)
Match contra Persons em qualquer status (verified, pending_verification, rejected). Rejeitado bloqueia — força o atleta a contatar a DZ.
Integração CBPq¶
Se o license_issuer = "CBPq" e license_number preenchido, o backend faz lookup server-side durante o submit (não no cliente — sem endpoint público de lookup, evita virar proxy de scraping). Resultados:
validated→ snapshot completo salvo emcbpq_lookup_snapshot(JSONB); ocbpqSyncService.Sync()popula licença + endorsements + histórico localmente. Com "validar licença automaticamente" ligado (padrão), a licença CBPq já entra validada (validation_source = cbpq_auto). Licença CBPq irregular/vencida é registrada mas continua bloqueada no manifesto (status/validade reprovam, mesmo validada).not_foundouunavailable→ licença fica pendente de validação (DZ valida manualmente); nunca bloqueia o envio.
Conta de portal¶
A conta no Portal do Atleta é sempre criada na submissão (a DZ escolheu oferecer o cadastro público, então o atleta ganha acesso imediato — CBPq ou não). O User é criado, vinculado com o perfil Atleta, portal_access_enabled = true, e o email de setup-password é enviado (login Google também resolve). O acesso ao portal é independente da validação da licença — o atleta entra e usa o portal; o que fica bloqueado até a licença ser validada é o embarque no manifesto. Se o provisionamento da conta falhar, o cadastro retorna erro (não finge sucesso) e a Pessoa pendente fica recuperável pela ação Verificar.
Anti-fraude¶
- reCAPTCHA v3 com threshold ≥ 0.5
- Rate limit 5 submissões/hora por IP (tabela
public_registration_attempts) - Audit:
self_registered_at,self_registered_ip,self_registered_user_agentgravados na Person
14. Relatório RTA (Atividades Realizadas)¶
Relatório em Relatórios para o comitê de segurança da CBPq. Conta, por mês (ou período escolhido),
1 por vaga saltada (load decolou/pousou; exclui vagas não compareceu/canceladas):
- Decolagens = nº de voos.
- Total de saltos (vagas totais) = duplos + alunos + esportivos (só saltos).
- Duplos = só o passageiro do tandem (classificação
tandem). Detalhado por tipo de salto. - Alunos = classificação
student(BBF/AFF). Detalhado por tipo de salto. - Esportivos = classificação
sportoustaff→ inclui piloto de tandem e câmera. Detalhado por tipo de salto. - Total Outros (vagas totais) = classificação
other(ou sem classificação) → o que não é salto, como voo panorâmico. É um total separado (um nível acima, ao lado de "Total de saltos"), fora das vagas totais de saltos. Detalhado por tipo. - Retorno a Bordo = vagas marcadas como Retorno a Bordo (embarcou, decolou e voltou sem saltar). Categoria própria, fora de duplos/alunos/esportivos, detalhada pelo tipo de salto original (ex.: quantos tandens voltaram). A decolagem continua contando; o salto, não.
Para um tipo cair em Outros, marque a classificação do tipo de salto como Outro em Configurações → Tipos de Salto (ex.: Voo Panorâmico). O Retorno a Bordo não depende da classificação do tipo — é a marca da vaga que define a categoria.
Acesso: capability reports.rta.view (perfis admin, operational e rta). O menu
Relatórios é um submenu — cada relatório tem sua própria capability, então relatórios diferentes
podem ser liberados a perfis diferentes (ex.: futuros relatórios financeiros só para financial).
Notas de manutenção (para devs)¶
Estas não afetam o usuário final, mas evitam pegadinhas no desenvolvimento.
PeopleRepository.Updatetem whitelist de colunas (Select(...)). Colunas novas na Pessoa não persistem silenciosamente até serem adicionadas à lista. Se um campo novo "não salva", é aqui.- O guia técnico do motor de cobrança (com diagramas) está em Motor de Cobrança (anexo técnico).