尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Transferências entre CDs — análise de rede Aplicativo web que planeja transferências de estoque entre todos os CDs da rede: cada CD pode ser origem (pelo que sobra) e destino (pelo que falta), na sequência que o planejador escolher — mostrando o benefício e o impacto fiscal (ICMS) por rota ao lado. Mudança de conceito (v2). Antes eram três bases e uma origem fixa (CD10 → demais CDs). Agora são duas bases e a rede inteira em uma análise só.檢視完整 README ↗
Aplicativo web que planeja transferências de estoque entre todos os CDs da rede: cada CD pode ser origem (pelo que sobra) e destino (pelo que falta), na sequência que o planejador escolher — mostrando o benefício e o impacto fiscal (ICMS) por rota ao lado.
Mudança de conceito (v2). Antes eram três bases e uma origem fixa (CD10 → demais CDs). Agora são duas bases e a rede inteira em uma análise só.
BASE ÚNICA DE CDs BASE DE PEDIDOS (opcional)
(todos os CDs empilhados) ano_mês · CD · produto
↓ o que sobra ↓ o que falta ↓
EXCESSO NECESSIDADE ←—— modo "saldo ideal" ou "pedidos"
Sequência de ORIGENS → Ordem de DESTINOS
CD10 → CD9 → CD8 CD1 → CD2 → CD7 → CD8
│ │
└── cada origem, na sua vez, olha TODOS os destinos na ordem
disponível + pendente − venda média 3m − estoque objetivoestoque objetivo − disponível − pendenteUm CD pode estar nas duas listas. A única regra absoluta: nenhum CD transfere para si mesmo.
lib/engine/)Puro, sem estado e testado (35 casos). Nenhum número de negócio é fixado em
código — tudo vem de ParametrosRede.
| # | Regra | Como o motor calcula |
|---|---|---|
| 1 | Preço | custo de reposição; se 0, preço de lista |
| 2 | Excesso (origem) | MAX(disp + pendente − venda média 3m − objetivo, 0). Com excesso físico ligado, o pendente sai da conta: só o que já está no CD é oferecido |
| 3 | Necessidade (destino) | modo saldo ideal: MAX(objetivo − disp − pendente, 0); modo pedidos: pedido projetado por (mês × CD) |
| 3b | Teto e piso de cobertura | necessidade limitada a venda_dia × dias − (disp + pendente + trânsito). Teto corta demanda inflada; piso garante o mínimo antirruptura. SKU sem giro no destino ignora os dois |
| 4 | Repartição | prioridade estrita (cascata gulosa) ou nivelar dias de cobertura (water-filling: enche primeiro quem está mais descoberto) |
| 5 | Ordem dos baldes | modo pedidos: mês → destino (mês 1 de todos os destinos antes do mês 2); modo saldo ideal: destino a destino |
| 6 | Embarque | opção de caixa fechada (múltiplos da embalagem, ROUNDDOWN), mínimo por linha (un e R$) e carga mínima por rota |
| 7 | Transferência imediata | MIN(transf, disp − venda média × fator), rateada entre os destinos na ordem, em caixa fechada |
| 8 | Cobertura | (disp + pendente) × 30 / venda média, na visão do CD de origem |
| 9 | Impacto fiscal | valor transferido × alíquota da ROTA (origem → destino) |
| 10 | Capacidade operacional | cada alocação consome três orçamentos ao mesmo tempo — expedição da origem, recebimento do destino e transporte da rota. O menor é o gargalo |
| 11 | Materialidade | só entram no plano rotas × SKU com transferência > 0 |
Invariantes verificados a cada análise (reconciliacao):
Desempenho: rede de 6 CDs × 20 mil produtos (120 mil linhas de base) em
menos de 1 s — teste de performance em lib/engine/calc.test.ts.
Todas essas regras vêm desligadas por padrão (o resultado sai igual à regra crua da base) e ficam na tela Nova análise. Cada grupo tem uma chave geral na barra do topo — Teto/piso de cobertura, Caixa fechada e mínimos e Capacidade operacional — mais um botão Desligar todas as restrições. Desligar não apaga nada: os valores continuam guardados e voltam a valer quando a chave é religada, o que torna barato comparar o plano com e sem restrição.
| Regra | Para que serve | Sugestão |
|---|---|---|
| Teto de cobertura (dias) | Um estoque objetivo inflado — ou 3 meses de pedido — puxa volume demais para um CD e trava capital do outro lado. O teto converte a demanda em dias de venda. | 60–90 dias |
| Piso de cobertura (dias) | Objetivo defasado ou zerado esconde uma ruptura real: sem piso, o CD em falta não aparece como destino. | 15–30 dias |
| Excesso físico | O pendente ainda não chegou; sugerir a transferência dele gera ordem que o WMS não consegue executar. | ligar quando o plano for executado no curto prazo |
| Nivelar dias de cobertura | Na prioridade estrita, o excesso escasso vai todo para o primeiro CD e o último fica em ruptura. O nivelamento reparte pelo giro. | usar quando os destinos têm importância parecida |
| Caixa fechada | Transferência quebrada não embarca no WMS. | ligar quando a operação exige caixa fechada |
| Mínimo por linha / carga mínima por rota | Cauda longa de 3 unidades e rotas de R$ 200 custam mais em frete e conferência do que o benefício. | mínimo por linha em R$ e carga mínima por rota |
O dashboard mostra a necessidade bruta → considerada por destino sempre que o teto ou o piso mudou o número, e o plano exporta a coluna não enviado por caixa fechada.
Um plano que sugere mais do que a rede consegue mover não é um plano, é uma lista de desejos. A análise aceita três limites, válidos ao mesmo tempo:
| Limite | O que representa |
|---|---|
| Expedição por origem | separação, embalagem e embarque que o CD consegue produzir na janela |
| Recebimento por destino | docas, conferência e endereços livres no CD que recebe |
| Transporte por rota | frota/viagens disponíveis entre aquele par de CDs |
Cada alocação consome os três — o menor é o gargalo. A capacidade é medida na unidade que faz sentido para a operação, e o motor converte cada unidade transferida usando dados do próprio SKU:
| Métrica | Fator por unidade | Coluna da base |
|---|---|---|
| Unidades | 1 | — |
| Caixas | 1 / embalagem de compra | Qt_Emb_Compra |
| Paletes | 1 / unidades por palete | unidades_por_palete |
| Peso (kg) | peso unitário | peso_unitario |
| Volume (m³) | cubagem unitária | cubagem_unitaria |
| Valor (R$) | preço unitário | custo/preço |
As três últimas colunas são opcionais — a validação da importação lista quais métricas a sua base sustenta. SKU sem o dado da métrica escolhida não consome capacidade, e o resultado diz quantos ficaram nessa situação.
Dois detalhes que fecham o ciclo:
O dashboard traz um painel com limite, comprometido, usado, % de utilização e quanto ficou barrado em cada ponto — e nomeia o gargalo da rede, que é onde ampliar capacidade libera mais transferência do que remexer prioridades.
npm test
A base anexada não precisa de nenhuma coluna de fórmula: o app lê só as
colunas cruas e recalcula tudo (mapa em CAMPOS_CALCULADOS, lib/data/schema.ts).
Os cabeçalhos aceitam variações e formato pt-BR (1.234,56); CSV em UTF-8 ou
latin1 é detectado automaticamente.
Base grande? Mande CSV. O CSV é lido em um passe direto (300 mil linhas em ~1,6 s, pico de ~270 MB). XLSX e XLSB passam pelo SheetJS, que carrega a planilha inteira: as mesmas 300 mil linhas levam ~18 s e passam de 1 GB de memória — perto do limite de uma função serverless.
Na importação, informe a data da posição de estoque — é ela que evita contar duas vezes (ou deixar de contar) uma transferência já faturada.
| Coluna | Campo | Obrigatória |
|---|---|---|
| CD / Depósito | cd | ✅ |
Código do produto (CodsemDv) | codigoProduto | ✅ |
| Estoque disponível | estoqueDisponivel | ✅ |
| Estoque objetivo | estoqueObjetivo | ✅ |
| Venda média 3 meses (média mensal) | vendaMedia3m | ✅ |
| Quantidade pendente | quantidadePendente | — |
Custo de reposição (custo, custo_reposicao…) / Preço de lista | custoReposicao / precoLista | — |
Embalagem de compra (embalagem, emb_compra…) | embCompra | — |
Descrição do produto (produto, descricao, descricao_produto) | produto | ✅ |
Categoria N3 (categoria_n3, CAT_NÍVEL_3, categoria) | categoriaN3 | ✅ — é a categoria exibida e filtrada no plano |
| Fornecedor · comprador · analista · categorias N1, N2, N4 | — | — |
| Unidades por palete | unidadesPorPalete | — (só para capacidade em paletes) |
| Peso unitário (kg) | pesoUnitario | — (só para capacidade em peso) |
| Cubagem unitária (m³) | cubagemUnitaria | — (só para capacidade em volume) |
Uma linha por (CD, produto). Chave repetida bloqueia a importação.
ano_mês (aceita 2026_07, 2026-07, 07/2026) · CD destino ·
código do produto · pedido.
CD origem · CD destino · código do produto · quantidade faturada ·
(opcional) documento/NF e data. O casamento é por rota + produto, na ordem
de aprovação, com baixa parcial quando a quantidade faturada é menor que a
aprovada. Linhas sem sugestão correspondente são listadas, e não alteram nada.
A data aceita dd/mm/aaaa (também com - ou .), aaaa-mm-dd, aaaammdd e
a célula de data do Excel (que chega como número, ex.: 46280). Data que
não é reconhecida usa a data do processamento e aparece como aviso na prévia.
A baixa grava tudo numa transação: ou entram todas as baixas e o registro da
importação, ou nada entra.
Você sobe a base a cada rodada. Entre uma rodada e outra, transferências são aprovadas, faturadas (ou não), e a base muda. Duas invariantes sustentam isso:
1. A data da posição de estoque decide o que já está refletido.
Ao importar, você informa quando a base foi extraída (não quando está subindo). O app compara essa data com a data de cada faturamento:
| Situação da sugestão | A base reflete? | O app… |
|---|---|---|
| Aprovada, não faturada | não — o estoque não saiu | desconta da origem, trata como trânsito no destino |
| Faturada antes da data da base | sim — a base já mostra a saída | não desconta (contar de novo seria descontar duas vezes) |
| Faturada depois da data da base | não — a base é anterior | continua descontando até a próxima base chegar |
É esse terceiro caso que evita o pior erro possível: importar uma base extraída antes do faturamento e o app sugerir de novo o que já saiu do CD.
2. Aprovar duas vezes a mesma rota + produto não soma volume inexistente.
Nenhum dos dois casos exige disciplina do operador: a trava é do app.
Outras proteções do ciclo:
análise → aprova linha → SUGESTÃO EM ABERTO ──(desconta origem e destino)──┐
│ │
│ importa faturamento │ próxima análise
▼ │
FATURADA (bases atualizadas já refletem) ───────┘
A caixa "Considerar sugestões já aprovadas" permite rodar uma análise do cenário cheio, ignorando a carteira, sem apagar nada.
Dashboard executivo — KPIs (excesso disponível, transferências, cobertura da necessidade, impacto fiscal), matriz origem × destino (heatmap na sequência escolhida), aproveitamento por origem, cobertura por destino, painel de capacidade com o gargalo da rede, maiores rotas e detalhe por rota (com quebra mensal no modo pedidos).
Nova análise — upload das duas bases com validação, escolha do modo de demanda, sequência de origens, ordem de destinos (cada CD mostra quanto tem de excesso/falta e o que já está comprometido), alíquotas por rota, regras de necessidade/materialidade e capacidade operacional. Um clique roda a análise.
Plano de transferência — uma linha por rota × SKU, filtros (origem, destino, categoria, comprador, cobertura, só imediata), busca, ordenação, exportação CSV/Excel e aprovação (selecionadas ou todas do filtro).
Carteira — sugestões em aberto e faturadas, cancelamento, modal de importação do faturamento (com prévia) e geração da ordem de transferência para ERP/WMS com o saldo em aberto. No dashboard, o histórico de análises permite excluir uma ou várias (seleção em lote). A confirmação lista o que sai, avisa se é a análise aberta e mostra quantas sugestões em aberto cada uma ainda tem na carteira — com a opção de cancelá-las junto (desligada por padrão). Excluir apaga o resultado e o plano guardado; faturamento já registrado nunca é apagado.
Acompanhamento — o que foi aprovado virou NF, e em quanto tempo:
aprovado = faturado + em aberto + cancelado, e o
atendimento é faturado ÷ (aprovado − cancelado);O período (30, 90, 180 dias ou tudo) filtra pela data de aprovação.
A decisão de arquitetura é simples: o app guarda resultado, não base.
A base pesada entra, é analisada e sai. O que fica é o resultado.
| Camada | O que é | Onde vive | Sobrevive a deploy/instância? |
|---|---|---|---|
| Carteira | sugestões aprovadas e baixas por faturamento | Postgres (Neon) | ✅ |
| Resultado das análises | parâmetros, KPIs, resumos por rota/origem/destino, o que foi aprovado | Postgres, ou o armazenamento quando não há banco | ✅ |
| Plano | as linhas origem × destino × SKU, só com o que o app usa | Armazenamento, em TSV com dicionário | ✅ |
| Insumo (base + pedidos) | a planilha normalizada | Armazenamento, em TSV comprimido | ✅ |
| Base crua de 900 mil linhas | o arquivo que você sobe | — | descartada após a importação |
O ponto: o plano é pequeno. A base tem 900 mil linhas com dezenas de colunas; o plano tem as linhas que interessam, com os campos que o app de fato mostra — e os campos deriváveis (valor, caixas, impacto fiscal, status de cobertura) nem são gravados, são recalculados na leitura.
Medido na escala real — 11 CDs × 80 mil produtos:
| Etapa | Tempo | Tamanho |
|---|---|---|
| Importar a base (880 mil linhas, CSV de 156 MB) | 18 s | insumo guardado: 7,6 MB |
| Rodar a análise (1 origem → 10 destinos) | 2 s (motor: 0,5 s) | 238.800 linhas de plano |
| Guardar o plano | — | 2,2 MB |
| Instância fria: abrir o dashboard | 16 ms | — |
| Instância fria: abrir o plano completo | 948 ms | 325 MB de RSS |
| Filtrar de novo (já em cache) | 54 ms | — |
Visualizar, filtrar, exportar e aprovar não dependem da base: só de rodar uma análise nova. Enquanto a base estava carregada, o processo usava 1,9 GB; depois que ela sai de cena, o app trabalha com 325 MB.
O corpo de uma função serverless no Vercel não passa de 4,5 MB, e a base real tem dezenas de MB. Com um Blob store conectado ao projeto, a tela envia o arquivo direto do navegador para o Vercel Blob e a importação recebe só a URL — o arquivo nunca atravessa a função. Sem o token, o app cai no upload tradicional (que funciona local e para arquivos pequenos) e a tela avisa qual caminho está ativo.
Vale para as três planilhas — base, pedidos e faturamento. Detalhes:
upload/.Números medidos com 300 mil linhas (CSV de 54 MB): importação em ~4 s, e o insumo normalizado ocupa 2,1 MB no armazenamento.
npm install
npm run dev # http://localhost:3000
Importe o repositório no Vercel (framework Next.js detectado
automaticamente; vercel.json ajusta o timeout das rotas pesadas).
Banco (Vercel Neon / Postgres): crie um banco Neon no painel do Vercel e
conecte ao projeto — POSTGRES_URL (ou DATABASE_URL) é injetada
automaticamente. Opcionalmente aplique o esquema completo:
npm run seed:pg
Guarda a carteira (sugestao_transferencia, faturamento_evento) e o
resultado das análises (analise_resultado) — as tabelas são criadas sob
demanda.
Armazenamento de arquivos (Vercel Blob): crie um Blob store com acesso
Private e conecte ao projeto. Lojas novas autenticam por OIDC: o
Vercel injeta só BLOB_STORE_ID, e o upload direto usa URL pré-assinada
emitida pelo servidor. Lojas antigas, com BLOB_READ_WRITE_TOKEN, seguem
funcionando pelo token de cliente. É o que habilita o upload direto (arquivos acima de
4,5 MB) e o que faz o insumo sobreviver à troca de instância. Sem ele, o app
grava em .data/ no disco local — bom para desenvolvimento, efêmero no
serverless.
Por que privado. A base traz estoque e custo de todos os CDs. Numa loja pública, qualquer pessoa com a URL de um arquivo lê o conteúdo. Na privada, toda leitura exige o token, que só o servidor tem. O acesso é escolhido na criação da loja e não muda depois; o app usa privado por padrão. Uma loja pública já existente só funciona com
BLOB_ACESSO=public— prefira criar outra, privada.
Sem banco e sem Blob, tudo roda em memória: útil para avaliar, mas a tela sinaliza o estado e nada sobrevive a um novo deploy.
Variáveis de ambiente
| Variável | Para quê |
|---|---|
POSTGRES_URL / DATABASE_URL | carteira e resultados das análises |
BLOB_STORE_ID | loja do Blob conectada por OIDC (lojas novas) |
BLOB_READ_WRITE_TOKEN | loja do Blob com token fixo (lojas antigas) |
BLOB_ACESSO | public só para uma loja pública antiga (padrão: privada) |
main.main dispara o deploy de produção; cada PR ganha um Preview.main: PR obrigatório + aprovação + status check
CI / test-build (.github/workflows/ci.yml, roda npm test e
npm run build).Autenticação (SSO):
lib/auth.tsé um stub pronto para o SSO corporativo (lêx-user-email). Em produção, plugue o provedor (ex.: Azure AD via NextAuth) mantendo a trilha de auditoria já existente.
app/ Telas + rotas de API
analise/ Nova análise (bases, sequências, parâmetros)
plano/ Plano de transferência + aprovação
carteira/ Sugestões aprovadas + faturamento + ordem ERP
acompanhamento/ Aprovado × faturado e prazo até a NF
api/ status · cds · params · import · importlog · analise ·
dashboard · plano[/export] · carteira[/ordem] · faturamento ·
acompanhamento
lib/engine/ Motor de rede (puro) + tipos + testes
lib/data/ Esquemas, parsing, validação, leitura de planilha, seed
lib/query/ Filtro/paginação/agregação server-side
lib/store/ Estado da instância, carteira, resultados das análises,
plano persistido, repositório do insumo e armazenamento
lib/export.ts CSV/Excel do plano e ordem de transferência
components/ Nav, UI e o seletor ordenado de CDs
Explícitos de propósito — o motor é guloso e determinístico, não um otimizador:
venda_media_3m é lida como média
mensal dos 3 meses. Se a base trouxer a soma do trimestre, o excesso e a
cobertura saem distorcidos — divida por 3 antes de importar.Frete por rota e consolidação de carga, validade (shelf life) e lead time no horizonte, capacidade por janela diária, solver de otimização global como modo avançado (a cascata gulosa segue como padrão) e integração direta com o ERP para dispensar a importação manual do faturamento.
DADOS_DIRpasta local do armazenamento em disco (padrão .data) |
DEMO_DATA | 1 força a base de exemplo; 0 desliga em qualquer ambiente |