openapi: 3.1.0
info:
  title: Coplan Portal Zeladoria API
  description: |-
    Backend API do Portal Zeladoria — perfil, chamados, histórico, anexos e avaliação.

    A autenticação permanece na **Central**. Este backend recebe a identidade já
    resolvida (JWT Bearer / Gx-Token; fallback e2e: headers `cliente` e `X-Usuario-Cpf`).
    O header `cliente` (slug do município) seleciona o banco PostgreSQL do tenant.

    **Clientes:** apps/portais Urbis (cidadão, persona `cidadao`) e Zeladoria (servidor, persona `servidor`); gestores web.

    **Produto (`X-Produto`):** `URBANO` força persona cidadão; `ZELADORIA` força servidor
    em listagem, dashboard, catálogo e abertura de chamado. Divergência com `?persona=` → 422.
    Gestão web / Postman pode omitir o header e escolher a persona livremente.

    **Gestor:** `GET/PATCH /gestor/chamados`, `GET /gestor/chamados/dashboard`, CRUD `/tipos-servico`.
    Papel (`X-Papel-Gestao` / JWT): `gestor` (tipos + chamados) ou `operador` (só chamados).

    Flags do tipo (`exigeFoto` / `exigeLocalizacao` / `exigeBem`): default tudo requerido na criação
    (bem só para persona servidor). Sem bem, a descrição do chamado é obrigatória.

    **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/zeladoria
    description: Servidor de Homologação
  - url: http://localhost:8080/zeladoria
    description: Servidor Local
tags:
  - name: Arquivos
    description: Upload de fotos do chamado e download pela URL devolvida.
  - name: Perfis
    description: Perfil local do Zeladoria vinculado ao CPF autenticado pela Central.
  - name: Tipos de serviço (gestor)
    description: |-
      CRUD do catálogo municipal. URBANO Gestão gerencia persona=cidadao;
      Zeladoria Gestão gerencia persona=servidor. O app mobile usa GET /catalogo/tipos.
  - name: Chamados
    description: |-
      Abertura, acompanhamento, status e avaliação de chamados.
      Apps mobile isolam por `X-Produto` (URBANO=cidadao, ZELADORIA=servidor).
  - name: Patrimônio
    description: |
      Consulta de órgãos, unidades, setores, locais e bens no Administrativo,
      para o wizard do chamado do servidor (mesmo encadeamento do portal-patrimonio-mobile).

      Não persiste cadastro patrimonial. Encaminha o token da Central
      (`Authorization` ou `Gx-Token`) para o ADM. Lista vazia no ADM
      (mensagem "Bem não encontrado") é sucesso com `itens: []`, não 422.
paths:
  /tipos-servico/{codigo}:
    put:
      tags:
        - Tipos de serviço (gestor)
      summary: Atualizar tipo de serviço (código imutável)
      operationId: atualizar
      parameters:
        - name: codigo
          in: path
          required: true
          schema:
            type: string
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TipoServicoRequestDTO"
        required: true
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/CatalogoTipoDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /perfis/me:
    get:
      tags:
        - Perfis
      summary: Consultar perfil do usuário autenticado
      operationId: me
      parameters:
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: Perfil encontrado
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/PerfilResponseDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Perfil não encontrado
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/PerfilResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
    put:
      tags:
        - Perfis
      summary: Criar ou atualizar o perfil do usuário autenticado
      operationId: upsert
      parameters:
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PerfilRequestDTO"
        required: true
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/PerfilResponseDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /tipos-servico:
    get:
      tags:
        - Tipos de serviço (gestor)
      summary: Listar tipos (gestor). Inclui inativos com incluirInativos=true.
      operationId: listar
      parameters:
        - name: persona
          in: query
          required: false
          schema:
            type: string
            enum:
              - cidadao
              - servidor
        - name: incluirInativos
          in: query
          required: false
          schema:
            type: boolean
            default: true
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/CatalogoTipoDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
    post:
      tags:
        - Tipos de serviço (gestor)
      summary: Criar tipo de serviço
      operationId: criar
      parameters:
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TipoServicoRequestDTO"
        required: true
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/CatalogoTipoDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /perfis:
    post:
      tags:
        - Perfis
      summary: Criar perfil
      operationId: criar_1
      parameters:
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PerfilRequestDTO"
        required: true
      responses:
        "201":
          description: Perfil criado
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/PerfilResponseDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Perfil já existe
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/PerfilResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /chamados:
    get:
      tags:
        - Chamados
      summary: Listar chamados do usuário autenticado
      description: |-
        Filtros: persona, status, busca e período.
        Com `X-Produto: URBANO` só cidadão; `X-Produto: ZELADORIA` só servidor.
        Divergência entre header e `?persona=` → 422.
      operationId: listar_1
      parameters:
        - name: persona
          in: query
          required: true
          schema:
            type: string
            enum:
              - cidadao
              - servidor
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum:
              - aberto
              - em_analise
              - em_andamento
              - concluido
              - cancelado
        - name: busca
          in: query
          required: false
          schema:
            type: string
        - name: dataInicio
          in: query
          required: false
          schema:
            type: string
            format: date
        - name: dataFim
          in: query
          required: false
          schema:
            type: string
            format: date
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ChamadoResponseDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
    post:
      tags:
        - Chamados
      summary: Abrir chamado (único endpoint ao final do wizard)
      description: |-
        Não persiste etapa a etapa. Cidadão e servidor usam este mesmo POST.

        Cidadão (default): tipo + lat/lng + título/descrição + até 5 fotos.
        Servidor: o mesmo, mais órgão (código + nome), bens_id interno e snapshot do bem (descrição; código de cadastro e plaqueta visíveis).
        Exige perfil.servidor=true quando persona=servidor.
        Com `X-Produto` a persona do body é forçada (URBANO→cidadao, ZELADORIA→servidor); divergência → 422.
        Fotos: ids ou urls devolvidos pelo POST /arquivos (até 5).
      operationId: criar_2
      parameters:
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChamadoRequestDTO"
        required: true
      responses:
        "201":
          description: Chamado criado
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/ChamadoResponseDTO"
        "400":
          description: Dados inválidos
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/ChamadoResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/ChamadoResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /chamados/{id}/avaliacao:
    get:
      tags:
        - Chamados
      summary: Consultar avaliação do chamado
      operationId: consultarAvaliacao
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/AvaliacaoResponseDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
    post:
      tags:
        - Chamados
      summary: Registrar avaliação
      operationId: avaliar
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AvaliacaoRequestDTO"
        required: true
      responses:
        "201":
          description: Avaliação registrada
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/AvaliacaoResponseDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Chamado não encontrado
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/AvaliacaoResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Chamado não concluído ou já avaliado
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/AvaliacaoResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /arquivos:
    post:
      tags:
        - Arquivos
      summary: Enviar foto do chamado
      description: |-
        Multipart campo `arquivo` (JPG, PNG ou WEBP, até 5 MB).
        Grave a `url` (ou o `id`) e envie em `fotos` no POST /chamados.
        O arquivo fica no disco do servidor; a API nunca devolve o path interno.
      operationId: enviar
      parameters:
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                arquivo:
                  type: string
                  format: binary
              required:
                - arquivo
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/ArquivoUploadResponseDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /tipos-servico/{codigo}/ativo:
    patch:
      tags:
        - Tipos de serviço (gestor)
      summary: Ativar ou desativar tipo
      operationId: definirAtivo
      parameters:
        - name: codigo
          in: path
          required: true
          schema:
            type: string
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      requestBody:
        content:
          application/json:
            schema:
              type: object
              additionalProperties:
                type: boolean
        required: true
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/CatalogoTipoDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /gestor/chamados/{id}/status:
    patch:
      tags:
        - Chamados
      summary: Atualizar status (gestor municipal)
      description: Não exige ser o solicitante do chamado. Mesmas regras de transição do PATCH /chamados/{id}/status.
      operationId: atualizarStatusGestor
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AtualizarStatusRequestDTO"
        required: true
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/ChamadoResponseDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /chamados/{id}/status:
    patch:
      tags:
        - Chamados
      summary: Atualizar status do chamado
      description: "Transições simples: aberto → em_analise → em_andamento → concluido; cancelado a partir dos estados anteriores."
      operationId: atualizarStatus
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AtualizarStatusRequestDTO"
        required: true
      responses:
        "200":
          description: Status atualizado
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/ChamadoResponseDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Chamado não encontrado
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/ChamadoResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Transição não permitida
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/ChamadoResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /perfis/me/servidor:
    get:
      tags:
        - Perfis
      summary: Indica se o usuário pode atuar como servidor
      operationId: isServidor
      parameters:
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/PerfilServidorDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /patrimonio/unidades:
    get:
      tags:
        - Patrimônio
      summary: Listar unidades do órgão
      description: Proxy de `POST ws_pat_unidade_adm_lista`. Exige `orgaoId` > 0.
      operationId: listarUnidades
      parameters:
        - name: orgaoId
          in: query
          required: true
          schema:
            type: integer
            format: int32
        - name: descricao
          in: query
          required: false
          schema:
            type: string
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/PatrimonioCadastroListaDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /patrimonio/setores:
    get:
      tags:
        - Patrimônio
      summary: Listar setores da unidade
      description: Proxy de `POST ws_pat_setor_lista`. Exige `unidadeId` > 0.
      operationId: listarSetores
      parameters:
        - name: unidadeId
          in: query
          required: true
          schema:
            type: integer
            format: int32
        - name: descricao
          in: query
          required: false
          schema:
            type: string
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/PatrimonioCadastroListaDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /patrimonio/orgaos:
    get:
      tags:
        - Patrimônio
      summary: Listar órgãos (passo do wizard do servidor)
      description: Proxy de `POST ws_pat_orgao_adm_lista`. Somente perfil servidor.
      operationId: listarOrgaos
      parameters:
        - name: descricao
          in: query
          required: false
          schema:
            type: string
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/OrgaoListaResponseDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /patrimonio/locais:
    get:
      tags:
        - Patrimônio
      summary: Listar locais físicos do setor
      description: Proxy de `POST ws_pat_local_fisico_lista`. Exige `setorId` > 0.
      operationId: listarLocais
      parameters:
        - name: setorId
          in: query
          required: true
          schema:
            type: integer
            format: int32
        - name: descricao
          in: query
          required: false
          schema:
            type: string
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/PatrimonioCadastroListaDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /patrimonio/bens:
    get:
      tags:
        - Patrimônio
      summary: Listar bens (passo do wizard do servidor)
      description: |-
        Órgão, unidade, setor e local são opcionais (igual à consulta do patrimônio-mobile).
        Com órgão, `pagina=0` e `registros=0` traz todos os bens.
        Sem órgão, a busca é paginada (20 por página).
        O chamado admite apenas um bem.
      operationId: listarBens
      parameters:
        - name: pagina
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 0
        - name: registros
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 0
        - name: tipo
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 1
        - name: plaqueta
          in: query
          required: false
          schema:
            type: integer
            format: int32
        - name: plaquetaIni
          in: query
          required: false
          schema:
            type: integer
            format: int32
        - name: plaquetaFim
          in: query
          required: false
          schema:
            type: integer
            format: int32
        - name: orgaoCodigo
          in: query
          required: false
          schema:
            type: string
        - name: unidadeCodigo
          in: query
          required: false
          schema:
            type: string
        - name: setorCodigo
          in: query
          required: false
          schema:
            type: string
        - name: localFisicoId
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 0
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/BemListaResponseDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /gestor/chamados:
    get:
      tags:
        - Chamados
      summary: Listar chamados do município (gestor)
      description: Escopo municipal (tenant). Filtra por persona (cidadao/servidor). Usado pelos gerenciadores Urbis e Zeladoria.
      operationId: listarGestor
      parameters:
        - name: persona
          in: query
          required: true
          schema:
            type: string
            enum:
              - cidadao
              - servidor
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum:
              - aberto
              - em_analise
              - em_andamento
              - concluido
              - cancelado
        - name: busca
          in: query
          required: false
          schema:
            type: string
        - name: dataInicio
          in: query
          required: false
          schema:
            type: string
            format: date
        - name: dataFim
          in: query
          required: false
          schema:
            type: string
            format: date
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ChamadoResponseDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /gestor/chamados/dashboard:
    get:
      tags:
        - Chamados
      summary: Dashboard municipal de chamados (gestor/operador)
      description: |-
        KPIs do município para a persona do gerenciador (cidadao no Urbis, servidor na Zeladoria).
        Inclui fila por status e média de avaliações.
        Papel via `X-Papel-Gestao` (gestor|operador) ou claim JWT; default gestor.
      operationId: dashboardGestor
      parameters:
        - name: persona
          in: query
          required: true
          schema:
            type: string
            enum:
              - cidadao
              - servidor
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: Bearer do token da Central.
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central.
          required: false
          schema:
            type: string
        - name: X-Papel-Gestao
          in: header
          description: Papel no gerenciador web — `gestor` (tipos + chamados) ou `operador` (só chamados).
          required: false
          schema:
            type: string
            enum:
              - gestor
              - operador
            example: gestor
        - name: X-Produto
          in: header
          description: Opcional na gestão; persona vem do query param.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/GestorDashboardKpisDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /chamados/{id}:
    get:
      tags:
        - Chamados
      summary: Consultar chamado por ID
      operationId: buscarPorId
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: Chamado encontrado
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/ChamadoResponseDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Chamado não encontrado
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/ChamadoResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /chamados/{id}/anexos:
    get:
      tags:
        - Chamados
      summary: Listar fotos do chamado
      description: |-
        Lote das fotos de um chamado (tela de detalhe e gerenciador).
        Cada item traz `chamadoId`, `url`, `nome` e `ordem`.
      operationId: listarAnexos
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/ChamadoAnexoListaDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /chamados/dashboard:
    get:
      tags:
        - Chamados
      summary: KPIs da home por persona
      description: Mesma regra de persona de GET /chamados (`X-Produto` força URBANO→cidadao / ZELADORIA→servidor).
      operationId: dashboard
      parameters:
        - name: persona
          in: query
          required: true
          schema:
            type: string
            enum:
              - cidadao
              - servidor
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                $ref: "#/components/schemas/DashboardKpisDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /catalogo/tipos:
    get:
      tags:
        - Chamados
      summary: Catálogo de tipos de serviço (passo 1 do wizard)
      description: |-
        Lê `zl_tipo_servico` ativos. Sem persona = cidadão (URBANO).
        Servidor: `?persona=servidor` (Zeladoria). Cada item traz exigeFoto/exigeLocalizacao/exigeBem.
        Com header `X-Produto` (URBANO/ZELADORIA) a persona é forçada; divergência → 422.
        CRUD do gestor: `/tipos-servico`.
      operationId: catalogo
      parameters:
        - name: persona
          in: query
          required: false
          schema:
            type: string
            enum:
              - cidadao
              - servidor
        - name: cliente
          in: header
          description: "Slug do município (database-per-tenant). App: municipio_id. Web: trecho da URL."
          required: true
          schema:
            type: string
            example: aguaboa
        - name: X-Usuario-Cpf
          in: header
          description: CPF do usuário autenticado pela Central (somente dígitos). Temporário até o JWT da Central.
          required: true
          schema:
            type: string
            example: "52998224725"
        - name: Authorization
          in: header
          description: "Bearer do token da Central. Futuro: JWT do produto Zeladoria, reutilizado nas APIs do ADM. Opcional nas rotas locais; obrigatório em /patrimonio/bens."
          required: false
          schema:
            type: string
            example: Bearer ...
        - name: Gx-Token
          in: header
          description: Alias GeneXus do token da Central (mesmo valor do Patrimônio).
          required: false
          schema:
            type: string
        - name: X-Produto
          in: header
          description: >-
            Produto do app: URBANO (cidadão / Urbis) ou ZELADORIA (servidor).
            Prevalece sobre o claim JWT. Com o header, list/dashboard/create/catálogo
            forçam a persona correspondente; divergência → 422. Gestão/Postman pode omitir.
          required: false
          schema:
            type: string
            example: URBANO
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/CatalogoTipoDTO"
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /arquivos/{cliente}/{id}:
    get:
      tags:
        - Arquivos
      summary: Baixar arquivo pela URL pública
      description: |-
        Público de propósito (UUID não enumerável), para Image.network e <img> no gerenciador.
        O slug do município na URL seleciona o banco do tenant.
      operationId: baixar
      parameters:
        - name: cliente
          in: path
          required: true
          schema:
            type: string
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                type: string
                format: binary
        "400":
          description: Requisição inválida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "404":
          description: Recurso não encontrado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "405":
          description: Método HTTP não suportado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "422":
          description: Regra de negócio violada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
        "500":
          description: Erro interno da aplicação
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorResponseDTO"
  /api/health:
    get:
      tags:
        - health-controller
      operationId: health
      responses:
        "200":
          description: OK
          content:
            "*/*":
              schema:
                type: object
                additionalProperties:
                  type: string
components:
  schemas:
    TipoServicoRequestDTO:
      type: object
      description: Criação/atualização de tipo de serviço (gestor URBANO/Zeladoria)
      properties:
        codigo:
          type: string
          description: Código estável (slug). Só na criação.
          example: iluminacao
          maxLength: 50
          minLength: 0
        nome:
          type: string
          description: Nome de exibição
          example: Iluminação Pública
          maxLength: 255
          minLength: 0
        descricao:
          type: string
          description: Descrição curta do wizard
          maxLength: 1000
          minLength: 0
        persona:
          type: string
          description: cidadao = URBANO | servidor = Zeladoria
          enum:
            - cidadao
            - servidor
          example: cidadao
        exigeFoto:
          type: boolean
          description: Exige ao menos uma foto na abertura
        exigeLocalizacao:
          type: boolean
          description: Exige pin de localização (lat/lng) na abertura
        exigeBem:
          type: boolean
          description: Exige bem patrimonial (somente servidor/Zeladoria). Se false, a descrição do chamado é obrigatória.
        ativo:
          type: boolean
          description: Visível no app
        ordem:
          type: integer
          format: int32
          description: Ordem no wizard (menor primeiro)
          example: 10
      required:
        - persona
    CatalogoTipoDTO:
      type: object
      description: Item do catálogo de tipos de serviço (app + gestor)
      properties:
        id:
          type: string
          description: Código do tipo (enviar em POST /chamados)
          example: iluminacao
        nome:
          type: string
          description: Nome para exibição
          example: Iluminação Pública
        descricao:
          type: string
          description: Descrição curta para o wizard
          example: Lâmpadas queimadas, postes danificados
        persona:
          type: string
          description: "Persona: cidadao (URBANO) ou servidor (Zeladoria)"
          enum:
            - cidadao
            - servidor
          example: cidadao
        personaDescricao:
          type: string
          description: Descrição da persona para exibição
          example: Cidadão
        exigeFoto:
          type: boolean
          description: Abertura exige ao menos uma foto
        exigeLocalizacao:
          type: boolean
          description: Abertura exige pin de localização (lat/lng)
        exigeBem:
          type: boolean
          description: Abertura exige bem patrimonial (Zeladoria/servidor). Se false, a descrição do chamado é obrigatória.
        ativo:
          type: boolean
          description: Tipo ativo no município
        ordem:
          type: integer
          format: int32
          description: Ordem de exibição no wizard
    PerfilRequestDTO:
      type: object
      description: Dados para criação ou atualização do perfil Zeladoria
      properties:
        nome:
          type: string
          description: Nome de exibição
          example: Maria Silva
          maxLength: 255
          minLength: 0
        email:
          type: string
          description: E-mail de contato
          example: maria@example.com
          maxLength: 255
          minLength: 0
        telefone:
          type: string
          description: Telefone de contato
          example: 65999990000
          maxLength: 20
          minLength: 0
        servidor:
          type: boolean
          description: |-
            Legado: o app não deve mais definir se é servidor.
            A elegibilidade vem do JWT/Central (usuario_servidor / servidor_id).
            Se enviado, é ignorado no upsert autenticado.
        status:
          type: string
          description: Situação do perfil
          enum:
            - ativo
            - inativo
          example: ativo
    PerfilResponseDTO:
      type: object
      description: Perfil local do Zeladoria, vinculado ao CPF autenticado pela Central
      properties:
        id:
          type: string
          format: uuid
          description: Identificador técnico interno
        cpf:
          type: string
          description: CPF do usuário neste município
        nome:
          type: string
          description: Nome
        email:
          type: string
          description: E-mail
        telefone:
          type: string
          description: Telefone
        servidor:
          type: boolean
          description: Pode atuar como servidor
        status:
          type: string
          description: Situação do cadastro
          enum:
            - ativo
            - inativo
          example: ativo
        statusDescricao:
          type: string
          description: Descrição da situação para exibição
          example: Ativo
        situacao:
          type: string
          description: Rótulo da situação para exibição
          example: Ativo
        centralUsuarioId:
          type: string
          description: Identificador do usuário na Central, quando houver
        centralPessoaId:
          type: integer
          format: int32
          description: pessoa_id na Central, quando houver
        servidorId:
          type: integer
          format: int32
          description: servidor_id no RH, quando houver
        criadoEm:
          type: string
          format: date-time
          description: Data/hora de criação
        atualizadoEm:
          type: string
          format: date-time
          description: Data/hora da última atualização
    ChamadoRequestDTO:
      type: object
      description: |-
        Payload único de abertura de chamado. O wizard do app (tipo, órgão/bem,
        localização, fotos, descrição/título) envia tudo aqui — não há POST por etapa
        nem endpoints separados de cidadão e servidor.

        Persona omitida = cidadão. Servidor exige perfil.servidor=true.
        Regras de foto, localização e bem vêm do catálogo
        (`exige_foto` / `exige_localizacao` / `exige_bem`).
        Quando exigeBem: órgão (código + nome), exatamente um bens_id (`patrimonioReferenciaId`) e
        snapshot do bem (descrição; código de cadastro e plaqueta opcionais).
        Quando NÃO exigeBem: a descrição do chamado é obrigatória.
        O bens_id não é exibido; o usuário vê plaqueta e código.
        Fotos: até 5 ids ou urls devolvidos pelo POST /arquivos (obrigatórias se exigeFoto).
        Lat/lng: obrigatórios se exigeLocalizacao.

        Campos da API em português.
      properties:
        tipoServico:
          type: string
          description: Código do tipo de serviço (catálogo / zl_tipo_servico.codigo)
          example: iluminacao
          maxLength: 50
          minLength: 0
        persona:
          type: string
          description: Persona do solicitante. Omitir = cidadão.
          enum:
            - cidadao
            - servidor
          example: cidadao
        titulo:
          type: string
          description: Título. Se omitido, usa o nome do tipo de serviço
          maxLength: 255
          minLength: 0
        descricao:
          type: string
          description: Descrição livre. Obrigatória quando o tipo não exige bem (máx. 500).
          maxLength: 500
          minLength: 0
        endereco:
          type: string
          description: Endereço textual obtido por geocoding ou informado pelo usuário
          maxLength: 500
          minLength: 0
        latitude:
          type: number
          description: Latitude do pin no mapa (obrigatória se o tipo exige localização)
          example: -15.601
          maximum: 90
          minimum: -90
        longitude:
          type: number
          description: Longitude do pin no mapa (obrigatória se o tipo exige localização)
          example: -56.0978
          maximum: 180
          minimum: -180
        fotos:
          type: array
          description: Até 5 fotos. Envie o `id` ou a `url` devolvidos pelo POST /arquivos.
          items:
            type: string
          maxItems: 5
          minItems: 0
        patrimonioOrgaoCodigo:
          type: string
          description: Código do órgão ADM (número, não o id interno). Snapshot no chamado.
          maxLength: 20
          minLength: 0
        patrimonioOrgaoDescricao:
          type: string
          description: Nome do órgão no momento da abertura. Snapshot se o cadastro for excluído.
          maxLength: 255
          minLength: 0
        patrimonioReferenciaId:
          type: string
          description: bens_id único do Administrativo. Interno; o usuário não vê. Obrigatório no servidor.
          maxLength: 100
          minLength: 0
        patrimonioBemDescricao:
          type: string
          description: Descrição do bem no momento da abertura. Snapshot se o cadastro for excluído.
          maxLength: 1000
          minLength: 0
        patrimonioBemCodigo:
          type: integer
          format: int32
          description: Código de cadastro (bens_codigo). Visível ao usuário; distinto do bens_id.
        patrimonioBemPlaqueta:
          type: integer
          format: int32
          description: Número da plaqueta (bens_cod_plaqueta). Visível ao usuário; distinto do bens_id.
        patrimonioTipo:
          type: string
          description: Tipo do bem patrimonial, quando informado
          enum:
            - movel
            - imovel
    ChamadoResponseDTO:
      type: object
      description: Chamado persistido. Código da API em português + rótulos de exibição.
      properties:
        id:
          type: string
          format: uuid
          description: Identificador técnico interno
          example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
        protocolo:
          type: string
          description: Número público do protocolo
          example: 202608170001
        tipoServico:
          type: string
          description: Código do tipo de serviço
          example: iluminacao
        tipoServicoNome:
          type: string
          description: Nome do tipo para exibição
          example: Iluminação Pública
        tipoServicoDescricao:
          type: string
          description: Descrição do tipo para exibição
          example: Lâmpadas queimadas, postes danificados
        titulo:
          type: string
          description: Título do chamado
          example: Iluminação Pública
        descricao:
          type: string
          description: Descrição informada pelo usuário
        urlFoto:
          type: string
          description: URL da primeira foto, quando houver
        fotos:
          type: array
          description: URLs das fotos, na ordem do anexo
          items:
            type: string
        latitude:
          type: number
          description: Latitude
          example: -15.601
        longitude:
          type: number
          description: Longitude
          example: -56.0978
        endereco:
          type: string
          description: Endereço textual
        status:
          type: string
          description: Situação do chamado
          enum:
            - aberto
            - em_analise
            - em_andamento
            - concluido
            - cancelado
          example: aberto
        statusDescricao:
          type: string
          description: Descrição da situação para exibição
          example: Aberto
        prioridade:
          type: string
          description: Prioridade
          enum:
            - baixa
            - media
            - alta
          example: media
        prioridadeDescricao:
          type: string
          description: Descrição da prioridade para exibição
          example: Média
        persona:
          type: string
          description: Persona que abriu o chamado
          enum:
            - cidadao
            - servidor
          example: cidadao
        personaDescricao:
          type: string
          description: Descrição da persona para exibição
          example: Cidadão
        criadoEm:
          type: string
          format: date-time
          description: Data/hora de criação
        nota:
          type: integer
          format: int32
          description: Nota da avaliação, quando existir
        comentario:
          type: string
          description: Comentário da avaliação, quando existir
        historico:
          type: array
          description: Linha do tempo de status
          items:
            $ref: "#/components/schemas/HistoricoEventoDTO"
        patrimonioOrgaoCodigo:
          type: string
          description: Código do órgão (somente servidor). Snapshot.
        patrimonioOrgaoDescricao:
          type: string
          description: Nome do órgão no momento da abertura (somente servidor). Snapshot.
        patrimonioReferenciaId:
          type: string
          description: bens_id no ADM (somente servidor). Interno; o usuário não vê.
        patrimonioBemDescricao:
          type: string
          description: Descrição do bem no momento da abertura (somente servidor). Snapshot.
        patrimonioBemCodigo:
          type: integer
          format: int32
          description: Código de cadastro (bens_codigo). Visível ao usuário; distinto do bens_id.
        patrimonioBemPlaqueta:
          type: integer
          format: int32
          description: Número da plaqueta (bens_cod_plaqueta). Visível ao usuário; distinto do bens_id.
        patrimonioTipo:
          type: string
          description: Tipo de patrimônio
          enum:
            - movel
            - imovel
          example: movel
        patrimonioTipoDescricao:
          type: string
          description: Descrição do tipo de patrimônio para exibição
          example: Móvel
        bensOrdemServicoId:
          type: integer
          format: int32
          description: Id da solicitação (pré-OS) em administrativo.bens_ordem_servico, quando criada
        bensOrdemServicoNumero:
          type: integer
          format: int64
          description: Número da solicitação no ADM
        bensOrdemServicoAno:
          type: integer
          format: int32
          description: Ano da solicitação no ADM
    HistoricoEventoDTO:
      type: object
      description: Evento do histórico do chamado
      properties:
        status:
          type: string
          description: Situação registrada no evento
          enum:
            - aberto
            - em_analise
            - em_andamento
            - concluido
            - cancelado
          example: aberto
        statusDescricao:
          type: string
          description: Descrição da situação para exibição
          example: Aberto
        mensagem:
          type: string
          description: Mensagem do evento
        criadoEm:
          type: string
          format: date-time
          description: Data/hora do evento
        tipoUsuario:
          type: string
          description: Tipo de usuário que gerou o evento
          example: cidadao
        tipoUsuarioDescricao:
          type: string
          description: Descrição do tipo de usuário para exibição
          example: Cidadão
    AvaliacaoRequestDTO:
      type: object
      description: Avaliação do atendimento. Nota 1-5; comentário opcional.
      properties:
        nota:
          type: integer
          format: int32
          description: Nota de 1 a 5
          example: 4
          maximum: 5
          minimum: 1
        comentario:
          type: string
          description: Comentário opcional
          maxLength: 1000
          minLength: 0
      required:
        - nota
    AvaliacaoResponseDTO:
      type: object
      description: Avaliação registrada para um chamado concluído
      properties:
        id:
          type: string
          format: uuid
          description: Identificador da avaliação
        chamadoId:
          type: string
          format: uuid
          description: Identificador do chamado avaliado
        nota:
          type: integer
          format: int32
          description: Nota de 1 a 5
          example: 5
        comentario:
          type: string
          description: Comentário opcional
        criadoEm:
          type: string
          format: date-time
          description: Data/hora do registro
    ArquivoUploadResponseDTO:
      type: object
      description: Metadados do arquivo após o upload. Envie `url` ou `id` em POST /chamados (fotos).
      properties:
        id:
          type: string
          format: uuid
          description: Identificador do arquivo
        url:
          type: string
          description: URL pública de download
        nome:
          type: string
          description: Nome original do arquivo
          example: poste.jpg
        tipoConteudo:
          type: string
          description: MIME type
          example: image/jpeg
        tamanho:
          type: integer
          format: int64
          description: Tamanho em bytes
          example: 184320
    AtualizarStatusRequestDTO:
      type: object
      description: Atualização de status do chamado
      properties:
        status:
          type: string
          description: Novo status
          enum:
            - aberto
            - em_analise
            - em_andamento
            - concluido
            - cancelado
          example: em_analise
        mensagem:
          type: string
          description: Mensagem do histórico. Se omitida, usa texto padrão.
          maxLength: 500
          minLength: 0
      required:
        - status
    PerfilServidorDTO:
      type: object
      description: Indica se o usuário autenticado pode atuar como servidor
      properties:
        servidor:
          type: boolean
          description: true quando o CPF tem persona servidor neste município
    PatrimonioCadastroItemDTO:
      type: object
      description: Item de cadastro do ADM (unidade, setor ou local físico)
      properties:
        id:
          type: integer
          format: int32
        codigo:
          type: string
        descricao:
          type: string
    PatrimonioCadastroListaDTO:
      type: object
      description: Lista de cadastro auxiliar do Administrativo
      properties:
        itens:
          type: array
          description: Itens retornados pelo ADM
          items:
            $ref: "#/components/schemas/PatrimonioCadastroItemDTO"
    OrgaoAdmDTO:
      type: object
      description: Órgão administrativo (`ws_pat_orgao_adm_lista`), no formato do portal-patrimonio-mobile
      properties:
        id:
          type: integer
          format: int32
        codigo:
          type: string
        descricao:
          type: string
    OrgaoListaResponseDTO:
      type: object
      description: Lista de órgãos do Administrativo. Sucesso é este objeto (não um envelope genérico).
      properties:
        itens:
          type: array
          description: Órgãos retornados pelo ADM
          items:
            $ref: "#/components/schemas/OrgaoAdmDTO"
    BemListaResponseDTO:
      type: object
      description: Lista de bens do órgão. `integracaoPendente` indica falha temporária no ADM.
      properties:
        itens:
          type: array
          description: Bens retornados pelo ADM
          items:
            $ref: "#/components/schemas/BemResumoDTO"
        integracaoPendente:
          type: boolean
          description: true quando a integração com o ADM não respondeu
    BemResumoDTO:
      type: object
      description: Bem do Administrativo, no formato usado pelo portal-patrimonio-mobile (ws_pat_bens_lista)
      properties:
        bensId:
          type: integer
          format: int32
        bensCodigo:
          type: integer
          format: int32
        bensCodPlaqueta:
          type: integer
          format: int32
        bensDescricao:
          type: string
        bensDataAquisicao:
          type: string
        bensEstadoAtual:
          type: integer
          format: int32
        bensTipo:
          type: integer
          format: int32
        bensSetorId:
          type: integer
          format: int32
        bensSetorOrgaoCodigo:
          type: string
        bensSetorUnidadeCodigo:
          type: string
        bensSetorCodigo:
          type: string
        bensSetorDescricao:
          type: string
    ChamadoAnexoItemDTO:
      type: object
      description: Foto vinculada a um chamado.
      properties:
        id:
          type: string
          format: uuid
          description: Identificador do vínculo anexo
        chamadoId:
          type: string
          format: uuid
          description: Identificador do chamado
        arquivoId:
          type: string
          format: uuid
          description: Identificador do arquivo
        url:
          type: string
          description: URL pública de download
        nome:
          type: string
          description: Nome original do arquivo
        tipoConteudo:
          type: string
          description: MIME type
        ordem:
          type: integer
          format: int32
          description: Ordem de exibição, a partir de 0
    ChamadoAnexoListaDTO:
      type: object
      description: Listagem em lote das fotos de um chamado.
      properties:
        chamadoId:
          type: string
          format: uuid
          description: Identificador do chamado
        fotos:
          type: array
          description: Fotos na ordem de anexo
          items:
            $ref: "#/components/schemas/ChamadoAnexoItemDTO"
    DashboardKpisDTO:
      type: object
      description: KPIs da home do mobile, por persona do usuário autenticado
      properties:
        total:
          type: integer
          format: int32
          description: Total de chamados da persona
          example: 12
        emAndamento:
          type: integer
          format: int32
          description: Abertos, em análise ou em andamento
          example: 7
        concluidos:
          type: integer
          format: int32
          description: Concluídos
          example: 4
        avaliacaoMedia:
          type: number
          format: double
          description: Média das avaliações, quando houver
    GestorDashboardKpisDTO:
      type: object
      description: Dashboard municipal de chamados (gestão)
      properties:
        total:
          type: integer
          format: int32
          description: Total de chamados da persona no município
        abertos:
          type: integer
          format: int32
          description: Status aberto
        emAnalise:
          type: integer
          format: int32
          description: Status em análise
        emAndamento:
          type: integer
          format: int32
          description: Status em andamento
        concluidos:
          type: integer
          format: int32
          description: Status concluído
        cancelados:
          type: integer
          format: int32
          description: Status cancelado
        filaAtiva:
          type: integer
          format: int32
          description: "Fila ativa: aberto + em análise + em andamento"
        totalAvaliacoes:
          type: integer
          format: int32
          description: Quantidade de chamados com avaliação
        avaliacaoMedia:
          type: number
          format: double
          description: Média das notas (1–5), null se não houver avaliações
    ApiErrorResponseDTO:
      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:
      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: tipoServico
        mensagem:
          type: string
          description: Mensagem descritiva do erro
          example: O tipo de serviço é obrigatório
