Pular para o conteúdo principal

Anúncios e Comunicados (App Mobile)

Pilar: 02 — Funcionalidades Core / App Mobile
Status: 🟢 Implementado
Última Revisão Técnica: 2026-08-03
Módulos Conectados: gatein-server (app/api/mobile/announcements.py, app/models.py), gatein-app (src/screens/AnnouncementsScreen, src/store/announcements)


1. Visão Geral e Arquitetura do Módulo

O módulo de Anúncios e Comunicados é o canal central de comunicação direta entre Terminais/Empresas do ecossistema Gatein e os motoristas de aplicativo. Através de banners visuais, comunicados de texto ou direcionamentos por URL externa, os terminais podem divulgar avisos de segurança, alertas operacionais no pátio ou novidades institucionais.

1.1 Diagrama de Sequência e Geolocalização Haversine

sequenceDiagram
autonumber
actor M as Motorista / App Mobile
participant S as Gatein Server (FastAPI)
participant DB as PostgreSQL
participant FCM as Firebase Messaging

M->>S: GET /mobile/announcements?lat=-23.5505&lng=-46.6333
S->>DB: Fetch empresas com Appointments/Trips ativos do CPF
S->>DB: Calcula distância Haversine <= 50km entre motorista e geofences/endereços de empresas
S->>DB: Agrupa Announcements por empresa (is_active == True)
S->>S: Aplica Sliding Window (Max 3 anúncios ativos simultâneos por empresa)
S->>S: Transforma IDs em Sqids ofuscados
S-->>M: Retorna lista ordenada de banners ativos + meta-dados da empresa
M->>M: Exibe carrossel/cards na HomeScreen / AnnouncementsScreen
M->>S: POST /mobile/announcements/log-events (Batch log: viewed)
S->>DB: Registra logs em announcement_logs (user_id, announcement_id, VIEWED)

2. Estruturas de Dados e Schemas Explícitos

2.1 Modelo Relacional no Banco de Dados (announcements)

A tabela announcements armazena a configuração física do anúncio publicado pelo painel Web da empresa:

ColunaTipo SQLConstraintsDescrição / Regra
idUUIDPRIMARY KEY, Default gen_random_uuid()Identificador único interno
company_idUUIDFOREIGN KEY (companies.id), NOT NULLEmpresa proprietária do anúncio
titleVARCHAR(255)NOT NULLTítulo principal do banner
subtitleVARCHAR(255)NULLABLESubtítulo ou resumo explicativo
descriptionTEXTNULLABLECorpo do texto completo do comunicado
image_urlVARCHAR(512)NULLABLEURL do banner gráfico (AWS S3 / Firebase Storage)
image_positionJSONBDEFAULT '{"x": 50, "y": 50}'Coordenadas de ajuste de corte da imagem (crop anchor)
urlVARCHAR(512)NULLABLELink externo para abertura em navegador/webview ao clicar
is_activeBOOLEANNOT NULL, Default TrueStatus de publicação
start_atTIMESTAMPTZNULLABLETimestamp de início do período de exibição
end_atTIMESTAMPTZNULLABLETimestamp de término e expiração
created_atTIMESTAMPTZNOT NULL, Default now()Data de criação do registro

2.2 Schema de Resposta Mobile (MobileAnnouncementListResponse)

{
"success": true,
"data": [
{
"id": "w8x9k2m1",
"company_id": "p4k2n8m9",
"title": "Alerta de Manutenção no Gate 02",
"subtitle": "Interdição temporária para obras na pista",
"description": "Informamos que o Gate 02 estará fechado entre 14h e 18h. Utilize o Gate 04.",
"image_url": "https://cdn.gatein.app/announcements/gate2.jpg",
"image_position": { "x": 50, "y": 50 },
"company_name": "Terminal Marítimo de Santos",
"company_branch_name": "Unidade Ponta da Praia",
"company_logo_url": "https://cdn.gatein.app/logos/terminal_santos.png",
"url": "https://terminal.com.br/comunicado-gate2"
}
]
}

3. Regras de Negócio Explícitas (RN-ANC-XXX)

RN-ANC-001: Elegibilidade Tripla por Empresa

Um motorista é elegível a receber anúncios de uma empresa se atender a pelo menos uma das seguintes condições:

  1. Possuir agendamento (Appointment) não deletado vinculado ao terminal da empresa.
  2. Possuir viagem (Trip) não deletada vinculada à transportadora (trucking_company_id).
  3. Estar localizado dentro do raio de 50 km das coordenadas efetivas da empresa (ver RN-ANC-002).

RN-ANC-002: Cálculo Geográfico Haversine e Coordenadas Efetivas

Quando a latitude e longitude do motorista são informadas na requisição (lat, lng), a API calcula a distância utilizando a fórmula trigonométrica de Haversine contra as coordenadas efetivas da empresa:

  • Se a empresa for do tipo terminal e possuir geofence configurada (geofence.center.lat/lng), essa coordenada do pátio é utilizada.
  • Caso contrário, utiliza-se o endereço cadastral da empresa (Company.address_lat/lng).
  • Empresas sem coordenadas cadastradas só são pareadas por agendamento ou viagem ativa (itens 1 e 2 da RN-ANC-001).

RN-ANC-003: Sliding Window de Ativação (Limite de 3 Anúncios Ativos por Empresa)

Para não sobrecarregar a interface do aplicativo, uma empresa pode ter no máximo 3 anúncios ativos em um mesmo instante temporal. O algoritmo calcula o conjunto de anúncios em janela deslizante:

  1. Filtra anúncios com is_active == True.
  2. Define a data de ativação activation_time = start_at if start_at else created_at.
  3. Ordena os anúncios por activation_time ASC.
  4. Mantém um conjunto ativo simulado e expurga itens cujo end_at < activation_time.
  5. Se a quantidade de ativos simultâneos no histórico for < 3, o anúncio é adicionado ao conjunto de exibição.

RN-ANC-004: Filtragem de Período Vigente

Apenas anúncios onde (start_at IS NULL OR start_at <= now()) E (end_at IS NULL OR end_at >= now()) no momento da requisição são incluídos no payload de retorno.

RN-ANC-005: Ofuscação Mandatória de Identificadores (Sqids)

IDs internos do banco (id do anúncio e company_id) são serializados para formato hash legível via encode_id(). O app mobile envia esses hashes nas rotas de callback de telemetria.

RN-ANC-006: Telemetria em Lote (Batch Logging)

A confirmação de visualização do banner no aplicativo é realizada através de submissão em lote na rota POST /mobile/announcements/log-events. O servidor decodifica a lista de announcement_id via decode_id() e insere registros na tabela auditável announcement_logs com o status VIEWED.

RN-ANC-007: Fallback de Logo da Empresa

O campo company_logo_url resolve chaves em ordem de prioridade no JSON company.config: logo -> logo_url -> icon_url. Se nenhuma estiver definida, retorna null para que o React Native renderize o avatar padrão.


4. Detalhamento de Endpoints

4.1 GET /mobile/announcements

Retorna a lista de anúncios vigentes para o motorista autenticado.

  • Query Parameters:
    • lat (float, opcional): Latitude do dispositivo (-90.0 a 90.0).
    • lng (float, opcional): Longitude do dispositivo (-180.0 a 180.0).
  • Headers: Authorization: Bearer <jwt_token>
  • HTTP 400: Se coordenadas estiverem fora do intervalo geográfico válido.

4.2 POST /mobile/announcements/log-events

Registra eventos de engajamento dos banners.

  • Payload:
{
"events": [
{
"announcement_id": "w8x9k2m1",
"event": "viewed",
"message": "Banner renderizado na HomeScreen",
"json_data": { "display_duration_ms": 3500 }
}
]
}
  • Resposta de Sucesso: { "success": true, "message": "Eventos de avisos registrados com sucesso." }

5. Regras de Segurança, Tenant e Tratamento de Erros

  1. Autenticação: Requer JWT de motorista válido em todas as chamadas.
  2. Tenant Isolation: O motorista nunca informa IDs de empresas diretamente; os anúncios elegíveis são calculados no backend a partir dos vínculos cadastrais do seu CPF ou posição GPS.
  3. Resiliência Offline:
    • O aplicativo mobile armazena a resposta em AsyncStorage / Zustand store.
    • Em caso de ausência de rede, os últimos anúncios cacheados continuam sendo exibidos.
    • Logs de visualização gerados em modo offline são acumulados na fila local e disparados em batch quando a conexão é restabelecida.