openapi: 3.0.3
info:
  title: NFeMais API
  version: 1.0.0
  description: |
    O [NFeMais](https://www.nfemais.com.br) disponibiliza uma API REST que permite o acesso
    aos contatos, produtos, vendas e demais módulos do sistema.

    ## Autenticação
    A API utiliza **OAuth2** como forma de autenticação. O `access_token` possui validade de
    **15 minutos**. Utilize o `refresh_token` para solicitar um novo sem pedir login novamente.

    ## Rate Limiting
    Limite de **60 requisições por minuto** por aplicação+usuário.
    Os headers `X-RateLimit-Limit` e `X-RateLimit-Remaining` são retornados em todas as respostas.

    ## Paginação
    As listagens retornam **50 registros por página**. Use o parâmetro `page` para navegar.

    ## Códigos de resposta
    | Código | Descrição |
    |--------|-----------|
    | `200`  | Sucesso |
    | `400`  | Dados inválidos ou campos ausentes |
    | `401`  | Token de acesso inválido |
    | `404`  | Registro não encontrado |
    | `410`  | Registro foi excluído do sistema |
    | `422`  | Dados fora do escopo definido |
    | `429`  | Limite de requisições atingido |

    ## Contato
    Dúvidas e solicitações: kasper@zipline.com.br

servers:
  - url: https://v4.nfemais.com.br/api
    description: Produção

security:
  - bearerAuth: []

# =============================================================================
# COMPONENTS
# =============================================================================
components:

  securitySchemes:
    bearerAuth:
      type: apiKey
      in: header
      name: Authorization
      description: "Token Bearer obtido via `/oauth/access_token`. Formato: `Bearer <access_token>`"

  parameters:
    pathCodigo:
      name: codigo
      in: path
      required: true
      schema:
        type: integer
    queryFiltro:
      name: filtro
      in: query
      schema:
        type: string
      description: Busca textual nos campos principais do recurso
    queryPage:
      name: page
      in: query
      schema:
        type: integer
        default: 1
    queryOrderBy:
      name: orderBy
      in: query
      schema:
        type: string
      description: "Ordenação: `campo,asc` ou `campo,desc`"
    queryUpdatedAfter:
      name: updatedAfter
      in: query
      schema:
        type: string
        format: date-time
      description: Filtra registros atualizados após esta data (requer ativação pelo suporte)
    queryUpdatedBefore:
      name: updatedBefore
      in: query
      schema:
        type: string
        format: date-time
      description: Filtra registros atualizados antes desta data (requer ativação pelo suporte)
    queryFields:
      name: fields
      in: query
      schema:
        type: string
      description: Campos a retornar, separados por vírgula
    queryDtIni:
      name: dtIni
      in: query
      schema:
        type: string
        format: date
      description: Data inicial (YYYY-MM-DD)
    queryDtFim:
      name: dtFim
      in: query
      schema:
        type: string
        format: date
      description: Data final (YYYY-MM-DD)

  schemas:

    # ── Paginação ──────────────────────────────────────────────
    PaginatedMeta:
      type: object
      properties:
        total:        { type: integer, example: 100 }
        per_page:     { type: integer, example: 50 }
        current_page: { type: integer, example: 1 }
        last_page:    { type: integer, example: 2 }
        next_page_url:
          type: string
          nullable: true
          example: "https://api.nfemais.com.br/v1/contatos?page=2"
        prev_page_url:
          type: string
          nullable: true
          example: null
        from: { type: integer, example: 1 }
        to:   { type: integer, example: 50 }

    # ── Respostas genéricas ───────────────────────────────────
    ErrorResponse:
      type: object
      properties:
        errCode:   { type: integer, example: 404 }
        errMsg:    { type: string,  example: "Nenhum registro com código 1 encontrado" }
        errObs:    { type: string,  nullable: true }
        errFields: { type: object,  nullable: true }

    SuccessResponse:
      type: object
      properties:
        code:   { type: integer, example: 200 }
        msg:    { type: string,  example: "Operação realizada com sucesso." }
        obs:    { type: string,  nullable: true }
        fields: { type: object,  nullable: true }

    # ── Contatos ──────────────────────────────────────────────
    ContatoSummary:
      type: object
      properties:
        codigo:  { type: integer, readOnly: true, example: 1 }
        tipo:
          type: array
          items: { type: string, enum: [cliente, fornecedor, transportadora] }
          example: ["cliente"]
        nome:    { type: string, example: "Empresa Exemplo LTDA" }
        emails:  { type: array, items: { type: string } }
        fones:   { type: array, items: { type: string } }
        tags:    { type: array, items: { type: string } }
        cidade:  { type: string }
        uf:      { type: string, example: "SP" }

    ContatoDetalhado:
      type: object
      properties:
        codigo:             { type: integer, readOnly: true }
        tipo:
          type: array
          items: { type: string, enum: [cliente, fornecedor, transportadora] }
        nome:               { type: string, maxLength: 60 }
        fantasia:           { type: string, maxLength: 60 }
        nomeContato:        { type: string, maxLength: 60 }
        cpfcnpj:            { type: string, example: "83294489654" }
        clienteFinal:       { type: boolean }
        indicadorIE:
          type: integer
          enum: [1, 2, 9]
          description: "1=Contribuinte ICMS, 2=Isento de IE, 9=Não contribuinte"
        inscricaoEstadual:  { type: string }
        inscricaoEstadualST: { type: string }
        inscricaoMunicipal: { type: string }
        suframa:            { type: string }
        emails:  { type: array, items: { type: string } }
        fones:   { type: array, items: { type: string } }
        logradouro:  { type: string }
        numero:      { type: string }
        complemento: { type: string }
        bairro:      { type: string }
        cidade:      { type: string }
        codIBGE:     { type: string, example: "355030" }
        uf:          { type: string, example: "SP" }
        cep:         { type: string }
        pais:        { type: string }
        tags:    { type: array, items: { type: string } }
        obs:     { type: string }
        dtNasc:  { type: string, format: date,      example: "1990-05-12" }
        dtCad:   { type: string, format: date-time, readOnly: true }

    ContatoInput:
      type: object
      required: [nome, tipo]
      properties:
        nome:           { type: string, maxLength: 60 }
        fantasia:       { type: string, maxLength: 60 }
        nomeParaContato: { type: string }
        tipo:
          type: array
          items: { type: string, enum: [cliente, fornecedor, transportadora] }
        cpfcnpj:        { type: string }
        dtNasc:         { type: string, format: date }
        emails:         { type: array, items: { type: string } }
        fones:          { type: array, items: { type: string } }
        cep:            { type: integer }
        logradouro:     { type: string }
        numero:         { type: string }
        complemento:    { type: string }
        bairro:         { type: string }
        cidade:         { type: string }
        codIBGE:        { type: string }
        uf:             { type: string, minLength: 2, maxLength: 2 }
        pais:           { type: string }
        clienteFinal:   { type: boolean }
        indicadorIE:    { type: integer, enum: [1, 2, 9] }
        inscricaoMunicipal:  { type: string }
        inscricaoEstadual:   { type: string }
        inscricaoEstadualST: { type: string }
        suframa:        { type: string }
        obs:            { type: string }
        tags:           { type: array, items: { type: string } }

    # ── Produtos ──────────────────────────────────────────────
    ProdutoBase:
      type: object
      properties:
        codigo:           { type: integer, readOnly: true }
        descricao:        { type: string }
        codigoProprio:    { type: string }
        estoque:          { type: number }
        estoqueMinimo:    { type: number }
        controlarEstoque: { type: boolean }
        margemLucro:      { type: number }
        precoCusto:       { type: number }
        precoVenda:       { type: number }
        origemFiscal:
          type: integer
          enum: [0,1,2,3,4,5,6,7,8]
          description: "0=Nacional, 1..8=Estrangeira (ver tabela ICMS)"
        unidadeTributada: { type: string, example: "UN" }
        refEanGtin:       { type: string }
        ncm:              { type: string }
        excecaoIPI:       { type: integer }
        codigoCEST:       { type: string }
        pesoBruto:        { type: number }
        pesoLiquido:      { type: number }
        codigoGrupoTributos: { type: integer }
        anotacoesNFE:     { type: string }
        anotacoesInternas: { type: string }
        tags:    { type: array, items: { type: string } }
        dtCad:   { type: string, format: date-time, readOnly: true }

    ProdutoInput:
      allOf:
        - $ref: '#/components/schemas/ProdutoBase'
        - type: object
          required: [descricao]

    # ── Serviços ──────────────────────────────────────────────
    ServicoBase:
      type: object
      properties:
        codigo:              { type: integer, readOnly: true }
        descricao:           { type: string }
        precoVenda:          { type: number }
        codigoGrupoTributos: { type: integer }
        itemListaServico:    { type: string, description: "Código LC116" }
        cnae:                { type: string }
        UcListServ:          { type: string }
        tags:    { type: array, items: { type: string } }
        dtCad:   { type: string, format: date-time, readOnly: true }

    ServicoInput:
      allOf:
        - $ref: '#/components/schemas/ServicoBase'
        - type: object
          required: [descricao]

    # ── Usuários ──────────────────────────────────────────────
    UsuarioSummary:
      type: object
      properties:
        codigo:        { type: integer, readOnly: true }
        nome:          { type: string }
        login:         { type: string }
        email:         { type: string }
        tags:          { type: array, items: { type: string } }
        usuarioSistema: { type: boolean }
        vendedor:      { type: boolean }
        comisProd:     { type: number }
        comisServ:     { type: number }
        comisFin:      { type: number }

    UsuarioDetalhado:
      allOf:
        - $ref: '#/components/schemas/UsuarioSummary'
        - type: object
          properties:
            permissoes:
              type: array
              items: { type: string }
              description: Lista de permissões do usuário

    # ── Categorias ────────────────────────────────────────────
    Categoria:
      type: object
      properties:
        codigo:   { type: integer, readOnly: true }
        descricao: { type: string }
        ativo:    { type: integer }

    CategoriaInput:
      type: object
      required: [descricao]
      properties:
        descricao: { type: string }

    # ── Config. Tributária ────────────────────────────────────
    ConfigTrib:
      type: object
      properties:
        codigo:   { type: integer, readOnly: true }
        descricao: { type: string }

    # ── NFe ───────────────────────────────────────────────────
    NFe:
      type: object
      properties:
        codigo:  { type: integer, readOnly: true }
        chNFe:   { type: string, description: "Chave de acesso (44 dígitos)" }
        nNF:     { type: string, description: "Número da nota fiscal" }
        dhEmi:   { type: string, format: date-time }
        situacao: { type: integer }
        tags:    { type: array, items: { type: string } }

    # ── Webhooks ──────────────────────────────────────────────
    Webhook:
      type: object
      properties:
        codigo: { type: integer, readOnly: true }
        url:    { type: string, format: uri }
        evento: { type: string }
        ativo:  { type: boolean }

    WebhookInput:
      type: object
      required: [url, evento]
      properties:
        url:    { type: string, format: uri }
        evento: { type: string }

  responses:
    Unauthorized:
      description: Token de acesso inválido ou ausente
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorResponse' }
          example:
            errCode: 401
            errMsg: "Não foi possível acessar o sistema. Verifique seu \"access_token\"."
            errObs: "access_denied"
            errFields: null
    NotFound:
      description: Registro não encontrado
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorResponse' }
          example:
            errCode: 404
            errMsg: "Nenhum registro com código 1 encontrado"
            errObs: null
            errFields: null
    Gone:
      description: Registro foi excluído do sistema
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorResponse' }
          example:
            errCode: 410
            errMsg: "Registro com código 1 não existe."
            errObs: null
            errFields: null
    BadRequest:
      description: Dados inválidos ou campos obrigatórios ausentes
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorResponse' }
    TooManyRequests:
      description: Limite de 60 requisições/minuto atingido
      headers:
        X-RateLimit-Limit:
          schema: { type: integer }
        X-RateLimit-Remaining:
          schema: { type: integer }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/ErrorResponse' }

# =============================================================================
# TAGS
# =============================================================================
tags:
  - name: Autenticação
    description: OAuth2 — obtenção e renovação de tokens
  - name: Contatos
    description: Clientes, fornecedores e transportadoras
  - name: Produtos
    description: Produtos para venda e controle de estoque
  - name: Serviços
    description: Serviços para venda
  - name: NFe
    description: Notas Fiscais Eletrônicas (produto)
  - name: Categorias
    description: Categorias de produtos/serviços
  - name: Usuários
    description: Usuários do sistema
  - name: Config. Tributária
    description: Grupos de tributação (configTrib)
  - name: Webhooks
    description: Notificações de eventos
  - name: Relatórios
    description: Relatórios gerenciais
  - name: Empresa
    description: Dados cadastrais da empresa

# =============================================================================
# PATHS
# =============================================================================
paths:

  # ── OAuth ──────────────────────────────────────────────────
  /oauth/access_token:
    post:
      tags: [Autenticação]
      summary: Obter access_token
      description: |
        Utiliza o `personal_token` JWT para obter um `access_token`.
        O `access_token` tem validade de **15 minutos**.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [grant_type, personal_token]
              properties:
                grant_type:
                  type: string
                  enum: [personal]
                personal_token: { type: string, description: "JWT obtido nas configurações da conta" }
            examples:
              personal_token:
                summary: Com personal_token
                value:
                  grant_type: personal
                  personal_token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
      x-postman:
        event:
          - listen: test
            script:
              type: text/javascript
              exec:
                - "var res = pm.response.json();"
                - "if (res.access_token) {"
                - "  pm.collectionVariables.set('bearerAuth', 'Bearer ' + res.access_token);"
                - "  console.log('✅ bearerAuth definido:', pm.collectionVariables.get('bearerAuth').substring(0, 30) + '...');"
                - "}"
      responses:
        '200':
          description: Token obtido com sucesso
          content:
            application/json:
              schema:
                type: object
                properties:
                  access_token:  { type: string }
                  token_type:    { type: string, example: "Bearer" }
                  expires_in:    { type: integer, example: 900, description: "Validade em segundos" }
                  refresh_token: { type: string }
                  personal_token: { type: string }

  # ── Contatos ───────────────────────────────────────────────
  /v1/contatos:
    get:
      tags: [Contatos]
      summary: Listar contatos
      parameters:
        - $ref: '#/components/parameters/queryFiltro'
        - $ref: '#/components/parameters/queryPage'
        - $ref: '#/components/parameters/queryOrderBy'
        - $ref: '#/components/parameters/queryUpdatedAfter'
        - $ref: '#/components/parameters/queryUpdatedBefore'
        - $ref: '#/components/parameters/queryFields'
        - name: tipo
          in: query
          schema: { type: string, enum: [cliente, fornecedor, transportadora] }
      responses:
        '200':
          description: Lista paginada de contatos
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/PaginatedMeta'
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: '#/components/schemas/ContatoSummary' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

    post:
      tags: [Contatos]
      summary: Criar contato
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ContatoInput' }
            example:
              nome: "Empresa Exemplo LTDA"
              fantasia: "Exemplo"
              tipo: ["cliente"]
              cpfcnpj: "83294489654"
              emails: ["exemplo@example.com.br"]
              fones: ["11999999999"]
              cep: 4320040
              logradouro: "Rua Exemplo"
              numero: "999"
              uf: "SP"
              clienteFinal: false
              indicadorIE: 9
      responses:
        '200':
          description: Contato criado
          content:
            application/json:
              schema:
                type: object
                properties:
                  codigo: { type: integer }
                  nome:   { type: string }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /v1/contatos/{codigo}:
    parameters:
      - $ref: '#/components/parameters/pathCodigo'
    get:
      tags: [Contatos]
      summary: Detalhar contato
      responses:
        '200':
          description: Dados completos do contato
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ContatoDetalhado' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '410': { $ref: '#/components/responses/Gone' }
    put:
      tags: [Contatos]
      summary: Editar contato
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ContatoInput' }
      responses:
        '200':
          description: Contato atualizado
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ContatoDetalhado' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      tags: [Contatos]
      summary: Excluir contato
      responses:
        '200':
          description: Contato excluído
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SuccessResponse' }
        '404': { $ref: '#/components/responses/NotFound' }

  # ── Produtos ───────────────────────────────────────────────
  /v1/produtos:
    get:
      tags: [Produtos]
      summary: Listar produtos
      parameters:
        - $ref: '#/components/parameters/queryFiltro'
        - $ref: '#/components/parameters/queryPage'
        - $ref: '#/components/parameters/queryOrderBy'
        - $ref: '#/components/parameters/queryUpdatedAfter'
        - $ref: '#/components/parameters/queryUpdatedBefore'
        - $ref: '#/components/parameters/queryFields'
      responses:
        '200':
          description: Lista paginada de produtos
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/PaginatedMeta'
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: '#/components/schemas/ProdutoBase' }
        '401': { $ref: '#/components/responses/Unauthorized' }
    post:
      tags: [Produtos]
      summary: Criar produto
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ProdutoInput' }
      responses:
        '200':
          description: Produto criado
          content:
            application/json:
              schema:
                type: object
                properties:
                  codigo:   { type: integer }
                  descricao: { type: string }
        '400': { $ref: '#/components/responses/BadRequest' }

  /v1/produtos/{codigo}:
    parameters:
      - $ref: '#/components/parameters/pathCodigo'
    get:
      tags: [Produtos]
      summary: Detalhar produto
      responses:
        '200':
          description: Dados completos do produto
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ProdutoBase' }
        '404': { $ref: '#/components/responses/NotFound' }
        '410': { $ref: '#/components/responses/Gone' }
    put:
      tags: [Produtos]
      summary: Editar produto
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ProdutoInput' }
      responses:
        '200':
          description: Produto atualizado
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ProdutoBase' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      tags: [Produtos]
      summary: Excluir produto
      responses:
        '200':
          description: Produto excluído
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SuccessResponse' }
        '404': { $ref: '#/components/responses/NotFound' }

  # ── Serviços ───────────────────────────────────────────────
  /v1/servicos:
    get:
      tags: [Serviços]
      summary: Listar serviços
      parameters:
        - $ref: '#/components/parameters/queryFiltro'
        - $ref: '#/components/parameters/queryPage'
        - $ref: '#/components/parameters/queryOrderBy'
      responses:
        '200':
          description: Lista paginada de serviços
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/PaginatedMeta'
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: '#/components/schemas/ServicoBase' }
    post:
      tags: [Serviços]
      summary: Criar serviço
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ServicoInput' }
      responses:
        '200':
          description: Serviço criado
          content:
            application/json:
              schema:
                type: object
                properties:
                  codigo:   { type: integer }
                  descricao: { type: string }

  /v1/servicos/{codigo}:
    parameters:
      - $ref: '#/components/parameters/pathCodigo'
    get:
      tags: [Serviços]
      summary: Detalhar serviço
      responses:
        '200':
          description: Dados do serviço
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ServicoBase' }
        '404': { $ref: '#/components/responses/NotFound' }
        '410': { $ref: '#/components/responses/Gone' }
    put:
      tags: [Serviços]
      summary: Editar serviço
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ServicoInput' }
      responses:
        '200':
          description: Serviço atualizado
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ServicoBase' }
    delete:
      tags: [Serviços]
      summary: Excluir serviço
      responses:
        '200':
          description: Serviço excluído
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SuccessResponse' }

  # ── NFe ────────────────────────────────────────────────────
  /v1/nfe:
    get:
      tags: [NFe]
      summary: Listar NF-es emitidas
      parameters:
        - name: dtIni
          in: query
          required: true
          schema: { type: string, format: date }
        - name: dtFim
          in: query
          required: true
          schema: { type: string, format: date }
        - $ref: '#/components/parameters/queryFiltro'
        - $ref: '#/components/parameters/queryPage'
      responses:
        '200':
          description: Lista paginada de NF-es
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/PaginatedMeta'
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: '#/components/schemas/NFe' }

  /v1/nfe/salvar:
    post:
      tags: [NFe]
      summary: Salvar NF-e a partir de XML
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                xml:      { type: string, description: "Conteúdo XML da NF-e" }
                codVenda: { type: integer, description: "Venda a vincular" }
                enviar:   { type: boolean, description: "Transmite automaticamente" }
      responses:
        '200':
          description: NF-e salva com sucesso

  /v1/nfe/{codigo}:
    parameters:
      - $ref: '#/components/parameters/pathCodigo'
    get:
      tags: [NFe]
      summary: Detalhar NF-e
      responses:
        '200':
          description: Dados da NF-e
          content:
            application/json:
              schema: { $ref: '#/components/schemas/NFe' }
        '404': { $ref: '#/components/responses/NotFound' }

  /v1/nfe/{codigo}/xml:
    get:
      tags: [NFe]
      summary: Download do XML da NF-e
      parameters:
        - $ref: '#/components/parameters/pathCodigo'
      responses:
        '200':
          description: Arquivo XML
          content:
            application/xml:
              schema: { type: string, format: binary }

  /v1/nfe/{codigo}/danfe:
    get:
      tags: [NFe]
      summary: Download do DANFE (PDF)
      parameters:
        - $ref: '#/components/parameters/pathCodigo'
      responses:
        '200':
          description: Arquivo PDF do DANFE
          content:
            application/pdf:
              schema: { type: string, format: binary }

  # ── Categorias ─────────────────────────────────────────────
  /v1/categorias:
    get:
      tags: [Categorias]
      summary: Listar categorias
      parameters:
        - $ref: '#/components/parameters/queryFiltro'
        - $ref: '#/components/parameters/queryPage'
        - $ref: '#/components/parameters/queryOrderBy'
      responses:
        '200':
          description: Lista paginada de categorias
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/PaginatedMeta'
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: '#/components/schemas/Categoria' }
    post:
      tags: [Categorias]
      summary: Criar categoria
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CategoriaInput' }
      responses:
        '200':
          description: Categoria criada
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Categoria' }

  /v1/categorias/{codigo}:
    parameters:
      - $ref: '#/components/parameters/pathCodigo'
    get:
      tags: [Categorias]
      summary: Detalhar categoria
      responses:
        '200':
          description: Dados da categoria
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Categoria' }
    put:
      tags: [Categorias]
      summary: Editar categoria
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CategoriaInput' }
      responses:
        '200':
          description: Categoria atualizada
    delete:
      tags: [Categorias]
      summary: Excluir categoria
      responses:
        '200':
          description: Categoria excluída
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SuccessResponse' }

  # ── Usuários ───────────────────────────────────────────────
  /v1/usuarios:
    get:
      tags: [Usuários]
      summary: Listar usuários
      parameters:
        - $ref: '#/components/parameters/queryFiltro'
        - $ref: '#/components/parameters/queryPage'
        - $ref: '#/components/parameters/queryOrderBy'
        - $ref: '#/components/parameters/queryFields'
        - name: vendedor
          in: query
          schema: { type: boolean }
        - name: usuarioSistema
          in: query
          schema: { type: boolean }
      responses:
        '200':
          description: Lista paginada de usuários
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/PaginatedMeta'
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: '#/components/schemas/UsuarioSummary' }
  /v1/usuarios/{userId}:
    parameters:
      - name: userId
        in: path
        required: true
        schema: { type: integer }
    get:
      tags: [Usuários]
      summary: Detalhar usuário
      responses:
        '200':
          description: Dados do usuário
          content:
            application/json:
              schema: { $ref: '#/components/schemas/UsuarioDetalhado' }
        '404': { $ref: '#/components/responses/NotFound' }

  # ── Config. Tributária ─────────────────────────────────────
  /v1/configTrib:
    get:
      tags: [Config. Tributária]
      summary: Listar grupos de tributação
      parameters:
        - $ref: '#/components/parameters/queryFiltro'
        - $ref: '#/components/parameters/queryPage'
      responses:
        '200':
          description: Lista paginada de configurações tributárias
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/PaginatedMeta'
                  - type: object
                    properties:
                      data:
                        type: array
                        items: { $ref: '#/components/schemas/ConfigTrib' }

  /v1/configTrib/{codigo}:
    parameters:
      - $ref: '#/components/parameters/pathCodigo'
    get:
      tags: [Config. Tributária]
      summary: Detalhar configuração tributária
      responses:
        '200':
          description: Dados do grupo de tributação
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ConfigTrib' }

  # ── Webhooks ───────────────────────────────────────────────
  /v1/webhooks:
    get:
      tags: [Webhooks]
      summary: Listar webhooks cadastrados
      responses:
        '200':
          description: Lista de webhooks
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Webhook' }
    post:
      tags: [Webhooks]
      summary: Cadastrar webhook
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/WebhookInput' }
      responses:
        '200':
          description: Webhook cadastrado
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Webhook' }

  # ── Relatórios ─────────────────────────────────────────────
  /v1/relatorios/{relatorio}:
    post:
      tags: [Relatórios]
      summary: Gerar relatório
      description: |
        Relatórios disponíveis:

        **Fiscal:** `nfeResumo`, `nfeTotaisPorPeriodo`

        **Contatos:** `personalizadoContatos`

        **Estoque/Produtos:** `personalizadoProdutos`

        **Sistema:** `registroEventos`, `registroAcessos`
      parameters:
        - name: relatorio
          in: path
          required: true
          schema:
            type: string
            enum:
              - nfeResumo
              - nfeTotaisPorPeriodo
              - personalizadoContatos
              - personalizadoProdutos
              - registroEventos
              - registroAcessos
          description: Identificador do relatório
      requestBody:
        content:
          application/json:
            schema:
              type: object
              description: Parâmetros do relatório (variam por tipo)
              properties:
                dtIni: { type: string, format: date }
                dtFim: { type: string, format: date }
      responses:
        '200':
          description: Dados do relatório
          content:
            application/json:
              schema: { type: object }

  # ── Empresa ────────────────────────────────────────────────
  /v1/empresa:
    get:
      tags: [Empresa]
      summary: Dados cadastrais da empresa
      responses:
        '200':
          description: Dados da empresa
          content:
            application/json:
              schema:
                type: object
                properties:
                  razaoSocial: { type: string }
                  fantasia:    { type: string }
                  cnpj:        { type: string }
                  ie:          { type: string }
                  im:          { type: string }
                  email:       { type: string }
                  fone:        { type: string }
                  logradouro:  { type: string }
                  numero:      { type: string }
                  complemento: { type: string }
                  bairro:      { type: string }
                  cidade:      { type: string }
                  uf:          { type: string }
                  cep:         { type: string }
