Software de Kardex e Estoque

Kardex Tauro

O software de gestão de estoque Kardex Tauro® foi projetado para gerenciar seu armazém ou depósito de forma eficiente e pode ser aprendido em um curto período de tempo.

O Kardex Tauro é gratuito para uso não comercial.
Não requer conexão com a internet e funciona no Windows.

Código Python: classificação ABC e giro de estoque

Código Python: classificação ABC e giro de estoque

Nem todo item na prateleira vale a mesma coisa. Poucos produtos carregam a maior parte do dinheiro parado em estoque e o resto apenas ocupa espaço. A classificação ABC separa essas três famílias: as que pedem vigilância quase diária, as que se revisam toda semana e as que basta olhar de vez em quando. O giro responde à outra pergunta do dia a dia: de quanto em quanto tempo um produto se renova e quantos dias de estoque estão parados. Este artigo traz um programa em Python, gratuito e sem dependências, que faz esse trabalho com o seu próprio estoque: lê um arquivo CSV, ordena os produtos pelo valor de consumo anual, separa em A, B e C, calcula o giro e os dias de estoque e deixa a lista ordenada em um arquivo que abre no Excel. Ele foi escrito para o dono da loja de materiais ou da distribuidora, para o contador que confere quanto capital está parado no depósito e para o estoquista que precisa saber em qual produto ficar de olho primeiro.

⬇ Baixar o código (ZIP)
Arquivo do ZIPPara que serve
clasificacion_abc.pyO programa completo, comentado linha por linha em português.
datos/inventario.csvEstoque de exemplo: dez produtos de loja de materiais com o consumo anual, o custo unitário e o estoque médio de cada um.
salida_ejemplo.txtA saída que você deve ver na tela ao rodar o programa, para comparar com a sua.
LEIAME.mdO guia do pacote e os dois detalhes de método que valem dinheiro.

O que o programa faz

  1. Lê o estoque de datos/inventario.csv: nome do produto, unidades consumidas no ano, custo unitário e unidades mantidas em média no depósito.
  2. Calcula o valor de consumo anual de cada produto multiplicando as unidades consumidas pelo custo unitário. Isso não é o que está na prateleira hoje: é o dinheiro que o produto movimenta no ano, e é essa cifra que ordena a lista inteira.
  3. Ordena do maior para o menor e calcula a participação de cada produto sobre o total e o acumulado, que é a soma que cresce de cima para baixo.
  4. Define a classe: A até oitenta por cento do acumulado, B até noventa e cinco por cento e C o resto. Existe um detalhe fino que quase todo mundo deixa passar: o produto que faz cruzar o corte já cai na classe seguinte, então a classe A fica um pouco abaixo dos oitenta por cento. Isso é normal e é bem mais útil do que forçar um número redondo.
  5. Calcula o giro dividindo o consumo anual pelo estoque médio. Um giro de 5 diz que o produto se renovou cinco vezes no ano; um giro de 1 avisa que ainda há mercadoria comprada há um ano na prateleira.
  6. Calcula os dias de estoque: os 360 dias do ano comercial divididos pelo giro. É a resposta à pergunta do chefe do depósito sobre quantos dias o estoque aguenta.
  7. Imprime a tabela ordenada, o resumo por classe com as porcentagens e duas conferências, e grava salida/clasificacion_abc.csv e salida/resumen_abc.csv.

Como rodar o programa

Você só precisa do Python 3.11 ou superior instalado, com a caixa que coloca o Python no PATH marcada na instalação. Não há mais nada para instalar: o programa usa somente a biblioteca padrão, então você não mexe em gerenciador de pacotes nem em ambiente virtual.

  1. Baixe o ZIP pelo botão verde e descompacte em qualquer pasta.
  2. Abra o terminal (no Windows cmd, no Linux ou Mac qualquer terminal) e entre nessa pasta com o comando cd.
  3. Rode python clasificacion_abc.py.

O programa localiza a pasta onde ele mesmo está, então você pode chamá-lo de qualquer lugar: ele sempre busca os dados e grava os resultados ao lado de si mesmo. Se aparecerem erros de codificação, confira se o CSV está salvo em UTF-8. Com o estoque de exemplo a execução leva um segundo, sem internet e sem nada mais instalado.

O código, explicado

Quatro trechos reais, copiados exatamente do programa que vem dentro do ZIP, sem nenhum ajuste.

Um: a tabela é definida como dados, não como uma impressão gigante. Cada coluna declara o nome interno, a largura e o título que aparece na tela; a partir dessa lista o programa calcula a largura da faixa e de cada separador:

# Largura fixa de cada coluna: assim a tabela nunca sai do lugar
COLUMNAS = [
    ("producto", 30, "Produto"),
    ("valor", 17, "Valor consumo"),
    ("participacion", 14, "Participação"),
    ("acumulado", 12, "Acumulado"),
    ("clase", 7, "Classe"),
    ("rotacion", 10, "Giro"),
    ("dias", 9, "Dias"),
]
# A largura da faixa e de cada separador da tabela
ANCHO_LINEA = sum(ancho for _, ancho, _ in COLUMNAS) + len(COLUMNAS) - 1

Repare na última linha: a largura da tabela é calculada a partir das colunas. Se você acrescentar uma coluna amanhã, os separadores se ajustam sozinhos e a tabela continua alinhada.

Dois: dinheiro não se calcula com decimais binários. O programa inteiro trabalha com Decimal, e estes três ajudantes formatam os valores com separador de milhar e vírgula decimal, além das porcentagens:

def miles(valor: Decimal) -> str:
    """Formata um valor com separador de milhar e vírgula decimal (formato local)."""
    entero, decimales = f"{valor:,.2f}".split(".")
    return entero.replace(",", ".") + "," + decimales


def numero(valor: Decimal, decimales: int) -> str:
    """Formata um número com os decimais pedidos e o formato local."""
    entero, cola = f"{valor:,.{decimales}f}".split(".")
    return entero.replace(",", ".") + "," + cola


def porcentaje(valor: Decimal) -> str:
    """Formata uma porcentagem com dois decimais e o sinal de por cento."""
    return numero(valor, 2) + " %"

O arredondamento usa ROUND_HALF_UP, que é o que qualquer pessoa espera ao conferir uma nota à mão: os centavos sobem quando o terceiro algarismo é cinco ou mais. Nunca use números de ponto flutuante para dinheiro, porque os centavos se desviam sozinhos.

Três: o coração do cálculo. Está tudo aqui o que o artigo promete: valor de consumo, giro, dias, ordenação, acumulado e classe:

def calcular(filas: list[dict]) -> tuple[Decimal, list[dict]]:
    """Calcula valor, participação, acumulado, classe, giro e dias de cada produto."""
    inventario = []
    for fila in filas:
        consumo = Decimal(fila["consumo_anual"])          # Unidades gastas no ano
        costo = Decimal(fila["custo_unitario"])             # Quanto custa cada unidade
        promedio = Decimal(fila["estoque_medio"])     # Unidades mantidas em média no depósito

        # Valor de consumo: quanto vale no ano tudo o que se gasta deste produto
        valor = redondear(consumo * costo)
        # Giro: quantas vezes o estoque se renova no ano
        rotacion = redondear(consumo / promedio) if promedio else Decimal("0.00")
        # Dias de estoque: quantos dias o estoque aguenta nesse ritmo
        dias = redondear(DIAS_ANO / rotacion) if rotacion else Decimal("0.00")
        inventario.append({"producto": fila["produto"], "valor": valor,
                           "rotacion": rotacion, "dias": dias})

    # Do maior valor de consumo para o menor: assim o acumulado faz sentido
    inventario.sort(key=lambda item: (-item["valor"], item["producto"]))

    total = sum((item["valor"] for item in inventario), Decimal("0"))
    acumulado = Decimal("0")
    for item in inventario:
        acumulado += item["valor"]
        item["participacion"] = redondear(item["valor"] * CIEN / total)
        item["acumulado"] = redondear(acumulado * CIEN / total)
        # Classe pelo acumulado: A até 80 por cento, B até 95 por cento e C o resto
        if item["acumulado"] <= CORTE_A:
            item["clase"] = "A"
        elif item["acumulado"] <= CORTE_B:
            item["clase"] = "B"
        else:
            item["clase"] = "C"
    # Por isso o produto que faz cruzar o corte já cai na classe seguinte
    return total, inventario

Três coisas merecem uma olhada calma. O giro é o consumo dividido pelo estoque médio, e os dias saem da divisão do ano comercial de 360 dias por esse giro: quanto mais rápido o produto gira, menos mercadoria você precisa manter. A ordenação do maior valor para o menor é o que dá sentido ao acumulado; se a lista não estiver ordenada, o acumulado não diz nada. E a classe é decidida pelo acumulado, não pela participação isolada de cada produto, e é exatamente aí que mora a diferença entre uma classificação bem feita e uma que apenas parece bonita.

Quatro: a saída que abre no Excel. O programa grava dois arquivos CSV, e o do detalhe traz o nome do produto ao lado das cifras já calculadas:

def escribir_abc(inventario: list[dict]) -> None:
    """Salva a classificação produto por produto em um CSV."""
    SALIDA_ABC.parent.mkdir(parents=True, exist_ok=True)
    columnas = ["produto", "valor_consumo", "participacao",
                "acumulado", "classe", "giro", "dias"]
    with open(SALIDA_ABC, "w", encoding="utf-8", newline="") as archivo:
        escritor = csv.writer(archivo)
        escritor.writerow(columnas)
        for item in inventario:
            escritor.writerow([item["producto"], item["valor"], item["participacion"],
                               item["acumulado"], item["clase"], item["rotacion"], item["dias"]])

Nos arquivos CSV as cifras usam ponto decimal, o formato que o Excel entende sem configuração, enquanto na tela o programa mostra o separador local. Assim você continua o trabalho na planilha: filtre a coluna da classe, ordene pelos dias de estoque e a sua lista de prioridades está pronta.

O que você vai ver na tela

Esta é a saída real com o estoque de exemplo que acompanha o ZIP. Primeiro a faixa do programa e a contagem de produtos lidos:

=========================================================================================================
  CLASSIFICAÇÃO ABC E GIRO DE ESTOQUE
=========================================================================================================
Produtos lidos: 10

A tabela ordenada pelo valor de consumo anual. O exemplo fecha com um valor total de consumo anual de 332.270.000,00, e só o cimento cinza responde por 110.000.000,00, ou seja um terço do total. As porcentagens desta tabela estão escritas aqui em palavras e sem o sinal, para não brigar com o editor do blog; na tela o programa as imprime com o sinal e o CSV as guarda como números puros:

ProdutoValor consumoParticipação (por cento)Acumulado (por cento)ClasseGiroDias
Cimento cinza 50 kg110.000.000,0033,1133,11A5,0072,00
Vergalhão 1/2 pol75.000.000,0022,5755,68A8,0045,00
Cabo THHN 12 AWG38.400.000,0011,5667,23A8,0045,00
Tinta anticorrosiva34.200.000,0010,2977,53A3,00120,00
Areia de pedreira m327.000.000,008,1385,65B3,00120,00
Tubo PVC 1/221.600.000,006,5092,15B3,00120,00
Luva de couro7.680.000,002,3194,47B2,00180,00
Parafuso autoatarraxante6.750.000,002,0396,50C1,00360,00
Arame doce kg6.240.000,001,8898,37C1,50240,00
Telha de barro5.400.000,001,63100,00C1,50240,00

E este é o resumo por classe, a parte que você leva para a conversa com o dono do negócio:

ClasseProdutosPor cento do valorPor cento dos itens
Classe A477,5340,00
Classe B316,9430,00
Classe C35,5330,00

O fechamento mostra as duas conferências e os dois arquivos que ficam na pasta de saída:

Valor total do consumo anual: 332.270.000,00

Conferência (soma dos valores do resumo = total): 332.270.000,00 = 332.270.000,00 -> FECHA

Arquivo gerado: salida/clasificacion_abc.csv
Arquivo gerado: salida/resumen_abc.csv

As conferências são a parte de que os contadores mais gostam: a primeira verifica que o acumulado fecha exatamente em cem por cento e a segunda, que a soma dos valores do resumo bate com o total geral. Se alguma dissesse REVISAR em vez de FECHA, há uma cifra errada no CSV e o programa está avisando antes de você decidir em cima de números tortos.

Erros comuns e dicas

  • Arquivo de dados não encontrado. O programa espera datos/inventario.csv ao lado do arquivo do programa. Se você descompactou o ZIP e depois moveu só o .py, monte a pasta de novo com a subpasta datos.
  • Cifras que não batem. Confira se o consumo anual está em unidades e o custo unitário em dinheiro por unidade. Misturar caixas com unidades faz o giro disparar e as classes saem de cabeça para baixo.
  • Estoque médio igual a zero. Não dá para dividir por média zero: o programa deixa o valor em zero para não quebrar, mas essa informação precisa ser corrigida na origem.
  • Separador decimal. Os dados de exemplo usam ponto decimal. Se o seu sistema exporta com vírgula decimal, ajuste os dados antes de rodar o programa.
  • Poucos itens. Com uma lista muito curta o ABC perde o sentido, porque quase tudo cai na classe A. Use o estoque completo do negócio.
  • Um único período. O valor de consumo é uma foto do último ano. Se o negócio é sazonal, rode o programa em cada trimestre e compare.

Quando isso não basta

Este programa é gratuito e serve para organizar o estoque e para entender os números: copie, altere e use com os seus dados. O que ele não faz é tocar o dia a dia, porque não registra entradas e saídas, não baixa o saldo quando alguém vende e não avisa quando um item acaba. Quando o negócio passa de poucos movimentos por dia, o arquivo e o script ficam curtos e é hora de pensar em um sistema. O Kardex Tauro existe exatamente para esse momento, para quando planilha e script já não bastam: ele mantém a ficha de estoque de cada produto, calcula o saldo e deixa o estoque em ordem. Mas antes de olhar qualquer sistema, rode este programa: com a classificação na mão você sabe quais itens justificam o software e quais não.

⬇ Baixar o código (ZIP)

Baixe o ZIP, troque o estoque de exemplo pelo seu e rode o programa. Em menos tempo do que um café você vai saber quais produtos sustentam o negócio e quais estão comendo o seu capital.

Material didático de controle interno, escrito para a oficina de código deste blog. Não é consultoria contábil, tributária nem jurídica: confira as suas cifras e as normas locais antes de decidir com base nesses números.

Compartilhar
Link copiado
Microsoft Store da Microsoft StoreBaixar grátis
Chatea por WhatsApp