openapi: 3.0.3
info:
  title: Coplan Transporte Público API
  description: |
    Backend API — Linha, Percurso, PercursoPonto, Parada, Horário, Veículo, Alocação e Configuração.

    **Admin (Gerenciador Web):**
    - Mapa: GET + PUT replace em `/percursos/{id}/pontos`
    - Horários: operações por dia em `/percursos/{id}/horarios/dias` ou replace total em `/percursos/{id}/horarios`
    - Paradas: CRUD em `/percursos/{id}/paradas` e `/paradas/{id}`
    - Frota: CRUD em `/veiculos` e `/veiculos/{id}`
    - Escala operacional: alocações em `/veiculos/{id}/alocacoes`, `/percursos/{id}/alocacoes` e `/alocacoes/{id}`
    - Configuração: `/configuracoes` e flags de rastreamento em `/configuracoes/rastreamento`

    **Público (App cidadão):** namespace `/publico` — somente leitura, linhas/percursos ativos, grade com próxima partida e configurações de rastreamento.

    Header **cliente** obrigatório (slug do município). Alias: X-Cliente.

    **Erros:** respostas 4xx/5xx seguem `ApiErrorResponseDTO` com `mensagem` e `erros` opcional.
  version: 0.0.1-SNAPSHOT
  contact:
    name: API Support

servers:
  - url: https://hom.coplan.inf.br/transportepublico
    description: Servidor de Homologação
  - url: http://localhost:8080/transportepublico
    description: Servidor Local

tags:
  - name: Linhas
    description: CRUD de linhas de transporte público
  - name: Percursos
    description: CRUD de percursos de transporte público
  - name: PercursoPontos
    description: Pontos geográficos de percursos de transporte público
  - name: Paradas
    description: Paradas de embarque/desembarque vinculadas a percursos
  - name: Horarios (Admin)
    description: Horários de partida de percursos — API administrativa do Gerenciador Web
  - name: Veiculos
    description: CRUD administrativo da frota municipal
  - name: Alocacoes de veiculo
    description: Alocação operacional veículo–percurso por intervalo de tempo
  - name: Configuracoes
    description: Parâmetros funcionais do município (chave/valor) e flags de rastreamento
  - name: Consulta Publica
    description: API de consulta para o aplicativo do cidadão (somente leitura)
  - name: Health
    description: Verificação de saúde da aplicação

paths:
  /api/health:
    get:
      tags:
        - Health
      summary: Health check
      description: Verifica se a aplicação está em execução. Não requer header cliente.
      operationId: health
      responses:
        '200':
          description: Aplicação saudável
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: UP
                  application:
                    type: string
                    example: transportepublico

  /linhas:
    get:
      tags:
        - Linhas
      summary: Listar linhas
      description: Retorna todas as linhas cadastradas no município
      operationId: listarLinhas
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
      responses:
        '200':
          description: Lista de linhas
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/LinhaResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      tags:
        - Linhas
      summary: Criar linha
      description: Cadastra uma nova linha com código, nome e situação
      operationId: criarLinha
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinhaRequestDTO'
      responses:
        '201':
          description: Linha criada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinhaResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /linhas/{id}:
    get:
      tags:
        - Linhas
      summary: Buscar linha por ID
      operationId: buscarLinhaPorId
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: Linha encontrada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinhaResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      tags:
        - Linhas
      summary: Atualizar linha
      operationId: atualizarLinha
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinhaRequestDTO'
      responses:
        '200':
          description: Linha atualizada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinhaResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      tags:
        - Linhas
      summary: Excluir linha
      operationId: excluirLinha
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '204':
          description: Linha excluída
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /linhas/{linhaId}/percursos:
    get:
      tags:
        - Linhas
      summary: Listar percursos da linha
      description: Retorna os percursos vinculados à linha informada
      operationId: listarPercursosPorLinha
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - name: linhaId
          in: path
          required: true
          description: Identificador da linha
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Lista de percursos da linha
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PercursoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /percursos:
    get:
      tags:
        - Percursos
      summary: Listar percursos
      description: Retorna todos os percursos cadastrados no município
      operationId: listarPercursos
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
      responses:
        '200':
          description: Lista de percursos
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PercursoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      tags:
        - Percursos
      summary: Criar percurso
      description: Cadastra um novo percurso vinculado a uma linha
      operationId: criarPercurso
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PercursoRequestDTO'
      responses:
        '201':
          description: Percurso criado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PercursoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /percursos/{id}:
    get:
      tags:
        - Percursos
      summary: Buscar percurso por ID
      operationId: buscarPercursoPorId
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: Percurso encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PercursoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      tags:
        - Percursos
      summary: Atualizar percurso
      operationId: atualizarPercurso
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PercursoRequestDTO'
      responses:
        '200':
          description: Percurso atualizado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PercursoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      tags:
        - Percursos
      summary: Excluir percurso
      operationId: excluirPercurso
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '204':
          description: Percurso excluído
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /percursos/{percursoId}/pontos:
    get:
      tags:
        - PercursoPontos
      summary: Listar pontos do percurso
      description: Retorna os pontos do percurso ordenados por ordem ascendente. Fluxo oficial para carregar o mapa.
      operationId: listarPontosPorPercurso
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/PercursoIdPath'
      responses:
        '200':
          description: Lista de pontos do percurso
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PercursoPontoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      tags:
        - PercursoPontos
      summary: Substituir pontos do percurso (fluxo oficial do mapa)
      description: |
        Replace transacional da lista completa de pontos do percurso.

        - A lista enviada substitui integralmente os pontos existentes.
        - Array vazio `[]` remove todos os pontos.
        - A ordem de cada item é persistida literalmente.
        - Em caso de falha, nenhum ponto é salvo parcialmente.
      operationId: substituirPontos
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/PercursoIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/PercursoPontoRequestDTO'
      responses:
        '200':
          description: Lista de pontos após substituição, ordenada por ordem ASC
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PercursoPontoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      tags:
        - PercursoPontos
      summary: '[Temporário] Criar ponto do percurso'
      description: Uso temporário para testes. No fluxo do mapa, preferir PUT replace em `/percursos/{percursoId}/pontos`.
      operationId: criarPonto
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/PercursoIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PercursoPontoRequestDTO'
      responses:
        '201':
          description: Ponto criado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PercursoPontoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /pontos/{id}:
    get:
      tags:
        - PercursoPontos
      summary: '[Temporário] Buscar ponto por ID'
      description: Uso temporário para testes. No fluxo do mapa, preferir GET em `/percursos/{percursoId}/pontos`.
      operationId: buscarPontoPorId
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: Ponto encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PercursoPontoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      tags:
        - PercursoPontos
      summary: '[Temporário] Atualizar ponto'
      description: Uso temporário para testes. No fluxo do mapa, preferir PUT replace em `/percursos/{percursoId}/pontos`.
      operationId: atualizarPonto
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PercursoPontoRequestDTO'
      responses:
        '200':
          description: Ponto atualizado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PercursoPontoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      tags:
        - PercursoPontos
      summary: '[Temporário] Excluir ponto'
      description: Uso temporário para testes. No fluxo do mapa, preferir PUT replace em `/percursos/{percursoId}/pontos`.
      operationId: excluirPonto
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '204':
          description: Ponto excluído
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /percursos/{percursoId}/paradas:
    get:
      tags:
        - Paradas
      summary: Listar paradas do percurso
      description: Retorna as paradas do percurso ordenadas por ordem ascendente.
      operationId: listarParadasPorPercurso
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/PercursoIdPath'
      responses:
        '200':
          description: Lista de paradas do percurso
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ParadaResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      tags:
        - Paradas
      summary: Criar parada
      description: Cadastra uma parada vinculada ao percurso informado.
      operationId: criarParada
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/PercursoIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ParadaRequestDTO'
      responses:
        '201':
          description: Parada criada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ParadaResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /paradas/{id}:
    get:
      tags:
        - Paradas
      summary: Buscar parada por ID
      operationId: buscarParadaPorId
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: Parada encontrada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ParadaResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      tags:
        - Paradas
      summary: Atualizar parada
      operationId: atualizarParada
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ParadaRequestDTO'
      responses:
        '200':
          description: Parada atualizada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ParadaResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      tags:
        - Paradas
      summary: Excluir parada
      operationId: excluirParada
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '204':
          description: Parada excluída
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /percursos/{percursoId}/horarios:
    get:
      tags:
        - Horarios (Admin)
      summary: Listar horários do percurso
      description: Retorna todos os horários ordenados por dia da semana e horário.
      operationId: listarHorariosPorPercurso
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/PercursoIdPath'
      responses:
        '200':
          description: Lista de horários do percurso
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/HorarioResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      tags:
        - Horarios (Admin)
      summary: Substituir grade completa do percurso
      description: |
        Replace transacional de **todos** os horários do percurso.

        Use apenas quando o operador desejar substituir a programação inteira.
        Para editar um ou mais dias sem afetar os demais, use os endpoints `/horarios/dias`.
      operationId: substituirGradeCompleta
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/PercursoIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/HorarioRequestDTO'
      responses:
        '200':
          description: Lista de horários após substituição
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/HorarioResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /percursos/{percursoId}/horarios/dias/{diaSemana}:
    put:
      tags:
        - Horarios (Admin)
      summary: Substituir horários de um dia
      description: |
        Substitui os horários de partida de um único dia da semana.

        Os demais dias permanecem inalterados.
        Array vazio remove todos os horários daquele dia.
      operationId: substituirHorariosDoDia
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/PercursoIdPath'
        - name: diaSemana
          in: path
          required: true
          description: Dia da semana (1=segunda … 7=domingo)
          schema:
            type: integer
            minimum: 1
            maximum: 7
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HorarioDiaReplaceDTO'
      responses:
        '200':
          description: Lista completa de horários do percurso após a operação
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/HorarioResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      tags:
        - Horarios (Admin)
      summary: Excluir horários de um dia
      description: Remove todos os horários de partida de um único dia. Os demais dias permanecem inalterados.
      operationId: excluirHorariosDoDia
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/PercursoIdPath'
        - name: diaSemana
          in: path
          required: true
          description: Dia da semana (1=segunda … 7=domingo)
          schema:
            type: integer
            minimum: 1
            maximum: 7
      responses:
        '204':
          description: Horários do dia excluídos
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /percursos/{percursoId}/horarios/dias:
    put:
      tags:
        - Horarios (Admin)
      summary: Substituir horários de vários dias
      description: |
        Substitui os horários dos dias informados em uma única transação.

        Dias não listados permanecem inalterados.
        Ideal para cadastro em lote (ex.: segunda a sexta).
      operationId: substituirHorariosDosDias
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/PercursoIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HorarioMultiDiaReplaceDTO'
      responses:
        '200':
          description: Lista completa de horários do percurso após a operação
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/HorarioResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      tags:
        - Horarios (Admin)
      summary: Excluir horários de vários dias
      description: Remove todos os horários dos dias informados. Os demais dias permanecem inalterados.
      operationId: excluirHorariosDosDias
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/PercursoIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HorarioDiasExcluirDTO'
      responses:
        '204':
          description: Horários dos dias excluídos
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /veiculos:
    get:
      tags:
        - Veiculos
      summary: Listar veículos
      description: Retorna todos os veículos cadastrados no município, ordenados pelo número do veículo (`prefixo`).
      operationId: listarVeiculos
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
      responses:
        '200':
          description: Lista de veículos
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/VeiculoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      tags:
        - Veiculos
      summary: Criar veículo
      description: Cadastra um veículo da frota. O número do veículo (`prefixo`) é obrigatório e único.
      operationId: criarVeiculo
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VeiculoRequestDTO'
      responses:
        '201':
          description: Veículo criado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VeiculoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /veiculos/{id}:
    get:
      tags:
        - Veiculos
      summary: Buscar veículo por ID
      operationId: buscarVeiculoPorId
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: Veículo encontrado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VeiculoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      tags:
        - Veiculos
      summary: Atualizar veículo
      description: |
        Campos `ultimaLatitude`, `ultimaLongitude` e `ultimaLocalizacao` são somente leitura
        e não são alterados por este endpoint.
      operationId: atualizarVeiculo
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VeiculoRequestDTO'
      responses:
        '200':
          description: Veículo atualizado
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VeiculoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      tags:
        - Veiculos
      summary: Excluir veículo
      description: Exige veículo inativo e sem alocações futuras ou em andamento.
      operationId: excluirVeiculo
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '204':
          description: Veículo excluído
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /veiculos/{veiculoId}/alocacoes:
    get:
      tags:
        - Alocacoes de veiculo
      summary: Listar alocações do veículo
      description: Retorna as alocações operacionais do veículo ordenadas por início.
      operationId: listarAlocacoesPorVeiculo
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/VeiculoIdPath'
      responses:
        '200':
          description: Lista de alocações do veículo
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/VeiculoAlocacaoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    post:
      tags:
        - Alocacoes de veiculo
      summary: Criar alocação operacional
      description: |
        Define qual percurso o veículo executa no intervalo informado.
        Não substitui a grade horária (`tp_horario`).
      operationId: criarAlocacaoVeiculo
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/VeiculoIdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VeiculoAlocacaoRequestDTO'
      responses:
        '201':
          description: Alocação criada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VeiculoAlocacaoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /percursos/{percursoId}/alocacoes:
    get:
      tags:
        - Alocacoes de veiculo
      summary: Listar alocações do percurso
      description: |
        Retorna veículos alocados ao percurso (visão Linha → Percurso → Veículos).
        Usado pelo Gerenciador Web na escala operacional.
      operationId: listarAlocacoesPorPercurso
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/PercursoIdPath'
      responses:
        '200':
          description: Lista de alocações do percurso
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/VeiculoAlocacaoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /alocacoes/{id}:
    get:
      tags:
        - Alocacoes de veiculo
      summary: Buscar alocação por ID
      operationId: buscarAlocacaoPorId
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '200':
          description: Alocação encontrada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VeiculoAlocacaoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      tags:
        - Alocacoes de veiculo
      summary: Atualizar alocação operacional
      operationId: atualizarAlocacao
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VeiculoAlocacaoRequestDTO'
      responses:
        '200':
          description: Alocação atualizada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VeiculoAlocacaoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    delete:
      tags:
        - Alocacoes de veiculo
      summary: Excluir alocação operacional
      operationId: excluirAlocacao
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/IdPath'
      responses:
        '204':
          description: Alocação excluída
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /configuracoes:
    get:
      tags:
        - Configuracoes
      summary: Listar configurações
      description: Retorna todas as configurações cadastradas no município.
      operationId: listarConfiguracoes
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
      responses:
        '200':
          description: Lista de configurações
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ConfiguracaoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /configuracoes/chaves/{chave}:
    get:
      tags:
        - Configuracoes
      summary: Buscar configuração por chave
      operationId: buscarConfiguracaoPorChave
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/ChavePath'
      responses:
        '200':
          description: Configuração encontrada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfiguracaoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      tags:
        - Configuracoes
      summary: Atualizar configuração por chave
      operationId: atualizarConfiguracaoPorChave
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/ChavePath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConfiguracaoRequestDTO'
      responses:
        '200':
          description: Configuração atualizada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfiguracaoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /configuracoes/rastreamento:
    get:
      tags:
        - Configuracoes
      summary: Obter flags de rastreamento
      description: |
        Retorna `rastreamentoHabilitado` e `rastreamentoProvedorExterno` para o Gerenciador Web.

        - `rastreamentoHabilitado=false`: app não exibe ônibus no mapa.
        - `rastreamentoHabilitado=true` + `rastreamentoProvedorExterno=false`: backend próprio (futuro).
        - `rastreamentoHabilitado=true` + `rastreamentoProvedorExterno=true`: provedor externo (futuro).
      operationId: obterConfiguracaoRastreamento
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
      responses:
        '200':
          description: Flags de rastreamento
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfiguracaoRastreamentoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      tags:
        - Configuracoes
      summary: Atualizar flags de rastreamento
      operationId: atualizarConfiguracaoRastreamento
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConfiguracaoRastreamentoRequestDTO'
      responses:
        '200':
          description: Flags atualizadas
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfiguracaoRastreamentoResponseDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /publico/linhas:
    get:
      tags:
        - Consulta Publica
      summary: Listar linhas ativas
      operationId: listarLinhasAtivas
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
      responses:
        '200':
          description: Lista de linhas ativas
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PublicoLinhaDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /publico/linhas/{linhaId}/percursos:
    get:
      tags:
        - Consulta Publica
      summary: Listar percursos ativos da linha
      operationId: listarPercursosAtivos
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - name: linhaId
          in: path
          required: true
          description: Identificador da linha
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Lista de percursos ativos
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PublicoPercursoDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          description: Linha não encontrada ou inativa
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponseDTO'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /publico/percursos/{percursoId}/grade-horaria:
    get:
      tags:
        - Consulta Publica
      summary: Grade horária com próxima partida
      description: |
        Retorna a grade completa dos sete dias, hora atual do servidor e próxima partida cadastrada.

        Sem rastreamento: próxima partida é o próximo horário de saída do percurso na programação.
      operationId: obterGradeHoraria
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
        - $ref: '#/components/parameters/PercursoIdPath'
      responses:
        '200':
          description: Grade horária do percurso
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicoGradeHorariaDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          description: Percurso não encontrado ou inativo
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponseDTO'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /publico/configuracoes:
    get:
      tags:
        - Consulta Publica
      summary: Configurações públicas do município
      description: |
        Retorna flags de rastreamento para o aplicativo do cidadão.
        Somente leitura — não expõe chaves internas nem demais parâmetros administrativos.
      operationId: obterConfiguracoesPublicas
      parameters:
        - $ref: '#/components/parameters/ClienteHeader'
      responses:
        '200':
          description: Configurações públicas
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicoConfiguracaoDTO'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '500':
          $ref: '#/components/responses/InternalServerError'

components:
  parameters:
    ClienteHeader:
      name: cliente
      in: header
      required: true
      description: Slug do município selecionado no app (database-per-tenant). Alias aceito X-Cliente.
      schema:
        type: string
        example: aguaboa
    IdPath:
      name: id
      in: path
      required: true
      description: Identificador único (UUID)
      schema:
        type: string
        format: uuid
    PercursoIdPath:
      name: percursoId
      in: path
      required: true
      description: Identificador do percurso
      schema:
        type: string
        format: uuid
    VeiculoIdPath:
      name: veiculoId
      in: path
      required: true
      description: Identificador do veículo
      schema:
        type: string
        format: uuid
    ChavePath:
      name: chave
      in: path
      required: true
      description: Chave da configuração (ex. rastreamento_habilitado)
      schema:
        type: string
        example: rastreamento_habilitado

  responses:
    BadRequest:
      description: Requisição inválida
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponseDTO'
    NotFound:
      description: Recurso não encontrado
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponseDTO'
    MethodNotAllowed:
      description: Método HTTP não suportado
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponseDTO'
    UnprocessableEntity:
      description: Regra de negócio violada
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponseDTO'
    InternalServerError:
      description: Erro interno da aplicação
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponseDTO'

  schemas:
    ApiErrorResponseDTO:
      type: object
      description: Resposta padronizada de erro da API
      properties:
        mensagem:
          type: string
          description: Mensagem principal do erro
          example: Requisição inválida.
        erros:
          type: array
          description: Detalhes por campo ou parâmetro, quando aplicável
          items:
            $ref: '#/components/schemas/ApiFieldErrorDTO'

    ApiFieldErrorDTO:
      type: object
      description: Detalhe de erro de validação em um campo ou parâmetro
      properties:
        campo:
          type: string
          description: Nome do campo ou parâmetro
          example: diaSemana
        mensagem:
          type: string
          description: Mensagem descritiva do erro
          example: O dia da semana deve estar entre 1 e 7

    SentidoPercurso:
      type: string
      enum:
        - IDA
        - VOLTA
        - CIRCULAR
      example: IDA

    LinhaRequestDTO:
      type: object
      description: Dados para criação ou atualização de linha
      required:
        - codigo
        - nome
        - ativa
      properties:
        codigo:
          type: string
          maxLength: 20
          description: Código público da linha
          example: '101'
        nome:
          type: string
          maxLength: 255
          description: Nome descritivo do serviço
          example: Centro / Vila Aurora
        ativa:
          type: boolean
          description: Situação operacional da linha
          example: true

    LinhaResponseDTO:
      type: object
      description: Representação de uma linha de transporte público
      properties:
        id:
          type: string
          format: uuid
          description: Identificador único
        codigo:
          type: string
          description: Código público da linha
          example: '101'
        nome:
          type: string
          description: Nome descritivo do serviço
          example: Centro / Vila Aurora
        ativa:
          type: boolean
          description: Indica se a linha está ativa
          example: true
        situacao:
          type: string
          description: Rótulo da situação para exibição
          example: Ativa

    PercursoRequestDTO:
      type: object
      description: Dados para criação ou atualização de percurso
      required:
        - linhaId
        - nome
        - sentido
        - ativo
      properties:
        linhaId:
          type: string
          format: uuid
          description: Identificador da linha vinculada
        nome:
          type: string
          maxLength: 255
          description: Nome descritivo do percurso
          example: Centro - Bairro Industrial
        sentido:
          $ref: '#/components/schemas/SentidoPercurso'
        extensaoKm:
          type: number
          format: double
          description: Extensão do percurso em quilômetros
          example: 12.50
        ativo:
          type: boolean
          description: Indica se o percurso está ativo
          example: true

    PercursoResponseDTO:
      type: object
      description: Representação de um percurso de transporte público
      properties:
        id:
          type: string
          format: uuid
          description: Identificador único
        linhaId:
          type: string
          format: uuid
          description: Identificador da linha vinculada
        linhaCodigo:
          type: string
          description: Código da linha vinculada
          example: '101'
        linhaNome:
          type: string
          description: Nome da linha vinculada
          example: Centro / Vila Aurora
        nome:
          type: string
          description: Nome descritivo do percurso
          example: Centro - Bairro Industrial
        sentido:
          $ref: '#/components/schemas/SentidoPercurso'
        extensaoKm:
          type: number
          format: double
          description: Extensão do percurso em quilômetros
          example: 12.50
        ativo:
          type: boolean
          description: Indica se o percurso está ativo
          example: true
        situacao:
          type: string
          description: Rótulo da situação para exibição
          example: Ativo

    PercursoPontoRequestDTO:
      type: object
      description: Dados para criação ou atualização de ponto de percurso
      required:
        - ordem
        - latitude
        - longitude
      properties:
        ordem:
          type: integer
          minimum: 1
          description: Ordem do ponto no percurso
          example: 1
        latitude:
          type: number
          format: double
          description: Latitude do ponto
          example: -15.1234567
        longitude:
          type: number
          format: double
          description: Longitude do ponto
          example: -56.1234567

    PercursoPontoResponseDTO:
      type: object
      description: Representação de um ponto geográfico de percurso
      properties:
        id:
          type: string
          format: uuid
          description: Identificador único
        percursoId:
          type: string
          format: uuid
          description: Identificador do percurso vinculado
        ordem:
          type: integer
          description: Ordem do ponto no percurso
          example: 1
        latitude:
          type: number
          format: double
          description: Latitude do ponto
          example: -15.1234567
        longitude:
          type: number
          format: double
          description: Longitude do ponto
          example: -56.1234567

    ParadaRequestDTO:
      type: object
      description: Dados para criação ou atualização de parada
      required:
        - ordem
        - nome
        - latitude
        - longitude
      properties:
        ordem:
          type: integer
          minimum: 1
          description: Ordem da parada na sequência operacional do percurso
          example: 1
        nome:
          type: string
          maxLength: 255
          description: Nome da parada
          example: Terminal Central
        descricao:
          type: string
          maxLength: 500
          description: Descrição opcional da parada
          example: Em frente à praça
        latitude:
          type: number
          format: double
          description: Latitude da parada
          example: -15.5989000
        longitude:
          type: number
          format: double
          description: Longitude da parada
          example: -56.0949000
        ativa:
          type: boolean
          description: Indica se a parada está ativa
          default: true
          example: true

    ParadaResponseDTO:
      type: object
      description: Parada de embarque/desembarque de um percurso
      properties:
        id:
          type: string
          format: uuid
          description: Identificador da parada
        percursoId:
          type: string
          format: uuid
          description: Identificador do percurso
        ordem:
          type: integer
          description: Ordem na sequência operacional
        nome:
          type: string
          description: Nome da parada
        descricao:
          type: string
          description: Descrição da parada
        latitude:
          type: number
          format: double
          description: Latitude
        longitude:
          type: number
          format: double
          description: Longitude
        ativa:
          type: boolean
          description: Situação ativa/inativa

    HorarioRequestDTO:
      type: object
      description: Dados para criação ou atualização de horário de partida
      required:
        - diaSemana
        - horario
      properties:
        diaSemana:
          type: integer
          minimum: 1
          maximum: 7
          description: Dia da semana (1=segunda … 7=domingo)
          example: 1
        horario:
          type: string
          pattern: '^([01]?\d|2[0-3]):[0-5]\d$'
          description: Horário de partida no formato HH:mm
          example: '06:30'

    HorarioResponseDTO:
      type: object
      description: Horário de partida de um percurso
      properties:
        id:
          type: string
          format: uuid
          description: Identificador do horário
        percursoId:
          type: string
          format: uuid
          description: Identificador do percurso
        diaSemana:
          type: integer
          description: Dia da semana (1=segunda … 7=domingo)
        horario:
          type: string
          description: Horário de partida no formato HH:mm

    HorarioDiaReplaceDTO:
      type: object
      description: Substituição dos horários de partida de um único dia da semana
      required:
        - horarios
      properties:
        horarios:
          type: array
          description: Lista de horários de partida no formato HH:mm. Array vazio remove todos os horários do dia.
          items:
            type: string
            example: '06:00'
          example:
            - '06:00'
            - '06:30'
            - '07:00'

    HorarioDiaItemDTO:
      type: object
      description: Horários de partida de um dia da semana
      required:
        - diaSemana
        - horarios
      properties:
        diaSemana:
          type: integer
          minimum: 1
          maximum: 7
          description: Dia da semana (1=segunda … 7=domingo)
          example: 1
        horarios:
          type: array
          description: Horários de partida no formato HH:mm
          items:
            type: string

    HorarioMultiDiaReplaceDTO:
      type: object
      description: Substituição dos horários de partida de vários dias (demais dias permanecem inalterados)
      required:
        - dias
      properties:
        dias:
          type: array
          description: Dias a substituir com suas respectivas listas de horários
          minItems: 1
          items:
            $ref: '#/components/schemas/HorarioDiaItemDTO'

    HorarioDiasExcluirDTO:
      type: object
      description: Exclusão dos horários de partida de vários dias
      required:
        - diasSemana
      properties:
        diasSemana:
          type: array
          description: Dias da semana a limpar (1=segunda … 7=domingo)
          minItems: 1
          items:
            type: integer
            minimum: 1
            maximum: 7
          example:
            - 6
            - 7

    PublicoLinhaDTO:
      type: object
      description: Linha ativa para consulta pública
      properties:
        id:
          type: string
          format: uuid
          description: Identificador da linha
        codigo:
          type: string
          description: Código público da linha
          example: '101'
        nome:
          type: string
          description: Nome da linha
          example: Centro / Vila Aurora

    PublicoPercursoDTO:
      type: object
      description: Percurso ativo para consulta pública
      properties:
        id:
          type: string
          format: uuid
          description: Identificador do percurso
        nome:
          type: string
          description: Nome do percurso
        sentido:
          $ref: '#/components/schemas/SentidoPercurso'
        linhaCodigo:
          type: string
          description: Código da linha vinculada
          example: '101'
        linhaNome:
          type: string
          description: Nome da linha vinculada

    PublicoPercursoResumoDTO:
      type: object
      description: Resumo do percurso na grade horária pública
      properties:
        id:
          type: string
          format: uuid
          description: Identificador do percurso
        nome:
          type: string
          description: Nome do percurso
        sentido:
          $ref: '#/components/schemas/SentidoPercurso'

    PublicoProximaSaidaDTO:
      type: object
      description: Próxima partida cadastrada do percurso
      properties:
        diaSemana:
          type: integer
          description: Dia da semana da partida (1=segunda … 7=domingo)
        horario:
          type: string
          description: Horário de partida no formato HH:mm
          example: '10:00'

    PublicoGradeDiaDTO:
      type: object
      description: Horários de partida agrupados por dia da semana
      properties:
        diaSemana:
          type: integer
          description: Dia da semana (1=segunda … 7=domingo)
        descricao:
          type: string
          description: Descrição do dia
          example: Segunda-feira
        horarios:
          type: array
          description: Horários de partida no formato HH:mm
          items:
            type: string

    PublicoGradeHorariaDTO:
      type: object
      description: Grade horária de partida do percurso para o aplicativo do cidadão
      properties:
        linha:
          $ref: '#/components/schemas/PublicoLinhaDTO'
        percurso:
          $ref: '#/components/schemas/PublicoPercursoResumoDTO'
        diaAtual:
          type: integer
          description: Dia da semana atual no servidor (1=segunda … 7=domingo)
        horaAtualServidor:
          type: string
          description: Hora atual do servidor no formato HH:mm
        proximaSaida:
          nullable: true
          allOf:
            - $ref: '#/components/schemas/PublicoProximaSaidaDTO'
          description: Próxima partida cadastrada após o instante atual; null se não houver horários
        grade:
          type: array
          description: Grade completa com os sete dias da semana
          items:
            $ref: '#/components/schemas/PublicoGradeDiaDTO'

    TipoVeiculo:
      type: string
      enum:
        - ONIBUS
        - MICRO_ONIBUS
        - VAN
        - OUTRO
      example: ONIBUS

    StatusOperacionalVeiculo:
      type: string
      enum:
        - DISPONIVEL
        - EM_OPERACAO
        - INDISPONIVEL
        - RESERVADO
      example: DISPONIVEL

    VeiculoRequestDTO:
      type: object
      description: Dados para criação ou atualização de veículo da frota
      required:
        - prefixo
        - tipoVeiculo
        - acessivel
        - arCondicionado
        - wifi
        - entradaUsb
        - bicicletario
        - statusOperacional
        - ativo
      properties:
        prefixo:
          type: string
          maxLength: 20
          description: Número do veículo (identificador operacional exibido ao usuário)
          example: '2042'
        placa:
          type: string
          maxLength: 10
          description: Placa do veículo (opcional)
          example: ABC1D23
        tipoVeiculo:
          $ref: '#/components/schemas/TipoVeiculo'
        fabricante:
          type: string
          maxLength: 100
          example: Mercedes-Benz
        modelo:
          type: string
          maxLength: 100
          example: OF-1721
        anoFabricacao:
          type: integer
          minimum: 1900
          maximum: 2100
          example: 2018
        capacidadePassageiros:
          type: integer
          minimum: 1
          maximum: 500
          example: 44
        identificadorExterno:
          type: string
          maxLength: 150
          description: Identificador do dispositivo na empresa de rastreamento (opcional)
          example: IMEI-359123456789012
        acessivel:
          type: boolean
          default: false
        arCondicionado:
          type: boolean
          default: false
        wifi:
          type: boolean
          default: false
        entradaUsb:
          type: boolean
          default: false
        bicicletario:
          type: boolean
          default: false
        observacoes:
          type: string
          maxLength: 1000
        statusOperacional:
          $ref: '#/components/schemas/StatusOperacionalVeiculo'
        ativo:
          type: boolean
          example: true

    VeiculoResponseDTO:
      type: object
      description: Representação de um veículo da frota municipal
      properties:
        id:
          type: string
          format: uuid
          description: Identificador técnico interno
        prefixo:
          type: string
          description: Número do veículo (prefixo operacional)
          example: '2042'
        placa:
          type: string
          example: ABC1D23
        tipoVeiculo:
          $ref: '#/components/schemas/TipoVeiculo'
        tipoVeiculoDescricao:
          type: string
          example: Ônibus
        fabricante:
          type: string
        modelo:
          type: string
        anoFabricacao:
          type: integer
        capacidadePassageiros:
          type: integer
        identificadorExterno:
          type: string
          description: Identificador externo de rastreamento (quando informado)
        acessivel:
          type: boolean
        arCondicionado:
          type: boolean
        wifi:
          type: boolean
        entradaUsb:
          type: boolean
        bicicletario:
          type: boolean
        observacoes:
          type: string
        statusOperacional:
          $ref: '#/components/schemas/StatusOperacionalVeiculo'
        statusOperacionalDescricao:
          type: string
          example: Disponível
        ativo:
          type: boolean
        situacaoCadastro:
          type: string
          example: Ativo
        ultimaLatitude:
          type: number
          format: double
          readOnly: true
          description: Última latitude conhecida (somente leitura)
          example: -15.1234567
        ultimaLongitude:
          type: number
          format: double
          readOnly: true
          description: Última longitude conhecida (somente leitura)
          example: -56.1234567
        ultimaLocalizacao:
          type: string
          format: date-time
          readOnly: true
          description: Data/hora da última localização conhecida (somente leitura)

    VeiculoAlocacaoRequestDTO:
      type: object
      description: Dados para criação ou atualização de alocação operacional do veículo
      required:
        - percursoId
        - inicio
        - fim
      properties:
        percursoId:
          type: string
          format: uuid
          description: Percurso que o veículo executará no intervalo informado
        inicio:
          type: string
          format: date-time
          description: Início do intervalo (inclusivo)
          example: '2026-06-24T07:00:00'
        fim:
          type: string
          format: date-time
          description: Fim do intervalo (exclusivo)
          example: '2026-06-24T09:00:00'
        observacoes:
          type: string
          maxLength: 500

    VeiculoAlocacaoResponseDTO:
      type: object
      description: Alocação operacional — veículo executando um percurso em um intervalo
      properties:
        id:
          type: string
          format: uuid
        veiculoId:
          type: string
          format: uuid
        veiculoPrefixo:
          type: string
          example: '2042'
        percursoId:
          type: string
          format: uuid
        percursoNome:
          type: string
          example: Centro → Bairro Norte
        linhaId:
          type: string
          format: uuid
        linhaCodigo:
          type: string
          example: '101'
        linhaNome:
          type: string
          example: Centro / Vila Aurora
        inicio:
          type: string
          format: date-time
        fim:
          type: string
          format: date-time
        observacoes:
          type: string

    ConfiguracaoRequestDTO:
      type: object
      description: Atualização de uma configuração por chave
      required:
        - valor
      properties:
        valor:
          type: string
          maxLength: 1000
          example: 'true'
        descricao:
          type: string
          maxLength: 500

    ConfiguracaoResponseDTO:
      type: object
      description: Configuração funcional do município (chave/valor)
      properties:
        id:
          type: string
          format: uuid
        chave:
          type: string
          example: rastreamento_habilitado
        valor:
          type: string
          example: 'false'
        descricao:
          type: string

    ConfiguracaoRastreamentoRequestDTO:
      type: object
      description: Flags de rastreamento do município
      required:
        - rastreamentoHabilitado
        - rastreamentoProvedorExterno
      properties:
        rastreamentoHabilitado:
          type: boolean
          example: false
        rastreamentoProvedorExterno:
          type: boolean
          example: false

    ConfiguracaoRastreamentoResponseDTO:
      type: object
      description: Configurações de rastreamento do município
      properties:
        rastreamentoHabilitado:
          type: boolean
          example: false
        rastreamentoProvedorExterno:
          type: boolean
          example: false

    PublicoConfiguracaoDTO:
      type: object
      description: Configurações públicas do município para o aplicativo do cidadão
      properties:
        rastreamentoHabilitado:
          type: boolean
          description: Indica se o município disponibiliza rastreamento de veículos no app
          example: false
        rastreamentoProvedorExterno:
          type: boolean
          description: Indica se as posições virão de provedor externo (quando habilitado)
          example: false
