App Estudante
Aplicativo (Expo/React Native) usado pelo estudante beneficiário do transporte universitário — Android nativo
e PWA web, sem iOS (decisão deliberada). Voltagem plena da IDV Unipass: framboesa
#e92c58 (identidade + ação) + roxo #831e91 (conquista/gamificação), mosaico
multicolor em momentos de celebração, Nunito em display. Ver arquitetura
do ecossistema.
Existe um UX-spec de 2026-07-04 que propôs um redesign completo (nav com carteirinha central, split framboesa/roxo, CTA sticky). Esta página descreve o que está de fato implementado hoje (verificado direto no código, não a proposta) — onde a proposta ainda não foi totalmente adotada, isso está sinalizado explicitamente como exceção.
1. Visão geral
O App Estudante é a superfície voltada ao beneficiário do transporte: confirmar presença semanal, acompanhar a rota e o ônibus, mostrar a carteirinha digital para o fiscal, e (opcionalmente) engajar com um sistema de gamificação. Estudantes não acessam o painel Gestão — este app é sua única superfície.
Plataformas
Android nativo (APK/AAB via EAS) + PWA web instalável. Sem build iOS.
Papel
Estudante — login por CPF, auto-provisionamento de conta no primeiro acesso
2. Conceitos específicos do domínio
Confirmação Diária é o fluxo formal e principal do app: o estudante confirma presença numa grade semanal, sujeita a cutoff, e alimenta o planejamento/manifesto da operação. Sinalização é um modelo mais simples e antigo ("vou usar o transporte nesta data") sem cutoff nem vínculo a manifesto — usado hoje apenas como evidência secundária para detectar walk-in nas estatísticas de disciplina. Os dois sistemas não têm nenhuma referência cruzada no código — são vestígios de dois momentos de design do produto que continuam ativos em paralelo. Para o tutorial de usuário final, documentar apenas Confirmação Diária como o fluxo real. (Existe uma tela de "sinalizar ponto" no código do app, mas ela não é exposta como uma das 5 tabs — não deveria ganhar destaque no tutorial.)
São duas coisas diferentes: walk-in é o embarque de um estudante sem Vínculo ativo com a rota (bloqueado por padrão, exige override do motorista com justificativa); fora do manifesto é uma confirmação criada manualmente por um operador depois que o manifesto da rota já fechou — nunca acontece pelo fluxo do próprio app estudante.
Vocabulário adicional: card_token (identificador único da carteirinha, formato SGTU-{16 hex}) · DeviceToken (token de push por usuário+plataforma) · GamificationState (XP/nível/streak/badges/missão semanal).
3. Navegação
5 tabs, com a Carteirinha elevada ao centro (implementado, não só proposto — commit ddf84bf, 2026-07-04):
| Posição | Tab | Ícone |
|---|---|---|
| 1 | Início | House |
| 2 | Minhas Rotas | Route |
| 3 (centro, destacada) | Carteirinha | IdCard — 10% maior, realce de mosaico quando ativa |
| 4 | Confirmar | CalendarCheck — badge de pendências |
| 5 | Perfil | User |
Notificações não é mais uma tab — foi migrada para um sino no header da tela Início, com badge de não-lidos.
4. Telas e fluxos
4.1 Início
Hub do dia: responde "o que é do meu transporte hoje?" e permite agir em 1 toque (confirmar pendência ou acompanhar viagem ativa). Ordem de leitura: header com sino de notificações → banner de penalidade (se ativo) → estado do dia → viagem ativa (se houver) → gamificação → ações rápidas.
Quando há viagem ativa, o banner mostra quantas paradas faltam até o ponto do estudante, cruzando os eventos de chegada/saída do veículo em cada ponto com a posição do estudante na rota. Se nenhum ponto foi concluído ainda, mostra a contagem total de paradas da rota.
Quando a rota exige confirmação com antecedência de dias (cutoff_dias_antes > 0), o
banner de cutoff na Início já mira a confirmação de amanhã, não hoje — porque a janela de
hoje já fechou no dia anterior.
Quando não há rota vinculada ou viagem ativa, a tela não mostra uma ilustração de estado vazio — apenas degrada o texto para "Sem rota hoje". Diferente de outras telas do app (Minhas Rotas, Notificações), que têm empty state ilustrado com o mosaico da marca.
4.2 Minhas Rotas
Tela de referência (leitura, não CRUD): cada card mostra nome da rota, badge "Ativa" (na primeira/rota corrente), direção/turno, dias da semana, e em destaque visual o ponto de embarque + horário — o dado que o estudante mais busca. Empty state com mosaico decorativo quando não há vínculo de rota.
4.3 Confirmar
O "batimento transacional" do app — grade semanal (segunda a sexta) com estados de célula: confirmado (verde), cancelado (X cinza), vazio (traço neutro, distinto de cancelado) e travado (após o cutoff, opacidade reduzida, preserva o que de fato aconteceu).
CTA "Confirmar todas"
Cor framboesa, sticky na base da tela (zona do polegar) — não mais no topo
Recompensa ao confirmar
Haptic + pulso de escala no botão + +10 XP flutuante; confirmação em lote da semana inteira dispara também um burst de mosaico
Estudante abre a aba Confirmar no domingo à noite, vê a grade da próxima semana com o aviso de cutoff em destaque, toca "Confirmar todas" — recebe feedback tátil e visual imediato (XP, burst de mosaico, e no primeiro uso, o badge "Calourado"). Se um dia específico não for necessário, desmarca a célula individual antes do cutoff daquele dia.
Depois do cutoff, o estudante não pode mais criar, alterar ou cancelar a confirmação daquele dia/turno —
só admin/gestor podem forçar uma alteração tardia (parâmetro force=true, nunca exposto ao app
estudante).
4.4 Carteirinha
O artefato-herói do app — elevado à tab central, com o padrão visual mais alto de acabamento. Estrutura: zona superior em gradiente framboesa→roxo (foto, nome, curso) sobre um card branco de dados (matrícula, CPF completo, validade, situação, faixa co-branding com brasão/nome do município).
L1 — relógio vivo: HH:MM:SS ao vivo, tanto no card quanto no modal de QR (screenshot
congela o horário, denunciando a fraude). L2/L3 — faixa-checksum com shimmer: cores da
faixa derivadas de um hash determinístico de cardToken + data (UTC) — muda por dia, reproduzível
entre dispositivos, e praticamente sempre distinta entre estudantes diferentes no mesmo dia. QR sob demanda:
só aparece dentro de um modal aberto por botão — nunca exposto por padrão na tela. O QR continua sendo a
validação criptográfica real; as 3 camadas acima são dissuasão visual de primeiro olhar, não a segurança em si.
A "validade" exibida não vem do calendário acadêmico real da instituição — é um corte hardcoded: 31 de julho (1º semestre) ou 31 de dezembro (2º semestre), conforme o mês da consulta. Não reflete o calendário específico de cada IES.
Existe um endpoint público (sem autenticação) que valida uma carteirinha pelo seu
card_token — é o que um fiscal/portaria consulta. O retorno é reduzido de propósito: nome, foto,
curso, turno, IES, situação e validade — mas omite deliberadamente o CPF, para não expor PII
numa URL pública. Contraste importante para o tutorial: na carteirinha exibida ao próprio estudante
o CPF aparece completo (política de zero-masking), mas na superfície pública de validação o CPF nunca é
retornado.
Reduced motion: shimmer e tick do relógio são omitidos (não acelerados) — substituídos por faixa estática e um timestamp fixo "Aberto em DD/MM HH:MM". Nenhuma capacidade de dissuasão anti-fraude é perdida, só a affordance de movimento.
4.5 Perfil
Header (avatar framboesa + dados) → atalho para Carteirinha → histórico de viagens → Conquistas (gamificação, sempre em roxo — nunca framboesa, separação de papel de cor deliberada e documentada no código) → dados pessoais/acadêmicos → preferências → logout.
4.6 Notificações
4 categorias: Todas / Não lidas / Alertas / Comunicados. Distinção visual por severidade real, não decorativa: crítico = vermelho + "Urgente"; aviso/categoria prioritária (penalidade, cutoff, cancelamento) = âmbar + "Obrigatório"; comunicado informativo comum = framboesa + "Comunicado" (única categoria que usa a cor de marca do estudante em vez de semântica de severidade).
4.7 Login e primeiro acesso
Tela de login permanece em azul institucional neutro (o papel do usuário ainda é desconhecido nesse ponto — decisão deliberada, não resíduo). O fluxo de onboarding gamificado (4 passos: avatar → perfil → tutorial de embarque → meta da primeira semana) é exibido apenas no primeiro acesso.
5. Gamificação
Todo o design de progressão (fórmula de XP por ação, catálogo de badges, contagem de streak) vive no
app, não no backend. O servidor apenas persiste e sincroniza o estado: XP
sobe por max(servidor, cliente) (nunca aceita um valor menor, protegendo contra regressão
acidental), badges são a união dos dois lados, e streak/streak_frozen/missão
semanal são sobrescritos pelo valor que o app enviar, sem validação de regra do servidor.
Isso é relevante para o tutorial: "gamificação" não é uma feature de servidor com API própria de regras — é
uma camada de engajamento cliente com um envelope de sincronização simples.
O que existe: XP acumulado, nível (derivado por faixas de XP: 0/500/2000/5000/10000), streak de confirmações, badges (lista livre de strings), missão semanal (progresso/meta). Cor: roxo puro em toda a seção de gamificação — separado deliberadamente da framboesa (identidade/ação).
O campo streak_frozen existe no backend e é aceito na sincronização, mas não há
nenhuma regra automática (nenhum job, nenhum signal) que decida quando congelar a sequência do
estudante — a decisão, se existir, é inteiramente do cliente. Não documentar como uma política formal do
produto até que uma regra de servidor exista.
6. Disciplina — perspectiva do estudante
O estudante enxerga automaticamente apenas suas próprias ocorrências e penalidades (escopo automático via API compartilhada — não é um endpoint separado). A única ação de escrita que o estudante executa é reconhecer uma penalidade (confirmar ciência) — nunca pode alterar o status dela.
Um estudante com suspensão temporária ou definitiva ativa continua conseguindo confirmar presença e embarcar normalmente — não há nenhum gate no backend que impeça isso hoje. Se o produto espera que uma suspensão bloqueie o uso do transporte, é um gap de implementação a resolver, não uma regra de negócio existente a documentar como se already funcionasse.
7. O que o estudante efetivamente recebe
O módulo administrativo de Comunicação (templates/canais WhatsApp/SMS/e-mail configurados no Gestão) é hoje um stub — não entrega nada de fato. O que o estudante realmente recebe no inbox de notificações vem de um sistema totalmente separado (Alertas), disparado automaticamente por eventos operacionais de viagem — persistidos como alertas de tipo "operação" e também enviados por push: viagem confirmada/publicada, viagem iniciada/finalizada, motorista chegou ao ponto e aviso livre do motorista. As categorias de comunicação "institucionais" (atraso, cancelamento, mudança de ponto) existem como taxonomia administrativa, mas não chegam ao estudante por nenhum canal implementado hoje.
8. Autenticação e primeiro acesso
O endpoint de login por CPF tenta primeiro casar com um motorista; se não achar, tenta estudante. Se o estudante já tem conta mas nunca definiu senha própria, o sistema aceita CPF como senha e força a troca (self-heal). Se o estudante ainda não tem conta de usuário nenhuma, o sistema a cria automaticamente no primeiro login bem-sucedido com CPF. Qualquer falha (CPF inexistente, senha errada, conta bloqueada) retorna a mesma mensagem genérica — proteção deliberada contra enumeração de CPFs válidos.
Para definir a senha própria (fim do primeiro acesso), o estudante precisa informar a data de nascimento cadastrada. Se o cadastro não tiver data de nascimento, a troca self-service é recusada — o caminho vira reset assistido por um gestor, nunca um bypass silencioso.
9. Requisitos não-funcionais
9.1 Plataforma e offline
Diferente do app motorista (fila offline completa para embarque/desembarque/ocorrências), o app estudante depende de rede para confirmar presença e demais ações — não há fila de escrita offline equivalente. A Carteirinha funciona sem rede, mas por um mecanismo indireto: os dados do estudante ficam em cache local por até 24h após a última busca bem-sucedida, não por um "modo offline" desenhado especificamente para o cartão.
9.2 LGPD e armazenamento local
No Android nativo, o cache de dados do estudante (nome, CPF, foto, card_token — dados de
menores) é armazenado cifrado (AES-128, chave em keystore seguro do device). No PWA web, esse mesmo
cache cai para um armazenamento não-cifrado — limitação da biblioteca de storage no navegador, não
uma escolha deliberada. Isso é relevante especificamente para o cenário de tablet/quiosque compartilhado
(ex.: portaria de instituição): em um navegador não higienizado entre sessões, dados do último estudante
autenticado ficam recuperáveis por qualquer pessoa com acesso ao mesmo perfil do navegador, sem precisar de
root/jailbreak. Token de autenticação em si não sofre disso — usa cookie HttpOnly via BFF, não localStorage.
9.3 Acessibilidade (WCAG 2.2 AA)
Framboesa #e92c58 como texto é borderline (~4.0:1, abaixo do 4.5:1 exigido) — o app já usa
consistentemente a variante escurecida accentDark para todo texto pequeno, reservando a cor
crua para fills/backgrounds grandes. Cobertura ampla de accessibilityLabel/accessibilityRole
e alvos de toque ≥48px nas telas principais.
9.4 Push notifications
Registro de DeviceToken por usuário+plataforma. O backend dispara push automaticamente por
evento de viagem: a cada chegada do motorista a um ponto (e nas transições de viagem publicada/iniciada/finalizada,
além de aviso do motorista), o servidor envia o push e persiste o alerta no inbox. É importante distinguir: isso é
push por evento de chegada a ponto, não um cálculo contínuo de proximidade em tempo real ("faltam
N paradas" calculado a cada segundo por GPS) — esse rastreamento contínuo não existe no backend (ver App Motorista).
O app tem o formato de payload de proximidade preparado, mas o gatilho real hoje é discreto (por evento), não contínuo.
10. Limitações e exceções conhecidas
- Resíduo de cor violeta hardcoded (fora do design system) em dois elementos do fluxo de onboarding gamificado — pendência pequena e concreta, não coberta pela revisão de paleta já feita
- Sem regra de servidor para "congelar" a sequência de confirmações (gamificação é só o app decidindo)
- Sem bloqueio de confirmação/embarque quando o estudante tem penalidade ativa
- Armazenamento local não-cifrado no PWA web (ver RNF 9.2)
- Módulo de Comunicação administrativo não entrega nada ao estudante (é stub) — o que chega de fato vem de Alertas
expo-imagenão é usado nas telas do estudante (convenção do projeto pede seu uso para imagens)- Validade da carteirinha usa corte semestral fixo (31/jul ou 31/dez), não o calendário acadêmico real da IES
11. Fora de escopo
- Build e distribuição iOS
- Fila de escrita offline (só o app motorista tem)
- Entrega real de comunicados institucionais (atraso/cancelamento/mudança de ponto) — modelo existe, canal não
- Regras de servidor para gamificação (XP por ação, streak-freeze automático, catálogo de badges)