Serviços Personalizados do Terminal (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/services.py,app/models.py),gatein-app(src/screens/ServicesScreen,src/screens/ServiceWebView)
1. Visão Geral e Arquitetura do Módulo
O módulo de Serviços Personalizados permite que cada Empresa/Terminal disponibilize portais web externos, utilitários, tabelas de tarifas, agendamentos de refeição no pátio ou funcionalidades nativas para os motoristas no aplicativo mobile. As URLs externas são exibidas em um componente de WebView seguro (ServiceWebView), com injeção automática de tokens JWT de curta duração para autenticação em sistemas terceiros e controle rígido contra domínios não autorizados.
1.1 Diagrama de Sequência e Injeção de Token no WebView
sequenceDiagram
autonumber
actor M as Motorista / App Mobile
participant S as Gatein Server (FastAPI)
participant DB as PostgreSQL
participant WV as WebView (App Mobile)
participant EXT as Sistema Terceiro (Ex: Portal de Agendamento)
M->>S: GET /mobile/companies/{company_id}/services
S->>DB: Query CompanyServices join AllowedDomains (is_active == True)
S-->>M: Retorna lista de serviços com URLs e meta-dados (Sqids)
M->>M: Seleciona um serviço do tipo "webview"
M->>S: GET /mobile/services/auth-token
S->>S: Gera JWT temporário (exp: 3 minutos / 180s) com user_id
S-->>M: Retorna token de integração
M->>WV: Inicializa ServiceWebView com URL do serviço + Header Auth
WV->>EXT: GET https://terceiro.com.br/portal (Header: Authorization Bearer <temp_jwt>)
EXT->>EXT: Valida JWT do Gatein e reconhece o motorista
EXT-->>WV: Renderiza portal personalizado dentro do app
2. Estruturas de Dados e Schemas Explícitos
2.1 Modelo Relacional no Banco de Dados (company_services e allowed_domains)
Tabela company_services:
| 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 serviço |
domain_id | UUID | FOREIGN KEY (allowed_domains.id), NOT NULL | Vínculo com a whitelist de domínios |
title | VARCHAR(255) | NOT NULL | Nome do serviço exibido no card |
description | TEXT | NULLABLE | Descrição das funcionalidades oferecidas |
url | VARCHAR(512) | NOT NULL | Endereço HTTP/HTTPS do portal |
icon_url | VARCHAR(512) | NULLABLE | URL da imagem do ícone do serviço |
is_active | BOOLEAN | NOT NULL, Default True | Status de ativação |
created_at | TIMESTAMPTZ | NOT NULL, Default now() | Timestamp de criação |
Tabela allowed_domains:
| Coluna | Tipo SQL | Constraints | Descrição / Regra |
|---|---|---|---|
id | UUID | PRIMARY KEY, Default gen_random_uuid() | Identificador único |
domain | VARCHAR(255) | UNIQUE, NOT NULL | Domínio autorizado (ex: *.terminal.com.br) |
is_active | BOOLEAN | NOT NULL, Default True | Status da whitelist |
2.2 Schemas de Resposta da API Mobile
Resposta de Serviços da Empresa (ServiceListResponse):
{
"success": true,
"data": [
{
"id": "w8x9k2m1",
"company_id": "p4k2n8m9",
"title": "Consulta de Fila de Pátio",
"description": "Veja a posição em tempo real do seu caminhão na triagem",
"url": "https://patios.terminal.com.br/triagem",
"icon_url": "https://cdn.gatein.app/icons/queue.png",
"is_active": true,
"created_at": "2026-08-01T10:00:00Z"
}
]
}
Resposta de Token de Integração (AuthTokenResponse):
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 180
}
}
3. Regras de Negócio Explícitas (RN-SVC-XXX)
RN-SVC-001: Trava Dupla de Ativação por Domínio Permitido
Um serviço cadastrado na tabela company_services só é retornado no aplicativo mobile se atender a duas condições simultâneas no banco de dados:
CompanyService.is_active == TrueAllowedDomain.is_active == True(o domínio vinculado emdomain_iddeve estar ativo na whitelist da plataforma).
RN-SVC-002: Token de Integração de Curta Duração (3 Minutos)
A rota GET /mobile/services/auth-token gera um JWT de integração com expiração estrita de 180 segundos (3 minutos) (exp_delta=timedelta(minutes=3)). Este token é injetado nos headers do ServiceWebView para prevenir vazamentos de credenciais permanentes em navegações de terceiros.
RN-SVC-003: Isolamento de Serviços por Empresa (Tenant Scope)
A rota GET /mobile/companies/{company_id}/services decodifica o company_id informado em formato Sqid e retorna exclusivamente os serviços vinculados àquela empresa. Não há mesclagem de serviços de múltiplos terminais em uma mesma listagem.
RN-SVC-004: Validação Rígida de Whitelist de Domínios na WebView
Antes de efetuar o carregamento da página no componente ServiceWebView, o aplicativo mobile verifica se o domínio da URL de destino pertence à lista de domínios permitidos retornada pela API. Se o motorista tentar navegar para uma URL fora da whitelist (ex: redirecionamento malicioso), a WebView interrompe o carregamento e exibe a tela de erro INVALID_DOMAIN_ACCESS.
RN-SVC-005: Fallback de Ícone do Serviço
Caso o campo icon_url seja nulo ou a imagem falhe ao carregar no aplicativo, a interface do React Native utiliza um ícone genérico padronizado em SVG (DefaultServiceIcon).
4. Detalhamento de Endpoints
4.1 GET /mobile/companies/{company_id}/services
Lista todos os serviços ativos disponibilizados por uma empresa específica.
4.2 POST /mobile/services/by-ids
Recebe um array de UUIDs/Sqids e retorna os detalhes estruturados dos serviços solicitados.
4.3 GET /mobile/services/auth-token
Gera o token JWT de integração (TTL: 180s) para o motorista autenticado.
5. Regras de Segurança e Tratamento de Erros
| Código HTTP | Código Interno | Causa Raiz | Ação no App Mobile |
|---|---|---|---|
400 Bad Request | INVALID_COMPANY_ID | Hash Sqid de empresa corrompido ou inexistente | Retornar à tela anterior |
403 Forbidden | DOMAIN_NOT_ALLOWED | URL do serviço pertence a domínio inativo | Bloquear abertura do WebView |
401 Unauthorized | INTEGRATION_TOKEN_EXPIRED | Token de 3 min expirou durante navegação | Re-solicitar token via background fetch |