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:
| Coluna | Tipo SQL | Constraints | Descrição / Regra |
|---|---|---|---|
id | UUID | PRIMARY KEY, Default gen_random_uuid() | Identificador único interno |
company_id | UUID | FOREIGN KEY (companies.id), NOT NULL | Empresa proprietária do anúncio |
title | VARCHAR(255) | NOT NULL | Título principal do banner |
subtitle | VARCHAR(255) | NULLABLE | Subtítulo ou resumo explicativo |
description | TEXT | NULLABLE | Corpo do texto completo do comunicado |
image_url | VARCHAR(512) | NULLABLE | URL do banner gráfico (AWS S3 / Firebase Storage) |
image_position | JSONB | DEFAULT '{"x": 50, "y": 50}' | Coordenadas de ajuste de corte da imagem (crop anchor) |
url | VARCHAR(512) | NULLABLE | Link externo para abertura em navegador/webview ao clicar |
is_active | BOOLEAN | NOT NULL, Default True | Status de publicação |
start_at | TIMESTAMPTZ | NULLABLE | Timestamp de início do período de exibição |
end_at | TIMESTAMPTZ | NULLABLE | Timestamp de término e expiração |
created_at | TIMESTAMPTZ | NOT 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:
- Possuir agendamento (
Appointment) não deletado vinculado ao terminal da empresa. - Possuir viagem (
Trip) não deletada vinculada à transportadora (trucking_company_id). - 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
terminale 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:
- Filtra anúncios com
is_active == True. - Define a data de ativação
activation_time = start_at if start_at else created_at. - Ordena os anúncios por
activation_time ASC. - Mantém um conjunto ativo simulado e expurga itens cujo
end_at < activation_time. - 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
- Autenticação: Requer JWT de motorista válido em todas as chamadas.
- 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.
- 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.
- O aplicativo mobile armazena a resposta em