openapi: 3.1.0
info:
  title: API de Produtos
  description: Gerenciamento de produtos, preços, estoque, locais de estoque, galeria de imagens e media.
  version: 1.0.0
  contact:
    name: UniSupri
servers:
  - url: https://api.sandbox.samdevel.com.br
    description: Sandbox (ambiente de homologação)
tags:
  - name: Produtos
    description: |-
      Gerencie o catálogo de produtos, incluindo especificações, dimensões e disponibilidade.
      
      **Campos principais:**
      - `sku`: código único do produto na loja
      - `price_type`: tipo de precificação. Use `default` (unitário) ou `wholesale` (atacado com faixas)
      - `base_unit`: unidade de medida (un, kg, pc, cx, pct, lt, mt, m2, m3)
      - `dimensions`: peso e dimensões para cálculo de frete (height, width, length em cm; weight em kg)
      - `is_available_for_sale`: controla se o produto aparece como disponível no catálogo
      - `is_industrializable`: indica se pode ser fabricado sob demanda (produção)
  - name: Dados Auxiliares
    description: |-
      Endpoints públicos para obter valores válidos de enums.
      
      Use antes de cadastrar produtos para obter os valores aceitos nos campos `base_unit` e `price_type`. Estas rotas **não exigem autenticação**.
  - name: Galeria de Imagens
    description: |-
      Gerencie até 6 fotos por produto, com thumbnail e reordenação.
      
      Você não vai encontrar uma rota que receba o arquivo da imagem direto aqui: o upload é centralizado em `POST /api/media/upload`, que devolve um `image_id`. A galeria trabalha sempre com esse id — assim a mesma imagem pode ser reaproveitada e o envio de arquivos fica num único lugar.
      
      **Fluxo de uso:**
      1. Faça upload da imagem via `POST /api/media/upload`
      2. Vincule à galeria via `POST /api/products/{id}/gallery` usando o `image_id` retornado
      3. A primeira imagem é definida automaticamente como thumbnail
      4. Reordene com `PUT /api/products/{id}/gallery/reorder`
  - name: Preços de Produtos
    description: |-
      Configure estratégias de precificação para seus produtos.
      
      **Tipos de preço:**
      - `default`: preço único para venda unitária (apenas 1 faixa com min_quantity=1)
      - `wholesale`: tabela de atacado com até 5 faixas de preço progressivas por quantidade mínima
      
      **Regras gerais:**
      - Máximo de **5 faixas** de preço por produto
      - Produtos `default` aceitam apenas 1 preço com `min_quantity = 1`
      - O preço base (a faixa com `min_quantity = 1`) é o piso da tabela e não pode ser removido: é ele que define o valor de venda unitária e serve de base para as demais faixas. Para zerar a tabela de atacado, remova as faixas maiores e mantenha apenas essa.
      - Para voltar o `price_type` de `wholesale` para `default`, deixe só o preço base: remova antes as faixas extras, senão a troca é recusada
      
      **Medidas de embalagem (atacado):**
      Faixas de atacado (`min_quantity >= 2`) descrevem a embalagem fechada vendida — caixa/fardo — usada para calcular o frete da venda fracionada. Os campos `package_weight`, `package_height`, `package_width` e `package_length` são **tudo-ou-nada**: ou os quatro vêm preenchidos, ou nenhum (preenchimento parcial retorna 422).
      
      A **primeira faixa de atacado configurada precisa trazer as medidas completas** — é a embalagem de referência do produto. Faixas seguintes podem ser criadas sem medidas, e nesse caso herdam a referência da faixa já medida; o produto nunca pode ficar sem ao menos uma faixa medida (a remoção da última faixa medida com outras faixas dependentes é recusada).
      
      **Atacado automático (`auto_wholesale_pricing` no produto):**
      Quando ligado, o preço de cada faixa `>= 2` deixa de ser informado manualmente e passa a ser **derivado do preço base unitário menos o `discount_percent`** da faixa. Nesse modo:
      - é obrigatório existir o preço base (`min_quantity = 1`) antes de criar faixas de atacado;
      - os descontos precisam ser **progressivos**: faixa de quantidade maior exige `discount_percent` maior (preço sempre decrescente);
      - alterar o preço base recalcula automaticamente todas as faixas.
  - name: Estoque de Produtos
    description: |-
      Gerencie o inventário dos produtos nos locais de estoque.
      
      **Conceitos:**
      - `quantity`: estoque físico total
      - `reserved_quantity`: unidades reservadas em carrinhos/pedidos
      - `available_quantity`: disponível para venda (quantity - reserved_quantity)
      
      **Movimentação** (flags no body do POST):
      - sem flag: define o valor absoluto do estoque
      - `increment: true`: adiciona ao estoque atual
      - `decrement: true`: subtrai do estoque atual
      
      > Locais do tipo **Fulfillment** têm o estoque sincronizado pelo WMS (o sistema do centro de distribuição), então esta rota não aceita alterações neles — o número viria do WMS e seria sobrescrito de qualquer forma. Ajuste o estoque apenas nos seus próprios locais (`seller_location`); os de Fulfillment se atualizam sozinhos conforme as movimentações no armazém.
  - name: Locais de Estoque
    description: |-
      Gerencie depósitos, centros de distribuição e lojas físicas que armazenam seus produtos.
      
      **Tipos de local:**
      - `seller_location`: depósito ou loja própria do vendedor
      - `fulfillment`: centro de distribuição gerenciado pela plataforma (somente leitura)
      
      **Status:**
      - `available`: local operacional, disponível para venda e cálculo de frete
      - `unavailable`: local desativado, produtos não aparecem como disponíveis
      
      Cada local pode ter múltiplos endereços vinculados (origem de envio, endereço fiscal, etc.).
  - name: Enderecos de Estoque
    description: |-
      Gerencie endereços físicos vinculados aos locais de estoque.
      
      Cada endereço define um ponto de origem para cálculo de frete e emissão de notas fiscais. Um local pode ter vários endereços com tipos diferentes (envio, fiscal, etc.).
paths:
  "/api/global/enums/product/units":
    get:
      tags:
        - Dados Auxiliares
      summary: Listar Unidades de Medida
      description: Retorna os valores válidos para o campo `base_unit` dos produtos. Rota pública, não requer autenticação.
      responses:
        "200":
          description: Lista de unidades de medida
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        value:
                          type: string
                        label:
                          type: string
                        description:
                          type: string
              example:
                success: true
                message_code: SUCCESS
                data:
                  - value: un
                    label: Unidade
                    description: Itens individuais (padrão).
                  - value: pc
                    label: Peça
                    description: Peças individuais.
                  - value: cx
                    label: Caixa
                    description: Caixas contendo múltiplos itens.
                  - value: pct
                    label: Pacote
                    description: Pacotes contendo múltiplos itens.
                  - value: kg
                    label: Quilograma
                    description: Produtos vendidos por peso em quilogramas.
                  - value: lt
                    label: Litro
                    description: Produtos líquidos vendidos por litro.
                  - value: mt
                    label: Metro
                    description: Produtos lineares vendidos por metro (cabos, tecidos, perfis).
                  - value: m2
                    label: Metro Quadrado
                    description: Produtos de área vendidos por metro quadrado (pisos, vidros, painéis).
                  - value: m3
                    label: Metro Cúbico
                    description: Produtos volumétricos vendidos por metro cúbico (madeiras, granéis).
  "/api/global/enums/product/price-types":
    get:
      tags:
        - Dados Auxiliares
      summary: Listar Tipos de Preço
      description: Retorna os valores válidos para o campo `price_type` dos produtos. Rota pública, não requer autenticação.
      responses:
        "200":
          description: Lista de tipos de preço
          content:
            "application/json":
              example:
                success: true
                message_code: SUCCESS
                data:
                  - value: default
                    label: Preço Padrão
                    description: Preço padrão para compras unitárias.
                  - value: wholesale
                    label: Preço de Atacado
                    description: Preço especial para compras em grandes quantidades.
  "/api/products":
    get:
      tags:
        - Produtos
      summary: Listar Produtos
      description: Retorna uma lista paginada de produtos do seller autenticado.
      parameters:
        - name: filter[search]
          in: query
          description: Busca geral (procura em nome e SKU)
          required: false
          schema:
            type: string
            example: camiseta
        - name: filter[sku]
          in: query
          description: Filtro específico por SKU
          required: false
          schema:
            type: string
            example: SKU-001
        - name: filter[name]
          in: query
          description: Filtro específico por Nome
          required: false
          schema:
            type: string
            example: Produto Exemplo
        - name: filter[price_type]
          in: query
          description: Filtro por tipo de preço
          required: false
          schema:
            type: string
            enum:
              - default
              - wholesale
            example: wholesale
        - name: filter[base_unit]
          in: query
          description: Filtro por unidade de medida
          required: false
          schema:
            type: string
            enum:
              - un
              - kg
              - pc
              - cx
              - pct
              - lt
              - mt
              - m2
              - m3
            example: un
        - name: filter[status]
          in: query
          description: |-
            Recorte por situação do produto:
            - `ativos`: disponíveis para venda
            - `vendendo`: ativos com pelo menos um anúncio ativo
            - `disponiveis`: ativos ainda sem nenhum anúncio (prontos para anunciar)
            - `sem-estoque`: ativos sem saldo de estoque
            - `inativos`: indisponíveis para venda
            - `industrializaveis`: fabricados sob demanda
          required: false
          schema:
            type: string
            enum:
              - ativos
              - vendendo
              - disponiveis
              - sem-estoque
              - inativos
              - industrializaveis
            example: ativos
        - name: sort
          in: query
          description: "Campo de ordenação. Prefixe com `-` para descendente. Valores permitidos: `name`, `sku`, `price_type`, `base_unit`, `created_at`, `updated_at`."
          required: false
          schema:
            type: string
            example: "-created_at"
        - name: include_dimensions_formatted
          in: query
          description: Incluir formatação amigável das dimensões na resposta
          required: false
          schema:
            type: boolean
            default: false
        - name: page
          in: query
          description: Número da página
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          description: Itens por página
          schema:
            type: integer
            default: 15
      responses:
        "200":
          description: Lista de produtos recuperada com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: The request was successful.
                data:
                  meta:
                    search_query: ""
                    filters: []
                    pagination:
                      page: 1
                      per_page: 15
                      last_page: 1
                      has_prev_page: false
                      has_next_page: false
                      records:
                        from: 1
                        to: 2
                        records: 2
                  data:
                    - id: 01H8X7...A
                      name: Produto Exemplo A
                      sku: SKU-001
                      price_type: default
                      quantity: 100
                      reserved_quantity: 5
                      available_quantity: 95
                      base_unit: un
                      dimensions:
                        width: 10
                        height: 10
                        length: 10
                        weight: 0.5
                      is_available_for_sale: true
                      is_industrializable: false
                      auto_wholesale_pricing: false
                      allow_sale_without_stock: false
                      thumbnail:
                        id: 01JMZXZ72881MD2P37N2K97YJJ
                        resources:
                          - name: thumbnail
                            size: 135x135
                            url: https://cdn.example.com/data/thumbnail_img.webp
                          - name: md
                            size: 400x400
                            url: https://cdn.example.com/data/md_img.webp
                      price: 50
                    - id: 01H8X7...B
                      name: Produto Exemplo B
                      sku: SKU-002
                      price_type: wholesale
                      quantity: 200
                      reserved_quantity: 0
                      available_quantity: 200
                      base_unit: kg
                      dimensions:
                        width: 20
                        height: 15
                        length: 30
                        weight: 1.2
                      is_available_for_sale: true
                      is_industrializable: false
                      auto_wholesale_pricing: false
                      allow_sale_without_stock: false
                      thumbnail: null
                      min_price: 40
                      max_price: 45
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
      security:
        - bearer: []
    post:
      tags:
        - Produtos
      summary: Criar Produto
      description: |-
        Cadastra um novo produto para o seller. O campo `sku` deve ser único por loja.
        
        Lojas que operam em **modo espelho** (subloja que herda o catálogo da matriz) não criam SKUs próprios — a tentativa retorna `422` com o código `SUBSTORE_CANNOT_CREATE_SKU`.
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ProductInput"
            example:
              name: Novo Produto Exemplo
              sku: PROD-NEW-001
              base_unit: un
              price_type: default
              dimensions:
                height: 15.5
                width: 10
                length: 20
                weight: 0.75
              is_available_for_sale: true
              is_industrializable: false
              allow_sale_without_stock: false
      responses:
        "201":
          description: Produto criado com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 201
                message_code: RESOURCE_CREATED
                description: Recurso criado com sucesso.
                data:
                  id: 01H8X...
                  name: Novo Produto Exemplo
                  sku: PROD-NEW-001
                  base_unit: un
                  dimensions:
                    width: 10
                    height: 15.5
                    length: 20
                    weight: 0.75
                  is_available_for_sale: true
                  is_industrializable: false
                  allow_sale_without_stock: false
                  price_type: default
                  auto_wholesale_pricing: false
                  prices: []
                  quantity: 0
                  reserved_quantity: 0
                  available_quantity: 0
                  thumbnail: null
                  images: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "409":
          "$ref": "#/components/responses/Duplicated"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
  "/api/products/{id}":
    get:
      tags:
        - Produtos
      summary: Detalhes do Produto
      description: Retorna detalhes de um produto específico, incluindo preços e estoque por local.
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
      responses:
        "200":
          description: Detalhes do produto
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                description: The request was successful.
                data:
                  product:
                    id: 01H8X...
                    name: Produto Exemplo A
                    sku: SKU-001
                    price_type: default
                    quantity: 100
                    reserved_quantity: 5
                    available_quantity: 95
                    base_unit: un
                    dimensions:
                      width: 10
                      height: 10
                      length: 10
                      weight: 0.5
                    is_available_for_sale: true
                    is_industrializable: false
                    auto_wholesale_pricing: false
                    allow_sale_without_stock: false
                    thumbnail:
                      id: 01JMZXZ72881MD2P37N2K97YJJ
                      resources:
                        - name: thumbnail
                          size: 135x135
                          url: https://cdn.example.com/data/thumbnail_img.webp
                        - name: md
                          size: 400x400
                          url: https://cdn.example.com/data/md_img.webp
                    price: 50
                  prices:
                    - value: 50
                      min_quantity: 1
                      discount_percent: null
                      package_weight: null
                      package_height: null
                      package_width: null
                      package_length: null
                  stock:
                    - location_stock:
                        location_id: 01H81AV32307PVBSV4RXF15LOC
                        store_id: 01H81AV32307PVBSV4RXF15ST1
                        name: Depósito Central
                        status: available
                        location_type: seller_location
                        allows_pickup: true
                        allows_production: false
                      quantity: 100
                      reserved_quantity: 5
                      available_quantity: 95
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    put:
      tags:
        - Produtos
      summary: Atualizar Produto
      description: |-
        Atualiza os dados de um produto existente. Os campos `name` e `dimensions` (completo) são obrigatórios em toda atualização; os demais são opcionais e mantêm o valor atual quando omitidos. O `sku` não pode ser alterado — se enviado, é simplesmente ignorado.
        
        **Atenção ao trocar `price_type`:** voltar de `wholesale` para `default` só é aceito quando o produto tem uma única faixa de preço com `min_quantity = 1`. A trava existe para não deixar faixas de atacado órfãs num produto que passou a ser unitário — se ainda houver faixas extras, remova-as primeiro (veja a API de Preços) e então faça a troca.
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ProductUpdateInput"
            examples:
              simple_update:
                summary: Atualização Simples (Nome e Tipo de Preço)
                value:
                  name: Produto Exemplo A - Atualizado
                  price_type: wholesale
                  dimensions:
                    width: 10
                    height: 10
                    length: 10
                    weight: 0.5
              full_update:
                summary: Atualização Completa (Todos os campos)
                value:
                  name: Produto Exemplo A - Revisado
                  base_unit: cx
                  price_type: default
                  dimensions:
                    width: 12.5
                    height: 10
                    length: 25
                    weight: 1.5
                  is_available_for_sale: true
                  is_industrializable: false
                  auto_wholesale_pricing: false
                  allow_sale_without_stock: false
      responses:
        "200":
          description: Produto atualizado com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: RESOURCE_UPDATED
                description: Recurso atualizado com sucesso.
                data:
                  id: 01H8X...
                  name: Produto Exemplo A - Atualizado
                  sku: SKU-001
                  base_unit: un
                  dimensions:
                    width: 10
                    height: 10
                    length: 10
                    weight: 0.5
                  is_available_for_sale: true
                  is_industrializable: false
                  allow_sale_without_stock: false
                  price_type: wholesale
                  auto_wholesale_pricing: false
                  prices:
                    - value: 50
                      min_quantity: 1
                      discount_percent: null
                      package_weight: null
                      package_height: null
                      package_width: null
                      package_length: null
                    - value: 40
                      min_quantity: 10
                      discount_percent: 20
                      package_weight: null
                      package_height: null
                      package_width: null
                      package_length: null
                  quantity: 100
                  reserved_quantity: 5
                  available_quantity: 95
                  thumbnail: null
                  images: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Duplicated"
        "422":
          description: Erro de validação ou regra de negócio
          content:
            "application/json":
              examples:
                validation:
                  summary: Erro de validação de campos
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      name:
                        - O campo name é obrigatório.
                      "dimensions.height":
                        - O campo dimensions.height é obrigatório.
                    meta: []
                price_type_change:
                  summary: Não é possível alterar price_type
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Não é possível alterar o tipo de preço para "default" pois existem múltiplos preços cadastrados.
                    data: []
                    errors:
                      - Não é possível alterar o tipo de preço para "default" pois existem múltiplos preços cadastrados.
                    meta: []
      security:
        - bearer: []
  "/api/products/{id}/gallery":
    get:
      tags:
        - Galeria de Imagens
      summary: Listar Imagens do Produto
      description: Retorna a galeria de imagens do produto, incluindo o ID da thumbnail atual e o limite máximo de 6 imagens.
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
      responses:
        "200":
          description: Galeria do produto
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                data:
                  images:
                    - id: 01JMZXZ72881MD2P37N2K97YJJ
                      resources:
                        - name: thumbnail
                          size: 135x135
                          url: https://cdn.example.com/data/thumbnail_img.webp
                        - name: sm
                          size: 200x200
                          url: https://cdn.example.com/data/sm_img.webp
                        - name: md
                          size: 400x400
                          url: https://cdn.example.com/data/md_img.webp
                        - name: lg
                          size: 800x800
                          url: https://cdn.example.com/data/lg_img.webp
                  thumbnail_id: 01JMZXZ72881MD2P37N2K97YJJ
                  total: 1
                  max: 6
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    post:
      tags:
        - Galeria de Imagens
      summary: Adicionar Imagem ao Produto
      description: Vincula uma imagem (já carregada via `POST /api/media/upload`) à galeria do produto. Máximo de 6 imagens. A primeira imagem é automaticamente definida como thumbnail.
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - image_id
              properties:
                image_id:
                  type: string
                  description: ID da imagem obtido via POST /api/media/upload
            example:
              image_id: 01JMZXZ72881MD2P37N2K97YJJ
      responses:
        "201":
          description: Imagem adicionada com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 201
                message_code: RESOURCE_CREATED
                data:
                  images:
                    - id: 01JMZXZ72881MD2P37N2K97YJJ
                      resources:
                        - name: thumbnail
                          size: 135x135
                          url: https://cdn.example.com/data/thumbnail_img.webp
                        - name: sm
                          size: 200x200
                          url: https://cdn.example.com/data/sm_img.webp
                        - name: md
                          size: 400x400
                          url: https://cdn.example.com/data/md_img.webp
                        - name: lg
                          size: 800x800
                          url: https://cdn.example.com/data/lg_img.webp
                  thumbnail_id: 01JMZXZ72881MD2P37N2K97YJJ
                  total: 1
                  max: 6
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Erro de validação ou regra de negócio
          content:
            "application/json":
              examples:
                validation:
                  summary: Campo image_id inválido ou ausente
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      image_id:
                        - O campo image_id é obrigatório.
                    meta: []
                limit_reached:
                  summary: Limite de 6 imagens atingido
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Limite máximo de 6 fotos por produto atingido.
                    data: []
                    errors:
                      - Limite máximo de 6 fotos por produto atingido.
                    meta: []
                duplicate:
                  summary: Imagem já está na galeria
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Esta imagem já está na galeria do produto.
                    data: []
                    errors:
                      - Esta imagem já está na galeria do produto.
                    meta: []
      security:
        - bearer: []
  "/api/products/{id}/gallery/{image_id}":
    delete:
      tags:
        - Galeria de Imagens
      summary: Remover Imagem do Produto
      description: Remove uma imagem da galeria. Se a imagem removida for a thumbnail, a primeira imagem restante será promovida automaticamente.
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
        - name: image_id
          in: path
          required: true
          description: ID da imagem a ser removida
          schema:
            type: string
      responses:
        "200":
          description: Imagem removida com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                data:
                  images: []
                  thumbnail_id: null
                  total: 0
                  max: 6
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          description: Produto ou imagem não encontrada
          content:
            "application/json":
              examples:
                product:
                  summary: Produto não encontrado
                  value:
                    success: false
                    code: 404
                    message_code: NOT_FOUND
                    description: O recurso solicitado não foi encontrado.
                    data: []
                    errors:
                      - Produto não encontrado.
                    meta: []
                image:
                  summary: Imagem não encontrada na galeria
                  value:
                    success: false
                    code: 404
                    message_code: NOT_FOUND
                    description: O recurso solicitado não foi encontrado.
                    data: []
                    errors:
                      - Imagem não encontrada na galeria do produto.
                    meta: []
      security:
        - bearer: []
  "/api/products/{id}/gallery/thumbnail":
    put:
      tags:
        - Galeria de Imagens
      summary: Definir Thumbnail
      description: Define qual imagem da galeria será usada como thumbnail (destaque). A imagem deve já estar na galeria do produto.
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - image_id
              properties:
                image_id:
                  type: string
                  description: ID da imagem que será a thumbnail
            example:
              image_id: 01JMZXZ72881MD2P37N2K97YJJ
      responses:
        "200":
          description: Thumbnail definida com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                data:
                  thumbnail_id: 01JMZXZ72881MD2P37N2K97YJJ
                  thumbnail:
                    id: 01JMZXZ72881MD2P37N2K97YJJ
                    resources:
                      - name: thumbnail
                        size: 135x135
                        url: https://cdn.example.com/data/thumbnail_img.webp
                      - name: md
                        size: 400x400
                        url: https://cdn.example.com/data/md_img.webp
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Erro de validação ou regra de negócio
          content:
            "application/json":
              examples:
                validation:
                  summary: Campo image_id ausente
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      image_id:
                        - O campo image_id é obrigatório.
                    meta: []
                not_in_gallery:
                  summary: Imagem não está na galeria
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: A imagem deve estar na galeria do produto para ser definida como thumbnail.
                    data: []
                    errors:
                      - A imagem deve estar na galeria do produto para ser definida como thumbnail.
                    meta: []
      security:
        - bearer: []
  "/api/products/{id}/gallery/reorder":
    put:
      tags:
        - Galeria de Imagens
      summary: Reordenar Imagens
      description: "Define a nova ordem das imagens da galeria. Todos os IDs enviados devem pertencer à galeria do produto. Atenção: imagens da galeria que ficarem fora da lista são removidas — envie sempre a lista completa. Se a thumbnail atual não estiver na lista, a primeira imagem da nova ordem será promovida."
      parameters:
        - name: id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - images
              properties:
                images:
                  type: array
                  minItems: 1
                  items:
                    type: string
                  description: Array de IDs de imagens na nova ordem desejada (imagens omitidas são removidas da galeria)
            example:
              images:
                - 01JMZXZ72881MD2P37N2K97YJJ
                - 01JN0A1B2C3D4E5F6G7H8I9J0K
                - 01JN1X2Y3Z4A5B6C7D8E9F0G1H
      responses:
        "200":
          description: Imagens reordenadas com sucesso
          content:
            "application/json":
              example:
                success: true
                code: 200
                message_code: SUCCESS
                data:
                  images:
                    - id: 01JMZXZ72881MD2P37N2K97YJJ
                      resources:
                        - name: thumbnail
                          size: 135x135
                          url: ...
                        - name: sm
                          size: 200x200
                          url: ...
                        - name: md
                          size: 400x400
                          url: ...
                        - name: lg
                          size: 800x800
                          url: ...
                    - id: 01JN0A1B2C3D4E5F6G7H8I9J0K
                      resources:
                        - name: thumbnail
                          size: 135x135
                          url: ...
                        - name: sm
                          size: 200x200
                          url: ...
                        - name: md
                          size: 400x400
                          url: ...
                        - name: lg
                          size: 800x800
                          url: ...
                    - id: 01JN1X2Y3Z4A5B6C7D8E9F0G1H
                      resources:
                        - name: thumbnail
                          size: 135x135
                          url: ...
                        - name: sm
                          size: 200x200
                          url: ...
                        - name: md
                          size: 400x400
                          url: ...
                        - name: lg
                          size: 800x800
                          url: ...
                  thumbnail_id: 01JMZXZ72881MD2P37N2K97YJJ
                  total: 3
                  max: 6
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Erro de validação ou regra de negócio
          content:
            "application/json":
              examples:
                validation:
                  summary: Array de imagens inválido
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      images:
                        - O campo images é obrigatório.
                    meta: []
                not_in_gallery:
                  summary: Imagem não pertence à galeria
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Imagem não pertence à galeria do produto.
                    data: []
                    errors:
                      - Imagem 01JN0A1... não pertence à galeria do produto.
                    meta: []
      security:
        - bearer: []
  "/api/products/{id}/prices":
    get:
      tags:
        - Preços de Produtos
      summary: Listar Preços do Produto
      description: |-
        Retorna a lista de faixas de preço de um produto, ordenadas por `min_quantity`.
        
        - **Produto `default` (preço padrão):** retorna uma única faixa (`min_quantity = 1`) com as medidas de embalagem nulas — as dimensões físicas ficam no próprio produto.
        - **Produto `wholesale` (atacado):** retorna a faixa base (`min_quantity = 1`, com as medidas da unidade projetadas a partir do produto) seguida das faixas de atacado (`min_quantity >= 2`, com as medidas da embalagem fechada).
        - `discount_percent` só vem preenchido quando o atacado é automático (`auto_wholesale_pricing`): nesse modo o preço de cada faixa é derivado do preço base menos o desconto (a faixa base vem com `0`). No atacado manual ele é `null`.
        - Produto sem preços cadastrados: `data` vem vazio e `meta.message` informa "Não há preços cadastrados".
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: ID do produto
      responses:
        "200":
          description: Lista de preços recuperada com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Price"
              examples:
                default_price:
                  summary: Produto com preço padrão (preço único, sem faixas de atacado)
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      - value: 29.9
                        min_quantity: 1
                        discount_percent: null
                        package_weight: null
                        package_height: null
                        package_width: null
                        package_length: null
                wholesale_manual:
                  summary: "Atacado manual: preço de cada faixa definido à mão (discount_percent null)"
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      - value: 50
                        min_quantity: 1
                        discount_percent: null
                        package_weight: 1.2
                        package_height: 10
                        package_width: 12
                        package_length: 15
                      - value: 45
                        min_quantity: 10
                        discount_percent: null
                        package_weight: 12.5
                        package_height: 30
                        package_width: 40
                        package_length: 50
                      - value: 40
                        min_quantity: 50
                        discount_percent: null
                        package_weight: 12.5
                        package_height: 30
                        package_width: 40
                        package_length: 50
                wholesale_auto:
                  summary: "Atacado automático: discount_percent define o preço de cada faixa (auto_wholesale_pricing)"
                  value:
                    success: true
                    message_code: SUCCESS
                    data:
                      - value: 50
                        min_quantity: 1
                        discount_percent: 0
                        package_weight: 1.2
                        package_height: 10
                        package_width: 12
                        package_length: 15
                      - value: 45
                        min_quantity: 10
                        discount_percent: 10
                        package_weight: 12.5
                        package_height: 30
                        package_width: 40
                        package_length: 50
                      - value: 40
                        min_quantity: 50
                        discount_percent: 20
                        package_weight: 12.5
                        package_height: 30
                        package_width: 40
                        package_length: 50
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    post:
      tags:
        - Preços de Produtos
      summary: Adicionar Preço
      description: |-
        Adiciona uma nova faixa de preço para o produto (ex: preço de atacado para compras acima de X unidades).
        
        Regras de negócio:
        - Máximo de 5 faixas de preço por produto (400 BAD_REQUEST se exceder).
        - Não pode haver duplicidade de min_quantity (409 DUPLICATED).
        - Produtos do tipo `default` não aceitam min_quantity >= 2 (422 UNPROCESSABLE_ENTITY).
        - Em produtos `wholesale`, faixas `min_quantity >= 2` exigem as medidas completas da embalagem (peso + altura + largura + comprimento). A primeira faixa de atacado precisa trazer as medidas; faixas seguintes podem omiti-las herdando a referência (422 se faltar uma faixa medida ou se o preenchimento for parcial).
        - Em produtos `wholesale` com `auto_wholesale_pricing` ligado: o preço base (`min_quantity = 1`) precisa existir antes das faixas de atacado (422), e os `discount_percent` devem ser progressivos (422).
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: ID do produto
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/PriceInput"
            examples:
              base_price:
                summary: Preço base (unitário, min_quantity = 1)
                value:
                  price: 50
                  min_quantity: 1
              wholesale_first_tier:
                summary: Primeira faixa de atacado (com medidas obrigatórias)
                value:
                  price: 45
                  min_quantity: 10
                  package_weight: 12.5
                  package_height: 30
                  package_width: 40
                  package_length: 50
              wholesale_next_tier:
                summary: Faixa de atacado seguinte (medidas herdadas)
                value:
                  price: 40
                  min_quantity: 50
              wholesale_auto_tier:
                summary: Faixa de atacado automática (preço derivado do desconto)
                value:
                  min_quantity: 50
                  discount_percent: 20
                  package_weight: 12.5
                  package_height: 30
                  package_width: 40
                  package_length: 50
      responses:
        "201":
          description: Preço criado com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/Price"
              example:
                success: true
                message_code: RESOURCE_CREATED
                data:
                  value: 45
                  min_quantity: 10
                  discount_percent: null
                  package_weight: 12.5
                  package_height: 30
                  package_width: 40
                  package_length: 50
        "400":
          description: Limite máximo de preços atingido
          content:
            "application/json":
              example:
                success: false
                code: 400
                message_code: BAD_REQUEST
                description: Preço máximo atingido.
                data: []
                errors:
                  - Preço máximo atingido.
                meta: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Duplicated"
        "422":
          description: Erro de validação ou regra de negócio
          content:
            "application/json":
              examples:
                validation:
                  summary: Erro de validação de campos
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      price:
                        - O campo Preço deve ser um número.
                      min_quantity:
                        - O campo Quantidade mínima deve ser um número.
                    meta: []
                price_quantity_invalid:
                  summary: Produto DEFAULT não aceita min_quantity >= 2
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Quantidade de preço inválida.
                    data: []
                    errors:
                      - Quantidade de preço inválida.
                    meta: []
                package_dimensions_required:
                  summary: Faixa de atacado sem medidas completas da embalagem
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Cada produto de atacado precisa de ao menos uma faixa com peso e dimensões completas da embalagem. Preencha as medidas (peso, altura, largura e comprimento) por inteiro.
                    data: []
                    errors:
                      - Cada produto de atacado precisa de ao menos uma faixa com peso e dimensões completas da embalagem. Preencha as medidas (peso, altura, largura e comprimento) por inteiro.
                    meta: []
                wholesale_base_price_missing:
                  summary: Atacado automático sem preço base definido
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Defina o preço unitário base antes de criar faixas de atacado automáticas.
                    data: []
                    errors:
                      - Defina o preço unitário base antes de criar faixas de atacado automáticas.
                    meta: []
                wholesale_discount_not_progressive:
                  summary: Descontos do atacado automático não progressivos
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: "O desconto deve aumentar conforme a quantidade mínima: faixas maiores precisam de desconto maior que as menores."
                    data: []
                    errors:
                      - "O desconto deve aumentar conforme a quantidade mínima: faixas maiores precisam de desconto maior que as menores."
                    meta: []
      security:
        - bearer: []
  "/api/products/{id}/prices/{min_quantity}":
    put:
      tags:
        - Preços de Produtos
      summary: Atualizar Preço
      description: |-
        Atualiza uma faixa de preço existente, identificada pela quantidade mínima na URL (não é possível mudar a própria `min_quantity` — para isso, remova e recrie).
        
        Além do `price`, em produtos `wholesale` o corpo aceita `discount_percent` e as medidas da embalagem (`package_weight`, `package_height`, `package_width`, `package_length`). O merge é parcial para as medidas e o `discount_percent`: campo ausente no corpo mantém o valor já salvo. **Atenção ao `price`:** ele não participa do merge — na precificação manual (e na faixa base), omitir o `price` zera o valor da faixa. Sempre reenvie o preço atual quando for alterar só as medidas. As mesmas regras de medidas completas, atacado automático e desconto progressivo do POST se aplicam.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: ID do produto
        - name: min_quantity
          in: path
          required: true
          schema:
            type: integer
          description: "Quantidade mínima que identifica a faixa de preço (ex: 1, 10)"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                price:
                  type: number
                  nullable: true
                  description: Novo valor monetário. Ignorado nas faixas `min_quantity >= 2` quando o produto está em atacado automático (o preço é derivado do desconto).
                discount_percent:
                  type: number
                  nullable: true
                  minimum: 0
                  exclusiveMaximum: 100
                  description: Novo percentual de desconto sobre o preço base (atacado automático). Precisa manter a progressividade entre as faixas.
                package_weight:
                  type: number
                  nullable: true
                  minimum: 0.001
                  description: Peso da embalagem fechada, em kg.
                package_height:
                  type: number
                  nullable: true
                  minimum: 0.01
                  description: Altura da embalagem fechada, em cm.
                package_width:
                  type: number
                  nullable: true
                  minimum: 0.01
                  description: Largura da embalagem fechada, em cm.
                package_length:
                  type: number
                  nullable: true
                  minimum: 0.01
                  description: Comprimento da embalagem fechada, em cm.
            examples:
              update_price:
                summary: Atualizar só o valor
                value:
                  price: 48.5
              update_wholesale_measures:
                summary: Atualizar medidas da embalagem de uma faixa de atacado (reenvie o price no modo manual, senão ele zera)
                value:
                  price: 45
                  package_weight: 13
                  package_height: 32
                  package_width: 42
                  package_length: 52
              update_auto_discount:
                summary: Atualizar desconto (atacado automático)
                value:
                  discount_percent: 18
      responses:
        "200":
          description: Preço atualizado com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/Price"
              example:
                success: true
                message_code: RESOURCE_UPDATED
                data:
                  value: 48.5
                  min_quantity: 1
                  discount_percent: null
                  package_weight: 1.2
                  package_height: 10
                  package_width: 12
                  package_length: 15
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Erro de validação ou regra de negócio do atacado
          content:
            "application/json":
              examples:
                validation:
                  summary: Erro de validação de campos
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      discount_percent:
                        - O campo Percentual de desconto deve ser menor que 100.
                    meta: []
                package_dimensions_required:
                  summary: Faixa de atacado sem medidas completas da embalagem
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Cada produto de atacado precisa de ao menos uma faixa com peso e dimensões completas da embalagem. Preencha as medidas (peso, altura, largura e comprimento) por inteiro.
                    data: []
                    errors:
                      - Cada produto de atacado precisa de ao menos uma faixa com peso e dimensões completas da embalagem. Preencha as medidas (peso, altura, largura e comprimento) por inteiro.
                    meta: []
                wholesale_base_price_missing:
                  summary: Atacado automático sem preço base definido
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: Defina o preço unitário base antes de criar faixas de atacado automáticas.
                    data: []
                    errors:
                      - Defina o preço unitário base antes de criar faixas de atacado automáticas.
                    meta: []
                wholesale_discount_not_progressive:
                  summary: Descontos do atacado automático não progressivos
                  value:
                    success: false
                    code: 422
                    message_code: UNPROCESSABLE_ENTITY
                    description: "O desconto deve aumentar conforme a quantidade mínima: faixas maiores precisam de desconto maior que as menores."
                    data: []
                    errors:
                      - "O desconto deve aumentar conforme a quantidade mínima: faixas maiores precisam de desconto maior que as menores."
                    meta: []
      security:
        - bearer: []
    delete:
      tags:
        - Preços de Produtos
      summary: Remover Preço
      description: |-
        Remove uma faixa de preço específica do produto.
        
        Regras de negócio:
        - Não é possível remover o preço base (min_quantity < 2). A API retorna 400 BAD_REQUEST.
        - Em produtos `wholesale`, não é possível remover a última faixa que carrega as medidas da embalagem enquanto houver outras faixas de atacado dependendo dela (o produto precisa manter ao menos uma faixa medida). A API retorna 422 UNPROCESSABLE_ENTITY.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: ID do produto
        - name: min_quantity
          in: path
          required: true
          schema:
            type: integer
          description: Quantidade mínima da faixa de preço a ser removida
      responses:
        "204":
          description: Preço removido com sucesso (sem corpo de resposta)
        "400":
          description: Preço base não pode ser removido
          content:
            "application/json":
              example:
                success: false
                code: 400
                message_code: BAD_REQUEST
                description: Preço mínimo não pode ser removido.
                data: []
                errors:
                  - Preço mínimo não pode ser removido.
                meta: []
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          description: Não é possível remover a última faixa medida com faixas dependentes
          content:
            "application/json":
              example:
                success: false
                code: 422
                message_code: UNPROCESSABLE_ENTITY
                description: Cada produto de atacado precisa de ao menos uma faixa com peso e dimensões completas da embalagem. Preencha as medidas (peso, altura, largura e comprimento) por inteiro.
                data: []
                errors:
                  - Cada produto de atacado precisa de ao menos uma faixa com peso e dimensões completas da embalagem. Preencha as medidas (peso, altura, largura e comprimento) por inteiro.
                meta: []
      security:
        - bearer: []
  "/api/products/{product_id}/stock":
    get:
      tags:
        - Estoque de Produtos
      summary: Consultar Estoque do Produto
      description: Retorna o total de estoque do produto, além da distribuição detalhada por localização e a lista de locais disponíveis.
      operationId: getProductStock
      parameters:
        - name: product_id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
            example: 01H81AV32307PVBSV4RXF15EK9
      responses:
        "200":
          description: Dados de estoque recuperados com sucesso.
          content:
            "application/json":
              schema:
                allOf:
                  - "$ref": "#/components/schemas/SuccessResponse"
                  - type: object
                    properties:
                      data:
                        "$ref": "#/components/schemas/ProductStockOverview"
              example:
                success: true
                message_code: SUCCESS
                data:
                  total: 150
                  stocks:
                    - id: 321
                      product_sku:
                        id: 01H81AV32307PVBSV4RXF15EK9
                        name: Caneta Esferográfica Azul
                        sku: CAN-AZ-01
                        base_unit: un
                        dimensions: null
                        is_available_for_sale: true
                        is_industrializable: false
                        allow_sale_without_stock: false
                        price_type: fixed
                        auto_wholesale_pricing: false
                        prices:
                          - value: 2.5
                            min_quantity: 1
                            discount_percent: 0
                            package_weight: null
                            package_height: null
                            package_width: null
                            package_length: null
                        quantity: 150
                        reserved_quantity: 5
                        available_quantity: 145
                        thumbnail: null
                        images: []
                      location_stock:
                        location_id: 01H81AV32307PVBSV4RXF15LOC
                        store_id: 42
                        name: Matriz - São Paulo
                        status: available
                        location_type: seller_location
                        allows_pickup: false
                        allows_production: false
                      quantity: 100
                      reserved_quantity: 5
                      available_quantity: 95
                  locations:
                    - location_id: 01H81AV32307PVBSV4RXF15LOC
                      store_id: 42
                      name: Matriz - São Paulo
                      status: available
                      location_type: seller_location
                      allows_pickup: false
                      allows_production: false
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    post:
      tags:
        - Estoque de Produtos
      summary: Atualizar Estoque do Produto
      description: Atualiza a quantidade de estoque de um produto em uma localização específica. Permite definir um valor absoluto ou incrementar/decrementar.
      operationId: updateProductStock
      parameters:
        - name: product_id
          in: path
          required: true
          description: ID do produto
          schema:
            type: string
            example: 01H81AV32307PVBSV4RXF15EK9
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/StockUpdateInput"
            examples:
              set_absolute:
                summary: Definir quantidade absoluta
                value:
                  location_id: 01H81AV32307PVBSV4RXF15LOC
                  quantity: 50
              increment:
                summary: Incrementar estoque
                value:
                  location_id: 01H81AV32307PVBSV4RXF15LOC
                  quantity: 10
                  increment: true
              decrement:
                summary: Decrementar estoque
                value:
                  location_id: 01H81AV32307PVBSV4RXF15LOC
                  quantity: 5
                  decrement: true
      responses:
        "200":
          description: Estoque atualizado com sucesso.
          content:
            "application/json":
              schema:
                allOf:
                  - "$ref": "#/components/schemas/SuccessResponse"
                  - type: object
                    properties:
                      data:
                        "$ref": "#/components/schemas/ProductStock"
              example:
                success: true
                message_code: RESOURCE_UPDATED
                data:
                  id: 321
                  product_id: 01H81AV32307PVBSV4RXF15EK9
                  location_id: 01H81AV32307PVBSV4RXF15LOC
                  quantity: 50
                  reserved_quantity: 0
                  available_quantity: 50
        "400":
          description: "Requisição inválida (ex: tentar incrementar e decrementar ao mesmo tempo)."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
              example:
                success: false
                message_code: BAD_REQUEST
                data: Não é permitido incrementar e decrementar estoque ao mesmo tempo.
                message: Não é permitido incrementar e decrementar estoque ao mesmo tempo.
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Duplicated"
        "422":
          description: "Erro de validação ou operação não permitida (ex: Fulfillment)."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ErrorResponse"
              examples:
                validation_error:
                  summary: Erro de Validação
                  value:
                    success: false
                    code: 422
                    message_code: VALIDATION_ERROR
                    description: Foram encontrados erros de validação na requisição.
                    data: []
                    errors:
                      quantity:
                        - O campo quantity deve ser pelo menos 1.
                      location_id:
                        - O campo location_id é obrigatório.
                    meta: []
                fulfillment_error:
                  summary: Erro de Fulfillment
                  value:
                    success: false
                    message_code: UNPROCESSABLE_ENTITY
                    data:
                      message: "Operação não permitida: estoques em localizações do tipo fulfillment são sincronizados via WMS e não podem ser alterados por esta rota."
      security:
        - bearer: []
  "/api/locations-stock/enums":
    get:
      tags:
        - Dados Auxiliares
      summary: Listar Enums
      description: Retorna os valores e rotulos para os enums utilizados nos locais de estoque. A lista `address_types` traz todos os tipos de endereco da plataforma; para enderecos de locais de estoque, os aceitos no cadastro sao `logistics`, `local_real` e `local_devolucao`.
      responses:
        "200":
          description: Sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    type: object
                    properties:
                      location_types:
                        type: array
                        items:
                          type: object
                      location_statuses:
                        type: array
                        items:
                          type: object
                      address_types:
                        type: array
                        items:
                          type: object
              example:
                success: true
                message_code: SUCCESS
                data:
                  location_types:
                    - value: seller_location
                      label: Local do Vendedor
                      description: Localização pertencente ao vendedor.
                    - value: fulfillment
                      label: Centro de Distribuição
                      description: Localização gerenciada pela plataforma.
                  location_statuses:
                    - value: available
                      label: Disponível
                      description: Localização disponível para uso.
                    - value: unavailable
                      label: Indisponível
                      description: Localização indisponível para uso.
                  address_types:
                    - value: main
                      label: Principal
                    - value: other
                      label: Outro
                    - value: residential
                      label: Residencial
                    - value: delivery
                      label: Entrega
                    - value: billing
                      label: Cobrança
                    - value: corporate
                      label: Corporativo
                    - value: commercial
                      label: Comercial
                    - value: financial
                      label: Financeiro
                    - value: logistics
                      label: Logística
                    - value: local_real
                      label: Local Real
                    - value: local_devolucao
                      label: Local de Devolução
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
      security:
        - bearer: []
  "/api/locations-stock":
    get:
      tags:
        - Locais de Estoque
      summary: Listar Locais de Estoque
      description: Retorna todos os locais de estoque disponiveis para a loja do seller.
      responses:
        "200":
          description: Lista de locais recuperada com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/LocationStock"
              example:
                success: true
                message_code: SUCCESS
                data:
                  - location_id: 01K9T...
                    store_id: 42
                    name: Deposito Central
                    status: available
                    location_type: seller_location
                    allows_pickup: true
                    allows_production: false
                    branch:
                      uid: 01K9B...
                      name: Filial Campinas
                      document: "12345678000199"
                  - location_id: 01K9U...
                    store_id: 42
                    name: Loja Fisica SP
                    status: unavailable
                    location_type: seller_location
                    allows_pickup: false
                    allows_production: false
                    branch: null
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
      security:
        - bearer: []
    post:
      tags:
        - Locais de Estoque
      summary: Criar Local de Estoque
      description: Cria um novo local de estoque para o seller. Existe um limite de locais por loja — ao atingi-lo, a API responde 403 orientando a contatar o suporte.
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/LocationStockInput"
            example:
              name: Novo Deposito Zona Norte
              status: available
              location_type: seller_location
              allows_pickup: false
              allows_production: false
              company_branch_uid: 01K9B...
      responses:
        "201":
          description: Local criado com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/LocationStock"
              example:
                success: true
                message_code: RESOURCE_CREATED
                data:
                  location_id: 01KA...
                  store_id: 42
                  name: Novo Deposito Zona Norte
                  status: available
                  location_type: seller_location
                  allows_pickup: false
                  allows_production: false
                  branch:
                    uid: 01K9B...
                    name: Filial Campinas
                    document: "12345678000199"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "409":
          "$ref": "#/components/responses/Duplicated"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
  "/api/locations-stock/{locationId}":
    get:
      tags:
        - Locais de Estoque
      summary: Detalhes do Local
      description: Retorna detalhes de um local de estoque especifico.
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: ID do local de estoque
      responses:
        "200":
          description: Detalhes do local
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/LocationStock"
              example:
                success: true
                message_code: SUCCESS
                data:
                  location_id: 01K9T...
                  store_id: 42
                  name: Deposito Central
                  status: available
                  location_type: seller_location
                  allows_pickup: true
                  allows_production: false
                  branch: null
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    put:
      tags:
        - Locais de Estoque
      summary: Atualizar Local de Estoque
      description: Atualiza dados do local (nome, flags). Vale só para locais do tipo `seller_location` — os `fulfillment` são geridos pela plataforma, então chegam aqui como somente leitura e tentar editá-los é recusado.
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: ID do local de estoque
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              properties:
                name:
                  type: string
                  maxLength: 255
                  description: Nome do local
                allows_pickup:
                  type: boolean
                  description: Permite retirada no local
                allows_production:
                  type: boolean
                  description: Permite producao sob demanda
                company_branch_uid:
                  type: string
                  nullable: true
                  description: ID da filial a vincular ao local. Precisa ser uma filial ativa da empresa. Envie null para desvincular a filial atual.
            example:
              name: Deposito Central - Expansao
              allows_pickup: true
              allows_production: false
              company_branch_uid: 01K9B...
      responses:
        "200":
          description: Local atualizado com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/LocationStock"
              example:
                success: true
                message_code: RESOURCE_UPDATED
                data:
                  location_id: 01K9T...
                  store_id: 42
                  name: Deposito Central - Expansao
                  status: available
                  location_type: seller_location
                  allows_pickup: true
                  allows_production: false
                  branch:
                    uid: 01K9B...
                    name: Filial Campinas
                    document: "12345678000199"
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
  "/api/locations-stock/{locationId}/status":
    put:
      tags:
        - Locais de Estoque
      summary: Alterar Status do Local
      description: "Atualiza o status de um local de estoque (ex: available, unavailable)."
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: ID do local de estoque
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              type: object
              required:
                - status
              properties:
                status:
                  type: string
                  enum:
                    - available
                    - unavailable
                  description: Novo status do local
            example:
              status: unavailable
      responses:
        "200":
          description: Status atualizado com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/LocationStock"
              example:
                success: true
                message_code: RESOURCE_UPDATED
                data:
                  location_id: 01K9T...
                  store_id: 42
                  name: Deposito Central
                  status: unavailable
                  location_type: seller_location
                  allows_pickup: false
                  allows_production: false
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Duplicated"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
  "/api/locations-stock/{locationId}/addresses":
    get:
      tags:
        - Enderecos de Estoque
      summary: Listar Enderecos
      description: Lista os enderecos vinculados a um local de estoque especifico.
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: ID do local de estoque pai
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          schema:
            type: integer
            default: 15
        - name: type
          in: query
          schema:
            type: string
          description: "Filtrar por tipo de endereco (ex: logistics, local_real, local_devolucao)"
      responses:
        "200":
          description: Lista de enderecos
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    type: object
                    description: "Envelope paginado: os itens ficam em data.data e os metadados em data.meta."
                    properties:
                      meta:
                        "$ref": "#/components/schemas/PaginationMeta"
                      data:
                        type: array
                        items:
                          "$ref": "#/components/schemas/Address"
              example:
                success: true
                message_code: SUCCESS
                data:
                  meta:
                    search_query: ""
                    filters: []
                    pagination:
                      page: 1
                      per_page: 15
                      last_page: 1
                      has_prev_page: false
                      has_next_page: false
                      records:
                        from: 1
                        to: 1
                        records: 1
                  data:
                    - id: 01K9A...
                      uid: 01K9A...
                      street: Avenida Paulista
                      number: "1000"
                      complement: null
                      reference: null
                      can_deliver_to_neighbor: false
                      delivery_instructions: null
                      district: Bela Vista
                      city: Sao Paulo
                      state: SP
                      country: BR
                      zip_code: 01310-100
                      latitude: -23.561414
                      longitude: -46.655881
                      place_id: null
                      address_components: null
                      full_address: Avenida Paulista, 1000 - Bela Vista, Sao Paulo - SP, 01310-100
                      is_complete: true
                      type: logistics
                      type_label: Logística
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    post:
      tags:
        - Enderecos de Estoque
      summary: Adicionar Endereco
      description: Vincula um novo endereco a um local de estoque.
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
          description: ID do local de estoque pai
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/AddressInput"
            example:
              zip_code: 01310-100
              street: Avenida Paulista
              number: "1000"
              district: Bela Vista
              city: Sao Paulo
              state: SP
              type: logistics
      responses:
        "201":
          description: Endereco criado com sucesso
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/Address"
              example:
                success: true
                message_code: RESOURCE_CREATED
                data:
                  id: 01K9A...
                  uid: 01K9A...
                  street: Avenida Paulista
                  number: "1000"
                  complement: null
                  reference: null
                  can_deliver_to_neighbor: false
                  delivery_instructions: null
                  district: Bela Vista
                  city: Sao Paulo
                  state: SP
                  country: BR
                  zip_code: 01310-100
                  latitude: -23.561414
                  longitude: -46.655881
                  place_id: null
                  address_components: null
                  full_address: Avenida Paulista, 1000 - Bela Vista, Sao Paulo - SP, 01310-100
                  is_complete: true
                  type: logistics
                  type_label: Logística
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Conflict"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
  "/api/locations-stock/{locationId}/addresses/{addressId}":
    get:
      tags:
        - Enderecos de Estoque
      summary: Detalhes do Endereco
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
        - name: addressId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Detalhes do endereco
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/Address"
              example:
                success: true
                message_code: SUCCESS
                data:
                  id: 01K9A...
                  uid: 01K9A...
                  street: Avenida Paulista
                  number: "1000"
                  complement: null
                  reference: null
                  can_deliver_to_neighbor: false
                  delivery_instructions: null
                  district: Bela Vista
                  city: Sao Paulo
                  state: SP
                  country: BR
                  zip_code: 01310-100
                  latitude: -23.561414
                  longitude: -46.655881
                  place_id: null
                  address_components: null
                  full_address: Avenida Paulista, 1000 - Bela Vista, Sao Paulo - SP, 01310-100
                  is_complete: true
                  type: logistics
                  type_label: Logística
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
    put:
      tags:
        - Enderecos de Estoque
      summary: Atualizar Endereco
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
        - name: addressId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/AddressUpdateInput"
            example:
              number: "1001"
              complement: Sala 5
      responses:
        "200":
          description: Endereco atualizado
          content:
            "application/json":
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message_code:
                    type: string
                  data:
                    "$ref": "#/components/schemas/Address"
              example:
                success: true
                message_code: SUCCESS
                data:
                  id: 01K9A...
                  uid: 01K9A...
                  street: Avenida Paulista
                  number: "1001"
                  complement: Sala 5
                  reference: null
                  can_deliver_to_neighbor: false
                  delivery_instructions: null
                  district: Bela Vista
                  city: Sao Paulo
                  state: SP
                  country: BR
                  zip_code: 01310-100
                  latitude: -23.561414
                  longitude: -46.655881
                  place_id: null
                  address_components: null
                  full_address: Avenida Paulista, 1001 - Sala 5 - Bela Vista, Sao Paulo - SP, 01310-100
                  is_complete: true
                  type: logistics
                  type_label: Logística
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
        "409":
          "$ref": "#/components/responses/Conflict"
        "422":
          "$ref": "#/components/responses/ValidationError"
      security:
        - bearer: []
    delete:
      tags:
        - Enderecos de Estoque
      summary: Remover Endereco
      parameters:
        - name: locationId
          in: path
          required: true
          schema:
            type: string
        - name: addressId
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Endereco desvinculado/removido com sucesso. Sem corpo de resposta.
        "401":
          "$ref": "#/components/responses/Unauthorized"
        "403":
          "$ref": "#/components/responses/Forbidden"
        "404":
          "$ref": "#/components/responses/NotFound"
      security:
        - bearer: []
components:
  securitySchemes:
    integrationToken:
      type: http
      scheme: bearer
      description: "Token de Integração da loja (formato: sk_...). Criado em Configurações → Tokens de Integração no painel do seller."
  schemas:
    Product:
      type: object
      properties:
        id:
          type: string
          description: ID do produto (26 caracteres)
        name:
          type: string
        sku:
          type: string
        price_type:
          type: string
          enum:
            - default
            - wholesale
        base_unit:
          type: string
          enum:
            - un
            - kg
            - pc
            - cx
            - pct
            - lt
            - mt
            - m2
            - m3
        dimensions:
          type: object
          properties:
            width:
              type: number
              description: Largura em cm
            height:
              type: number
              description: Altura em cm
            length:
              type: number
              description: Comprimento em cm
            weight:
              type: number
              description: Peso em kg
        is_available_for_sale:
          type: boolean
          description: Indica se o produto está disponível para venda
          default: true
        is_industrializable:
          type: boolean
          description: Indica se o produto pode ser fabricado sob demanda
        auto_wholesale_pricing:
          type: boolean
          description: Indica se os preços das faixas de atacado são derivados automaticamente do percentual de desconto
        allow_sale_without_stock:
          type: boolean
          description: Permite venda sem estoque disponível (pedido sob encomenda)
        dimensions_formatted:
          type: object
          nullable: true
          description: Dimensões formatadas para exibição (somente se include_dimensions_formatted=true)
          properties:
            width:
              type: string
              nullable: true
              example: 10,00 cm
            height:
              type: string
              nullable: true
              example: 10,00 cm
            length:
              type: string
              nullable: true
              example: 10,00 cm
            weight:
              type: string
              nullable: true
              example: 500g
        quantity:
          type: number
          description: Quantidade total em estoque
        reserved_quantity:
          type: number
          description: Quantidade reservada
        available_quantity:
          type: number
          description: Quantidade disponível para venda (quantity - reserved_quantity)
        price:
          type: number
          description: Preço de venda (somente para price_type=default)
        min_price:
          type: number
          description: Menor preço entre as faixas (somente para price_type=wholesale)
        max_price:
          type: number
          description: Maior preço entre as faixas (somente para price_type=wholesale)
        thumbnail:
          type: object
          nullable: true
          description: Imagem de destaque do produto (variantes thumbnail e md)
          properties:
            id:
              type: string
              description: ID da imagem
            resources:
              type: array
              items:
                type: object
                properties:
                  name:
                    type: string
                    description: Nome da variante (thumbnail, sm, md, lg)
                  size:
                    type: string
                    description: "Dimensões da variante (ex: 400x400)"
                  url:
                    type: string
                    format: uri
    ProductInput:
      type: object
      required:
        - name
        - sku
        - base_unit
        - price_type
        - dimensions
      properties:
        name:
          type: string
          maxLength: 255
        sku:
          type: string
          maxLength: 50
          description: Deve ser único por loja
        base_unit:
          type: string
          enum:
            - un
            - kg
            - pc
            - cx
            - pct
            - lt
            - mt
            - m2
            - m3
        price_type:
          type: string
          enum:
            - default
            - wholesale
        is_available_for_sale:
          type: boolean
          default: true
        is_industrializable:
          type: boolean
          nullable: true
        allow_sale_without_stock:
          type: boolean
          nullable: true
          description: Permite venda sem estoque disponível (pedido sob encomenda)
        auto_wholesale_pricing:
          type: boolean
          description: Opcional. Deriva automaticamente os preços das faixas de atacado a partir do percentual de desconto
        dimensions:
          type: object
          required:
            - width
            - height
            - length
            - weight
          properties:
            width:
              type: number
              minimum: 0.001
              description: Largura em cm
            height:
              type: number
              minimum: 0.001
              description: Altura em cm
            length:
              type: number
              minimum: 0.001
              description: Comprimento em cm
            weight:
              type: number
              minimum: 0.001
              description: Peso em kg
    ProductUpdateInput:
      type: object
      description: Corpo da atualização de produto. Diferente da criação, o `sku` não é aceito (é ignorado se enviado) e apenas `name` e `dimensions` são obrigatórios.
      required:
        - name
        - dimensions
      properties:
        name:
          type: string
          maxLength: 255
        base_unit:
          type: string
          description: Opcional. Mantém o valor atual quando omitido
          enum:
            - un
            - kg
            - pc
            - cx
            - pct
            - lt
            - mt
            - m2
            - m3
        price_type:
          type: string
          description: Opcional. Veja as regras de troca na descrição do endpoint
          enum:
            - default
            - wholesale
        is_available_for_sale:
          type: boolean
          description: Opcional
        is_industrializable:
          type: boolean
          description: Opcional
        allow_sale_without_stock:
          type: boolean
          description: Opcional. Permite venda sem estoque disponível (pedido sob encomenda)
        auto_wholesale_pricing:
          type: boolean
          description: Opcional. Deriva automaticamente os preços das faixas de atacado a partir do percentual de desconto
        dimensions:
          type: object
          required:
            - width
            - height
            - length
            - weight
          properties:
            width:
              type: number
              minimum: 0.001
              description: Largura em cm
            height:
              type: number
              minimum: 0.001
              description: Altura em cm
            length:
              type: number
              minimum: 0.001
              description: Comprimento em cm
            weight:
              type: number
              minimum: 0.001
              description: Peso em kg
    Price:
      type: object
      properties:
        value:
          type: number
          description: Valor unitário da faixa
        min_quantity:
          type: integer
          description: Quantidade mínima para a faixa valer
        discount_percent:
          type: number
          nullable: true
          description: Percentual de desconto da faixa em relação ao preço-base (usado no atacado automático)
        package_weight:
          type: number
          nullable: true
          description: Peso da embalagem da faixa em kg
        package_height:
          type: number
          nullable: true
          description: Altura da embalagem da faixa em cm
        package_width:
          type: number
          nullable: true
          description: Largura da embalagem da faixa em cm
        package_length:
          type: number
          nullable: true
          description: Comprimento da embalagem da faixa em cm
    Stock:
      type: object
      properties:
        location_stock:
          type: object
          properties:
            location_id:
              type: string
              description: ID do local de estoque
            store_id:
              type: string
              description: ID da loja
            name:
              type: string
            status:
              type: string
              enum:
                - available
                - unavailable
            location_type:
              type: string
              enum:
                - seller_location
                - fulfillment
            allows_pickup:
              type: boolean
              description: Local permite retirada
            allows_production:
              type: boolean
              description: Local permite produção sob demanda
        quantity:
          type: number
        reserved_quantity:
          type: number
        available_quantity:
          type: number
    PaginationMeta:
      type: object
      properties:
        search_query:
          type: string
        filters:
          type: array
          items:
            type: string
        pagination:
          type: object
          properties:
            page:
              type: integer
            per_page:
              type: integer
            last_page:
              type: integer
            has_prev_page:
              type: boolean
            has_next_page:
              type: boolean
            records:
              type: object
              properties:
                from:
                  type: integer
                to:
                  type: integer
                records:
                  type: integer
    PriceInput:
      type: object
      properties:
        price:
          type: number
          nullable: true
          description: Valor monetário da faixa. Obrigatório na precificação manual (se omitido vira 0). Em produtos `wholesale` com `auto_wholesale_pricing` ligado é ignorado nas faixas `min_quantity >= 2` — o preço é calculado a partir do preço base e do `discount_percent`.
        min_quantity:
          type: integer
          default: 1
          description: "Quantidade mínima para ativar este preço (Padrão: 1). Produtos `default` só aceitam `min_quantity = 1`."
        discount_percent:
          type: number
          nullable: true
          minimum: 0
          description: Percentual de desconto sobre o preço base unitário (0 a menos de 100). Usado nas faixas de atacado automáticas; os descontos precisam ser progressivos (faixa de quantidade maior exige desconto maior).
          exclusiveMaximum: 100
        package_weight:
          type: number
          nullable: true
          minimum: 0.001
          description: Peso da embalagem fechada, em kg. Faz parte das medidas obrigatórias das faixas de atacado (`min_quantity >= 2`).
        package_height:
          type: number
          nullable: true
          minimum: 0.01
          description: Altura da embalagem fechada, em cm. Faz parte das medidas obrigatórias das faixas de atacado.
        package_width:
          type: number
          nullable: true
          minimum: 0.01
          description: Largura da embalagem fechada, em cm. Faz parte das medidas obrigatórias das faixas de atacado.
        package_length:
          type: number
          nullable: true
          minimum: 0.01
          description: Comprimento da embalagem fechada, em cm. Faz parte das medidas obrigatórias das faixas de atacado.
    ProductStockOverview:
      type: object
      properties:
        total:
          type: integer
          description: Quantidade total de estoque somando todas as localizações.
          example: 150
        stocks:
          type: array
          description: Lista detalhada de estoque do produto em cada localização.
          items:
            "$ref": "#/components/schemas/ProductStockDetail"
        locations:
          type: array
          description: Lista de todas as localizações disponíveis para a loja.
          items:
            "$ref": "#/components/schemas/LocationStock"
    ProductStock:
      type: object
      properties:
        id:
          type: integer
          description: Identificador interno do registro de estoque.
          example: 321
        product_id:
          type: string
          description: ID do produto.
          example: 01H81AV32307PVBSV4RXF15EK9
        location_id:
          type: string
          description: ID da localização de estoque.
          example: 01H81AV32307PVBSV4RXF15LOC
        quantity:
          type: integer
          description: Quantidade atual em estoque.
          example: 50
        reserved_quantity:
          type: integer
          description: Quantidade reservada (não disponível para venda imediata).
          example: 0
        available_quantity:
          type: integer
          description: Quantidade disponível para venda (quantity - reserved_quantity).
          example: 50
    ProductStockDetail:
      type: object
      description: Registro de estoque do produto em uma localização, com o produto e o local aninhados.
      properties:
        id:
          type: integer
          description: Identificador interno do registro de estoque.
          example: 321
        product_sku:
          type: object
          description: Dados do produto (id, name, sku, base_unit, dimensions, flags de venda, prices, totais de estoque, thumbnail e images).
        location_stock:
          "$ref": "#/components/schemas/LocationStock"
        quantity:
          type: integer
          description: Quantidade física nesta localização.
          example: 100
        reserved_quantity:
          type: integer
          description: Quantidade reservada em carrinhos/pedidos abertos.
          example: 5
        available_quantity:
          type: integer
          description: Quantidade disponível para venda (quantity - reserved_quantity).
          example: 95
    LocationStock:
      type: object
      properties:
        location_id:
          type: string
          description: ID da localização.
          example: 01H81AV32307PVBSV4RXF15LOC
        store_id:
          type: integer
          description: Identificador interno da loja dona do local.
          example: 42
        name:
          type: string
          description: Nome da localização.
          example: Matriz - São Paulo
        status:
          type: string
          enum:
            - available
            - unavailable
          description: Status da localização.
          example: available
        location_type:
          type: string
          enum:
            - seller_location
            - fulfillment
          description: Tipo da localização.
          example: seller_location
        allows_pickup:
          type: boolean
          description: Indica se o local permite retirada em mãos.
          example: false
        allows_production:
          type: boolean
          description: Indica se o local permite produção sob demanda.
          example: false
    StockUpdateInput:
      type: object
      required:
        - location_id
        - quantity
      properties:
        location_id:
          type: string
          description: ID da localização onde o estoque será alterado.
          example: 01H81AV32307PVBSV4RXF15LOC
        quantity:
          type: integer
          minimum: 1
          description: Quantidade a ser definida, incrementada ou decrementada.
          example: 10
        increment:
          type: boolean
          description: Se verdadeiro, soma a quantidade ao estoque atual.
          default: false
          example: false
        decrement:
          type: boolean
          description: Se verdadeiro, subtrai a quantidade do estoque atual.
          default: false
          example: false
    SuccessResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message_code:
          type: string
          example: SUCCESS
        data:
          type: object
        meta:
          type: object
          description: Presente apenas quando há metadados a retornar.
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        code:
          type: integer
          example: 400
        message_code:
          type: string
          example: BAD_REQUEST
        description:
          type: string
          example: Erro na requisição.
        data:
          type: array
          items: {}
          example: []
        errors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
        meta:
          type: array
          items: {}
          example: []
    LocationStockInput:
      type: object
      required:
        - name
        - status
        - location_type
      properties:
        name:
          type: string
        status:
          type: string
          enum:
            - available
            - unavailable
          default: available
        location_type:
          type: string
          enum:
            - seller_location
          default: seller_location
        allows_pickup:
          type: boolean
          default: false
          description: Permite retirada no local
        allows_production:
          type: boolean
          default: false
          description: Permite producao sob demanda
        company_branch_uid:
          type: string
          nullable: true
          description: ID da filial a vincular ao local. Precisa ser uma filial ativa da empresa; omita para deixar o local respondendo pela matriz.
    Address:
      type: object
      properties:
        id:
          type: string
          description: ID do endereco
        uid:
          type: string
          description: Mesmo valor de id (mantido por compatibilidade)
        street:
          type: string
        number:
          type: string
        complement:
          type: string
          nullable: true
        reference:
          type: string
          nullable: true
          description: Ponto de referencia
        can_deliver_to_neighbor:
          type: boolean
          description: Se o entregador pode deixar com um vizinho
        delivery_instructions:
          type: string
          nullable: true
          description: Instrucoes de entrega
        district:
          type: string
        city:
          type: string
        state:
          type: string
        country:
          type: string
        zip_code:
          type: string
          description: CEP formatado (00000-000)
        latitude:
          type: number
        longitude:
          type: number
        place_id:
          type: string
          nullable: true
          description: Identificador do lugar no provedor de geocodificacao
        address_components:
          nullable: true
          description: Componentes estruturados do endereco retornados pelo provedor de geocodificacao
        full_address:
          type: string
          description: Endereco completo em uma unica linha
        is_complete:
          type: boolean
          description: Indica se o endereco ja possui coordenadas resolvidas
        type:
          type: string
          enum:
            - logistics
            - local_real
            - local_devolucao
          description: O tipo do endereco. Pelo menos um endereco do tipo 'logistics' e obrigatorio para o local de estoque operar.
        type_label:
          type: string
          nullable: true
          description: Rotulo legivel do tipo
    AddressInput:
      type: object
      required:
        - zip_code
        - street
        - number
        - district
        - city
        - state
        - type
      properties:
        zip_code:
          type: string
          description: CEP (apenas numeros ou com traco, formato 00000-000 ou 00000000)
        street:
          type: string
          minLength: 3
        number:
          type: string
          maxLength: 20
        complement:
          type: string
          nullable: true
          maxLength: 255
        reference:
          type: string
          nullable: true
          maxLength: 60
          description: Ponto de referencia
        can_deliver_to_neighbor:
          type: boolean
          nullable: true
          description: Se o entregador pode deixar com um vizinho
        delivery_instructions:
          type: string
          nullable: true
          maxLength: 150
          description: Instrucoes de entrega
        district:
          type: string
          minLength: 2
        city:
          type: string
          minLength: 2
        state:
          type: string
          minLength: 2
          maxLength: 2
        country:
          type: string
          minLength: 2
          maxLength: 2
          default: BR
        latitude:
          type: number
          nullable: true
          description: Se omitido, a plataforma tenta resolver as coordenadas pelo CEP ou por geocodificacao
        longitude:
          type: number
          nullable: true
        place_id:
          type: string
          nullable: true
        address_components:
          type: array
          nullable: true
          items: {}
        type:
          type: string
          enum:
            - logistics
            - local_real
            - local_devolucao
          description: "Tipo de endereco para este local. Valores validos: logistics, local_real, local_devolucao"
    AddressUpdateInput:
      type: object
      description: Mesmos campos do cadastro, porem todos opcionais — envie apenas o que quer alterar.
      properties:
        zip_code:
          type: string
          description: CEP (apenas numeros ou com traco, formato 00000-000 ou 00000000)
        street:
          type: string
          minLength: 3
        number:
          type: string
          maxLength: 20
        complement:
          type: string
          nullable: true
          maxLength: 255
        reference:
          type: string
          nullable: true
          maxLength: 60
        can_deliver_to_neighbor:
          type: boolean
          nullable: true
        delivery_instructions:
          type: string
          nullable: true
          maxLength: 150
        district:
          type: string
          minLength: 2
        city:
          type: string
          minLength: 2
        state:
          type: string
          minLength: 2
          maxLength: 2
        country:
          type: string
          minLength: 2
          maxLength: 2
        latitude:
          type: number
          nullable: true
        longitude:
          type: number
          nullable: true
        place_id:
          type: string
          nullable: true
        address_components:
          type: array
          nullable: true
          items: {}
        type:
          type: string
          nullable: true
          enum:
            - logistics
            - local_real
            - local_devolucao
          description: Novo tipo do vinculo do endereco com o local
    CategoriesResponse:
      type: object
      properties:
        success:
          type: boolean
        message_code:
          type: string
        data:
          type: array
          items:
            "$ref": "#/components/schemas/Category"
      required:
        - success
        - message_code
        - data
    Category:
      type: object
      properties:
        id:
          type: integer
          description: ID da categoria
        name:
          type: string
        description:
          type:
            - string
            - "null"
        departament:
          "$ref": "#/components/schemas/Department"
        parent:
          "$ref": "#/components/schemas/ParentCategory"
        is_visible:
          type: boolean
        priority:
          type: integer
        domain:
          type: string
        level:
          type: integer
        hierarchy:
          type: array
          description: Breadcrumb completo da categoria (cada nível com id e nome)
          items:
            "$ref": "#/components/schemas/HierarchyLevel"
        settings:
          type: array
          items: {}
        catalog_overrides:
          type:
            - object
            - "null"
        total_children:
          type: integer
        children:
          type: array
          description: Subcategorias (recursivo). Na busca, só voltam folhas — portanto vazio.
          items:
            "$ref": "#/components/schemas/Category"
      required:
        - id
        - name
        - departament
        - parent
    HierarchyLevel:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
      required:
        - id
        - name
    Department:
      type: object
      properties:
        id:
          type: integer
          description: ID do departamento
        name:
          type: string
        icon_svg:
          type:
            - string
            - "null"
      required:
        - id
        - name
    ParentCategory:
      type: object
      properties:
        id:
          type: integer
        parent_id:
          type:
            - integer
            - "null"
        name:
          type: string
        hierarchy:
          type: array
          items:
            "$ref": "#/components/schemas/HierarchyLevel"
      required:
        - id
        - parent_id
        - name
    CategoryAttributesResponse:
      type: object
      properties:
        success:
          type: boolean
        message_code:
          type: string
        data:
          type: array
          description: Com `matrix=true`, lista de grupos (`AttributeGroup`). Sem `matrix`, lista plana de atributos (`AttributeItem`).
          items:
            oneOf:
              - "$ref": "#/components/schemas/AttributeGroup"
              - "$ref": "#/components/schemas/AttributeItem"
      required:
        - success
        - message_code
        - data
    AttributeGroup:
      type: object
      properties:
        key:
          type:
            - string
            - "null"
          description: "Chave da matriz (ex: identification, appearance, technical). `null` para atributos sem matriz."
        label:
          type: string
        items:
          type: array
          items:
            "$ref": "#/components/schemas/AttributeItem"
      required:
        - key
        - label
        - items
    AttributeItem:
      type: object
      properties:
        id:
          type: integer
          description: ID do vínculo categoria-atributo
        attribute_id:
          type: integer
          description: ID do atributo
        category_id:
          type: integer
          description: ID da categoria
        is_required:
          type: boolean
          description: Indica se o atributo e obrigatorio para a categoria
        is_feature:
          type: boolean
          description: Indica se e uma caracteristica do produto
        is_variant:
          type: boolean
          description: Indica se e um atributo de variacao
        is_combinable:
          type: boolean
          description: Indica se pode ser usado em combinacoes para variacoes
        is_filter:
          type: boolean
          description: Indica se o atributo aparece como filtro na vitrine
        attribute_data:
          "$ref": "#/components/schemas/AttributeData"
      required:
        - id
        - attribute_id
        - category_id
        - is_required
        - is_feature
        - is_variant
        - is_combinable
        - is_filter
    AttributeData:
      type: object
      description: "Definição do atributo. O bloco `value_definition` é autodescritivo: traz os campos esperados (`fields`), as opções pré-cadastradas (`options`, quando houver) e as unidades disponíveis."
      properties:
        id:
          type: integer
          description: ID do atributo
        name:
          type: string
        type:
          type: string
          enum:
            - default
            - default_unit
            - default_list
            - dimension_3d
            - dimension_2d
            - color
            - image
            - brand
            - packaging
          description: Tipo estrutural do atributo. `default_unit` exige `unit`; `dimension_2d/3d` exigem width/height(/depth)+unit; `color` aceita opção pré-cadastrada ou cor customizada (value + hex).
        value_type:
          type:
            - string
            - "null"
          enum:
            - text
            - float
            - number
            - date
            - boolean
            - select
            - radio
            - checkbox
            - null
          description: Tipo do valor para atributos default. Tipos especializados (color, image, brand, packaging) ignoram este campo.
        is_feature:
          type: boolean
        is_variant:
          type: boolean
        is_calculable:
          type: boolean
          description: "Atributo usado em cálculos (ex: Quantidade, que precisa casar com a unidade base do SKU)"
        domain:
          type:
            - string
            - "null"
        ui:
          type: object
          properties:
            public_name:
              type:
                - string
                - "null"
            description:
              type:
                - string
                - "null"
            matrix:
              type:
                - string
                - "null"
              description: Matriz de organização (identification, appearance, technical, dimensions, ...)
            select_type:
              type: string
              enum:
                - select
                - radio
                - checkbox
                - text
              description: Como o valor é escolhido. `text` = digitação livre; os demais exigem opção pré-cadastrada (exceto atributos de cor, que aceitam cor customizada).
        value_definition:
          type: object
          properties:
            fields:
              type: object
              description: Schema dos campos esperados no valor (montagem dinâmica de formulário)
            options:
              type: array
              description: Opções pré-cadastradas (presente apenas quando o atributo tem opções)
              items:
                "$ref": "#/components/schemas/AttributeOption"
            default_unit:
              type:
                - string
                - "null"
            available_units:
              type: array
              items:
                type: string
    AttributeOption:
      type: object
      properties:
        id:
          type: integer
          description: ID da opção (use em `value_id` ao criar o anúncio)
        attribute_id:
          type: integer
        value:
          type: string
        component:
          type: object
          description: "Payload visual da opção, varia por tipo de atributo. Para cor: `{ value, main_color, hex, brightness }`."
          additionalProperties: true
        position:
          type: integer
        is_default:
          type: boolean
      required:
        - id
        - attribute_id
        - value
        - component
        - position
        - is_default
    AttributeCombinationsResponse:
      type: object
      properties:
        success:
          type: boolean
        message_code:
          type: string
        data:
          type: object
          properties:
            combinations:
              type: array
              items:
                "$ref": "#/components/schemas/AttributeCombination"
            primary_attribute:
              "$ref": "#/components/schemas/PrimaryAttributeInfo"
          required:
            - combinations
            - primary_attribute
      required:
        - success
        - message_code
        - data
    AttributeCombination:
      type: object
      properties:
        attributes:
          type: array
          items:
            "$ref": "#/components/schemas/CombinationAttribute"
        attributes_details:
          type: object
          additionalProperties:
            type: string
        description:
          type: string
      required:
        - attributes
        - attributes_details
        - description
    CombinationAttribute:
      type: object
      properties:
        attribute_id:
          type: integer
          description: ID do atributo
        attribute_name:
          type: string
        value:
          type: string
        unit:
          type: string
          description: Unidade de medida (presente apenas quando aplicável)
        hex:
          type: string
          description: Cor em hexadecimal (presente apenas para atributos de cor)
        main_color:
          type: string
          description: Cor principal (presente apenas para atributos de cor)
        brightness:
          type: string
          enum:
            - light
            - dark
          description: Luminosidade calculada da cor (presente apenas para atributos de cor)
        width:
          type:
            - string
            - number
          description: Largura (presente apenas para atributos de dimensão)
        height:
          type:
            - string
            - number
          description: Altura (presente apenas para atributos de dimensão)
      required:
        - attribute_id
        - attribute_name
        - value
    PrimaryAttributeInfo:
      type: object
      description: Atributo principal das combinações — facilita organizar fotos por variação no front.
      properties:
        id:
          type: integer
          description: ID do atributo principal
        name:
          type:
            - string
            - "null"
        has_options:
          type: boolean
        default_unit:
          type:
            - string
            - "null"
        values:
          type: array
          items:
            type: object
            properties:
              value:
                type: string
              label:
                type: string
              hex:
                type: string
              main_color:
                type: string
              brightness:
                type: string
            required:
              - value
              - label
      required:
        - id
        - name
        - has_options
        - default_unit
        - values
    CreateAdvertisementRequest:
      type: object
      description: É obrigatório informar `seller_sku` (anúncio sem variações) ou `variations` — um dos dois precisa estar presente. Os SKUs devem existir previamente na loja.
      properties:
        title:
          type: string
          maxLength: 100
          description: Titulo do anuncio
        category_id:
          type: integer
          description: ID da categoria selecionada (precisa ser categoria folha existente)
        gtin:
          "$ref": "#/components/schemas/GTIN"
        seller_sku:
          type:
            - string
            - "null"
          description: SKU do produto para anúncio sem variações. Obrigatório quando `variations` não for enviado. O SKU precisa existir na loja.
        attributes:
          type: array
          items:
            "$ref": "#/components/schemas/AdvertisementAttribute"
          description: Atributos de caracteristicas do produto. Obrigatório quando a categoria possui atributos com `is_required=true`.
        variations:
          type: array
          items:
            "$ref": "#/components/schemas/AdvertisementVariation"
          description: Variacoes do produto. Obrigatório quando `seller_sku` não for enviado.
        images:
          type: array
          items:
            "$ref": "#/components/schemas/ImageReference"
          description: Imagens do anuncio principal. Opcional na criação, mas o checklist exige pelo menos uma imagem para publicar.
        description:
          "$ref": "#/components/schemas/PublicationDescription"
          description: Descrição longa do anúncio. Opcional. Pode ser informada na criação ou depois via PUT /api/items/{id}/description.
        technical_sheets:
          type: array
          items:
            "$ref": "#/components/schemas/TechnicalSheetInput"
          description: Fichas técnicas do anúncio. Opcionais. Também podem ser criadas ou editadas depois via /api/items/{id}/technical-sheets.
      required:
        - title
        - category_id
    PublicationDescription:
      type: object
      description: Aceita um objeto `{ layout, raw_content }` ou uma string simples (legado, equivale a `layout=markdown`).
      properties:
        layout:
          type: string
          enum:
            - text
            - html
            - markdown
          description: "Formato do `raw_content`. Default: `markdown`."
        raw_content:
          type: string
          description: Conteúdo bruto da descrição no formato indicado por `layout`.
    TechnicalSheetInput:
      type: object
      required:
        - type
        - title
      properties:
        type:
          type: string
          enum:
            - technical_specification
            - characteristics
            - materials
            - installation
            - maintenance
            - safety
            - environmental
            - custom
          description: Tipo da ficha técnica.
        title:
          type: string
          maxLength: 255
          description: Título exibido para a seção da ficha.
        items:
          type: array
          items:
            type: object
            required:
              - item_key
              - item_value
            properties:
              item_key:
                type: string
                maxLength: 255
              item_value:
                type: string
                maxLength: 1000
              display_order:
                type: integer
                minimum: 0
    GTIN:
      type: object
      properties:
        type:
          type:
            - integer
            - string
          enum:
            - 8
            - 12
            - 13
            - 14
            - "8"
            - "12"
            - "13"
            - "14"
          description: "Tipo de GTIN: 8, 12, 13 ou 14 dígitos (13 para EAN-13). Nas respostas, vem como string."
        value:
          type: string
          description: Numero do codigo de barras
    AdvertisementAttribute:
      type: object
      description: "Valor de um atributo do anúncio. Campos extras dependem do tipo do atributo: cor customizada usa `hex`/`main_color`/`brightness`; dimensões usam `width`/`height`/`depth` + `unit`; listas usam `items`."
      properties:
        id:
          type: integer
          description: ID do atributo
        value_id:
          type:
            - integer
            - "null"
          description: ID da opção selecionada (para atributos com opcoes pré-cadastradas)
        option_id:
          type:
            - integer
            - "null"
          description: Alias aceito para `value_id`
        value:
          type:
            - string
            - integer
            - "null"
          description: Valor do atributo
        unit:
          type:
            - string
            - "null"
          description: Unidade de medida (obrigatória em atributos do tipo default_unit; precisa estar em available_units)
        hex:
          type:
            - string
            - "null"
          description: "Cor customizada em hexadecimal (#RGB ou #RRGGBB). Para atributos de cor, `value` (nome da cor) + `hex` válido dispensam opção pré-cadastrada."
        main_color:
          type:
            - string
            - "null"
          description: "Cor principal da cor customizada (ex: Vermelho)"
        brightness:
          type:
            - string
            - "null"
          description: Luminosidade da cor (light/dark). Se omitido, é calculada a partir do hex.
        width:
          type:
            - string
            - number
            - "null"
          description: Largura (atributos dimension_2d/dimension_3d)
        height:
          type:
            - string
            - number
            - "null"
          description: Altura (atributos dimension_2d/dimension_3d)
        depth:
          type:
            - string
            - number
            - "null"
          description: Profundidade (atributos dimension_3d)
        fields:
          type:
            - object
            - "null"
          description: "Forma alternativa de enviar dimensões: `{ width, height, depth, unit }`"
          properties:
            width: {}
            height: {}
            depth: {}
            unit:
              type: string
        items:
          type:
            - array
            - "null"
          description: Itens do atributo (obrigatório em atributos do tipo default_list)
    AdvertisementVariation:
      type: object
      properties:
        seller_sku:
          type: string
          description: SKU do produto para esta variacao (precisa existir na loja)
        attributes:
          type: array
          items:
            "$ref": "#/components/schemas/VariationAttribute"
          description: Atributos que definem esta variacao
        images:
          type: array
          items:
            "$ref": "#/components/schemas/ImageReference"
          description: Imagens especificas desta variacao
      required:
        - seller_sku
    VariationAttribute:
      type: object
      description: Informe `attribute_id` e `value` (ou `value_id` para opção pré-cadastrada). Atributos de cor aceitam cor customizada com `value` + `hex` (+ `main_color`/`brightness` opcionais), sem opção pré-cadastrada.
      properties:
        attribute_id:
          type: integer
          description: ID do atributo
        attribute_name:
          type:
            - string
            - "null"
        value:
          type:
            - string
            - integer
            - "null"
        unit:
          type:
            - string
            - "null"
        value_id:
          type:
            - integer
            - "null"
          description: ID da opção pré-cadastrada
        option_id:
          type:
            - integer
            - "null"
          description: Alias aceito para `value_id`
        hex:
          type:
            - string
            - "null"
          description: "Cor customizada em hexadecimal (#RGB ou #RRGGBB)"
        main_color:
          type:
            - string
            - "null"
        brightness:
          type:
            - string
            - "null"
        width:
          type:
            - string
            - number
            - "null"
        height:
          type:
            - string
            - number
            - "null"
        depth:
          type:
            - string
            - number
            - "null"
        fields:
          type:
            - object
            - "null"
          description: "Forma alternativa de enviar dimensões: `{ width, height, depth, unit }`"
    ImageReference:
      type: object
      properties:
        id:
          type: string
          description: ID da imagem (UUID retornado por POST /api/media/upload)
      required:
        - id
    SimulatedAdvertisementResponse:
      type: object
      properties:
        success:
          type: boolean
        message_code:
          type: string
        data:
          "$ref": "#/components/schemas/CreatedAdvertisementData"
      required:
        - success
        - message_code
        - data
    CreatedAdvertisementResponse:
      type: object
      properties:
        success:
          type: boolean
        message_code:
          type: string
        data:
          "$ref": "#/components/schemas/CreatedAdvertisementData"
      required:
        - success
        - message_code
        - data
    CreatedAdvertisementData:
      type: object
      description: Objeto do anúncio, retornado por create, publish e simulate.
      properties:
        item_id:
          type: string
          description: "ID do anúncio: prefixo da plataforma + 13 dígitos (ex: SAM-0000000000007)"
        title:
          type: string
        short_description:
          type:
            - string
            - "null"
        slug:
          type:
            - string
            - "null"
        type:
          allOf:
            - "$ref": "#/components/schemas/TypeValue"
          description: "Tipo do anúncio: `default` (Padrão) ou `catalog` (Catálogo)"
        status:
          allOf:
            - "$ref": "#/components/schemas/StatusValue"
          description: "Status do anúncio: `draft`, `pending_review`, `active`, `paused` ou `inactive`"
        seller_sku:
          type:
            - string
            - "null"
          description: SKU vinculado diretamente ao anúncio (quando não há variações)
        price_range:
          type:
            - array
            - "null"
          description: Faixa de preço calculada a partir das ofertas (pode ser null em rascunhos)
        score:
          type:
            - integer
            - "null"
          description: Score de qualidade do anúncio
        campaigns:
          type: array
        pricing_context:
          type: array
          description: Contexto de precificação por faixa de quantidade (descontos, campanhas)
        frontend_sync_status:
          type: string
          description: "Status de sincronização com a vitrine (ex: pending)"
        last_frontend_sync_at:
          type:
            - string
            - "null"
          description: Formato Y-m-d H:i:s
        submitted_for_review_at:
          type:
            - string
            - "null"
          description: Quando o anúncio foi enviado para revisão. Formato Y-m-d H:i:s
        reviewed_at:
          type:
            - string
            - "null"
          description: Quando a plataforma revisou o anúncio. Formato Y-m-d H:i:s
        rejection_reason:
          type:
            - string
            - "null"
          description: Motivo da rejeição quando o anúncio volta para rascunho
        gtin:
          allOf:
            - "$ref": "#/components/schemas/GTIN"
          description: Presente apenas quando informado
        store:
          allOf:
            - "$ref": "#/components/schemas/Store"
          description: Loja dona do anúncio (presente quando carregada)
        department:
          "$ref": "#/components/schemas/Department"
        category:
          "$ref": "#/components/schemas/CategoryRef"
        thumbnail:
          type:
            - object
            - "null"
          description: "Miniatura no tamanho `sm`: `{ id, resource }`"
        images:
          type: array
          items:
            "$ref": "#/components/schemas/Image"
        attributes:
          type: array
          items:
            "$ref": "#/components/schemas/PublicationAttributeOut"
        variations:
          type: array
          items:
            "$ref": "#/components/schemas/PublicationVariationOut"
      required:
        - item_id
        - title
        - type
        - status
        - department
        - category
        - images
        - attributes
        - variations
    Image:
      type: object
      properties:
        id:
          type: string
          description: ID da imagem (UUID)
        resources:
          type: array
          description: Variações de tamanho da imagem (`{ name, url, ... }`)
          items:
            type: object
      required:
        - id
        - resources
    PublicationAttributeOut:
      type: object
      properties:
        attribute_id:
          type: integer
          description: ID do atributo
        attribute_name:
          type:
            - string
            - "null"
        attribute_value:
          type:
            - string
            - integer
            - "null"
        value_unit:
          type:
            - string
            - "null"
        value_id:
          type:
            - integer
            - "null"
        component:
          type:
            - object
            - "null"
          description: "Payload visual do valor. Para cor: `{ value, main_color, hex, brightness }`."
      required:
        - attribute_id
        - attribute_name
        - attribute_value
        - value_unit
        - value_id
        - component
    PublicationVariationOut:
      type: object
      description: Variação do anúncio. Em anúncios padrão o identificador vem como `offer_id`; em anúncios de catálogo, como `option_id`.
      properties:
        offer_id:
          type: string
          description: ID da variação (26 caracteres). Presente em anúncios padrão.
        option_id:
          type: string
          description: ID da opção de catálogo (26 caracteres). Presente em anúncios de catálogo.
        description:
          type: string
          description: "Descrição legível da combinação (ex: Voltagem: 110 V, Cor: Azul)"
        attributes:
          type: array
          description: Atributos da variação (`{ attribute_id, attribute_name, value, unit?, value_id?, hex?, main_color?, brightness? }`)
          items:
            type: object
        status:
          type: string
          enum:
            - active
            - pending
            - hidden
          description: Status da variação
        type:
          type: string
          enum:
            - offer
            - option
        images:
          type: array
          items:
            "$ref": "#/components/schemas/Image"
        seller_sku:
          type:
            - string
            - "null"
          description: Presente quando `type=offer`
        offer:
          "$ref": "#/components/schemas/OfferData"
      required:
        - description
        - attributes
        - status
        - type
        - images
        - offer
    OfferData:
      type: object
      properties:
        price_type:
          type: string
          description: "Tipo de precificação (ex: default, wholesale)"
        price_from:
          type: number
          description: Preço "De" quando há desconto
        prices:
          type: array
          description: Faixas de preço (atacado)
          items:
            type: object
        quantity:
          type: integer
        price:
          type: number
          description: Preço "Por"
        original_price:
          type:
            - number
            - "null"
        has_discount:
          type: boolean
        discount_percent:
          type: integer
        wholesale_tiers:
          type: array
          description: Presente apenas quando há faixas de atacado
          items:
            type: object
        buybox:
          type: object
          description: Dados da buybox (presente apenas em anúncios de catálogo)
      required:
        - price_type
        - price_from
        - prices
        - quantity
        - price
        - original_price
        - has_discount
        - discount_percent
    TypeValue:
      type: object
      properties:
        value:
          type: string
          enum:
            - default
            - catalog
        label:
          type: string
      required:
        - value
        - label
    StatusValue:
      type: object
      properties:
        value:
          type: string
          enum:
            - draft
            - pending_review
            - active
            - paused
            - inactive
        label:
          type: string
      required:
        - value
        - label
    Store:
      type: object
      properties:
        store_id:
          type: string
          description: ID da loja (26 caracteres)
        store_name:
          type: string
        store_code:
          type: string
        store_status:
          type: string
        store_url:
          type:
            - string
            - "null"
        store_type:
          type: string
        commission_table_id:
          type:
            - integer
            - string
            - "null"
          description: ID da tabela de comissão
        max_locations:
          type:
            - integer
            - "null"
        features:
          type: array
        company:
          type: object
          description: Dados cadastrais da empresa
        shares_products:
          type: boolean
        shares_stock_locations:
          type: boolean
        shares_logistics:
          type: boolean
        is_primary:
          type: boolean
        primary_store:
          type:
            - object
            - "null"
      required:
        - store_id
        - store_name
        - store_code
    CategoryRef:
      type: object
      properties:
        id:
          type: integer
          description: ID da categoria
        parent_id:
          type:
            - integer
            - "null"
        name:
          type: string
        hierarchy:
          type: array
          items:
            "$ref": "#/components/schemas/HierarchyLevel"
      required:
        - id
        - parent_id
        - name
    AttachSKURequest:
      type: object
      properties:
        seller_sku:
          type: string
          maxLength: 64
          description: SKU do produto a vincular. Precisa pertencer (ou estar associado) à loja autenticada.
        status:
          type: string
          description: "Status inicial da oferta. Default: `active`."
          enum:
            - active
            - paused
            - inactive
      required:
        - seller_sku
    AttachedSKUResponse:
      type: object
      properties:
        success:
          type: boolean
        message_code:
          type: string
        data:
          "$ref": "#/components/schemas/AttachedSKUData"
      required:
        - success
        - message_code
        - data
    AttachedSKUData:
      type: object
      properties:
        offer_id:
          type: string
          description: ID da oferta criada (26 caracteres). Use em PUT/DELETE /api/items/catalog/offers/{offerId}.
        seller_sku:
          type: string
        status:
          type: string
          enum:
            - active
            - paused
            - inactive
            - pending
        is_buybox_winner:
          type: boolean
          description: Recalculado em background após o vínculo — costuma vir `false` na resposta imediata
        last_buybox_calculation:
          type:
            - string
            - "null"
        price_data:
          "$ref": "#/components/schemas/PriceData"
        stock_data:
          "$ref": "#/components/schemas/StockData"
        publication:
          type:
            - object
            - "null"
          description: Resumo do anúncio de catálogo
          properties:
            item_id:
              type: string
              description: ID do anúncio
            title:
              type: string
            status:
              type: string
            type:
              type: string
            thumbnail_id:
              type:
                - string
                - "null"
              description: ID da imagem de capa (UUID)
        option:
          "$ref": "#/components/schemas/OptionData"
      required:
        - offer_id
        - seller_sku
        - status
        - is_buybox_winner
        - last_buybox_calculation
        - price_data
        - stock_data
        - publication
        - option
    PriceData:
      type: object
      description: "Snapshot de preço da oferta. Preço único: `{ price }`. Atacado: `{ type: \"wholesale\", tiers: [...] }`."
      properties:
        price:
          type: number
          description: Preço único (presente quando não há faixas de atacado)
        type:
          type: string
          enum:
            - wholesale
          description: Presente apenas quando há faixas de atacado
        tiers:
          type: array
          description: Faixas de preço por quantidade (presente apenas no atacado)
          items:
            "$ref": "#/components/schemas/PriceTier"
    PriceTier:
      type: object
      properties:
        min_quantity:
          type: integer
        price:
          type: number
      required:
        - min_quantity
        - price
    StockData:
      type: object
      properties:
        id:
          type: integer
          description: ID interno do registro de estoque
        product_id:
          type: string
          description: ID do SKU do produto (26 caracteres)
        location_id:
          type: string
          description: ID do local de estoque (26 caracteres)
        quantity:
          type: integer
        reserved_quantity:
          type: integer
        available_quantity:
          type: integer
      required:
        - id
        - product_id
        - location_id
        - quantity
        - reserved_quantity
        - available_quantity
    OptionData:
      type: object
      properties:
        uid:
          type: string
          description: ID da opção de catálogo (26 caracteres)
        description:
          type: string
        attributes:
          type: array
          items:
            "$ref": "#/components/schemas/OptionAttribute"
        type:
          type: string
          enum:
            - option
            - offer
        status:
          type: string
          enum:
            - active
            - pending
            - hidden
        images:
          type: array
          items:
            type: object
      required:
        - uid
        - description
        - attributes
        - type
        - status
        - images
    OptionAttribute:
      type: object
      properties:
        attribute_id:
          type: integer
          description: ID do atributo
        attribute_name:
          type: string
        value:
          type: string
        unit:
          type:
            - string
            - "null"
        value_id:
          type:
            - integer
            - "null"
      required:
        - attribute_id
        - attribute_name
        - value
    TechnicalSheet:
      type: object
      description: "Ficha técnica de um anúncio: lista ordenada de pares chave/valor agrupada por tipo (especificações, materiais, segurança etc.)."
      properties:
        sheet_id:
          type: string
          description: ID da ficha técnica (26 caracteres)
        publication_id:
          type: string
          description: "`item_id` do anúncio dono da ficha"
        type:
          type: object
          description: Tipo da ficha técnica
          properties:
            value:
              type: string
              enum:
                - technical_specification
                - characteristics
                - materials
                - installation
                - maintenance
                - safety
                - environmental
                - custom
            label:
              type: string
              description: "Rótulo amigável do tipo (ex.: Ficha Técnica, Materiais)"
            description:
              type: string
          required:
            - value
            - label
            - description
        title:
          type: string
          maxLength: 255
        items:
          type: array
          items:
            "$ref": "#/components/schemas/TechnicalSheetItem"
      required:
        - sheet_id
        - publication_id
        - type
        - title
        - items
    TechnicalSheetItem:
      type: object
      description: Linha da ficha técnica (par chave/valor).
      properties:
        item_id:
          type: string
          description: ID do item da ficha (26 caracteres)
        item_key:
          type: string
          maxLength: 255
          description: "Nome da característica (ex.: Potência)"
        item_value:
          type: string
          maxLength: 1000
          description: "Valor da característica (ex.: 650 W)"
        display_order:
          type: integer
          minimum: 0
          description: Posição de exibição (0 = primeiro)
      required:
        - item_id
        - item_key
        - item_value
        - display_order
    MediaCreatedResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message_code:
          type: string
          example: RESOURCE_CREATED
        data:
          "$ref": "#/components/schemas/MediaItem"
      required:
        - success
        - message_code
        - data
    MediaItemResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message_code:
          type: string
          example: SUCCESS
        data:
          "$ref": "#/components/schemas/MediaItem"
      required:
        - success
        - message_code
        - data
    MediaCollectionResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message_code:
          type: string
          example: SUCCESS
        data:
          type: object
          properties:
            meta:
              "$ref": "#/components/schemas/PaginationMeta"
            data:
              type: array
              items:
                "$ref": "#/components/schemas/MediaItem"
      required:
        - success
        - message_code
        - data
    MediaItem:
      type: object
      description: Objeto que representa uma imagem/mídia na API de seller. Campos internos de propriedade (`owner_id`, `owner_model`) ficam de fora do payload de propósito — você não precisa deles para integrar, então não vai encontrá-los aqui.
      properties:
        id:
          type: string
          description: Identificador único da imagem (26 caracteres). É o valor usado como `{file_hash}` nas rotas de detalhe e exclusão.
          example: 01JMZXZ72881MD2P37N2K97YJJ
        original_source:
          type: string
          format: uri
          description: URL da imagem original, já convertida para WebP em alta qualidade.
          example: https://cdn.example.com/products/2026/05/28/original_01JMZXZ72881MD2P37N2K97YJJ.webp
        urls:
          type: array
          description: "Variações de tamanho geradas automaticamente (todas em WebP). Uma entrada por variação: `thumbnail` (135x135), `sm` (200x200), `md` (400x400), `lg` (800x800) e `xl` (1600x1600)."
          items:
            type: object
            properties:
              name:
                type: string
                enum:
                  - thumbnail
                  - sm
                  - md
                  - lg
                  - xl
                description: Nome da variação de tamanho.
                example: thumbnail
              size:
                type: string
                description: Dimensões máximas da variação, no formato largura x altura.
                example: 135x135
              url:
                type: string
                format: uri
                description: URL pública da variação.
                example: https://cdn.example.com/products/2026/05/28/thumbnail_01JMZXZ72881MD2P37N2K97YJJ.webp
            required:
              - name
              - size
              - url
        in_use:
          type: boolean
          description: Indica se a imagem está associada a algum recurso.
          example: false
        reference_id:
          type: string
          nullable: true
          description: ID do recurso referenciado (se houver).
        reference_model:
          type: string
          nullable: true
          enum:
            - product
            - publication
            - store
          description: Tipo do recurso vinculado, em formato curto. Use o mesmo valor no filtro `filter[reference_model]=...`.
        created_at:
          type: string
          format: date-time
          description: Data de criação da imagem.
          example: "2026-05-28T10:00:00-03:00"
        updated_at:
          type: string
          format: date-time
          description: Data da última atualização da imagem.
          example: "2026-05-28T10:00:00-03:00"
      required:
        - id
        - original_source
        - urls
        - in_use
        - reference_id
        - reference_model
        - created_at
        - updated_at
    MediaCloneFromSkusResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message_code:
          type: string
          example: OK
        data:
          type: object
          properties:
            results:
              type: object
              description: Mapa key → lista de mídias clonadas. As keys são exatamente as enviadas nos targets.
              additionalProperties:
                type: array
                items:
                  "$ref": "#/components/schemas/ClonedMediaItem"
            ignored_skus:
              type: array
              description: SKUs que não pertencem (ou não estão associados) à loja autenticada. Não geram erro — as keys correspondentes voltam vazias.
              items:
                type: string
              example:
                - CAMISETA-VERM-M
          required:
            - results
            - ignored_skus
      required:
        - success
        - data
    ClonedMediaItem:
      type: object
      description: Mídia recém-clonada a partir de uma foto de produto. Nasce sem vínculo (`in_use=false`) — vincule ao anúncio normalmente via `images[].id`; se não for usada, a limpeza diária remove a cópia após ~72 horas.
      properties:
        id:
          type: string
          description: ID da nova mídia (26 caracteres). Use este valor nas `images` do anúncio/variação.
          example: 01JNB2T4G93QRT5V6W7X8Y9Z0A
        source_image_id:
          type: string
          description: ID da imagem de origem (foto da galeria do produto). Útil para rastrear de onde a cópia veio e evitar duplicar sugestões.
          example: 01JMZXZ72881MD2P37N2K97YJJ
        original_source:
          type: string
          format: uri
          description: URL da cópia em alta qualidade (WebP).
          example: https://cdn.example.com/products/2026/06/11/original_01JNB2T4G93QRT5V6W7X8Y9Z0A.webp
        urls:
          type: array
          description: "Variações de tamanho copiadas da imagem de origem (mesmos nomes do upload: `thumbnail`, `sm`, `md`, `lg`, `xl`)."
          items:
            type: object
            properties:
              name:
                type: string
                example: sm
              size:
                type: string
                nullable: true
                example: 200x200
              url:
                type: string
                format: uri
                example: https://cdn.example.com/products/2026/06/11/sm_01JNB2T4G93QRT5V6W7X8Y9Z0A.webp
            required:
              - name
              - url
      required:
        - id
        - source_image_id
        - original_source
        - urls
  responses:
    Unauthorized:
      description: Token não fornecido, inválido ou expirado
      content:
        "application/json":
          example:
            success: false
            code: 401
            message_code: UNAUTHORIZED
            description: A autenticação é necessária e falhou ou não foi fornecida.
            data: []
            errors:
              - O access_token da loja é obrigatório no cabeçalho Authorization
            meta: []
    Forbidden:
      description: Sem permissão para acessar este recurso
      content:
        "application/json":
          example:
            success: false
            code: 403
            message_code: FORBIDDEN
            description: Você não tem permissão para acessar este recurso.
            data: []
            errors:
              - "Você não tem permissão para acessar este recurso (escopos ausentes: store_products_write)"
            meta: []
    NotFound:
      description: Recurso não encontrado
      content:
        "application/json":
          example:
            success: false
            code: 404
            message_code: NOT_FOUND
            description: O recurso solicitado não foi encontrado.
            data: []
            errors:
              - Produto não encontrado.
            meta: []
    Duplicated:
      description: "Recurso duplicado (ex: SKU já existe nesta loja)"
      content:
        "application/json":
          example:
            success: false
            code: 409
            message_code: DUPLICATED
            description: Este SKU já está cadastrado para esta loja.
            data: []
            errors:
              - Este SKU já está cadastrado para esta loja.
            meta: []
    ValidationError:
      description: Erro de validação nos campos enviados
      content:
        "application/json":
          example:
            success: false
            code: 422
            message_code: VALIDATION_ERROR
            description: Foram encontrados erros de validação na requisição.
            data: []
            errors:
              sku:
                - O campo SKU é obrigatório.
              name:
                - O campo Nome é obrigatório.
              "dimensions.height":
                - O campo dimensions.height é obrigatório.
            meta: []
    Conflict:
      description: Endereco identico ja vinculado ao local de estoque
      content:
        "application/json":
          example:
            success: false
            message_code: CONFLICT
            data:
              message: Já existe um endereço idêntico vinculado ao local de estoque.
security:
  - integrationToken: []
