Documentação do Sistema
By Sport Hub · Manual técnico completo · Auditoria: 14/05/2026 · Atualizado: 07/09/2026
Dados ao Vivo
Alunos Total
0
0 ativos · 0 inadimplentes
Cobranças (Cora)
0
0 abertas · 0 pagas · 0 canceladas
Responsáveis
0
Registros na entidade Guardian
Turmas / Colaboradores
19 / 0
Turmas ativas e colaboradores
Padrão Oficial do Sistema Financeiro
📄 Charge — Mensalidades (Cora)
- Toda mensalidade emitida como boleto/PIX
- Integrado à API Cora via PHP proxy mTLS
- Contém boleto_url, pix_emv, linha_digitavel
- Status: OPEN → PAID via webhook Cora (coraWebhook — implementado e ativo)
- Fonte de verdade para receita de mensalidade
💳 Payment — Controle Interno
- Receitas manuais (avulso, evento, produto)
- Despesas fixas e variáveis
- Folha de pagamento de colaboradores
- Confirmações manuais de recebimento
- ⛔ NÃO usar para mensalidades Cora
⚠️ Payment para mensalidades será descontinuado.
Regra Crítica — Não Violar
Não criar registros de Payment para mensalidades integradas à Cora. Mensalidades com boleto ou PIX bancário devem usar exclusivamente a entidade Charge. A duplicidade entre Payment e Charge é o principal problema arquitetural do sistema hoje.
Fluxo Oficial de Cobrança Mensal
Cobrança Bancária (Boleto/PIX via Cora)
Webhook Cora implementado e ativo (coraWebhook). Ao receber evento PAID, Charge é atualizada automaticamente e e-mail de confirmação é enviado ao responsável.
Controle de Inadimplência (via Payment — provisório)
⚠️ Lê apenas Payment, ignora Charge. Deve ser adaptado após implementação do webhook Cora.
Status do Sistema — Auditoria de Código: 14/05/2026
Funcionando
- ✓Cadastro de alunos (5 etapas)
- ✓Matrículas online (/matricula)
- ✓Gestão de turmas/unidades
- ✓Emissão boleto/PIX via API Cora
- ✓Webhook Cora → Charge PAID + e-mail
- ✓Controle financeiro (DRE, Dashboard, LivroCaixa)
- ✓Folha de pagamento colaboradores
- ✓Portal do aluno/responsável com sliding session
- ✓Controle de acesso RBAC (9 perfis)
- ✓Onboarding por e-mail (emails 2 e 3 agendados)
- ✓Auditoria imutável do sistema
- ✓Módulo Responsáveis com impersonação admin
- ✓sendSystemEmail via Resend + EmailTemplate
- ✓Renovação automática de sessão (renewPortalSession)
Com Problemas / Atenção
manageCollaboratorAccess
Automação entity (Collaborator create) falha — sem token de usuário retorna 401. Workaround: inviteAllCollaborators manual.
CoraIntegration.jsx
UI usa dados mockados (setTimeout/simulação) — não reflete produção
updateStudentDelinquency
Lê APENAS Payment — ignora Charge.status=PAID. Alunos que pagam via Cora ficam Inadimplentes.
coraWebhook sem assinatura
Nenhuma verificação HMAC ou segredo — qualquer POST pode marcar Charge como PAID
onStudentSave em modo de teste
Validações de campos obrigatórios DESATIVADAS via comentário explícito no código. Apenas chama generateStudentCharge.
Student restauração pós-webhook
coraWebhook não chama Student.update após Charge=PAID. Restauração Inadimplente→Ativo ausente.
calculatePayroll incompleto
Função retorna apenas dados do Collaborator — cálculo real está em calculateCollaboratorPayroll.
Não Implementado
- ✗Reconciliação automática de Charges via callCora
- ✗Cancelamento automático de Charges vencidas
- ✗Emissão automática de NF-e
- ✗Notificações WhatsApp/SMS
- ✗Portal responsável financeiro separado
- ✗Vínculo charge_id em Payment
- ✗Validação de assinatura no webhook Cora
- ✗Restauração automática Student→Ativo pós-webhook
- ✗AuditLog de eventos do webhook
- ✗Expiração configurável de token JWT
Guia Rápido de Uso
Como cadastrar um aluno
- 1Acesse /alunos → clique em "Novo Aluno"
- 2Preencha as 5 etapas: Dados → Endereço → Responsável → Autorizações → Operacional
- 3Na etapa Operacional: defina Unidade, Turma, Mensalidade e Dia de Vencimento
- 4Salvar → aluno convidado por e-mail automaticamente (role='user')
- 5Sistema valida campos obrigatórios (onStudentSave) — se incompleto, status muda para Inativo
- 6Payment de mensalidade criado automaticamente (generateStudentCharge) — em processo de descontinuação para mensalidades Cora
Mensalidades devem ser geradas exclusivamente via Charge (Cora). O fluxo via Payment está sendo descontinuado para mensalidades e não deve ser utilizado para novos lançamentos integrados.
Como gerar cobranças (Boleto/PIX via Cora)
- 1Acesse /financeiro → clique em "Gerar Cobranças Mensais"
- 2Sistema executa gerarCobrancasMensais() para todos os alunos Ativos sem Charge no mês
- 3Para cada aluno: criarBoletoCora() → PHP proxy → API Cora
- 4Charge salvo com boleto_url, pix_emv e linha_digitavel
- 5⚠️ Corrigir bug de duplicidade antes de usar em produção em larga escala
Como acompanhar pagamentos
- /financeiro → aba Receitas: lista Payments com status Pendente/Pago/Atrasado
- Confirmar recebimento: clicar no registro → marcar como Pago
- Charges (Cora): status atualizado automaticamente via webhook da Cora (coraWebhook) quando o pagamento é confirmado pelo banco
- /financeiro-dashboard → análise por unidade/turma/período
- /dre → demonstrativo de resultado | /livro-caixa → extrato cronológico
Como identificar inadimplência
- Automático: updateStudentDelinquency roda diariamente às 09h
- Critério: Payment de Mensalidade vencido há mais de 30 dias
- Resultado: Student.status → Inadimplente (registrado em FinancialLog)
- Reverter: confirmar Payment como Pago → na próxima execução aluno volta para Ativo
- /alunos → filtro por status 'Inadimplente' para listar casos
Auditoria de Bugs — Leitura Direta do Código (14/05/2026)
Cada item abaixo foi verificado lendo o código-fonte real das funções listadas. Status: ✅ CORRIGIDO ⚠️ AINDA ABERTO 🔵 EM PROGRESSO
manageCollaboratorAccess — 15 erros consecutivos
✅ CORRIGIDO em 14/05/2026Função aceita chamada via frontend E trigger sem auth. invite_pending=true como fallback quando trigger não tem contexto de auth. Permissões granulares implementadas.
coraWebhook — validação de assinatura HMAC-SHA256
✅ CORRIGIDO em 14/05/2026Validação HMAC-SHA256 implementada com graceful degrade. Sem CORA_WEBHOOK_SECRET: processa com warning. Com secret: valida e rejeita POST inválidos. PENDENTE: configurar CORA_WEBHOOK_SECRET no painel da Cora.
onStudentSave — modo teste / validações reativadas
✅ CORRIGIDO em 14/05/2026Modo teste removido. Validações de campos obrigatórios reativadas. Cadastro incompleto → Student Inativo. Logs de debug removidos.
updateStudentDelinquency — leitura de Charge.status=PAID
✅ CORRIGIDO em 14/05/2026Agora verifica Charge.status=PAID além de Payment. Aluno que paga via boleto Cora é restaurado para Ativo automaticamente. FinancialLog registrado em cada mudança de status.
Restauração automática Student→Ativo após Charge=PAID
⚠️ AINDA ABERTOCódigo lido (functions/coraWebhook): após atualizar Charge→PAID, função apenas chama tryClaimAndSendEmail(). Nenhuma chamada a Student.update ou verificação de status do aluno.
Emails onboarding 2 e 3 — migrados para EmailTemplate
⚠️ AINDA ABERTOCódigo lido (functions/processOnboardingQueue): usa buildEmailHtml() hardcoded internamente via base44.integrations.Core.SendEmail — NÃO usa sendSystemEmail nem EmailTemplate do banco.
CoraIntegration.jsx — UI usa dados mockados (setTimeout)
⚠️ AINDA ABERTONão lido diretamente nesta auditoria, mas confirmado pela documentação anterior. Componente usa simulação e não executa chamadas reais à Cora.
AuditLog de eventos de webhook
⚠️ AINDA ABERTOCódigo lido (functions/coraWebhook): nenhuma chamada a AuditLog.create ou FinancialLog.create. Eventos de pagamento via webhook não são rastreados.
generateMonthlyCharges — duplicação com gerarCobrancasMensais
⚠️ AINDA ABERTOCódigo lido: generateMonthlyCharges (Scheduled dia 1) cria Payment. gerarCobrancasMensais (manual) cria Charge via criarBoletoCora. Dois sistemas paralelos sem vínculo entre si.
calculatePayroll — calcula folha: jornada + salário + transporte
⚠️ EM PROGRESSOCódigo lido (functions/calculatePayroll): função recebe collaborator_id e month, busca o colaborador mas NÃO implementa o cálculo completo — apenas retorna o registro do Collaborator. Lógica real de cálculo está em calculateCollaboratorPayroll.
Integração Cora — Detalhe Técnico Completo
Fluxo Completo de Emissão
Fluxo Completo de Recebimento
⚠️ Student.status NÃO é restaurado automaticamente após webhook. updateStudentDelinquency continua lendo apenas Payment.
Payload enviado à Cora (criarBoletoCora)
{
customer: {
name: guardian.full_name,
email: guardian.email,
document: { identity: cpf, type: "CPF" }
},
services: [{
name: "Mensalidade YYYY-MM nome | Ref: 000001",
amount: <centavos>
}],
payment_terms: { due_date: "YYYY-MM-DD" }
}Payload do Webhook (Cora → coraWebhook)
// Formato aceito:
{ invoice: { id: "..." }, status: "PAID" }
// OU:
{ invoice_id: "...", status: "PAID" }
// Charge atualizada:
{
status: "PAID",
paid_at: new Date().toISOString(),
payment_updated_by: "webhook"
}Dados salvos na entidade Charge (após emissão)
- cora_invoice_id: ID da fatura na Cora (chave de lookup no webhook)
- boleto_url: URL completa do boleto para download
- linha_digitavel: código para pagamento em banco
- codigo_barras: código de barras completo
- pix_emv: string PIX copia-e-cola
- unique_key: studentId_YYYY-MM (controle de duplicidade local)
- external_id: referenceId sequencial gerado pelo sistema (000001, 000002...)
- json_request: payload enviado à Cora (para debug)
- json_response: resposta completa da Cora (para debug)
Autenticação com a Cora (mTLS)
- mTLS: certificado cliente (CORA_CERT_PEM) + chave privada (CORA_PRIVATE_KEY_PEM) configurados no PHP proxy externo
- PHP proxy: https://api.bysporthub.com.br/cora-boleto.php (emissão) e cora-token.php (token)
- Token OAuth2: obtido via getCoraToken() → GET cora-token.php → retorna access_token
- Base URL Cora: https://matls-clients.api.cora.com.br
- Secrets usados: CORA_PRIVATE_KEY_PEM, CORA_CERT_PEM, CORA_API_BASE_URL, CORA_TOKEN_URL, CORA_CLIENT_ID
Pendências críticas na integração
- ⚠️ coraWebhook não valida assinatura — qualquer POST falsificado pode marcar Charge como PAID
- ⚠️ Student.status=Inadimplente não é restaurado automaticamente após Charge=PAID
- ⚠️ Evento do webhook não é gravado em AuditLog ou FinancialLog — sem rastreabilidade
- ⚠️ updateStudentDelinquency ignora Charge — inadimplência baseada apenas em Payment
Sistema Equipe — Documentação Completa
1. Visão Geral
O módulo "Equipe" é o sistema de controle de identidade e acesso (IAM) do By Sport Hub. Ele gerencia quem pode acessar o sistema, quais páginas cada perfil visualiza e quais ações cada role pode executar. É composto por dois submenus: Colaboradores e Controle de Acesso.
🔑 Autenticação: Magic Link (sem senha)
O sistema NÃO armazena senhas. A autenticação é delegada à plataforma Base44 via magic link: o usuário informa o email, recebe um link único por email, clica nele e recebe um token JWT de sessão. Não existe login com usuário/senha.
2. Menu "Equipe" — Estrutura e Navegação
Controle de Acesso → /acesso
admin (exclusivo)Permite visualizar e editar os perfis (roles) de cada usuário. Contém: cards de configuração de cada role (clicável para abrir PermissionPanel), legenda de perfis, lista de usuários ativos com troca de role e unidade inline, botão para sincronizar alunos (StudentUserSyncPanel) e botão para corrigir roles em lote (BulkRoleUpdatePanel).
Colaboradores → /colaboradores
admin, financeiro, coordenador, professor, estagiario, contadorCadastro e gestão de colaboradores. Funcionalidades: cadastrar/editar/excluir colaborador, definir jornada semanal (WorkScheduleForm), visualizar contracheque (ContraChequeModal), gerar link público de cadastro (/cadastro-colaborador), enviar convites em lote (inviteAllCollaborators), corrigir acessos (fixCollaboratorAccess). Visibilidade de dados financeiros restrita a admin/Gestor/Contador.
Usuários → /usuarios
admin (exclusivo)Lista todos os usuários da plataforma organizados em 3 abas: Administradores (roles: admin, financeiro, coordenador, professor, contador, estagiario), Alunos/Responsáveis (roles: aluno, user) e Inativos (role: inativo). Permite busca por nome e email.
Auditoria → /auditoria
admin (exclusivo)Log completo de todas as ações sensíveis do sistema. Registra: usuário, ação, módulo, registro afetado, valor anterior e novo (JSON), severidade (info/warning/critical) e data/hora.
3. Entidades Envolvidas
User (nativa Base44)
| Campo | Tipo | Descrição |
|---|---|---|
| id | UUID | Identificador único — gerado automaticamente |
| string (único) | Email de acesso — usado para autenticação via magic link | |
| full_name | string | Nome completo do usuário |
| role | string (enum) | Perfil de acesso: admin | financeiro | coordenador | professor | estagiario | contador | aluno | user | inativo |
| unit_id | string (FK → Unit) | Unidade vinculada — usado para coordenador/professor restrito à unidade |
| created_date | datetime | Data de criação — gerada automaticamente |
Observação: Entidade nativa Base44. Não é possível criar usuários diretamente — apenas convidar via base44.users.inviteUser(email, role). A senha não existe como campo pois a autenticação é via magic link.
Collaborator
| Campo | Tipo | Descrição |
|---|---|---|
| full_name | string * | Nome completo |
| user_email | string | Email do User vinculado — usado para correlacionar Collaborator ↔ User |
| cpf | string | CPF do colaborador |
| birth_date | date | Data de nascimento |
| role | enum | Gestor | Coordenador Financeiro | Coordenador | Professor | Estagiário | Contador |
| contract_type | enum | Profissional | Estágio |
| unit_id | string (FK → Unit) | Unidade onde atua |
| salary_base | number | Salário base em R$ |
| transport_allowance | number | Valor diário de auxílio transporte |
| pix_key | string | Chave PIX para pagamento |
| education_status | enum | Graduado | Cursando graduação | Não informado |
| course / institution / graduation_year | string | Dados de formação |
| status | enum | Cadastrado | Ativo | Pendente de Aprovação | Pendente de Complementação Financeira | Inativo | Recusado |
| onboarding_complete | boolean | Indica se concluiu o onboarding (jornada + dados financeiros) |
| work_schedule_filled | boolean | Indica se jornada semanal foi preenchida |
Observação: Um Collaborator NÃO é um User automaticamente. A vinculação ocorre via user_email. O convite para acesso ao sistema é feito separadamente via inviteUser().
RolePermission
| Campo | Tipo | Descrição |
|---|---|---|
| role | enum * | Role ao qual a permissão se aplica: admin | financeiro | coordenador | professor | contador | estagiario |
| page | enum * | Página: dashboard | matriculas | alunos | turmas | financeiro | produtos | colaboradores | calendario | portal | comunicacoes | folha-pagamento | contracheque | dre | ranking |
| permissions.view | boolean | Pode visualizar a página |
| permissions.create | boolean | Pode criar registros |
| permissions.edit | boolean | Pode editar registros |
| permissions.delete | boolean | Pode excluir registros |
| advanced_rules.full_access | boolean | Acesso total (sem restrições) |
| advanced_rules.restrict_to_unit | boolean | Restrito apenas à sua unidade |
| advanced_rules.only_own_data | boolean | Vê apenas seus próprios dados |
| special_permissions | array<string> | Ex: view_other_contracheques, access_payroll, generate_charges, manage_roles, approve_enrollments, edit_student_fees |
| modified_by | string | Email do admin que alterou — rastreabilidade |
Observação: Armazena permissões granulares por role+página. Editável em /acesso → PermissionPanel. Estas configurações são informativas — o controle de rota real acontece via ProtectedRoute em App.jsx.
CollaboratorWorkSchedule
| Campo | Tipo | Descrição |
|---|---|---|
| collaborator_id | string * | FK → Collaborator.id |
| day_of_week | enum * | Segunda | Terça | Quarta | Quinta | Sexta | Sábado | Domingo |
| start_time | string | Horário de início (HH:MM) |
| end_time | string | Horário de término (HH:MM) |
| unit_id | string | FK → Unit.id — unidade do turno |
| hours_worked | number | Horas calculadas automaticamente |
Observação: Usada para calcular jornada mensal e auxílio transporte. Cada linha = um turno em um dia da semana. Múltiplos turnos no mesmo dia = múltiplas linhas.
AuditLog
| Campo | Tipo | Descrição |
|---|---|---|
| user_email | string * | Quem executou a ação |
| user_name | string | Nome de quem executou |
| user_role | string | Role no momento da ação |
| action | string * | Descrição da ação (ex: 'Aluno criado', 'Role alterado') |
| module | enum * | Financeiro | Alunos | Turmas | Colaboradores | Acesso | Produtos | Eventos | Folha | Sistema |
| record_id | string | ID do registro afetado |
| record_name | string | Nome/descrição do registro |
| before | string (JSON) | Estado anterior do registro (snapshot) |
| after | string (JSON) | Estado novo do registro (snapshot) |
| severity | enum | info | warning | critical |
| details | string | Detalhes adicionais livres |
Observação: Imutável por design — não há operação de delete via UI. Consultado em /auditoria (admin) e exibido no bloco 'Dados ao Vivo' desta documentação.
4. Roles e Permissões
Gestor (admin)
Páginas: Todas as páginas sem restrição
Obs: Único com acesso a /acesso, /usuarios, /auditoria, /folha-pagamento
Coordenador Financeiro (financeiro)
Páginas: /, /financeiro, /financeiro-dashboard, /dre, /colaboradores, /ranking-unidades, /inteligencia-financeira, /livro-caixa
Obs: Acesso financeiro global. Sem restrição por unidade.
Coordenador (coordenador)
Páginas: /alunos, /turmas, /chamada, /produtos, /eventos, /matriculas, /calendario, /comunicacoes, /colaboradores, /ranking-unidades, /portal, /financeiro-dashboard
Obs: Restrito à sua unit_id. Não acessa DRE, financeiro detalhado ou folha.
Professor (professor)
Páginas: /alunos, /turmas, /chamada, /produtos, /eventos, /colaboradores, /ranking-unidades, /portal
Obs: Sem acesso financeiro. Foco em operação diária de aulas.
Estagiário (estagiario)
Páginas: Mesmo que professor
Obs: Mesmo perfil do professor. Contrato diferente (Estágio). Pode ter restrições futuras.
Contador (contador)
Páginas: /, /financeiro, /financeiro-dashboard, /dre, /colaboradores, /ranking-unidades, /inteligencia-financeira
Obs: Somente leitura financeira. Sem acesso operacional.
Aluno / Responsável (aluno / user)
Páginas: /portal (exclusivo)
Obs: Ambos os roles redirecionam para /portal. Sem acesso ao backoffice.
Inativo (inativo)
Páginas: Nenhuma
Obs: Bloqueado antes de qualquer renderização. Vê apenas tela de acesso revogado.
⚙️ Controle Granular via RolePermission
Além do controle de rotas, existe a entidade RolePermission que permite configurar permissões por role+página (view, create, edit, delete) e regras avançadas (restrict_to_unit, only_own_data). Estas configurações são editáveis em /acesso pelo admin e armazenadas no banco — mas a aplicação das permissões no frontend depende de cada componente consultar RolePermission individualmente.
5. Fluxo de Autenticação
- 1Usuário acessa qualquer página do app
- 2App.jsx carrega AuthProvider → checkAppState() é chamado
- 3Base44 SDK tenta recuperar token JWT do localStorage/cookie
- 4Se sem token → authError { type: 'auth_required' } → base44.auth.redirectToLogin() → redireciona para página de login Base44
- 5Na tela de login Base44: usuário informa email → plataforma envia magic link por email
- 6Usuário clica no link → Base44 valida o token one-time → emite JWT de sessão → redireciona de volta ao app com ?token=...
- 7App recebe o token → base44.auth.me() → retorna objeto User com { id, email, full_name, role, unit_id }
- 8AuthContext.setUser(user) e setIsAuthenticated(true)
- 9App.jsx verifica user.role → ProtectedRoute decide qual rota renderizar
- 10Timer de inatividade iniciado: 10 minutos sem interação → base44.auth.logout() automático
- 11Logout manual: base44.auth.logout(redirectUrl) → limpa token → redireciona para login
Expiração de sessão
Token JWT: gerenciado pela Base44 (validade interna). Inatividade no app: 10 minutos (configurado em App.jsx via INACTIVITY_TIMEOUT = 10 × 60 × 1000ms).
Eventos que resetam o timer
mousemove, keydown, click, scroll, touchstart — qualquer interação do usuário reseta o contador de 10 minutos.
6. Proteção de Rotas
ProtectedRoute (App.jsx)
Componente inline em App.jsx que envolve cada rota. Lógica:
ROLE_PERMISSIONS[role] → lista de paths permitidos
Se pathname ∉ allowedRoutes → redireciona para defaultPath do role
admin → sem restrição (acesso total)
aluno|user → forçado para /portal (App.jsx nível acima)
inativo → bloqueado antes das rotas (tela "Acesso Revogado")
Tabela de roles e rotas padrão (redirect)
| Role | Rota Padrão (redirect) | Bloqueio |
|---|---|---|
| admin | / (dashboard) | Nenhum |
| financeiro | / (dashboard) | Rotas operacionais (chamada, matrículas) |
| coordenador | /matriculas | Financeiro avançado, acesso, folha |
| professor | /chamada | Financeiro, acesso, matrículas, dre |
| estagiario | /chamada | Idem professor |
| contador | / (dashboard) | Operacional, alunos, turmas, chamada |
| aluno / user | /portal | Todo o backoffice |
| inativo | — | Tudo — tela de bloqueio |
Fluxo para colaboradores novos (CollaboratorOnboarding)
- 1.Novo colaborador se cadastra em /cadastro-colaborador (público)
- 2.Collaborator criado com status 'Pendente de Aprovação'
- 3.Admin aprova em /colaboradores → status → 'Ativo' com onboarding_complete=false
- 4.App.jsx detecta collabStatus='onboarding' → exibe CollaboratorOnboarding
- 5.Colaborador preenche dados complementares → onboarding_complete=true
- 6.App libera acesso normal às rotas permitidas para seu role
7. Funções e Hooks Relevantes
AuthContext (lib/AuthContext.jsx)
Context React que centraliza: user, isAuthenticated, isLoadingAuth, authError, logout(), navigateToLogin(), checkAppState(). Usado em toda a app via useAuth().
useAuth()
Hook que expõe o AuthContext. Todo componente que precise do usuário atual usa: const { user } = useAuth().
base44.auth.me()
Chamada ao SDK que retorna o User autenticado atual com todos os campos (id, email, full_name, role, unit_id). Usada em backend functions para validar autenticação.
base44.auth.logout(redirectUrl?)
Limpa o token JWT e redireciona. Se redirectUrl informado, vai para essa URL após logout. Usado no timer de inatividade e no botão de logout.
base44.auth.redirectToLogin(nextUrl?)
Redireciona para a tela de login Base44. Após login, retorna para nextUrl. Usado quando authError.type === 'auth_required'.
base44.users.inviteUser(email, role)
Convida um email para o app com o role especificado. Envia email com magic link de primeiro acesso. Usado ao criar Student e ao aprovar Collaborator.
listStudents (function)
Backend function que usa asServiceRole para listar alunos bypassando RLS — necessário pois RLS bloqueia admin de ver alunos sem email ou guardian_email.
manageCollaboratorAccess (function ⚠️)
Automation entity que convida colaborador automaticamente ao criar Collaborator. Atualmente com erros consecutivos.
inviteAllCollaborators (function)
Itera todos os Collaborators ativos sem convite e chama inviteUser para cada um. Chamada manualmente via botão em /colaboradores.
fixCollaboratorAccess (function)
Sincroniza roles dos colaboradores: para cada Collaborator ativo, encontra o User pelo email e atualiza o role para o equivalente Base44.
standardizeStudentRoles (function)
Garante que todos os usuários com email de aluno/responsável tenham role='user'. Chamada após criar novo Student.
Timer de Inatividade (App.jsx)
useRef + setTimeout de 10 minutos. Reseta em qualquer evento DOM (mousemove, keydown, click, scroll, touchstart). Ao expirar: base44.auth.logout().
8. Relação com Outros Módulos
↔ Student
Ao cadastrar Student, o sistema convida student.email e student.guardian_email como role='user'. O User resultante acessa apenas /portal. RLS do Student usa user.email e user.guardian_email para controle de leitura.
↔ Guardian
Guardian.email é usado no convite de acesso. Um Guardian pode ser responsável financeiro de vários Students. O User criado para o guardian acessa o portal de todos os Students vinculados.
↔ Collaborator
Collaborator.user_email vincula o registro ao User da plataforma. O role do Collaborator (ex: 'Professor') é mapeado para o role Base44 (ex: 'professor') pela função fixCollaboratorAccess. São entidades separadas — Collaborator = dados HR, User = acesso ao sistema.
↔ Financeiro
canViewFinancial = ['admin', 'Gestor', 'Contador'].includes(role). Dados salariais de Collaborator (salary_base, transport_allowance) só são visíveis para estes roles. PayrollLog registra quem acessou contracheques.
↔ Auditoria
AuditLog.user_email + user_role registra cada ação sensível. O role no momento da ação fica gravado permanentemente, mesmo que o role mude depois.
↔ Calendário / Comunicações
HolidayNotificationLog registra notificações enviadas a alunos e colaboradores. Comunicações usa student.email e collaborator.email para disparar mensagens. Filtros de acesso baseados em unit_id do User.
9. Diagrama de Entidades e Fluxo
10. Resumo Final
✅ Pontos Fortes
- ✓Magic link: sem exposição de senha
- ✓RBAC com 8 roles distintos
- ✓Timer de inatividade (10 min)
- ✓Onboarding guiado para colaboradores
- ✓Fluxo automático de convite para alunos e responsáveis
- ✓Auditoria imutável de ações
- ✓RLS no banco para Student (leitura isolada)
- ✓Proteção em App.jsx antes de qualquer render
⚠️ Limitações Atuais
- ⚠manageCollaboratorAccess com erros consecutivos
- ⚠RolePermission não é aplicada automaticamente — depende de implementação por componente
- ⚠Collaborator e User são entidades separadas sem sync automático de role
- ⚠Sem 2FA ou confirmação adicional de identidade
- ⚠Sem histórico de mudanças de role por usuário
- ⚠Sem expiração configurável de token pela app (depende da plataforma)
🚀 Sugestões Futuras
- →Corrigir manageCollaboratorAccess (prioridade)
- →Sync automático Collaborator.role → User.role ao salvar
- →Hook usePermissions() que consulta RolePermission para cada componente
- →Log de mudanças de role em AuditLog (campo 'Acesso')
- →Portal separado para responsável financeiro
- →Notificação ao admin quando novo colaborador se cadastra
- →Dashboard de usuários ativos vs inativos em tempo real
Fluxo de Matrícula — Documentação Técnica
Dois fluxos coexistem no sistema
O fluxo antigo (via Enrollment) ainda processa registros existentes em /matriculas. O fluxo novo cria o Student diretamente, sem etapa de aprovação.
Comparativo dos Fluxos
| Aspecto | Fluxo Antigo | Fluxo Novo (atual) |
|---|---|---|
| O que cria no envio | Entidade Enrollment | Student diretamente |
| Requer aprovação? | Sim, via /matriculas | Não — já entra como Ativo |
| Cria Guardian? | Não no envio | Sim, no envio |
| E-mail de boas-vindas? | Sim, na aprovação | Não |
| Onboarding emails? | Sim (D+3, D+7) | Não |
| Redireciona para | Tela de sucesso | /matriculas (admin) |
Etapas do Formulário /matricula
Nome*, Sexo, Nascimento*, CPF, Telefone*, E-mail*, Foto
CPF* (busca auto), Nome*, Telefone, Parentesco, E-mail
CEP* (ViaCEP auto-complete), Rua*, Número, Bairro, Cidade*, Estado*
Unidade*, Turma*, Uso de imagem*, Aptidão física*, Termos*
Ao Submeter (Fluxo Novo)
Processo de Aprovação (/matriculas)
Enrollment → status: Aprovado
Cria Student (status: Ativo)
⚠ NÃO cria Guardian separado
inviteUser (aluno + responsável)
standardizeStudentRoles()
E-mail HTML de boas-vindas
triggerOnboardingEmails (D+3, D+7)
Enrollment → status: Rejeitado
NÃO cria Student
NÃO envia e-mail
NÃO convida usuário
Acesso ao Portal
O AccessToken não é criado automaticamente no fluxo de matrícula. O aluno precisa solicitar acesso ativamente pela tela inicial após receber o convite via inviteUser.
Gaps Identificados
Fluxo novo não envia e-mail de boas-vindas
Impacto: Aluno criado sem comunicação
Fluxo novo não dispara onboarding emails
Impacto: Sequência de onboarding não inicia
Fluxo antigo não cria Guardian separado
Impacto: Responsável não aparece em /responsaveis
E-mail de boas-vindas está hardcoded
Impacto: Não usa EmailTemplate, não é customizável
inviteUser envia e-mail em inglês
Impacto: Experiência inconsistente com identidade da marca
AccessToken não é criado automaticamente
Impacto: Aluno precisa solicitar acesso manualmente
Fluxo novo redireciona para /matriculas (admin)
Impacto: Usuário público pode acessar área restrita
Webhook Cora — Implementado (coraWebhook)
AtivoO que foi implementado
- Função coraWebhook recebe eventos POST da API Cora
- Evento payload.status === 'PAID' → busca Charge pelo cora_invoice_id → atualiza status para PAID + paid_at
- Após atualizar: chama tryClaimAndSendEmail() — envia e-mail de confirmação de pagamento ao responsável/aluno
- Template de e-mail: 'confirmacao_de_pagamento' via sendSystemEmail
- Campo payment_email_sent evita reenvio duplo do e-mail de confirmação
- Campo payment_notified marcado antes do envio de e-mail (flag de idempotência)
Fluxo do Webhook
Payload esperado: { invoice: { id: "..." }, status: "PAID" } ou { invoice_id: "...", status: "PAID" }
Pontos de atenção
- updateStudentDelinquency ainda lê apenas Payment — deve ser adaptado para verificar Charge.status = PAID também
- Sem validação de assinatura criptográfica do webhook Cora (risco de falsificação)
- Charge.status é atualizado mas Student.status (Inadimplente→Ativo) NÃO é restaurado automaticamente
- Log do evento não é gravado em FinancialLog ou AuditLog — sem rastreabilidade de webhook
Portal do Aluno / Responsável — Documentação Técnica
Visão Geral
O portal é uma área pública acessível via /portal, sem login Base44. A autenticação é feita por sessão local (localStorage) gerada a partir de um magic link enviado por e-mail. Admins acessam o portal via login Base44 normal e veem uma visão administrativa. Responsáveis podem ser impersonados por administradores.
Aluno (type=student)
Aba Conquistas, Frequência, Mensalidades, Eventos, Materiais, Mensagens. Sem aba de cobranças multi-aluno.
Responsável (type=guardian)
Aba Mensalidades (cobranças de TODOS os filhos agrupadas), Frequência, Eventos. Sem Conquistas, Materiais, Mensagens.
Admin (role=admin)
Vê visão administrativa: AdminPortalView com AdminBadgePanel. Sidebar completo disponível.
Fluxo de Autenticação do Portal
- 1Usuário acessa / (HomeEntry) → digita e-mail
- 2generateAccessToken() cria AccessToken na entidade (expira em 24h) e retorna o token
- 3sendPortalAccessLink() envia e-mail com link /portal/acesso?token=xxx
- 4Usuário clica no link → /portal/acesso valida o token via validateAccessToken()
- 5validateAccessToken marca o token como used=true (uso único)
- 6Sessão gravada em localStorage: { email, name, type, device_id, expires_at (+45 dias) }
- 7Usuário redirecionado para /portal
- 8A cada carregamento: renewPortalSession() verifica email em Guardian/Student e renova por +45 dias
- 9Sessão expira? → redirect para /portal/acesso?reason=expired
Funções Backend do Portal
generateAccessToken
OKGera token hex(32) aleatório. Invalida tokens anteriores do mesmo email. Cria AccessToken com expires_at = +24h.
validateAccessToken
OKValida token: existe? não foi usado? não expirou? → marca used=true → retorna { email, name, type }.
renewPortalSession
OKSliding session: verifica email em Guardian/Student. Se encontrado → retorna nova expires_at (+45 dias). Se não encontrado → invalida sessão.
getPortalStudents
OKUsa asServiceRole para buscar alunos bypassando RLS. type='guardian' → filtra por guardian_email. type='student' → filtra por email, fallback para guardian_email.
Dados exibidos no Portal por Aba
| Aba | Entidades consultadas | Quem vê |
|---|---|---|
| Conquistas (badges) | Student.badges + CustomBadge | Aluno (não responsável) |
| Mensalidades | Charge.filter({ student: id }) | Aluno + Responsável |
| Frequência | Attendance.filter({ student_id: id }) | Aluno + Responsável |
| Eventos | Event (status=Aberto) + EventRegistration | Aluno + Responsável |
| Materiais | Product (status=Disponível) | Aluno (não responsável) |
| Mensagens | StudentMessage.filter({ student_id: id }) | Aluno (não responsável) |
Funcionalidades do Portal
- Upload de foto do aluno: UploadFile → Student.photo_url
- Cancelamento de matrícula: muda Student.status para Inativo, envia e-mail de confirmação
- Exclusão de conta: Student marcado como Inativo + nota + e-mail de confirmação
- Seletor de perfil (Netflix-style): múltiplos filhos vinculados ao responsável
- Mensagem de aniversário automática: enviada via StudentMessage ao acessar o portal no dia do nascimento
- Compra de materiais: ProductSale via CartDrawerView + notificação ao admin
- Inscrição em eventos: EventRegistration
- Real-time: Student.subscribe() → atualiza badges em tempo real sem recarregar
Segurança e Sessão
- Sessão armazenada exclusivamente em localStorage (sem cookie)
- device_id gerado via crypto.getRandomValues (hex 32 chars) e vinculado à sessão
- Expiração padrão: 45 dias com renovação automática via renewPortalSession
- Token de acesso (AccessToken): uso único, expira em 24h
- Admin acessando o portal: detectado via base44.auth.isAuthenticated() + role=admin → AdminPortalView
- Portal montado dentro do <Layout> no App.jsx → admin vê sidebar completo
- Usuários não autenticados (comuns): fora do Layout → sem sidebar
Módulo Responsáveis (/responsaveis) — Documentação
Visão Geral
Página de gestão de Guardians. Acessível para admin, financeiro, coordenador. Exibe todos os responsáveis cadastrados separados em duas listas: Com acesso ao portal (Guardian.email ∈ User.email) e Sem acesso ao portal. Permite: expandir detalhes, ver alunos vinculados, gerar cobranças, enviar/reenviar acesso e (admin) impersonar o responsável.
Funcionalidades
Enviar/Reenviar Acesso
Chama generateAccessToken() → link de acesso → sendSystemEmail com template 'bem_vindo_responsavel'. Disponível para admin/financeiro/coordenador.
Acessar como (Impersonação)
EXCLUSIVO para admin. Cria portal_session e portal_impersonation no localStorage com expiração de 2h. Registra AuditLog (severity=warning). Redireciona para /portal como se fosse o responsável.
Gerar Cobrança
Abre GerarCobrancaDialog pré-selecionando o aluno vinculado ao responsável. Apenas para alunos Ativos com Guardian FK preenchido.
Vínculo de estudantes
Guardian é encontrado por guardian === guardian.id OU guardian_email === guardian.email (compatibilidade com fluxo antigo).
Fluxo de Impersonação (Admin → Portal como Responsável)
- 1Admin clica em 'Acessar como' no card do responsável (/responsaveis)
- 2handleImpersonate() cria impersonationSession: { is_impersonation: true, email, name, type: 'guardian', impersonated_by_email, expires_at: +2h }
- 3Salva portal_admin_backup no localStorage (backup da sessão admin)
- 4Salva portal_session com dados do responsável (expires_at: +2h)
- 5Salva portal_impersonation com dados completos da impersonação
- 6Registra AuditLog: severity='warning', action='Impersonação iniciada...'
- 7Redireciona para /portal
- 8StudentPortal.init() detecta portal_impersonation → adminMode = false → carrega como responsável
- 9Banner laranja exibe nome/email do responsável e botão 'Sair da visualização'
- 10Botão 'Sair': remove portal_session, portal_impersonation, portal_device_id → redireciona para /responsaveis
Dados Sensíveis, CPF e Exportações
CPF — Armazenamento e Uso
- Guardian.cpf: identificador único do responsável — usado para busca de duplicatas ao submeter nova matrícula
- Guardian.cpf é obrigatório para emissão de boleto na Cora (enviado no campo customer.document.identity sem máscara)
- Student.cpf: validado com algoritmo mod 11 (dígito verificador), armazenado em texto simples na entidade
- Student.guardian_cpf: campo denormalizado (cópia do CPF do responsável) no registro do aluno para consultas sem JOIN
- CPF não é exibido no portal do aluno/responsável — visível apenas no backoffice para admin, financeiro e coordenador
- CPF é enviado à Cora sem máscara (apenas dígitos) no payload de emissão de boleto
Exportações — Formatos e Funções
- Relatório de alunos (StudentReportDialog): exporta PDF via jsPDF — inclui full_name, cpf, email, phone, status, unit_id, class_id, monthly_fee, guardian_name, guardian_cpf — CPF exibido sem máscara no PDF
- Exportação financeira (Finance/LivroCaixa): CSV ou tabela impressa via window.print() — campos: data, descrição, valor, status, categoria
- Folha de pagamento (Payroll): PDF via jsPDF+autoTable — inclui colaborador, salário base, auxílio transporte, total. CPF do colaborador incluído no contracheque
- Documentação técnica (exportDocumentacaoPDF): este PDF — gerado sob demanda em /documentacao — não contém dados de alunos individuais
- Exportação de importação (BulkImportDialog): aceita CSV/Excel para importar alunos em lote — campos mapeados para entidade Student
- Todos os PDFs são gerados client-side via jsPDF (sem servidor) e baixados diretamente pelo navegador
- Nenhuma exportação automática ou agendada — todas são iniciadas manualmente pelo usuário admin/financeiro
Correções — 14/05/2026 · Seção A: Bugs Críticos Corrigidos
Todos os itens abaixo foram verificados por leitura direta do código após as correções.
manageCollaboratorAccess — 15 erros consecutivos
✅ CORRIGIDOArquivo: functions/manageCollaboratorAccess
Antes: CRÍTICO — colaboradores novos NÃO eram convidados automaticamente (15 erros consecutivos, trigger retornava 401 sem token de usuário)
Correção: Função aceita chamada via frontend E trigger sem auth. invite_pending=true como fallback quando trigger não tem contexto de auth. Permissões granulares implementadas. Tratamento de troca de e-mail antigo.
coraWebhook — validação HMAC-SHA256
✅ CORRIGIDOArquivo: functions/coraWebhook
Antes: ATENÇÃO — sem validação de assinatura, endpoint aberto a falsificações
Correção: Validação HMAC-SHA256 implementada com graceful degrade. Sem CORA_WEBHOOK_SECRET: processa com warning. Com secret: valida e rejeita POST inválidos. PENDENTE: configurar CORA_WEBHOOK_SECRET no painel da Cora e no Base44.
onStudentSave — modo teste removido
✅ CORRIGIDOArquivo: functions/onStudentSave
Antes: ATENÇÃO — validações de campos obrigatórios DESATIVADAS via comentário '// TESTE: Validações removidas temporariamente'
Correção: Modo teste removido, validações reativadas. Campos obrigatórios validados. Cadastro incompleto → Student Inativo. Logs de debug removidos.
updateStudentDelinquency — ignorava Charge.PAID
✅ CORRIGIDOArquivo: functions/updateStudentDelinquency
Antes: ATENÇÃO — lia apenas Payment, ignorava Charge=PAID. Alunos que pagam via boleto Cora continuavam Inadimplentes.
Correção: Agora verifica Charge.status=PAID além de Payment. Aluno que paga via boleto Cora é restaurado para Ativo automaticamente. FinancialLog registrado em cada mudança de status.
Correções — 14/05/2026 · Seção B: Padrão D — setLoading sem finally
Padrão D: setLoading(false) estava dentro do bloco try em vez de finally. Em caso de erro, o loading nunca terminava — tela travada. Todas as páginas abaixo corrigidas com try/catch/finally.
Correções — 14/05/2026 · Seção C: Fluxo de Matrícula e Aprovação
1. approveEnrollment — nova backend function
- Criada para substituir a aprovação direta do frontend.
- Resolve: 403 ao criar Student (RLS), enrollmentId undefined, auth.me() falhando antes de ler parâmetros.
- Fluxo: Guardian → Student → Charge → SOMENTE ENTÃO Enrollment=Aprovado
- Idempotência: verifica Student existente por CPF antes de criar.
- inviteUser movido para o frontend após retorno da função.
- Arquivo: functions/approveEnrollment
2. EnrollmentApproval — frontend simplificado
- Removidas chamadas diretas: Student.create(), Guardian.create(), Enrollment.update(Aprovado).
- Substituído por: functions.approveEnrollment({ enrollmentId })
- Arquivo: pages/EnrollmentApproval
3. EnrollmentForm (/matricula) — estabilidade e UX
- finally { setSaving(false) } — botão SEMPRE volta ao normal, mesmo em caso de erro
- Mensagem de erro amigável na UI (substituiu alert())
- 4 logs estratégicos: [Matricula] Submit iniciado, antes/depois da chamada, redirecionamento
- Error Boundary em App.jsx para a rota /matricula (components/ErrorBoundary.jsx)
- Confirmado: Cora NÃO está no fluxo crítico do submit — submitPublicEnrollment apenas cria Enrollment (status=Pendente)
- Arquivos: pages/EnrollmentForm, components/ErrorBoundary.jsx, App.jsx
Correções — 14/05/2026 · Seção D: Portal do Aluno (/portal)
1. PortalAccess (/portal/acesso)
- Corrigido: guard de admin não reconhecia sessão AppUser (magic link)
- Correção: verifica localStorage.appuser_session antes do JWT Base44
- Correção: navigate com replace: true para evitar loop no histórico
- Arquivo: pages/PortalAccess
2. StudentPortal (/portal) — adminMode e loading eterno
- Corrigido: adminMode nunca virava true para Gestor
- Causa: condição verificava role === 'admin' (minúsculo) mas Gestor tem role === 'Gestor' (G maiúsculo)
- Correção: isAdminRole() aceita admin, Gestor, gestor, financeiro, coordenador, contador
- Corrigido: loading eterno para admin — useEffect retornava cedo sem chamar setLoading(false)
- Correção: setLoading(false) imediato quando adminMode = true
- Arquivo: pages/StudentPortal
3. AdminBadgePanel — Modo Colaborador
- Corrigido: Student.list() bloqueado por RLS para Gestor (retornava [])
- Correção: substituído por base44.functions.invoke('listStudents') — mesmo padrão de pages/Students
- Corrigido: Promise.all sem .catch() — erro silencioso travava o painel
- Correção: try/catch/finally com guard de isLoadingAuth
- Arquivo: components/students/AdminBadgePanel
4. updateStudentBadges — nova backend function
- Criada para substituir Student.update() direto do frontend (bloqueado por RLS para Gestor)
- Usa asServiceRole para bypass de RLS
- Registra AuditLog a cada atribuição/remoção de badge
- Arquivo: functions/updateStudentBadges
5. AdminStudentProfile — toggleBadge
- Corrigido: Student.update() direto sem try/catch — erro silencioso
- Corrigido: optimistic update executava mesmo se banco rejeitou
- Corrigido: toast.success disparava sempre (mesmo em falha)
- Correção: usa updateStudentBadges backend function — estado local só atualizado após confirmação do banco
- Arquivo: components/students/AdminStudentProfile
Correções — 14/05/2026 · Seção E: /responsaveis e /insignias
1. /responsaveis (pages/Guardians)
- Corrigido: User.list() dentro do Promise.all sem isolamento → Guardians nunca carregavam (lista sempre vazia)
- Correção: User.list() isolado em try/catch separado
- Correção anterior também aplicada: loop infinito (Padrão A — Cenário A)
2. /insignias (pages/BadgeManagement)
- Corrigido: race condition de auth — loadData executava antes do isLoadingAuth resolver, Student.list() retornava []
- Correção: useEffect aguarda isLoadingAuth === false
- Correção: query de Student alinhada com pages/Students
3. BadgeAssignPanel
- Corrigido: bug de blur/foco — onBlur fechava dropdown antes do onMouseDown registrar no item selecionado
- Adicionado: feedback visual com contador de alunos disponíveis
Correções — 14/05/2026 · Seção F: Caso Ana Luísa Fróes Gonçalves
Dados do Caso
Enrollment ID: 6a06208b3fccd137ee1686e4
Problema: Enrollment aprovado mas Student não criado (registro órfão)
Guardian reutilizado: FLAVIA FROES
Data de resolução: 14/05/2026
Causa Raiz
O handleApprove original marcava Enrollment como Aprovado ANTES de criar o Student. Se a criação do Student falhasse (ex: RLS 403), o Enrollment ficava como Aprovado sem Student associado — registro órfão.
✅ Resolução
- Enrollment resetado para Pendente e re-aprovado via approveEnrollment corrigida
- Student criado com Guardian reutilizado (FLAVIA FROES)
- Enrollment aprovado corretamente em 14/05/2026
- Nova função approveEnrollment garante ordem: Guardian → Student → Charge → Enrollment=Aprovado
Correções — 14/05/2026 · Seção G: Pendências Abertas
⚠️ PENDENTE — Ação necessária pelo time
CORA_WEBHOOK_SECRET
Configurar no painel da Cora e nas variáveis de ambiente do Base44. Enquanto não configurado, o webhook processa normalmente mas sem validação de assinatura HMAC — risco de falsificação permanece.
🟡 Melhorias Futuras (Roadmap)
- Reconciliação automática de Charges via callCora
- Cancelamento automático de Charges vencidas
- Notificações WhatsApp/SMS ao gerar boleto
- Emissão automática NF-e ao confirmar pagamento
- Emails onboarding 2 e 3 migrados para EmailTemplate (hoje: HTML hardcoded em processOnboardingQueue)
- Error Boundary para demais rotas além de /matricula
- usePermissions() por componente (RolePermission não automática)
- Dashboard de cobrança em tempo real
- Portal do responsável financeiro separado
By Sport Hub · Auditoria técnica realizada em 14/05/2026 · Atualizado em 15/05/2026 · Gerada em 07/09/2026, 11:39:38 · Confidencial
