# SigMe API - Guia Técnico para Clientes e Integradores

## Objetivo

Este documento apresenta a base técnica da SigMe API para empresas que irão consumir, integrar, distribuir ou operar soluções sobre a plataforma.

O foco é o contrato público de integração:

- autenticação e autorização
- isolamento multiempresa
- catálogos e payloads estáveis
- padrões de requisição e resposta
- canais HTTP e WebSocket publicados pela API
- considerações operacionais para ambientes corporativos

Este documento não substitui a especificação OpenAPI. Use-o como visão técnica de onboarding; use o OpenAPI como referência de endpoints, parâmetros e schemas.

## Visão Geral da Plataforma

A SigMe API é uma API REST/JSON para monitoramento, gestão de ativos, usuários, empresas, dispositivos, instalações e atualizações em tempo real.

Características relevantes para integração:

- API REST com payloads JSON
- documentação OpenAPI/Swagger publicada pela própria aplicação
- autenticação por JWT Bearer e por API Key
- validação de payloads na entrada da API
- isolamento multiempresa por empresa autenticada
- telemetria e posição disponíveis via endpoints HTTP e WebSocket documentados

O contrato público de integração é a API HTTP/WebSocket e seus payloads. Processamentos assíncronos, quando existirem, continuam encapsulados por esses endpoints e estados retornados.

## Ambientes e Exposição

Ambientes e rotas públicas:

- ambiente local: `http://localhost:3000`
- ambiente produtivo: `https://api.knsh.com.br`
- portal de documentação: `/`
- Swagger UI: `/swagger`
- OpenAPI JSON: `/swagger/json`
- guia técnico HTML: `/docs/guide`
- guia técnico Markdown: `/docs/guide.md`

Recomendações de onboarding:

- comece pelo portal de documentação
- descubra endpoints e schemas pelo OpenAPI
- use o Swagger UI para autenticação manual e testes exploratórios
- use este guia para alinhar autenticação, tenancy e operação

## Autenticação e Autorização

### Mecanismos de Autenticação

A plataforma aceita dois mecanismos de autenticação:

| Mecanismo      | Header                          | Caso de uso                             |
| -------------- | ------------------------------- | --------------------------------------- |
| **JWT Bearer** | `Authorization: Bearer <token>` | Usuários autenticados via portal ou app |
| **API Key**    | `X-Api-Key: <chave>`            | Integrações server-to-server sem sessão |

Regras de precedência:

- Se `Authorization: Bearer` estiver presente, a autenticação é JWT. Token inválido retorna `401` imediatamente, sem tentar API Key.
- Se apenas `X-Api-Key` estiver presente, a autenticação é por API Key.
- Se nenhum dos dois estiver presente, a API retorna `401 Unauthorized`.
- Endpoints públicos não exigem autenticação.

### Autenticação JWT

Formato esperado:

```http
Authorization: Bearer <jwt>
```

Fluxos públicos:

- `POST /auth/register` — cria usuário e credencial de autenticação, vinculando à empresa informada (`companyID`, `roleID`, `typeID`)
- `POST /auth/login`
- `POST /auth/forgot-password`
- `POST /auth/reset-password`

Fluxos autenticados:

- `PATCH /auth/password`
- `GET /auth/refresh-token`

### Autorização

A autorização usa o papel e as permissões do usuário autenticado.

Conceitos relevantes:

- usuários de empresa operam apenas no escopo da própria empresa
- permissões são organizadas por módulo e ação (leitura, criação, atualização, exclusão e ações específicas)
- autenticação por API Key identifica a empresa e restringe o acesso aos dados dela, sem papéis ou permissões granulares de usuário

Formato de `permissions` no contexto autenticado:

- mapa por módulo com arrays de ações permitidas
- exemplo: `{"company": ["read", "update"], "user": ["read"], "color": ["read"]}`

Autenticação válida não implica acesso global. O token define o escopo e as permissões efetivas da operação.

## Autenticação por API Key para Integrações

Integrações server-to-server podem autenticar apenas com `X-Api-Key`, sem login, JWT ou refresh.

### Como funciona

```http
X-Api-Key: sigme_<chave_da_empresa>
```

- A chave identifica a empresa e restringe o acesso aos dados dela
- Não é necessário enviar `Authorization: Bearer`
- A chave plana é exibida apenas no momento da geração; depois disso não é recuperável
- A plataforma não armazena a chave em texto plano

### Verificar conectividade

```http
GET /integration/me
X-Api-Key: sigme_<chave>
```

Resposta `200`:

```json
{ "companyId": 7, "companyName": "Transportadora Exemplo Ltda" }
```

Use esse endpoint no startup da integração para validar a configuração.

### Endpoints acessíveis via API Key

A API Key cobre monitoramento e integração. Endpoints de gestão (usuários, veículos, dispositivos etc.) exigem JWT com permissões.

| Endpoint                                   | Descrição                                                                             |
| ------------------------------------------ | ------------------------------------------------------------------------------------- |
| `GET /integration/me`                      | Verificar conectividade e obter dados da empresa                                      |
| `GET /integration/events`                  | Listar alertas operacionais da empresa (paginado; `sinceId` para polling incremental) |
| `GET /integration/event-types`             | Catálogo de tipos de alerta                                                           |
| `GET /integration/devices/:deviceId/trail` | Histórico de posições de um dispositivo pelo ID externo                               |
| `GET /map/positions`                       | Visão geral das posições dos veículos                                                 |
| `GET /map/vehicles/:vehicleId`             | Posição detalhada e trilha histórica do veículo                                       |
| WebSocket `/location-stream`               | Atualização de posição em tempo real                                                  |

Tentativas de acessar endpoints de gestão com API Key retornam `403 Forbidden`.

### Escopo da API Key

Com API Key, a integração opera no escopo da empresa identificada pela chave. Operações que dependem de usuário, papel ou permissões granulares exigem JWT.

### Segurança e boas práticas

- Nunca expor a API Key em frontend ou repositório público
- Armazenar em variáveis de ambiente ou gerenciadores de secrets
- Rotacionar periodicamente em produção
- Em caso de comprometimento, revogar e regenerar pelo painel administrativo

## Modelo Multiempresa

A SigMe API é multi-tenant.

Regras relevantes:

- empresas clientes acessam apenas os próprios recursos
- recursos administrativos globais permanecem restritos a contextos com permissão apropriada
- clientes não devem inferir ou forçar escopo apenas por IDs enviados no request
- a API é a fonte de verdade para escopo, posse e autorização

Consequência prática:

- desenhe integrações com segregação rígida de dados por empresa
- listagens, detalhes, atualizações e eventos devem respeitar o tenant do token utilizado

## Padrões de Payload e Validação

A API valida e normaliza campos na entrada. Isso importa porque valores como documento, email e telefone podem ser persistidos em formato canônico.

Exemplos estabilizados:

- CPF, CNPJ, email e telefone são normalizados
- CEP e UF são normalizados nos contratos de endereço
- em criações, `statusID` pode ser omitido quando o recurso nasce ativo por padrão
- o documento da empresa é persistido em formato canônico

Recomendações:

- envie dados semanticamente corretos, mesmo quando a API normalize parte do conteúdo
- não dependa de comportamentos não documentados fora do OpenAPI e deste guia

## Convenção de Timestamps

Para datas e horas serializadas pela API:

- timezone padrão: `America/Sao_Paulo`
- formato de saída: ISO 8601 com offset explícito
- exemplo: `2026-04-07T12:35:00.000-03:00`

### Entrada (`startDate` / `endDate` em query)

- com offset ou `Z` → instante absoluto (`2026-07-24T16:41:00.000Z`, `2026-07-24T13:41:00.000-03:00`)
- **sem timezone** (ex.: `2026-07-24T13:41`) → horário de parede em `America/Sao_Paulo`
- clientes web no Brasil podem enviar o valor do input local sem converter para UTC

Implicações:

- não assumir saída em UTC com sufixo `Z`
- interpretar sempre o offset retornado pela API
- evitar parsing baseado em substring fixa de timezone

Quando um campo temporal estiver presente em payloads de leitura operacional, trate-o como horário já formatado no timezone padrão da API, salvo documentação específica em contrário.

## Catálogos e Contratos Canônicos

Alguns catálogos operacionais devem ser tratados como contratos estáveis.

### StatusType

Semântica canônica de `status-type`:

- `1 = active`
- `2 = inactive`
- `3 = pending`

Implicações:

- integrações podem tratar esses estados como catálogo conhecido
- a API continua sendo a fonte oficial para leitura do catálogo

### UserRole

Papéis canônicos de sistema: `principal_admin`, `admin` e `user`. Também existem papéis custom por organização.

Modelo de autorização:

- o JWT carrega `organizationID`, `organizationType` (`OWNER` \| `TRACKING` \| `CLIENT`), `membershipID`, `isPrincipalAdmin` e `scopeOrganizationIds`
- TRACKING pode filtrar leituras operacionais com `organizationID` exclusivo (um cliente directory por vez; default = frota da home; nunca mistura). Quando o cliente é selecionado, `driver` é omitido. Relatório de atividade de usuários não aceita este filtro
- `OWNER` opera catálogos/plataforma; não lista automaticamente veículos, motoristas ou mapa de tenants
- papéis de sistema são globais; o template de `admin`/`user` só pode ser alterado pelo operador da plataforma (`OWNER`)
- papéis custom pertencem a uma organização; `admin`/`principal_admin` da organização podem criar, renomear, editar o template e remover quando não estiver em uso
- o template custom é limitado ao teto do `principal_admin` do tipo da org (`TRACKING` ou `CLIENT`); grants acima desse teto são descartados na gravação
- nomes `principal_admin` / `admin` / `user` são reservados (`master` não é mais emitido)
- permissões efetivas do usuário ficam no vínculo (membership); `permissionsOverride=true` indica override manual
- `principal_admin` recebe o template do tipo da org no JWT (não o catálogo completo); o acesso a registros continua limitado ao escopo resolvido
- alterações de permissão passam a valer no JWT após novo login ou refresh
- `GET /permissions/catalog` devolve `modules`, `actions`, `required` (conjunto base: `auth.login`, `device.read`, `self.read` e reads silenciosos de catálogo: `manufacturer`, `device-type`, `status-type`, `user-type`, `color`, `default-command`) e `ceiling` (mapa máximo concedível). OWNER vê o catálogo completo; TRACKING/CLIENT vêem só o teto do `principal_admin` (sem módulos de catálogo na matriz). Grants base devem ser tratados como travados em UIs de gestão. Tokens/cargos antigos com `history` são expandidos para `tracking-report:report` e `speeding-report:report`
- o módulo `event` autoriza a listagem de eventos (`GET /event`); `notifications` cobre push e histórico de notificações

Endpoints relevantes:

| Método  | Caminho                                  | Descrição                                               |
| ------- | ---------------------------------------- | ------------------------------------------------------- |
| `GET`   | `/permissions/catalog`                   | Vocabulário concedível (`modules`, `actions`, `required`, `ceiling`) |
| `GET`   | `/user-role`                             | Lista papéis no escopo do autenticado                   |
| `POST`  | `/user-role`                             | Cria papel custom com template                          |
| `PUT`   | `/user-role/:id/permissions`             | Atualiza template (`propagate` opcional)                |
| `PATCH` | `/user-company-register/:id/permissions` | Override ou `reset` para o template da role             |
| `PUT`   | `/default-permissions/:roleName`         | Template de papel de sistema (somente `master`)         |

Implicações:

- trate os papéis canônicos como catálogo estável
- a autorização efetiva continua dependente do token e das permissões emitidas
- em gestão de acesso, prefira `roleID` e `/user-role/:id/permissions`

### UserType

O catálogo `user-type` classifica usuários operacionalmente e deve ser consumido pela API como fonte de verdade.

Implicações:

- trate esses tipos como catálogo estável
- sincronize pela API quando precisar de cópia local explícita

## Superfícies Públicas Principais

Principais áreas da superfície pública:

- `auth`: autenticação JWT, refresh e escopo do usuário autenticado
- `integration`: conectividade e autenticação server-to-server via API Key
- `company`: gestão empresarial
- `user`: identidade e gestão de usuários por empresa
- `address`: endereço com normalização de CEP e UF
- `status-type`, `user-role` e `user-type`: catálogos operacionais
- `preferences` (preferências de notificação do usuário), `parameters` (parâmetros do sistema por empresa), `default-permissions` e `permissions/catalog`: preferências/eventos e vocabulário/templates de permissão
- `activation-key`: geração, consulta e consumo de chaves operacionais
- `device`: cadastro de dispositivos e leitura operacional de telemetria
- `device-company-register` e `fcm-token`: vínculos empresa/dispositivo e notificação push
- `sim-card`: inventário de chips por empresa e vínculo opcional a dispositivos
- `vehicle`: veículos e catálogos de marca, modelo e tipo
- `vehicle-group`: grupos/frotas de veículos por empresa (ex.: Frota A, Frota B)
- `geofence`: cercas virtuais (polígono ou círculo) com vínculos a empresa, veículos ou grupos
- `event`, `notification` e `command`: leitura operacional, envio e histórico
- `user-activity`: auditoria de ações autenticadas
  - `GET /user-activity` aceita ordenação opcional via `sortBy` (`createdAt` | `action` | `module` | `method` | `endpoint` | `statusCode` | `ipAddress` | `user`) e `sortDir` (`asc` | `desc`); default `createdAt desc`
- `overview`: visão consolidada da home operacional
- `map`: posições, detalhe de ativos e trilha
- `driver`: motoristas, CNH, escala de trabalho e vínculo com veículos
- `installation`: vínculo dispositivo ↔ veículo e catálogo de tipos de instalação
- `speeding-report`: análise de velocidade da trajetória contra limites de via

Consulte sempre o OpenAPI para a lista completa e atualizada de endpoints.

### Driver

O módulo `driver` gerencia motoristas como um tipo especializado de usuário (`typeID=3`),
com dados de CNH, exames periódicos, escala semanal, atribuição de veículos e identificação RFID.

**Segregação User/Driver:**

- `GET /user` **não** retorna motoristas (filtra `typeID !== 3`)
- `GET /driver` retorna **apenas** motoristas (`typeID === 3`)
- Motoristas são criados exclusivamente via `POST /driver`, não via `/user`

**Vínculo de motoristas à empresa (gestão de cadastro):**

Motoristas são vinculados a uma empresa no momento da criação (`POST /driver`).
Para vincular/desvincular motoristas existentes a uma empresa, a plataforma expõe:

- `GET /company/:id/drivers` — lista os vínculos ativos de motoristas da empresa (dados do usuário, CNH, exames e veículos)
- `POST /company/:id/drivers` — `{ "userID": 12 }` cria/reativa o vínculo (typeID=3, role padrão, sem permissões e sem `Auth`)
- `DELETE /company/:id/drivers/:userID` — inativa o vínculo do motorista na empresa

Esses endpoints preservam o modelo do motorista: **sem** modelo de permissões, **sem** credenciais
de acesso e **sem** inativar vínculos do mesmo motorista em outras empresas.

**Listagens com contadores:**

- `GET /company` retorna em `_count` os totais de `deviceRegisters`, `users` (vínculos ativos) e `vehicles` (vínculos ativos)
- `GET /driver` retorna `driverExams` (por vínculo) e `vehicles` (vínculos ativos) para montar status de validade de exames e contagem de veículos
- `GET /vehicle` retorna `registers` ativos com o `user` (nome) quando o vínculo pertence a um motorista

**Vínculo duplo (admin + motorista):**

Uma mesma pessoa pode ter dois vínculos na mesma empresa:
um como usuário administrativo e outro como motorista.
A API trata isso como dois registros `UserCompanyRegister` distintos
(`@@unique([userID, companyID, typeID])`).

**Motorista não tem acesso à plataforma:**

O registro de motorista não cria credenciais de autenticação (`Auth`).
O e-mail é opcional para motoristas.

**CNH (DriverLicense):**

| Campo              | Descrição                    |
| ------------------ | ---------------------------- |
| `licenseNumber`    | Número da CNH (único)        |
| `category`         | Categoria: A, B, AB, C, D, E |
| `firstLicenseDate` | Data da primeira habilitação |
| `expirationDate`   | Data de validade             |
| `observations`     | Observações (opcional)       |

**Escala de trabalho (DriverSchedule):**

Escala semanal recorrente com dias da semana (0=Dom, 6=Sáb) e horários.

**Exames periódicos (DriverExam):**

Cada motorista possui registro de exames periódicos vinculados ao seu cadastro na empresa.
Os tipos de exame disponíveis são configurados por empresa via `GET/PATCH /parameters`
(chave `exams`). Os exames padrão são:

| Tipo                         | Descrição                 |
| ---------------------------- | ------------------------- |
| `toxicology`                 | Toxicológico              |
| `occupational_clinical_exam` | Exame Clínico Ocupacional |
| `visual_acuity`              | Acuidade Visual           |
| `tonal_audiometry`           | Audiometria Tonal         |
| `blood_count`                | Hemograma                 |

Cada registro possui `examType`, `lastExamDate` (data do último exame) e `observations` opcional.
Os exames são armazenados como array em `driverExams` no detalhe do motorista.

**Validade dos exames:**

A validade de cada exame é configurada por empresa nos parâmetros do sistema
(`GET/PATCH /parameters`, chave `exams`) em **dias**, contada a partir de `lastExamDate`.
Cada item de `exams` possui `type`, `enabled` e `validityDays` (opcional; quando ausente,
é usado o padrão do tipo). Padrões:

| Tipo                         | Validade padrão (dias) |
| ---------------------------- | ---------------------- |
| `toxicology`                 | 900                    |
| `occupational_clinical_exam` | 365                    |
| `visual_acuity`              | 365                    |
| `tonal_audiometry`           | 365                    |
| `blood_count`                | 365                    |

Exemplo de `PATCH /parameters`:

```json
{
  "exams": [
    { "type": "toxicology", "enabled": true, "validityDays": 900 },
    { "type": "blood_count", "enabled": false }
  ]
}
```

Quando `enabled` é `false`, o exame não é monitorado no status da tabela de motoristas.
Um exame é considerado **vencido** quando `lastExamDate + validityDays` é anterior à data atual.

**Vínculo com veículos (VehicleRegister):**

Motoristas podem ser vinculados a múltiplos veículos e vice-versa.
Cada vínculo possui:

| Campo            | Descrição                                 |
| ---------------- | ----------------------------------------- |
| `startDate`      | Data de início do vínculo (padrão: agora) |
| `endDate`        | Data de encerramento real (null = ativo)  |
| `plannedEndDate` | Data prevista de expiração (opcional)     |
| `statusID`       | Status do vínculo                         |

Quando `plannedEndDate` é informado, o sistema notifica os administradores
da empresa nos thresholds de 30, 15, 7 e 1 dias antes do vencimento.
O tipo de notificação é `driver_assignment_expiry` (categoria `system`).

**RFID do motorista:**

Cada motorista pode ter um `rfidCardId` cadastrado por empresa (campo em `UserCompanyRegister`).
O ID do cartão RFID é utilizado para validar o login do motorista em campo.
O endpoint de validação `GET /driver/rfid-validation` compara o `iddrv`
recebido nos pacotes de telemetria com o RFID cadastrado para o motorista
atribuído ao veículo do dispositivo.

**Alertas de vencimento da CNH:**

Um job diário (08:00) verifica CNHs com vencimento em 30, 15, 7 e 1 dias
e notifica os administradores da empresa via push (FCM).

O tipo de notificação é `driver_license_expiry` (categoria `system`).

**Endpoints principais:**

| Método  | Rota                                              | Descrição                                             |
| ------- | ------------------------------------------------- | ----------------------------------------------------- |
| `POST`  | `/driver`                                         | Criar motorista (typeID=3 fixo, sem Auth)             |
| `GET`   | `/driver`                                         | Listar motoristas (paginado, tenant)                  |
| `GET`   | `/driver/:id`                                     | Detalhes do motorista (CNH, escala, exames, veículos) |
| `PATCH` | `/driver/:id`                                     | Atualizar dados básicos                               |
| `POST`  | `/driver/:id/license`                             | Criar CNH                                             |
| `PATCH` | `/driver/:id/license`                             | Atualizar CNH                                         |
| `POST`  | `/driver/:id/license/delete`                      | Excluir CNH                                           |
| `GET`   | `/driver/:id/schedule`                            | Buscar escala                                         |
| `PUT`   | `/driver/:id/schedule`                            | Substituir escala (batch)                             |
| `GET`   | `/driver/:id/exams`                               | Listar exames periódicos                              |
| `PUT`   | `/driver/:id/exams`                               | Atualizar exames (batch upsert)                       |
| `GET`   | `/driver/:id/vehicles`                            | Veículos do motorista (inclui `plannedEndDate`)       |
| `POST`  | `/driver/:id/assign-vehicle`                      | Vincular veículo (`endDate` opcional = prazo)         |
| `POST`  | `/driver/:id/unassign-vehicle/:vehicleRegisterID` | Desvincular veículo                                   |
| `GET`   | `/driver/:id/rfid`                                | Consultar RFID card ID                                |
| `PATCH` | `/driver/:id/rfid`                                | Atualizar RFID card ID                                |
| `GET`   | `/driver/rfid-validation?deviceId=&iddrv=`        | Validar correspondência RFID × motorista              |

A CNH (`driverLicenses`), escala (`driverSchedules`) e exames (`driverExams`)
são retornadas embutidas no payload de `GET /driver/:id`. Os veículos (`vehicles`)
incluem `plannedEndDate` quando definido.

**Validação RFID:**

`GET /driver/rfid-validation?deviceId={deviceId}&iddrv={iddrv}`

Retorna:

```json
{
  "matched": true,
  "driver": { "id": 1, "name": "João" },
  "assignedDriver": { "id": 1, "name": "João" }
}
```

- `matched`: `true` se o `iddrv` coincide com o `rfidCardId` de um motorista atribuído ao veículo
- `driver`: motorista identificado pelo RFID (null se não houver match)
- `assignedDriver`: primeiro motorista atribuído ao veículo (para referência, mesmo sem match)

O fluxo de resolução é: `deviceId → Installation → Vehicle → VehicleRegister (ativo) → UserCompanyRegister.rfidCardId`.

## Overview Operacional

O módulo `overview` expõe a visão consolidada da home operacional.

Pontos relevantes:

- o endpoint aceita período explícito para telemetria agregada
- TRACKING pode passar `organizationID` de um cliente directory (escopo exclusivo, nunca mistura com a home)
- campos temporais públicos seguem a convenção de timestamp da API (`America/Sao_Paulo`)
- `generatedAt`, `period.startDate`, `period.endDate` e campos como `happenedAt` são ISO 8601 com offset
- a timeline e a distribuição de tipos de pacote do período representam contagem de registros válidos no intervalo consultado
- indicadores de snapshot representam o estado mais recente conhecido dos dispositivos, separados da leitura histórica do período

## Processamento Assíncrono

Alguns fluxos, como comandos, notificações e exportações, podem concluir de forma assíncrona.

Para o integrador:

- acompanhe o resultado pelos endpoints e estados públicos da API
- use o status/histórico documentados no OpenAPI

## Dispositivos

O módulo `device` cobre o cadastro de dispositivos e leituras operacionais de telemetria.

Pontos relevantes:

- a listagem de dispositivos respeita o escopo da empresa autenticada
- TRACKING lista devices da home por padrão; `organizationID` de um cliente directory substitui o escopo em `GET /device` (nunca mistura). Chips SIM continuam só da home
- `GET /device` e `GET /device/by-id/:id` incluem `packetData` com última telemetria processada (GPS, sinal, bateria e `isOnline`)
- limiares de online: tracker 5 min; demais tipos 30 min
- criação e atualização de vínculo com empresa validam a permissão sobre a empresa informada; TRACKING pode atribuir à home ou a uma filha directory
- `PATCH /device/:id` com troca de `companyID` e `POST` assign/unassign encerram a instalação ativa (`Installation.endedAt` + status inactive)
- dispositivos desconhecidos são emissores detectados na telemetria que ainda não possuem cadastro correspondente
- identificadores externos de dispositivo são tratados como case-insensitive na leitura operacional
- `GET /device/last-positions` (últimas posições conhecidas) e `GET /device/unknown` (emissores sem cadastro) são leituras voltadas a consumidores de telemetria (permissão `consumer:read`)

### Rotas, trilhas e relatórios

- `GET /device/tracking-report` retorna pacotes de telemetria para diagnóstico; exige permissão `tracking-report:report`
  - aceita `organizationID` exclusivo (TRACKING); `driver` é omitido quando o cliente é selecionado
  - mescla violações de cerca virtual (`GeofenceViolationLog`) como pontos sintéticos `packetTypeCode: 200` / `GEOFENCE_VIOLATION` e `201` / `GEOFENCE_SPEED_LIMIT` (badge)
  - quando há pacote **routine** do mesmo `deviceId` na janela de ±2s, a violação **substitui** esse pacote e herda timestamp, coords, speed e demais campos do pacote de referência (incluindo o que `packet-details` precisa); sem routine, ainda herda telemetria do pacote mais próximo na janela sem remover outros highlights
  - se o cliente filtrar `packetTypes`, a API ainda carrega os pacotes de referência para o merge e só então aplica o filtro no resultado (para `200`/`201` não perder velocidade)
  - filtro `packetTypes`: se vazio, inclui pacotes + violações; se preenchido, violações só quando `200` ou `201` estiverem na lista
  - informe exatamente um de `deviceId` (id externo) ou `vehicleId`
  - com `vehicleId`, cada pacote é recortado à vigência `[createdAt, endedAt)` das instalações daquele veículo (histórico de um device após a troca não permanece no veículo antigo)
  - paginação via `page`/`limit`, ou `all=true` para o período completo
  - ordenação opcional via `sortBy` (`timestamp` | `deviceId` | `lat` | `lng` | `speed` | `moving` | `packetType`) e `sortDir` (`asc` | `desc`); default `timestamp asc`
  - filtro opcional `packetTypes` com códigos de tipo de pacote separados por vírgula (catálogo 0–34 + servidor 200–255, ex.: `packetTypes=3,4,200`); omitido = todos os tipos
  - cada linha traz campos resumidos (`timestamp`, coords, `speed`, `moving`, `packetType`), `dataSummary` e `data` completo
  - `lightweight=true` (default `false`) omite `dataSummary` e `data` da resposta — use para tabelas/mapas de relatório e busque o pacote individual via `GET /device/packet-details` sob demanda
  - cada linha inclui `driver` (motorista vinculado ao veículo na data/hora do pacote, conforme o período do vínculo em `VehicleRegister`); `null` quando não há veículo/motorista identificado no instante
  - `moving` segue a mesma regra do mapa: `commomdt.movdt > 0` **ou** `spdgps > 0` (km/h) — alguns firmwares deixam `movdt` em 0 mesmo em movimento
- `GET /device/packet-details` retorna o pacote bruto completo (`data`) de um `deviceId` + `timestamp` específico (chave primária de `device_packets`); exige `tracking-report:report` **ou** `speeding-report:report`
  - parâmetros: `deviceId` (id externo) e `timestamp` (ISO 8601, o mesmo valor devolvido nas linhas de `tracking-report`/`speeding-report`)
  - retorna 404 se o dispositivo não pertencer à empresa do usuário (MASTER tem escopo global) ou se não houver pacote exatamente nesse instante
  - pensado para modais de detalhe de rota: buscar sob demanda ao clicar num marcador, em vez de embutir o pacote completo em cada linha da listagem
  - violações de cerca (`packetTypeCode: 200`/`201`) mescladas com replace+inherit usam o `deviceId`/`timestamp` do pacote de telemetria que gerou o evento — `packet-details` resolve esse pacote de referência normalmente
- `GET /device/route/:deviceId` aceita `cleanStops` (default `true`) para reduzir oscilação GPS enquanto o ativo está parado
  - `vehicleId` opcional recorta a rota às janelas de instalação `[createdAt, endedAt)` daquele veículo para o dispositivo; omitido = linha do tempo completa do device
  - mescla violações de cerca (`GeofenceViolationLog`) no período com a mesma regra de substituição do pacote routine + herança de velocidade
- `GET /map/vehicles/:vehicleId` aceita `cleanStops` (default `true`) na trilha retornada
- `GET /map/vehicles/:vehicleId` aceita `startDate`/`endDate` para delimitar a janela de telemetria da trilha (e da cadeia de instalações)
  - quando informados, a janela explícita prevalece sobre o default de 24h / lookback de `sinceIgnition`
  - a resposta inclui `trips[]` com trajetos IGN. ON → IGN. OFF na janela (`startedAt`, `endedAt` ou `null`, `open`); OFF órfão é ignorado; ON sem OFF gera trajeto aberto (`endedAt: null`)
  - a trilha continua limitada por `trailLimit` (máx. 1000) como rede de segurança
- `GET /map/vehicles/:vehicleId` aceita `trailMode` (`points` | `sinceIgnition`, default `points`) quando `startDate`/`endDate` são omitidos
  - em `points`, a trilha usa a janela padrão de pontos
  - em `sinceIgnition`, a trilha começa no último evento de ignição ligada encontrado na janela suportada; a resposta inclui `trailMode` efetivo e `ignitionOnAt` (ISO ou `null` quando houver fallback)
  - sem `deviceIds`, a trilha considera a cadeia de instalações do veículo na janela consultada, inclusive dispositivos já substituídos; cada ponto é recortado à vigência `[createdAt, endedAt)` da instalação naquele veículo (pacotes após uma troca de veículo não permanecem no veículo antigo); a posição ao vivo continua baseada apenas em instalações ativas
  - a trilha também mescla violações de cerca virtual (`packetTypeCode: 200`/`201`) na janela consultada, com a mesma regra replace+inherit
- pontos da trilha podem incluir `packetType` / `packetTypeCode` quando disponíveis
- o WebSocket `subscribe-vehicle` aceita o mesmo `trailMode` e os mesmos `startDate`/`endDate` (a trilha histórica continua vindo do HTTP; o WS só anexa atualizações ao vivo)
- `GET /speeding-report` analisa a velocidade da trajetória contra os limites de via (PostGIS/OSM); aceita `deviceIds` (vírgula), `vehicleId`, `startDate`/`endDate`, `all`, `page`/`limit`, `organizationID` (TRACKING exclusivo); exige `speeding-report:report`
  - com `vehicleId`, usa a cadeia histórica de instalações e recorta cada excesso à vigência `[createdAt, endedAt)` (não só a instalação ativa)
  - cada violação traz campos resumidos para tabela/mapa (`spdgps`, coords, `direction`, `roadMaxspeedKmh`, `excessKmh`, `highway`, `packetType`/`packetTypeCode`) — não inclui mais o pacote bruto nem os campos internos `matchSource`/`deviceType` (não consumidos pelo dashboard); use `GET /device/packet-details` para o pacote completo de um ponto específico
  - cada violação inclui `driver` (motorista vinculado ao veículo na data/hora da violação, conforme o período do vínculo)
- `GET /map/positions` e `GET /map/vehicles/:vehicleId` incluem `driver` (motorista atualmente vinculado ao veículo — vínculo ativo com `endDate = null`), exceto quando TRACKING consulta um cliente via `organizationID` (motorista omitido)
- `GET /event` (relatório de eventos) inclui `driver` por evento (motorista vinculado ao veículo na data/hora do evento), omitido no mesmo filtro de cliente TRACKING
  - o recorte operacional é a frota da organização (não quem recebeu o push): no instante do evento, o dispositivo estava instalado em um veículo daquele org (`[createdAt, endedAt)`); sem instalação vigente, cai no vínculo atual `DeviceCompanyRegister`
  - eventos `geofence_violation` / `geofence_speed_limit` expõem `position` a partir de `lat`/`lng` do payload, `parameters` sintéticos (direção+nome ou velocidade+limite) e `geofenceId` quando presente no payload
  - filtros opcionais: `type` (array/CSV de `notificationType`, ex.: `3,4,geofence_violation,geofence_speed_limit`), `vehicleId` (instalação vigente no instante do evento, não só a ativa), `driverId` (User id do motorista), `organizationID` (TRACKING: um cliente directory; default = home)
  - período (`startDate`/`endDate`) e ordenação por `eventTimestamp` usam a hora do pacote/evento (`data.timestamp`) com fallback para `createdAt`
  - ordenação opcional via `sortBy` (`eventTimestamp` | `createdAt` | `deviceID` | `notificationType`) e `sortDir` (`asc` | `desc`); default `eventTimestamp desc`

### Exportações e notificações

- exportações grandes: `POST /report-export` (job assíncrono)
  - tipos: `tracking`, `events`, `commands`, `user_activity`
  - permissão por tipo: `tracking` → `tracking-report:report`; `events` → `event:read`; `commands` → `command:read`; `user_activity` → `user-activity:read`
  - list/get/download exigem a permissão do tipo do job (listagem aceita qualquer uma delas)
  - formatos: `xlsx`, `csv`
  - para `tracking`, `filters.cleanStops` (default `true`) reduz oscilação em pontos parado antes de gerar o arquivo
  - status em `GET /report-export/:id`; download em `GET /report-export/:id/file`
  - ao concluir, a API emite notificação de sistema (`category=system`, `notificationType=report_export_ready`)
  - o create aceita `notification` (`readyTitle`/`readyBody`/`failedTitle`/`failedBody`) com placeholders `{fileName}`, `{rowCount}`, `{error}`
- `GET /notification` aceita filtro `category=event|system`
- `GET /notification/unread-counts` retorna contagens de não lidas
- `GET /notification/history` retorna o histórico de notificações do usuário autenticado
- `PATCH /notification/read-all` marca todas as notificações como lidas
- `PATCH /notification/:id/read` marca uma notificação específica como lida
- `POST /notification/notify` envia uma notificação de teste para o usuário autenticado
- `POST /fcm-token/register` registra o token de push do dispositivo; `DELETE /fcm-token` e `DELETE /fcm-token/all` removem tokens do usuário autenticado

## Chips (SimCard)

O módulo `sim-card` gerencia o inventário de chips SIM da empresa e o vínculo opcional com dispositivos.

Pontos relevantes:

- `GET /sim-card` lista o inventário da empresa TRACKING; `organizationID` de um cliente directory devolve só chips instalados nos devices daquele cliente; omitido = estoque + chips que não estão em devices de clientes directory
- campos obrigatórios: `iccid` (único), `phone`, `operatorID`, `brokerID` e `companyID` (resolvido pelo contexto autenticado; `master` deve informar `companyID`)
- `deviceID` opcional: `null` = estoque; preenchido = vinculado ao dispositivo
- um dispositivo pode ter vários chips ativos ao mesmo tempo
- `POST /sim-card/:id/attach` vincula sem desativar outros chips do device
- `POST /sim-card/:id/detach` devolve o chip ao estoque
- `GET /sim-card/stock` lista chips ativos sem dispositivo
- `GET /sim-card/available-for-device/:deviceID` lista estoque elegível para o device
- `POST /sim-card/assign-to-device` define o conjunto completo de chips do device (`{ deviceID, simCardIDs[] }`): IDs omitidos são desvinculados; lista vazia remove todos
- exclusão é lógica (`statusID = inactive`) e desvincula o dispositivo
- catálogos auxiliares: `sim-card-operator` e `sim-card-broker`
- o payload de device inclui `simCards[]` com os chips ativos vinculados
- usuários de empresa operam apenas chips da própria empresa; attach/assign exige device na mesma empresa do chip

Implicações práticas:

- trate chip como recurso de inventário independente do cadastro de device
- para vínculo device↔SIM, prefira `available-for-device` + `assign-to-device`
- sincronize operadora e broker pelos endpoints de catálogo antes de criar o chip

## Veículos

O módulo `vehicle` cobre o cadastro de veículos e os catálogos de marca, modelo e tipo.

Pontos relevantes:

- veículos são tenant-scoped pela empresa do contexto autenticado
- TRACKING lista a frota da home por padrão; `organizationID` de um cliente directory substitui o escopo em `GET /vehicle` e `GET /vehicle/all` (nunca mistura)
- criação aplica `statusID = active` por padrão quando o campo é omitido
- endpoints separados para `vehicle`, `vehicle-brand`, `vehicle-model` e `vehicle-type`
- a última posição do veículo depende dos dispositivos instalados e da telemetria consolidada pela API
- dispositivos disponíveis para instalação respeitam o escopo das empresas ativas vinculadas ao veículo

Implicações práticas:

- trate o veículo como recurso principal com escopo organizacional no token
- sincronize marcas, modelos e tipos como catálogos auxiliares
- `GET /vehicle/all` retorna todos os veículos da empresa sem paginação
- `GET /vehicle/:vehicleID/available-devices` lista os dispositivos elegíveis para instalação no veículo (mesmo escopo de empresa)
- `GET /vehicle/driver/:driverID` lista os veículos vinculados a um motorista
- consuma monitoramento pelos endpoints públicos da API

## Grupos de veículos (`vehicle-group`)

O módulo `vehicle-group` permite organizar a frota em grupos customizáveis por empresa (ex.: Frota A, Frota B), usados como alvo de cercas virtuais e futuras regras operacionais.

Pontos relevantes:

- recurso tenant-scoped; `master` informa `companyID` na criação
- membros são veículos com registro ativo na mesma empresa
- `PUT /vehicle-group/:id/members` substitui o conjunto completo de membros
- exclusão é lógica (`statusID = inactive`)
- permissão de módulo: `vehicle-group` (`create|read|update|delete`)

Endpoints:

| Método   | Rota                         | Descrição                          |
| -------- | ---------------------------- | ---------------------------------- |
| `POST`   | `/vehicle-group`             | Criar grupo (`name`, `vehicleIDs?`) |
| `GET`    | `/vehicle-group`             | Listar grupos do escopo            |
| `GET`    | `/vehicle-group/:id`         | Detalhe do grupo                   |
| `PATCH`  | `/vehicle-group/:id`         | Atualizar nome/descrição/membros   |
| `PUT`    | `/vehicle-group/:id/members` | Substituir membros                 |
| `DELETE` | `/vehicle-group/:id`         | Soft-delete                        |

## Cercas virtuais (`geofence`)

O módulo `geofence` gerencia cercas virtuais desenhadas no mapa (polígono ou círculo), com gatilhos de entrada/saída e escopo de aplicação.

Pontos relevantes:

- recurso tenant-scoped; `master` informa `companyID` na criação
- `geometry` aceita:
  - polígono: `{ "type": "polygon", "points": [{ "lat", "lng" }, ...] }` (≥ 3 pontos)
  - círculo: `{ "type": "circle", "center": { "lat", "lng" }, "radiusMeters": number }`
- `triggerEnter` / `triggerExit`: pelo menos um deve ser `true`, **ou** `speedLimitKmh` > 0 (cerca só de velocidade)
- `speedLimitKmh` opcional (km/h): enquanto o veículo está **dentro** e `spdgps` supera o limite, a API emite evento servidor **201** (`geofence_speed_limit`) em rising-edge
- escopo obrigatório: `applyToCompany: true` **ou** ≥1 `vehicleIDs` **ou** ≥1 `vehicleGroupIDs`
- veículos e grupos devem pertencer à mesma empresa da cerca
- `color` opcional (hex `#RRGGBB`) para renderização no cliente
- exclusão é lógica (`statusID = inactive`)
- permissão de módulo: `geofence` (`create|read|update|delete`)

**Presets de município (IBGE):** catálogo nacional em PostGIS (`roads-postgres.city_boundaries`), populado via `tools/roads/import-cities.sh`. Não é tenancy — qualquer usuário com `geofence:read` pode buscar e aplicar um polígono no editor; a cerca salva continua sendo a `Geofence` tenant (cópia JSONB).

| Método | Rota | Descrição |
| ------ | ---- | --------- |
| `GET` | `/geofence/presets/cities?q=&uf=&limit=` | Busca municípios por nome (prefixo, min. 2 chars) |
| `GET` | `/geofence/presets/cities/at?lat=&lng=` | Município que contém o ponto WGS84 |
| `GET` | `/geofence/presets/cities/:ibgeCode` | Geometria pronta `{ type: 'polygon', points }` (maior anel; `partsOmitted` se houver ilhas) |

Se o sidecar PostGIS não estiver configurado, esses endpoints respondem `503`. O CRUD manual de cercas não depende deles.

**Busca de endereços (Photon):** proxy autenticado para o geocoder OSM Brasil (`PHOTON_URL`). Serve para **centralizar o mapa** no editor de cercas (não aplica geometria). Resposta normalizada (não GeoJSON cru):

```json
[
  {
    "id": "W:171721829",
    "label": "Avenida Paulista — Paulista — Pernambuco — 53417-400",
    "name": "Avenida Paulista",
    "type": "street",
    "city": "Paulista",
    "state": "Pernambuco",
    "postcode": "53417-400",
    "lat": -7.9314398,
    "lng": -34.8882381,
    "extent": [-34.8899743, -7.9314988, -34.8863776, -7.9307134]
  }
]
```

| Método | Rota | Descrição |
| ------ | ---- | --------- |
| `GET` | `/geofence/presets/addresses?q=&limit=` | Busca ruas/lugares (min. 2 chars; `limit` 1–15, default 10) |

`extent` é `[minLng, minLat, maxLng, maxLat]` quando o Photon envia bounding box; caso contrário `null`. Sem `PHOTON_URL` ou sidecar indisponível → `503`.

**Alerta de violação (server-side):** após persistir telemetria, o `sigme_packet_consumer` publica GPS leve em `geofence_queue`. A API compara ponto×geometria (círculo/polígono), mantém `GeofencePresence` e:

- transição enter/exit (respeitando `triggerEnter`/`triggerExit`) → `GeofenceViolationLog` kind `boundary` + notificação `geofence_violation` (código sintético **200**)
- rising-edge de excesso de velocidade dentro da cerca (`speedLimitKmh`) → kind `speed_limit` + notificação `geofence_speed_limit` (código **201**)

Faixa de códigos: **1–199** eventos do rastreador; **200–255** eventos gerados pelo servidor.

```json
{
  "type": "geofence_violation",
  "payload": {
    "deviceId": "abc123",
    "title": "Geofence exit",
    "body": "Vehicle ABC1D23 exited Yard",
    "notificationType": "geofence_violation",
    "category": "event",
    "data": {
      "geofenceId": "1",
      "direction": "exit",
      "violationLogId": "99"
    }
  }
}
```

- **Não** reutiliza packtype firmware `15` / `FENCE` / `geofence_alert` nem `6` / `VEL. MAX`.
- Preferências: `"geofence_violation"` e `"geofence_speed_limit"` (defaults enabled).
- Destinatários: tokens FCM da empresa do dispositivo com a preferência ativa (`sigme_notifications`).
- Primeira observação de um par (cerca, veículo) só inicializa presença — sem push/log.
- Permanecer fora/dentro **não** reenvia boundary; permanecer acima do limite **não** reenvia speed (só rising-edge).
- No relatório de eventos (`GET /event`), `position` vem de `data.lat`/`data.lng`, `parameters` sintetiza direção/nome (boundary) ou speed/limit (201), e `geofenceId` é exposto para o cliente carregar a geometria via `GET /geofence/:id`.

**Relatório de rastreamento / mapa / rota:** violações entram como highlighted (`mapRole: badge`, códigos `200`/`201`). No mesmo `deviceId` + segundo, o pacote **routine** é substituído pela violação, que herda velocidade e demais telemetria do pacote. A mesma regra vale em `GET /device/tracking-report`, trilha de `GET /map/vehicles/:vehicleId` (e WS) e `GET /device/route/:deviceId`.

Endpoints:

| Método   | Rota                                   | Descrição                         |
| -------- | -------------------------------------- | --------------------------------- |
| `POST`   | `/geofence`                            | Criar cerca virtual               |
| `GET`    | `/geofence`                            | Listar cercas do escopo           |
| `GET`    | `/geofence/presets/addresses`          | Buscar endereços (Photon)         |
| `GET`    | `/geofence/presets/cities`             | Buscar presets IBGE por nome      |
| `GET`    | `/geofence/presets/cities/at`          | Município no ponto (lat/lng)      |
| `GET`    | `/geofence/presets/cities/:ibgeCode`   | Geometria do preset               |
| `GET`    | `/geofence/:id`                        | Detalhe da cerca                  |
| `PATCH`  | `/geofence/:id`                        | Atualizar cerca                   |
| `DELETE` | `/geofence/:id`                        | Soft-delete                       |

## Instalações

O módulo `installation` gerencia o vínculo entre dispositivos e veículos.

Pontos relevantes:

- a criação de instalação valida a posse do dispositivo (vínculo empresa/dispositivo ativo) e o escopo do veículo
- usuários de empresa só leem/mutam instalações de veículos da própria empresa; `master` opera sem essa restrição
- a cadeia de instalações do veículo determina quais dispositivos compõem a trilha histórica (inclusive dispositivos já substituídos) nas janelas cobertas
- catálogo auxiliar: `installation-type`

Endpoints:

| Método   | Rota                                | Descrição                                    |
| -------- | ----------------------------------- | -------------------------------------------- |
| `POST`   | `/installation`                     | Criar instalação (vincular device a veículo) |
| `PATCH`  | `/installation/remove-from-vehicle` | Remover dispositivo de um veículo            |
| `GET`    | `/installation`                     | Listar instalações (tenant)                  |
| `GET`    | `/installation/:id`                 | Detalhe de uma instalação                    |
| `PATCH`  | `/installation/:id`                 | Atualizar instalação                         |
| `DELETE` | `/installation/:id`                 | Excluir instalação                           |

## Permissões e Defaults

Perfis default de permissão usam o formato `module -> action[]`.

Consequências práticas:

- respostas autenticadas carregam permissões prontas para consumo por módulo e ação
- não infira permissões a partir de labels de UI; o contrato é o payload da API
- `color` é um módulo de permissão próprio; funcionalidades do catálogo de cores exigem grants explícitos em `color`
- `vehicle-group` e `geofence` são módulos próprios; ADMIN recebe CRUD e USER recebe `read` nos defaults
- mutações de catálogos de plataforma (`color`, marcas/modelos/tipos de veículo, fabricantes/tipos/modelos de dispositivo, operadoras/brokers de SIM, cargos/tipos de usuário e `default-command`) exigem contexto `master`, além das permissões de módulo
- papéis canônicos de sistema não podem ser renomeados ou removidos; papéis custom por empresa são gerenciáveis via API
- tipos canônicos de `user-type` não podem ser atualizados nem removidos via API

## Comandos Padrão (`default-command`)

Catálogo de templates para seleção ao enviar comando a um dispositivo.

Contrato:

- `GET /default-command` e `GET /default-command/:id` — leitura para quem possui `default-command:read`, `command:read` ou `command:send`
- query opcional `activeOnly=true` retorna apenas templates ativos
- `POST /default-command`, `PATCH /default-command/:id`, `DELETE /default-command/:id` — mutações restritas a `master`; delete inativa o registro
- payload de criação/atualização: `name`, `command`, `description?`, `statusID?`
- o envio operacional continua em `POST /command` com o campo `command` já resolvido

## Comandos

Envio e acompanhamento de comandos a dispositivos.

- `POST /command` — envia um comando ao dispositivo (`command:send`)
- `POST /command/cancel` — cancela comandos pendentes (`{ "ids": string[] }`)
- `GET /command` — lista os comandos enviados no escopo da empresa (`command:read`)
  - aceita `organizationID` exclusivo (TRACKING; default = home)
  - cada comando inclui `driver` (motorista vinculado ao veículo na data/hora do comando, conforme o período do vínculo), omitido no filtro de cliente
  - ordenação opcional via `sortBy` (`createdAt` | `command` | `deviceId`) e `sortDir` (`asc` | `desc`); default `createdAt desc`
- o template de seleção fica em `/default-command` (seção acima)

## Preferências de Notificação

O módulo `preferences` concentra as preferências de notificação por vínculo usuário/empresa (anteriormente chamadas de "parameters").

Pontos relevantes:

- `GET /preferences/default` — catálogo default de eventos de alerta (`settings:read`)
- `GET /preferences` — preferências do usuário autenticado (`preferences:read`); cai no default quando não há personalização
- `PATCH /preferences` — atualiza as preferências do usuário autenticado (`preferences:update`)
- o contrato usa `events` como coleção principal (frota da própria empresa)
- cada evento pode expor identificador, nome técnico, label, estado (`value`), categoria e visibilidade (`show`)
- operadores TRACKING podem enviar `clientAlerts`: mapa `{ "all": { "events": [...] }, "<companyIdDoCliente>": { "events": [...] } }`
- a chave `all` é o baseline compartilhado (clientes atuais e futuros); chaves numéricas só somam eventos extras — não removem o que está em `all`
- cliente sem chave numérica ainda herda `all`; sem `all` e sem chave do cliente, não há push TRACKING (opt-in)
- usuários CLIENT ignoram `clientAlerts` e usam só `events` da própria frota
- sincronize o catálogo pela API para evitar drift entre defaults e personalizações

## Parâmetros da Empresa

O módulo `parameters` guarda parâmetros do sistema por empresa (documento JSONB vinculado à empresa).

Pontos relevantes:

- `GET /parameters` — retorna os parâmetros da empresa do usuário autenticado (`parameters:read`); usuários `master` podem passar `?companyId=` para ler outra empresa
- `PATCH /parameters` — atualiza os parâmetros da empresa, mesclando as chaves de topo enviadas (`parameters:update`); `master` pode passar `?companyId=`
- a chave `exams` configura os exames periódicos dos motoristas: cada item possui `type`, `enabled` e `validityDays` (validade em dias; ver seção "Validade dos exames")
- o acesso é escopado à empresa do usuário autenticado; apenas `master` consegue visar outra empresa

## Integração em Tempo Real

A plataforma publica canais WebSocket para atualização operacional.

Pontos relevantes:

- autenticação do canal é obrigatória
- o gateway `/location-stream` aceita no handshake Socket.io:
  - JWT: `auth: { token: '<jwt>' }` ou header `Authorization: Bearer <jwt>`
  - API Key: `auth: { apiKey: '<chave>' }`
- payloads de localização e trilha são entregues já consolidados pela API

Recomendações:

- use realtime quando precisar de baixa latência ou atualização contínua
- use REST/OpenAPI como base de onboarding e integração inicial
- com API Key, conecte o WebSocket com `auth: { apiKey }` no handshake

## Tratamento de Erros

Clientes devem estar preparados para:

- `400 Bad Request` — payload inválido ou regra de domínio violada
- `401 Unauthorized` — autenticação inválida, ausente ou expirada
- `403 Forbidden` — operação fora da permissão ou escopo
- `404 Not Found` — recurso inexistente
- `409 Conflict` — violação de unicidade
- `5xx` — falha interna ou indisponibilidade temporária

Recomendações:

- retry apenas para falhas transitórias apropriadas
- não usar retry cego para `4xx`
- correlacionar request, tenant e resposta recebida

## Requisitos para Integração Corporativa

Considere desde o início:

- gestão segura de JWT e chaves de API
- separação entre homologação e produção
- observabilidade de chamadas, erros e latência
- rotação de credenciais
- gestão de escopo por empresa e por papel
- versionamento do cliente para payloads consumidos
- testes automatizados de contrato nos endpoints críticos

## Boas Práticas de Consumo

- use o OpenAPI como referência primária de endpoint, parâmetros e schema
- trate catálogos operacionais como contratos conhecidos e sincronizáveis pela API
- não acoplar integrações a comportamentos não documentados
- respeite o modelo multiempresa em todas as integrações
- isole credenciais por cliente, empresa ou ambiente
- revise periodicamente permissões e superfícies expostas

## Recomendação de Onboarding para Empresas Integradoras

1. validar acesso ao ambiente e à documentação OpenAPI
2. alinhar autenticação e credenciais técnicas
3. mapear o tenant e o escopo funcional da integração
4. integrar catálogos e cadastros-base
5. integrar fluxos operacionais e, por fim, realtime quando necessário
6. formalizar monitoramento, rotação de credenciais e testes de regressão

## Observação Final

Este guia deve evoluir com a API. Sempre que uma superfície pública relevante mudar, atualize a documentação com o contrato atual de consumo.