# Fullstore

Documentação técnica da integração entre o isnapp e a plataforma FullStore. Destinada à equipe de T.I. responsável pela manutenção, suporte e diagnóstico do serviço.

Token do serviço: **/bibliotecas/ba4e7bb5-4770-4780-beaa-b28980153cc7/wosk/webservice/fullStoreService**  
Namespace: **WOSK\\fullStoreService**

# Visão Geral

<div class="pw" id="bkmrk-cap%C3%ADtulo-1"><div class="ph"></div></div>## O que consolidamos

Nossa integração coleta dados de vendas de **quatro canais distintos** e os entrega à FullStore em um único JSON estruturado, permitindo que a plataforma calcule comissões e analise o desempenho de cada vendedor sem precisar acessar diretamente os bancos de dados internos.

<div class="pw" id="bkmrk-%F0%9F%8F%AA"><div class="fg"><div class="fi"><div class="ic">🏪</div></div></div></div>#### PDV

Vendas realizadas no balcão da loja física, com itens e NF-e.

<div class="pw" id="bkmrk-%F0%9F%94%84"><div class="fg"><div class="fi"><div class="ic">🔄</div></div></div></div>#### Trocas

Devoluções e trocas vinculadas à NF da venda original.

<div class="pw" id="bkmrk-%F0%9F%9A%9A"><div class="fg"><div class="fi"><div class="ic">🚚</div></div></div></div>#### Omni

Pedido criado pelo vendedor na loja com entrega em domicílio ou retirada em outra unidade.

<div class="pw" id="bkmrk-%F0%9F%9B%92"><div class="fg"><div class="fi"><div class="ic">🛒</div></div></div></div>#### E-commerce

Pedidos do site processados pelo sistema Linx (SQL Server).

## Sistemas envolvidos

<div class="pw" id="bkmrk-sistema-papel-banco-"><div class="tw"><table><thead><tr><th>Sistema</th><th>Papel</th><th>Banco</th></tr></thead><tbody><tr><td>**ERP OSK (loja)**</td><td>Fonte de dados — PDV, Trocas, Omni</td><td>MySQL</td></tr><tr><td>**Linx**</td><td>Fonte de dados — E-commerce e BI</td><td>SQL Server `LX_ZERO_300`</td></tr><tr><td>**FullStore**</td><td>Consumidora da API</td><td>—</td></tr></tbody></table>

</div></div>## Ação disponível

Disponibilizamos uma única ação: `getVendas`. Ela recebe CNPJ da loja e intervalo de datas, consulta os quatro canais em sequência e retorna tudo consolidado.

## Estrutura da resposta

```
{
  "success": true,
  "message": "Vendas retornadas com sucesso!",
  "data": {
    "vendas":           [ /* PDV — vendas com itens agrupados       */ ],
    "trocas":           [ /* Trocas agrupadas por NF original        */ ],
    "omni":             [ /* Pedidos omni com entrega/retirada        */ ],
    "vendas_ecommerce": [ /* Pedidos e-commerce vindos do SQL Server  */ ],
    "fallback": null      /* Preenchido se o período exceder o limite */
  }
}
```

## Restrições de período

<div class="pw" id="bkmrk-limite-de-200-dias-n"><div class="wb">**Limite de 200 dias no passado** Consultamos no máximo **200 dias retroativos** (`LIMITE_DIAS_CONSULTA = 200`). Se `dataInicio` ou `dataFim` estiver além desse limite, retornamos `success: true` com todos os arrays vazios e o objeto `fallback` preenchido com os parâmetros recebidos — sem erro de sistema.</div><div class="wb">**CNPJ obrigatório** O `cnpjLoja` é obrigatório e filtra *todos* os canais. Sem ele, a requisição falha na validação antes de qualquer consulta ao banco.</div>  
</div>

# Arquitetura e Tecnologias

<div class="pw" id="bkmrk-"><div class="ph"></div></div>Stack técnico, hierarquia de classes, dependências e conexões com bancos de dados.

## Diagrama de componentes

<div class="pw" id="bkmrk-fullstore-http-post-"><figure id="bkmrk-fullstore-componentes" style="margin:1.25em 0;"> FullStore HTTP POST getVendas isnapp controle.php PHP 7.3 setSubmit() MySQL OSK PDV, Trocas Omni, filiais SQL Server Linx/BI LX\_ZERO\_300 W\_BI\_FAT\_VENDEDOR... CNPJ + periodo CTEs + filiais prepared statement JSON success / message / data Figura 1 - Componentes e fluxo de dados da integracao. </figure></div>## Stack

<div class="pw" id="bkmrk-camada-tecnologia-de"><div class="tw"><table><thead><tr><th>Camada</th><th>Tecnologia</th><th>Detalhe</th></tr></thead><tbody><tr><td>Linguagem</td><td>PHP **7.3**</td><td>Namespaces, PDO, Traits</td></tr><tr><td>Framework</td><td>CodeIgniter (`MY_Controller`)</td><td>Base do backend</td></tr><tr><td>Banco operacional</td><td>MySQL / PDO</td><td>PDV, Omni, Trocas</td></tr><tr><td>Banco E-commerce</td><td>SQL Server / PDO `sqlsrv:`</td><td>Linx `LX_ZERO_300`</td></tr><tr><td>Resposta</td><td>JSON via `setSubmit()`</td><td>Envelope padrão `success / message / data`</td></tr></tbody></table>

</div></div>## Hierarquia de classes

<div class="pw" id="bkmrk-biblioteca-base-do-l"><figure id="bkmrk-fullstore-classes" style="margin:1.25em 0;"> biblioteca base do loader fullStoreService carrega classe por UUID/token utilOsk PDO MySQL OSK WOSK\\fullStoreService\\WebService valida, consulta canais e monta resposta WOSK\\Commons\\WebService trait: setSubmit(), logs db\_sqlserver PDO sqlsrv para e-commerce usa trait instancia Cadeia principal biblioteca -&gt; fullStoreService / utilOsk -&gt; WOSK\\fullStoreService\\WebService -&gt; trait + db\_sqlserver </figure></div>## Arquivos required

Em `controle.php`, carregamos três dependências via `require_once(APPPATH . '...')`:

<div class="pw" id="bkmrk-uuid-arquivo-fornece"><div class="tw"><table style="width:100%;"><thead><tr><th style="width:13.8292%;">UUID</th><th style="width:15.253%;">Arquivo</th><th style="width:70.9178%;">Fornece</th></tr></thead><tbody><tr><td style="width:13.8292%;">`4cb18a6c-...`</td><td style="width:15.253%;">`wosk_webservice.php`</td><td style="width:70.9178%;">Trait `WOSK\Commons\WebService` — formata JSON (`setSubmit`), valida campos e salva logs no MySQL</td></tr><tr><td style="width:13.8292%;">`592ccfa0-...`</td><td style="width:15.253%;">`utilOsk.php`</td><td style="width:70.9178%;">Classe pai — conecta ao MySQL OSK e expõe `$this->conexao` (PDO)</td></tr><tr><td style="width:13.8292%;">`0670acec-...`</td><td style="width:15.253%;">`db_sqlserver.php`</td><td style="width:70.9178%;">Gerencia conexão PDO com SQL Server (driver `sqlsrv:`)</td></tr></tbody></table>

</div></div>## Conexão MySQL

<div class="pw" id="bkmrk-como-configuramos"><div class="card"><div class="ct">Como configuramos</div></div></div>A classe `utilOsk` chama `getConfiguracao('db_osk_prod')` — credenciais armazenadas em Base64 na tabela de configuração do sistema. Em seguida, `_setConexao()` cria a conexão PDO:

```
// connection string:
"mysql:host={host};port={porta};dbname={banco}"
// PDO::ATTR_PERSISTENT  = true   (conexão persistente)
// PDO::ATTR_TIMEOUT     = 120    (timeout em segundos)
// charset               = UTF-8
```

## Conexão SQL Server

<div class="pw" id="bkmrk-instanciamos-em-busc"><div class="card"><div class="ct">Instanciamos em buscarDadosEcommercePorLoja()</div></div></div>```
$sqlserver = new \db_sqlserver(false, 'LX_ZERO_300', true);
// false  = produção (não homologação)
// 'LX_ZERO_300' = banco Linx
// true   = conecta imediatamente
```

<div class="pw" id="bkmrk-ambiente-host-porta-"><div class="card"></div><div class="tw"><table><thead><tr><th>Ambiente</th><th>Host</th><th>Porta</th><th>Banco</th></tr></thead><tbody><tr><td>**Produção**</td><td>`<HOST_SQLSERVER_PROD>`</td><td>`<PORTA_PROD>`</td><td>`LX_ZERO_300`</td></tr><tr><td>Homologação</td><td>`<HOST_SQLSERVER_HML>`</td><td>`<PORTA_HML>`</td><td>`LINX_HMLG`</td></tr></tbody></table>

</div><div class="db">**Credenciais hardcoded** As credenciais do SQL Server ficam em `db_sqlserver.php`. </div></div>## Tabelas MySQL utilizadas

<div class="pw" id="bkmrk-tabela-alias-o-que-f"><div class="tw"><table><thead><tr><th>Tabela</th><th>Alias</th><th>O que fornece</th></tr></thead><tbody><tr><td>`movimentacao`</td><td>m</td><td>Cabeçalho da transação (tipo, data, situação, módulo)</td></tr><tr><td>`movimentacao_detalhe`</td><td>md</td><td>Itens da venda (produto, qtde, preço)</td></tr><tr><td>`movimentacao_nfe`</td><td>nf / nfe</td><td>Número, série e situação da NF-e</td></tr><tr><td>`movimentacao_movimentacao`</td><td>mm</td><td>Vínculos entre movimentações (troca↔venda, omni↔orçamento)</td></tr><tr><td>`pessoa`</td><td>v</td><td>Nome e código do vendedor</td></tr><tr><td>`entidade`</td><td>e</td><td>Código da filial / loja</td></tr><tr><td>`juridica`</td><td>j</td><td>CNPJ da empresa</td></tr></tbody></table>

</div></div>## Objetos SQL Server utilizados

<div class="pw" id="bkmrk-objeto-tipo-o-que-fo"><div class="tw"><table><thead><tr><th>Objeto</th><th>Tipo</th><th>O que fornece</th></tr></thead><tbody><tr><td>`W_BI_FAT_VENDEDOR_VDK_OMNI_V0`</td><td>View BI</td><td>Pedidos e-commerce por vendedor: NF, valor, CPF, filial, pedido site/WMS</td></tr><tr><td>`FILIAIS`</td><td>Tabela</td><td>Cadastro de filiais — usamos para excluir franquias (`TIPO_FILIAL <> 'FRANQUIA'`)</td></tr></tbody></table>

</div>  
</div>

# Fluxo Funcional

<div class="pw" id="bkmrk-"><div class="ph"></div></div>Passo a passo de uma requisição, validações e tratamento de erros.

## Diagrama de sequência

<div class="pw" id="bkmrk-fullstore-isnapp-run"><figure id="bkmrk-fullstore-sequencia" style="margin:1.25em 0;"> FullStore isnapp run() + validação MySQL OSK SQL Server Linx/BI POST getVendas Normaliza período verifica limite 200 dias PDV / Trocas Omni + filiais consultas MySQL E-commerce W\_BI\_FAT\_VENDEDOR... placeholders por filial mapeia vendas\_ecommerce Monta JSON vendas / trocas / omni Figura 3 - Sequencia completa de uma chamada a acao getVendas. </figure></div>## Diagrama de fluxo (decisões)

<div class="pw" id="bkmrk-post-getvendas-a%C3%A7%C3%A3o-"><figure id="bkmrk-fullstore-decisoes" style="margin:1.25em 0;"> POST getVendas ação válida? success:false Acao nao encontrada parâmetros preenchidos? success:false O parametro {campo} é obrigatorio dentro de 200 dias? success:false fallback interno não exposto consulta quatro canais se algum canal tem dados: success:true não sim não sim não sim Figura 4 - Fluxo de decisão completo do endpoint. </figure></div>## Passo a passo detalhado

<div class="pw" id="bkmrk-recebimento-da-requi">1. **Recebimento da requisição**  
    Recebemos o payload via `run(array $params)`. O parâmetro `acao` deve ser `'getVendas'`; qualquer outro valor resulta em erro imediato.
2. **Validação de parâmetros**  
    Validamos via `validarParametrosObrigatorios()`: `dataInicio`, `dataFim` e `cnpjLoja` — presença e valor não vazio. Se algum faltar, lançamos uma exception com o nome do campo faltante.
3. **Normalização do período**  
    Via `normalizarPeriodo()`, adicionamos horários às datas: início fica `dataInicio 00:00:00` e fim `dataFim 23:59:59`, garantindo cobertura integral dos dias extremos.
4. **Verificação do limite**  
    Via `periodoExcedeLimite()`, checamos se alguma das datas está além de 200 dias no passado. Se sim, `getVendasConsolidadas()` monta arrays vazios e um objeto `fallback`, mas `run()` ainda trata o resultado como nenhum registro encontrado e retorna erro ao consumidor.
5. **Vendas PDV**  
    Executamos query com 2 CTEs nas tabelas `movimentacao + movimentacao_detalhe + movimentacao_nfe`. Retornamos uma linha por item e depois agrupamos por `identificador` via `agruparItensDasVendas()`, gerando o sub-array `itens`.
6. **Trocas**  
    Buscamos movimentações de entrada com `tipo_estoque = 'TROCA'`, vinculando à venda original via `movimentacao_movimentacao`. Consolidamos múltiplas trocas do mesmo documento somando valores em `agruparTrocasPorDocumento()`.
7. **Omni**  
    Buscamos pedidos omni criados pelo vendedor na loja (orçamentos com `tipo_estoque = 'ORCAMENTO'` e situação Confirmado/Finalizado), vinculados à venda real via `movimentacao_movimentacao`.
8. **E-commerce**  
    Em dois sub-passos: (1) resolvemos os códigos de filial ativas via MySQL/CNPJ; (2) consultamos a view SQL Server com placeholders paramétricos dinâmicos para cada filial.
9. **Resposta**  
    Montamos o objeto `data` com os quatro arrays e retornamos via `setSubmit(true, 'Vendas retornadas com sucesso!', $data)`.

</div>## Tratamento de erros

<div class="pw" id="bkmrk-situa%C3%A7%C3%A3o-comportamen"><div class="tw"><table><thead><tr><th>Situação</th><th>Comportamento</th><th>success</th></tr></thead><tbody><tr><td>Parâmetro obrigatório ausente</td><td>Mensagem com nome do campo faltante</td><td><span class="badge br">false</span></td></tr><tr><td>Ação inválida (≠ getVendas)</td><td>Mensagem de ação não encontrada</td><td><span class="badge br">false</span></td></tr><tr><td>Período além de 200 dias</td><td>Fallback interno é montado, mas a resposta final atual é erro de nenhum registro</td><td><span class="badge br">false</span></td></tr><tr><td>CNPJ sem filiais ativas no MySQL</td><td>`vendas_ecommerce` vazio; se os demais canais também estiverem vazios, retorna erro de nenhum registro</td><td><span class="badge bo">Depende</span></td></tr><tr><td>Falha no SQL Server</td><td>Exception capturada → retorno `false`</td><td><span class="badge br">false</span></td></tr><tr><td>Parâmetro `falha` enviado</td><td>Força erro (uso em testes)</td><td><span class="badge br">false</span></td></tr></tbody></table>

</div><div class="ib">**Logs automáticos de requisição** Toda chamada — mesmo as que resultam em erro — é salva automaticamente nas tabelas `wosk_webservice*` do banco integrador. Em caso de disputa ou diagnóstico, basta consultar `wosk_webservice_retorno` pelo timestamp.</div>  
</div>

# Canal PDV — Vendas

<div class="pw" id="bkmrk-"><div class="ph"></div></div>Capturamos as vendas registradas no módulo **PDV** — transações de saída com NF-e autorizada/cancelada quando existir vínculo em `movimentacao_nfe`. Cada venda pode ter um ou mais itens; nossa query retorna uma linha por item e o método `agruparItensDasVendas()` os agrupa em um array aninhado `itens`.

**Métodos:** `consultarVendasPdv()` → `agruparItensDasVendas()`

## Relacionamento das tabelas

<div class="pw" id="bkmrk-movimentacao-id%2C-dat"><figure id="bkmrk-fullstore-pdv-tabelas" style="margin:1.25em 0;"> movimentacao id, data\_emissao, situacao modulo = PDV, tipo = SAIDA id\_pessoa, id\_vendedor, id\_entidade movimentacao\_nfe numero, serie, situacao movimentacao\_detalhe codigo, EAN, qtde, valor\_rateio entidade loja da venda juridica cnpjLoja pessoa codigo vendedor nf.id\_movimentacao md.id\_movimentacao m.id\_entidade e.id\_pessoa m.id\_vendedor Figura 5 - Tabelas MySQL envolvidas no canal PDV e seus relacionamentos. </figure></div>## Filtros aplicados

<div class="pw" id="bkmrk-campo-valor-%2F-condi%C3%A7"><div class="tw"><table><thead><tr><th>Campo</th><th>Valor / Condição</th><th>Motivo</th></tr></thead><tbody><tr><td>`m.modulo`</td><td>`= 'PDV'`</td><td>Apenas transações do módulo de caixa</td></tr><tr><td>`m.tipo`</td><td>`= 'SAIDA'`</td><td>Apenas saídas (vendas)</td></tr><tr><td>`m.situacao`</td><td>`<> 'Cancelado'`</td><td>Excluímos movimentações canceladas no cabeçalho</td></tr><tr><td>`m.data_emissao`</td><td>`BETWEEN :dataInicio AND :dataFim`</td><td>Filtro de período normalizado</td></tr><tr><td>`j.cnpj`</td><td>`= :cnpjLoja`</td><td>Restringe à loja da requisição</td></tr><tr><td>`nf.situacao`</td><td>`IN ('AUTORIZADO','CANCELADO')`</td><td>NF-e com situação definida no SEFAZ</td></tr><tr><td>`md.situacao`</td><td>`= 'ATIVO'`</td><td>Apenas linhas de detalhe ativas</td></tr></tbody></table>

</div></div>## Estrutura da query (2 CTEs)

<div class="pw" id="bkmrk-ctes-utilizados-movi"><div class="card"><div class="ct">CTEs utilizados</div>1. **movimentacoes** — agrupa dados do cabeçalho: vendedor, NF-e, entidade, CNPJ
2. **itens** — junta os detalhes de produto aos cabeçalhos

</div></div>O SELECT final une os dois CTEs retornando uma linha por item, depois agrupamos em PHP.

## Retorno da API

```
// Um elemento do array "vendas"
{
  "codigo_cliente":  123,
  "cod_vendedor":    "V001",
  "cnpj_emp":        "12345678000190",
  "data_documento":  "2026-05-15T10:30:00-03:00",   // ISO 8601
  "identificador":   9001,
  "transacao":       9001,
  "usuario":         42,
  "documento":       "000001234",   // Número NF-e
  "serie":           "001",
  "operacao":        "S",
  "tipo_transacao":  "VENDA",       // Fixo neste canal
  "cancelado":       "N",           // Derivado de movimentacao.situacao; na query atual tende a "N"
  "delivery":        false,
  "itens": [
    {
      "cod_produto":        "PROD-001",
      "descricao_produto":  "Camiseta Branca M",
      "cod_barra":          "7891234567890",
      "quantidade":         2,
      "preco_unitario":     89.90,
      "valor_total":        179.80
    }
  ]
}
```

## Dicionário de campos

<div class="pw" id="bkmrk-campo-origem-no-banc"><div class="tw"><table><thead><tr><th>Campo</th><th>Origem no banco</th><th>Descrição</th></tr></thead><tbody><tr><td>`identificador`</td><td>`movimentacao.id`</td><td>Chave da venda — usada no agrupamento</td></tr><tr><td>`data_documento`</td><td>`movimentacao.data_emissao`</td><td>Data/hora da venda em ISO 8601</td></tr><tr><td>`cod_vendedor`</td><td>`pessoa.codigo`</td><td>Código do vendedor responsável</td></tr><tr><td>`codigo_cliente`</td><td>`movimentacao.id_pessoa`</td><td>ID do cliente</td></tr><tr><td>`cnpj_emp`</td><td>`juridica.cnpj`</td><td>CNPJ da loja</td></tr><tr><td>`documento`</td><td>`movimentacao_nfe.numero`</td><td>Número da NF-e</td></tr><tr><td>`serie`</td><td>`movimentacao_nfe.serie`</td><td>Série da NF-e</td></tr><tr><td>`cancelado`</td><td>Derivado de `movimentacao.situacao`</td><td>Como a query filtra `m.situacao <> 'Cancelado'`, na prática tende a `"N"`</td></tr><tr><td>`tipo_transacao`</td><td>Hardcoded</td><td>Sempre `"VENDA"` neste canal</td></tr><tr><td>`itens[].cod_barra`</td><td>`movimentacao_detalhe.codigo_ean`</td><td>EAN do produto</td></tr><tr><td>`itens[].valor_total`</td><td>`movimentacao_detalhe.valor_rateio`</td><td>Valor rateado retornado como total do item</td></tr></tbody></table>

</div><div class="wb">**Cancelamentos no cabeçalho são filtrados** O campo `cancelado` é calculado a partir de `movimentacao.situacao`, não de `movimentacao_nfe.situacao`; portanto, NF-e cancelada pode aparecer no join, mas esse cancelamento não altera automaticamente o flag retornado.</div>  
</div>

# Canal Trocas e Devoluções

<div class="pw" id="bkmrk-"><div class="ph"></div></div>Capturamos movimentações de *entrada* do tipo `TROCA`. Quando o cliente devolve ou troca uma peça, o sistema OSK registra uma nova movimentação de entrada e a vincula à venda original via tabela `movimentacao_movimentacao`.

**Métodos:** `getTrocas()` → `agruparTrocasPorDocumento()`

## Vínculo troca ↔ venda original

<div class="pw" id="bkmrk-troca-entrada-%2F-troc"><figure id="bkmrk-fullstore-trocas-vinculo" style="margin:1.25em 0;"> Troca ENTRADA / TROCA situacao = FINALIZADO movimentacao\_movimentacao id\_movimentacao\_pai = troca id\_movimentacao = venda original Venda original SAIDA / PDV movimentacao ms NF-e numero serie mm.id\_mov\_pai mm.id\_movimentacao nfe Figura 6 - movimentacao\_movimentacao conecta a troca a venda original. </figure></div>## Filtros aplicados

<div class="pw" id="bkmrk-campo-valor-motivo-m"><div class="tw"><table><thead><tr><th>Campo</th><th>Valor</th><th>Motivo</th></tr></thead><tbody><tr><td>`m.modulo`</td><td>`'PDV'`</td><td>Apenas trocas do PDV</td></tr><tr><td>`m.tipo`</td><td>`'ENTRADA'`</td><td>Mercadoria retornando ao estoque</td></tr><tr><td>`m.tipo_estoque`</td><td>`'TROCA'`</td><td>Distingue de compras e outras entradas</td></tr><tr><td>`m.situacao`</td><td>`'FINALIZADO'`</td><td>Apenas trocas concluídas</td></tr><tr><td>`m.data_emissao`</td><td>`BETWEEN :dataInicio AND :dataFim`</td><td>Período da requisição</td></tr><tr><td>`j.cnpj`</td><td>`= :cnpjLoja`</td><td>Restringe à loja</td></tr></tbody></table>

</div></div>## Estrutura da query (3 CTEs)

<div class="pw" id="bkmrk-ctes-em-ordem-de-exe"><div class="card"><div class="ct">CTEs em ordem de execução</div>1. **e** — filtra a entidade (loja) pelo CNPJ informado
2. **m** — agrega movimentações de troca: soma valores e quantidades por movimentação
3. **tr** — junta ao documento original via `movimentacao_movimentacao` e busca a NF de venda

</div></div>## Agrupamento por documento

A mesma NF pode ter múltiplas trocas (cliente troca um item hoje e outro amanhã). Em `agruparTrocasPorDocumento()` consolidamos assim:

<div class="pw" id="bkmrk-somamos-valor_vale-e">- Somamos `valor_vale` e `valor_original`
- A implementação atual não soma `quantidade` ao agrupar documentos repetidos; mantém a quantidade do primeiro registro do grupo
- Mantemos o **timestamp mais recente**
- Trocas sem vínculo com NF original são incluídas individualmente

</div>## Retorno da API

```
// Um elemento do array "trocas"
{
  "motivo":         "Defeito no produto",
  "doc_venda":      "000001234",             // NF da venda original
  "serie_venda":    "001",
  "cod_cliente":    123,
  "cnpj_emp":       "12345678000190",
  "valor_vale":     89.90,                   // Crédito gerado para o cliente
  "valor_original": 89.90,                   // Mesmo valor agregado da troca na implementação atual
  "timestamp":      "2026-05-18T14:22:00-03:00",
  "cod_vendedor":   "V001",
  "quantidade":     1
}
```

## Dicionário de campos

<div class="pw" id="bkmrk-campo-origem-descri%C3%A7"><div class="tw"><table><thead><tr><th>Campo</th><th>Origem</th><th>Descrição</th></tr></thead><tbody><tr><td>`doc_venda`</td><td>`movimentacao_nfe.numero` da venda</td><td>NF da venda que originou a troca</td></tr><tr><td>`serie_venda`</td><td>`movimentacao_nfe.serie` da venda</td><td>Série da NF original</td></tr><tr><td>`valor_vale`</td><td>Soma dos itens da troca</td><td>Crédito gerado para o cliente</td></tr><tr><td>`valor_original`</td><td>`SUM(md.valor_rateio)` da movimentação de troca</td><td>Na implementação atual, recebe o mesmo agregado usado em `valor_vale`</td></tr><tr><td>`timestamp`</td><td>`movimentacao.data_emissao`</td><td>Data/hora da troca — ISO 8601</td></tr><tr><td>`cod_vendedor`</td><td>`pessoa.codigo`</td><td>Vendedor que registrou a troca</td></tr><tr><td>`quantidade`</td><td>`SUM(md.qtde)` por movimentação de troca</td><td>Não é acumulado novamente quando múltiplas trocas são agrupadas pelo mesmo documento</td></tr></tbody></table>

</div><div class="wb">**Trocas sem documento original** Se a troca não encontrar uma NF de venda (`doc_venda` nulo), ela é incluída individualmente sem agrupamento. Isso ocorre quando a venda foi feita antes da implantação do controle de vínculos no sistema.</div>  
</div>

# Canal Omni

<div class="pw" id="bkmrk-"><div class="ph"></div></div>Pedido criado pelo vendedor na loja física com entrega em domicílio ou retirada em outra unidade.

<div class="pw" id="bkmrk-implementa%C3%A7%C3%A3o-recent"><div class="ib">**Implementação recente** Canal adicionado em maio de 2026 — commit `2b74ab7` ("IMPLEMENTACAO VENDA OMNI"). É o canal com a lógica mais complexa do endpoint, usando 4 CTEs e um SELECT final.</div></div>## Fluxo do pedido omni

<div class="pw" id="bkmrk-cliente-na-loja-aten"><figure id="bkmrk-fullstore-omni-fluxo" style="margin:1.25em 0;"> Cliente na loja atendimento presencial Pedido pelo site vendedor cria pedido no sistema integrado Orçamento omni tipo\_estoque = ORCAMENTO id\_grupo\_operacao = 192 id\_tipo\_movimentacao = 6 fulfillment do pedido Venda vinculada movimentacao\_movimentacao Retirada outra unidade pedido transita entre lojas JSON omni\[\] retirada entrega Figura 7 - Fluxo completo de um pedido omni-channel. </figure></div>O cliente vai fisicamente até a loja. O vendedor, usando o sistema, **cria o pedido pelo site** (plataforma de e-commerce VTEX). A partir daí, existem duas possibilidades de fulfillment:

<div class="pw" id="bkmrk-cliente-vai-%C3%A0-lojave">- **Cliente vai à loja**  
    Vendedor atende o cliente presencialmente e registra o pedido no sistema via plataforma web.
- **Pedido criado pelo site**  
    O sistema gera um *orçamento* (`tipo_estoque = 'ORCAMENTO'`) com `id_tipo_movimentacao = 6` e `id_grupo_operacao = 192`.
- **Fulfillment: Entrega em domicílio**  
    O produto é despachado para o endereço do cliente — a venda real é registrada como saída no sistema e vinculada ao orçamento via `movimentacao_movimentacao`.
- **Fulfillment: Retirada em outra unidade**  
    O cliente retira o produto em uma loja diferente da que realizou a venda — o pedido transita entre unidades antes de ser entregue.

</div>## Identificadores do canal omni

Identificamos os pedidos omni pela combinação de três campos em `movimentacao`:

<div class="pw" id="bkmrk-campo-valor-signific"><div class="tw"><table><thead><tr><th>Campo</th><th>Valor</th><th>Significado</th></tr></thead><tbody><tr><td>`id_grupo_operacao`</td><td>`192`</td><td>Grupo de operação exclusivo do canal omni</td></tr><tr><td>`id_tipo_movimentacao`</td><td>`6`</td><td>Tipo de movimentação omni</td></tr><tr><td>`tipo_estoque`</td><td>`'ORCAMENTO'`</td><td>Registrado como orçamento — não movimenta estoque diretamente</td></tr><tr><td>`situacao`</td><td>`'Confirmado'` ou `'Finalizado'`</td><td>Buscamos apenas pedidos que resultaram em venda real</td></tr></tbody></table>

</div></div>## Estrutura da query (4 CTEs)

<div class="pw" id="bkmrk-ctes-em-ordem-de-exe"><div class="card"><div class="ct">CTEs em ordem de execução</div>1. **e** — filtra a entidade (loja) pelo CNPJ
2. **orcamento** — busca orçamentos omni confirmados/finalizados no período
3. **mm** — busca vínculos em `movimentacao_movimentacao` para localizar a venda real (SAIDA)
4. **i** — busca os itens do *orçamento* via `movimentacao_detalhe` na implementação atual
5. **SELECT final** — une orçamento + vendedor + itens

</div></div>## Retorno da API

```
// Um elemento do array "omni"
{
  "id_cliente":      456,
  "cod_vendedor":    "V007",
  "cnpj_emp":        "12345678000190",
  "data_documento":  "2026-05-20 16:45:00",        // Data da venda vinculada, sem conversão ISO no método atual
  "id_pessoa":       88,                             // ID do vendedor em pessoa
  "identificador":   3001,                           // ID da movimentação de venda vinculada
  "transacao":       3001,
  "usuario":         42,
  "documento":       "ORÇ-00321",
  "vendedor":        "João Silva",                   // Nome completo do vendedor
  "cancelado":       "N",
  "itens": [
    {
      "cod_produto":        "PROD-099",
      "descricao_produto":  "Tênis Running 42",
      "cod_barra":          "7896543219870",
      "quantidade":         1,
      "preco_unitario":     349.90,
      "valor_total":        349.90
    }
  ]
}
```

<div class="pw" id="bkmrk-itens-e-cabe%C3%A7alho-v%C3%AA"><div class="wb">**Itens e cabeçalho vêm de fontes diferentes** Na implementação atual, o cabeçalho usa a movimentação vinculada pela tabela `movimentacao_movimentacao`, mas a CTE `i` busca os itens do orçamento (`i.id_movimentacao = orcamento.id_movimentacao`). Como o join com os itens é interno, se o orçamento não tiver itens ativos o registro omni não é retornado.</div>  
</div>

# Canal E-commerce

<div class="pw" id="bkmrk-"><div class="ph"></div></div>Consulta ao SQL Server (Linx) e mapeamento de colunas para o padrão FullStore.

Coletamos pedidos do e-commerce processados pelo sistema **Linx**. Esses dados ficam em um SQL Server separado (`LX_ZERO_300`), em uma view que já consolida faturamento por vendedor. Nosso processo ocorre em **dois passos**.

**Métodos:** `buscarDadosEcommercePorLoja()` → `getVendasEcommerce()`

## Diagrama de sequência — dois passos

<div class="pw" id="bkmrk-isnapp-buscardadosec"><figure id="bkmrk-fullstore-ecommerce-fluxo" style="margin:1.25em 0;"> isnapp buscarDadosEcommerce por Loja() Passo 1 - MySQL juridica + entidade j.cnpj = :cnpj e.situacao = ATIVO retorna codigos filial Placeholders :filial0, :filial1... bindValue() Passo 2 - SQL Server W\_BI\_FAT\_VENDEDOR... JOIN FILIAIS TIPO\_FILIAL != FRANQUIA vendas\_ecommerce\[\] resolve CNPJ monta IN consulta BI Figura 8 - Sequencia dos dois passos de consulta do canal e-commerce. </figure></div><div class="pw" id="bkmrk--2"><div class="flow"></div></div>## Mapeamento de colunas SQL Server → JSON

<div class="pw" id="bkmrk-coluna-sql-server-ca"><div class="tw"><table><thead><tr><th>Coluna SQL Server</th><th>Campo no JSON</th><th>Descrição</th></tr></thead><tbody><tr><td>`CGC_CPF`</td><td>`documento_cliente`</td><td>CPF/CNPJ do cliente</td></tr><tr><td>`VENDEDOR`</td><td>`cod_vendedor`</td><td>Código do vendedor no Linx</td></tr><tr><td>`CUPOM_VENDEDOR`</td><td>`cupom_vendedor`</td><td>Cupom aplicado pelo vendedor</td></tr><tr><td>`CODIGO_FILIAL`</td><td>`codigo_filial`</td><td>Código da filial (padded com zeros)</td></tr><tr><td>`EMISSAO`</td><td>`data_documento`</td><td>Data de emissão no formato retornado pelo SQL Server; o método atual não converte para ISO 8601</td></tr><tr><td>`PEDIDO_SITE`</td><td>`pedido_site`</td><td>Número do pedido no e-commerce</td></tr><tr><td>`PEDIDO_WMS`</td><td>`pedido_wms`</td><td>Número do pedido no WMS</td></tr><tr><td>`TICKET`</td><td>`ticket`</td><td>Ticket de atendimento</td></tr><tr><td>`NF`</td><td>`documento`</td><td>Número da NF</td></tr><tr><td>`SERIE`</td><td>`serie`</td><td>Série da NF</td></tr><tr><td>`VALOR_LIQUIDO`</td><td>`valor`</td><td>Valor líquido do pedido</td></tr><tr><td>`QTDE_LIQUIDA`</td><td>`qtde`</td><td>Quantidade líquida de itens</td></tr></tbody></table>

</div><div class="ib">**Tratamento de espaços** Aplicamos `rtrim()` e `ltrim()` em todos os campos mapeados — o SQL Server da Linx pode devolver strings com espaços à esquerda ou direita.</div></div>## Retorno da API

```
// Um elemento do array "vendas_ecommerce"
{
  "documento_cliente": "123.456.789-00",
  "cod_vendedor":      "EC001",
  "cupom_vendedor":    "VEND10OFF",         // Pode ser string vazia
  "codigo_filial":     "000001",
  "data_documento":    "2026-05-22 09:15:00",
  "pedido_site":       "PS-98765",
  "pedido_wms":        "WMS-54321",
  "ticket":            "TKT-11111",
  "documento":         "000005678",
  "serie":             "001",
  "valor":             "259.90",
  "qtde":              "2"
}
```

## Segurança — placeholders dinâmicos

O número de filiais varia por CNPJ. Em vez de concatenar valores na query, geramos placeholders e usamos `bindValue()` para cada um:

```
// Geração dos placeholders (PHP 7.3)
$placeholders = [];
foreach ($filiais as $i => $codigo) {
    $key = ':filial' . $i;
    $placeholders[] = $key;
    $stmt->bindValue($key, $codigo);
}
// Resultado na query: IN (:filial0, :filial1, :filial2)
// Nenhum valor externo é interpolado na string SQL.
```

<div class="pw" id="bkmrk-prote%C3%A7%C3%A3o-contra-sql-"><div class="sb">**Proteção contra SQL injection** Todos os parâmetros são vinculados via prepared statements — datas com `bindParam()` e códigos de filial com `bindValue()`. Nenhuma concatenação de input externo ocorre.</div><div class="wb">**CNPJ sem filial ativa** `buscarDadosEcommercePorLoja()` retorna um envelope de erro quando não encontra filial ativa no MySQL, mas `getVendasConsolidadas()` consome apenas a chave `data`. Na resposta final, isso aparece como `vendas_ecommerce: []`; se os demais canais também estiverem vazios, o endpoint retorna erro de nenhum registro.</div>  
</div>

# API — Parâmetros e Resposta

<div class="pw" id="bkmrk-"><div class="ph">  
</div></div>Especificação completa de getVendas: entrada, saída, erros e exemplos JSON.

## Identificação do serviço

<div class="pw" id="bkmrk-refer%C3%AAncia"><div class="card"><div class="ct">Referência</div></div></div>**Token:** `<TOKEN_FULLSTORE>`  
**Método HTTP:** `POST` | **Formato:** JSON

**BaseUrl: HTTPS://SEUSISTEMA.PDV.MODA/bibliotecas/ba4e7bb5-4770-4780-beaa-b28980153cc7/wosk/webservice/fullStoreService**

## Payload de entrada

```


{
    "acao": "getVendas",
    "key":" CHAVE API FORNECIDA",
    "parametros": {
        "dataInicio": "2026-03-10",
        "dataFim": "2026-03-10",
        "cnpjLoja": ""
    }
}
```

<div class="pw" id="bkmrk-campo-tipo-formato-o"><div class="tw"><table><thead><tr><th>Campo</th><th>Tipo</th><th>Formato</th><th>Obrig.</th><th>Descrição</th></tr></thead><tbody><tr><td>`acao`</td><td>string</td><td>literal</td><td><span class="badge br">Sim</span></td><td>Deve ser exatamente `"getVendas"`</td></tr><tr><td>`parametros.dataInicio`</td><td>string</td><td>`Y-m-d`</td><td><span class="badge br">Sim</span></td><td>Início do período. Ex: `"2026-05-01"`</td></tr><tr><td>`parametros.dataFim`</td><td>string</td><td>`Y-m-d`</td><td><span class="badge br">Sim</span></td><td>Fim do período. Ex: `"2026-05-31"`</td></tr><tr><td>`parametros.cnpjLoja`</td><td>string</td><td>14 dígitos</td><td><span class="badge br">Sim</span></td><td>CNPJ sem máscara — filtra todos os canais</td></tr><tr><td>`falha`</td><td>any</td><td>—</td><td><span class="badge bo">Teste</span></td><td>Se presente e não nulo, forçamos retorno de erro</td></tr></tbody></table>

</div><div class="wb">**Limite de 200 dias** Se `dataInicio` ou `dataFim` estiver além de 200 dias no passado, a implementação atual monta um `fallback` interno, mas a resposta final retorna `success: false` com mensagem de nenhum registro encontrado.</div></div>## Resposta de sucesso (com dados)

```
{
  "success": true,
  "message": "Vendas retornadas com sucesso!",
  "data": {
    "vendas": [
      {
        "codigo_cliente": 123, "cod_vendedor": "V001",
        "cnpj_emp": "12345678000190",
        "data_documento": "2026-05-15T10:30:00-03:00",
        "identificador": 9001, "transacao": 9001, "usuario": 42,
        "documento": "000001234", "serie": "001",
        "operacao": "S", "tipo_transacao": "VENDA",
        "cancelado": "N", "delivery": false,
        "itens": [
          { "cod_produto": "PROD-001", "descricao_produto": "Camiseta Branca M",
            "cod_barra": "7891234567890", "quantidade": 2,
            "preco_unitario": 89.90, "valor_total": 179.80 }
        ]
      }
    ],
    "trocas": [
      {
        "motivo": "Defeito", "doc_venda": "000001234", "serie_venda": "001",
        "cod_cliente": 123, "cnpj_emp": "12345678000190",
        "valor_vale": 89.90, "valor_original": 179.80,
        "timestamp": "2026-05-18T14:22:00-03:00",
        "cod_vendedor": "V001", "quantidade": 1
      }
    ],
    "omni": [
      {
        "id_cliente": 456, "cod_vendedor": "V007",
        "cnpj_emp": "12345678000190",
        "data_documento": "2026-05-20 16:45:00",
        "id_pessoa": 88, "identificador": 3001, "transacao": 3001, "usuario": 42,
        "documento": "ORÇ-00321", "vendedor": "João Silva", "cancelado": "N",
        "itens": [
          { "cod_produto": "PROD-099", "descricao_produto": "Tênis Running 42",
            "cod_barra": "7896543219870", "quantidade": 1,
            "preco_unitario": 349.90, "valor_total": 349.90 }
        ]
      }
    ],
    "vendas_ecommerce": [
      {
        "documento_cliente": "123.456.789-00", "cod_vendedor": "EC001",
        "cupom_vendedor": "VEND10OFF", "codigo_filial": "000001",
        "data_documento": "2026-05-22 09:15:00",
        "pedido_site": "PS-98765", "pedido_wms": "WMS-54321",
        "ticket": "TKT-11111", "documento": "000005678",
        "serie": "001", "valor": "259.90", "qtde": "2"
      }
    ],
    "fallback": null
  }
}
```

## Resposta — período excede o limite

```
{
  "success": false,
  "message": "Nenhum registro encontrado no periodo 2025-01-01 - 2025-06-30 para o cnpj 12345678000190",
  "data": null
}
```

## Respostas de erro

### Parâmetro obrigatório ausente

```
{ "success": false, "message": "O parametro dataInicio é obrigatorio", "data": null }
```

### Ação inválida

```
{ "success": false, "message": "Acao nao encontrada", "data": null }
```

### Nenhum dado no período

```
{
  "success": false,
  "message": "Nenhum registro encontrado no periodo 2026-05-01 - 2026-05-31 para o cnpj 12345678000190",
  "data": null
}
```

## Formato de datas

O formato de data varia por canal na implementação atual. PDV e Trocas convertem datas com `date('c', strtotime($valor))`. Omni e E-commerce retornam a data no formato vindo da consulta, sem conversão explícita para ISO 8601:

```
// PDV / Trocas
"data_documento": "2026-05-15T10:30:00-03:00"

// Omni / E-commerce
"data_documento": "2026-05-20 16:45:00"
```

# Configuração e Dependências

<div class="pw" id="bkmrk-"><div class="ph"></div></div>## Requires em controle.php

Carregamos três dependências via `require_once(APPPATH . '...')`:

```
require_once(APPPATH . 'controllers/bibliotecas/4cb18a6c-9c9a-4201-82e6-4f6cc3bea445/wosk_webservice.php');
require_once(APPPATH . 'controllers/bibliotecas/592ccfa0-2692-44e3-aaea-cc7c6c6cfcfa/utilOsk.php');
require_once(APPPATH . 'controllers/bibliotecas/0670acec-4aca-43a0-8943-659600407283/db_sqlserver.php');
```

<div class="pw" id="bkmrk-uuid-arquivo-o-que-f"><div class="tw"><table style="width:92.7381%;height:169.578px;"><thead><tr style="height:29.7969px;"><th style="width:14.0806%;height:29.7969px;">UUID</th><th style="width:20.4228%;height:29.7969px;">Arquivo</th><th style="width:65.4244%;height:29.7969px;">O que fornece</th></tr></thead><tbody><tr style="height:46.5938px;"><td style="width:14.0806%;height:46.5938px;">`4cb18a6c-...`</td><td style="width:20.4228%;height:46.5938px;">`wosk_webservice.php`</td><td style="width:65.4244%;height:46.5938px;">Trait `WOSK\Commons\WebService` — formata JSON, valida campos e salva logs</td></tr><tr style="height:46.5938px;"><td style="width:14.0806%;height:46.5938px;">`592ccfa0-...`</td><td style="width:20.4228%;height:46.5938px;">`utilOsk.php`</td><td style="width:65.4244%;height:46.5938px;">Classe pai — conecta ao MySQL e expõe `$this->conexao` (PDO)</td></tr><tr style="height:46.5938px;"><td style="width:14.0806%;height:46.5938px;">`0670acec-...`</td><td style="width:20.4228%;height:46.5938px;">`db_sqlserver.php`</td><td style="width:65.4244%;height:46.5938px;">Gerencia a conexão PDO com o SQL Server Linx (driver `sqlsrv:`)</td></tr></tbody></table>

</div></div>## Conexão MySQL

<div class="pw" id="bkmrk-como-configuramos"><div class="card"><div class="ct">Como configuramos</div></div></div>Em `utilOsk`, chamamos `getConfiguracao('db_osk_prod')` — as credenciais ficam em Base64 numa tabela de configuração do sistema. O método `_setConexao()` cria a conexão PDO:

```
// PHP 7.3 — connection string
"mysql:host={$host};port={$porta};dbname={$banco};charset=utf8"

PDO::ATTR_PERSISTENT     = true   // conexão persistente
PDO::ATTR_TIMEOUT        = 120    // timeout em segundos
PDO::ATTR_EMULATE_PREPARES = true
```

## Conexão SQL Server

<div class="pw" id="bkmrk-instanciamos-em-busc"><div class="card"><div class="ct">Instanciamos em buscarDadosEcommercePorLoja()</div></div></div>```
// PHP 7.3
$sqlserver = new \db_sqlserver(false, 'LX_ZERO_300', true);
// Parâmetros: ($homologacao = false, $banco, $conectar_agora = true)
// connection string: "sqlsrv:Server=<HOST_SQLSERVER>,<PORTA>;Database=LX_ZERO_300"
```

<div class="pw" id="bkmrk-ambiente-host-porta-"><div class="card"></div><div class="tw"><table><thead><tr><th>Ambiente</th><th>Host</th><th>Porta</th><th>Banco</th><th>Usuário</th></tr></thead><tbody><tr><td>**Produção**</td><td>`<HOST_SQLSERVER_PROD>`</td><td>`<PORTA_PROD>`</td><td>`LX_ZERO_300`</td><td>`<USUARIO_SQLSERVER>`</td></tr><tr><td>Homologação</td><td>`<HOST_SQLSERVER_HML>`</td><td>`<PORTA_HML>`</td><td>`LINX_HMLG`</td><td>`<USUARIO_SQLSERVER>`</td></tr></tbody></table>

</div><div class="db">**Credenciais hardcoded** As senhas do SQL Server ficam em `db_sqlserver.php`</div></div>## Variáveis de ambiente

<div class="pw" id="bkmrk-vari%C3%A1vel-usada-por-d"><div class="tw"><table><thead><tr><th>Variável</th><th>Usada por</th><th>Descrição</th></tr></thead><tbody><tr><td>`$_ENV["base_db"]`</td><td>`util`</td><td>Nome do banco MySQL principal</td></tr><tr><td>`$_ENV['WOSK_DB_HMG']`</td><td>`WOSK\Common`</td><td>Flag de homologação para conexões WOSK (`false` em produção)</td></tr><tr><td>`APPPATH`</td><td>`controle.php`</td><td>Constante CodeIgniter — base dos `require_once`</td></tr></tbody></table>

</div></div>## Logs de requisição

O trait `WOSK\Commons\WebService` salva automaticamente toda chamada no banco **integrador** (`{base_db}_integrador`):

<div class="pw" id="bkmrk-tabela-conte%C3%BAdo-wosk"><div class="tw"><table><thead><tr><th>Tabela</th><th>Conteúdo</th></tr></thead><tbody><tr><td>`wosk_webservice`</td><td>Registro principal da chamada</td></tr><tr><td>`wosk_webservice_parametros`</td><td>Parâmetros recebidos (JSON)</td></tr><tr><td>`wosk_webservice_retorno`</td><td>Resposta enviada (JSON)</td></tr><tr><td>`wosk_webservice_callback`</td><td>Tokens de callback</td></tr></tbody></table>

</div><div class="ib">**Diagnóstico via log** Em qualquer dúvida sobre o que retornamos em uma chamada anterior, consultamos `wosk_webservice_retorno` no banco integrador filtrando pelo timestamp e pelo token `<TOKEN_FULLSTORE>`.</div>  
</div>

# Troubleshooting e Testes

<div class="pw" id="bkmrk-"><div class="ph"></div></div>Problemas comuns, checklist de diagnóstico e como testar o endpoint manualmente.

## Problemas comuns

### 1. `vendas_ecommerce` sempre retorna vazio

<div class="pw" id="bkmrk-causas-poss%C3%ADveis-%E2%80%94-v"><div class="card"><div class="ct">Causas possíveis — verificar em ordem</div>1. O `cnpjLoja` não tem entidades ativas no MySQL:  
    `SELECT e.* FROM entidade e JOIN juridica j ON e.id_pessoa=j.id WHERE j.cnpj='CNPJ' AND e.situacao='ATIVO'`
2. A filial está como `TIPO_FILIAL = 'FRANQUIA'` no SQL Server — excluímos franquias.
3. A view `W_BI_FAT_VENDEDOR_VDK_OMNI_V0` não tem dados para o período/filial informados.
4. Falha na conexão SQL Server — verificar porta `1435` e extensão `pdo_sqlsrv`.

</div></div>### 2. Erro de nenhum registro por período além do limite

<div class="pw" id="bkmrk-causa"><div class="card"><div class="ct">Causa</div></div></div>O período informado ultrapassa **200 dias no passado**. A implementação atual monta um `fallback` internamente, mas a resposta final retorna `success:false` com mensagem de nenhum registro encontrado. Com a data de hoje **2026-06-09**, o limite de calendário recua até **2025-11-21**; por causa da comparação com horário atual, use **2025-11-22** como primeira data segura para evitar a borda.

```
// dataInicio: "2025-01-01" → excede → retorna success:false
// dataInicio: "2025-11-22" → dentro do limite com margem de segurança
```

### 3. Erro `success: false` — campo faltante

<div class="pw" id="bkmrk-solu%C3%A7%C3%A3o"><div class="card"><div class="ct">Solução</div></div></div>Verificar se o payload contém os três campos dentro de `parametros`: `dataInicio`, `dataFim` e `cnpjLoja`. Valores `null` ou string vazia também disparam a validação.

### 4. Trocas não aparecem mesmo com devoluções registradas

<div class="pw" id="bkmrk-causas-poss%C3%ADveis-a-t"><div class="card"><div class="ct">Causas possíveis</div>- A troca não está com `situacao = 'FINALIZADO'` — buscamos apenas finalizadas.
- O `tipo_estoque` não é `'TROCA'` — outros tipos de entrada são ignorados.
- A data de emissão da troca está fora do período solicitado.

</div></div><div class="pw" id="bkmrk--1"><div class="card"></div></div>## Checklist de diagnóstico

<div class="pw" id="bkmrk-%23-verifica%C3%A7%C3%A3o-comand"><div class="tw"><table><thead><tr><th>\#</th><th>Verificação</th><th>Comando / Query</th></tr></thead><tbody><tr><td>1</td><td>MySQL acessível?</td><td>`SELECT 1` via PDO</td></tr><tr><td>2</td><td>CNPJ existe em `juridica`?</td><td>`SELECT * FROM juridica WHERE cnpj = 'CNPJ'`</td></tr><tr><td>3</td><td>CNPJ tem filiais ativas?</td><td>`SELECT e.codigo FROM entidade e JOIN juridica j ON e.id_pessoa=j.id WHERE j.cnpj='CNPJ' AND e.situacao='ATIVO'`</td></tr><tr><td>4</td><td>Existem vendas PDV no período?</td><td>`SELECT COUNT(*) FROM movimentacao WHERE modulo='PDV' AND tipo='SAIDA' AND data_emissao BETWEEN '...' AND '...'`</td></tr><tr><td>5</td><td>SQL Server acessível?</td><td>`new \db_sqlserver(false, 'LX_ZERO_300', true)` — exception = falha de conexão</td></tr><tr><td>6</td><td>View tem dados?</td><td>No SSMS: `SELECT TOP 10 * FROM W_BI_FAT_VENDEDOR_VDK_OMNI_V0 WHERE EMISSAO >= 'data'`</td></tr><tr><td>7</td><td>Log da requisição salvo?</td><td>`SELECT * FROM wosk_webservice_retorno ORDER BY id DESC LIMIT 5`</td></tr></tbody></table>

</div></div>## Como testar o endpoint

### Via cURL

```
curl -X POST https://SEU_SERVIDOR/bibliotecas/<TOKEN_FULLSTORE> \
  -H "Content-Type: application/json" \
  -d '{
    "acao": "getVendas",
    "parametros": {
      "dataInicio": "2026-05-01",
      "dataFim":    "2026-05-31",
      "cnpjLoja":   "12345678000190"
    }
  }'
```

### Forçar erro (ambiente de testes)

```
{
  "acao":   "getVendas",
  "falha":  "teste",
  "parametros": {
    "dataInicio": "2026-05-01",
    "dataFim":    "2026-05-31",
    "cnpjLoja":   "12345678000190"
  }
}
```

## Interpretando a resposta

<div class="pw" id="bkmrk-cen%C3%A1rio-success-fall"><div class="tw"><table><thead><tr><th>Cenário</th><th>success</th><th>fallback</th><th>Arrays</th><th>Ação</th></tr></thead><tbody><tr><td>Funcionamento normal</td><td><span class="badge bg">true</span></td><td>`null`</td><td>Preenchidos</td><td>Processar normalmente</td></tr><tr><td>Sem movimento no período</td><td><span class="badge br">false</span></td><td>—</td><td>—</td><td>Validar se realmente não há dados no período</td></tr><tr><td>Período além do limite</td><td><span class="badge br">false</span></td><td>Não exposto na resposta atual</td><td>—</td><td>Ajustar datas da consulta</td></tr><tr><td>Parâmetro faltando</td><td><span class="badge br">false</span></td><td>—</td><td>—</td><td>Corrigir payload</td></tr><tr><td>Falha de banco de dados</td><td><span class="badge br">false</span></td><td>—</td><td>—</td><td>Verificar conectividade e logs PHP</td></tr></tbody></table>

</div><div class="sb">**Regra de ouro: verificar success antes dos arrays** Toda integração deve checar o campo top-level `success` antes de processar qualquer array. Na implementação atual, resposta sem nenhum registro retorna `success:false`; arrays vazios só aparecem quando pelo menos o envelope de dados é retornado.</div>  
</div>