📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Módulo 02: Lógica de Programação e Algoritmos. Recomendado revisar antes de começar.
Trilha de Especialização — Nível Introdutório (Curso 25: Sistemas Desktop com Python)
- Visão Geral dos 10 Miniprojetos: 🌱 Guia Pedagógico de Miniprojetos
- Catálogo Geral: 🏗️ Catálogo de Projetos
- ➡️ Implementação Prática: Miniprojeto 03 (Código Completo em 4 Degraus)
Em cantinas escolares, lanchonetes e pequenos quiosques, o controle de estoque de salgados, bebidas e doces é vital para garantir que nenhum produto falte no horário de pico do intervalo.
Neste projeto, o estudante aprende como:
.txt.Produto encapsulando as regras de negócio de baixa de estoque na venda, reposição e identificação de estoque crítico.produtos do SQLite3 com consultas filtradas (WHERE quantidade < 5).flowchart LR
D1["🪜 DEGRAU 1\nImperativo & .txt\nMenu while + estoque.txt\ncom validação de saldo"] --> D2["🪜 DEGRAU 2\nPrimeira Classe POO\nclass Produto\n(vender, repor, crítico)"]
D2 --> D3["🪜 DEGRAU 3\nSQLite Nativo\nUPDATE atômico &\nfiltros de quantidade"]
D3 --> D4["🪜 DEGRAU 4\nInterface Desktop\nCards de métricas, tabela\ne alertas em vermelho"]
style D1 fill:#fffde7,stroke:#fbc02d,stroke-width:2px
style D2 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style D3 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
style D4 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
sistemas_python_intro_03_estoque_cantina/
├── degrau_1_imperativo_txt/
│ ├── main.py # Script imperativo com menu e arquivo estoque.txt
│ └── test_degrau_1.py # Testes automatizados de validação de estoque
├── degrau_2_primeira_classe/
│ ├── main.py # Primeira classe POO (class Produto)
│ └── test_degrau_2.py # Testes unitários da classe Produto
├── degrau_3_sqlite/
│ ├── main.py # Banco relacional SQLite3 nativo com filtros
│ └── test_degrau_3.py # Testes unitários de consultas e baixas SQL
├── degrau_4_interface_customtkinter/
│ └── main.py # Aplicação Desktop com tabela, cards e alertas visuais
├── main.py # Hub Central / Launcher interativo
└── index.md # Este guia didático autoguiado
No VS Code / PyCharm, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:
cd sistemas_python_intro_03_estoque_cantina
# Executar o Hub Central (Menu interativo com todos os degraus):
python main.py
# Ou executar um degrau individualmente:
python degrau_1_imperativo_txt/main.py
python degrau_2_primeira_classe/main.py
python degrau_3_sqlite/main.py
python degrau_4_interface_customtkinter/main.py
[!TIP] Dica para execução a partir da raiz do repositório: Se você abriu o repositório completo no VS Code, basta executar:
python catalogo_projetos/sistemas_python_intro_03_estoque_cantina/main.py
.txt)No primeiro degrau, lemos e gravamos os itens no arquivo estoque.txt no formato nome,preco,quantidade. O programa impede vendas se a quantidade solicitada for maior que o saldo em estoque.
degrau_1_imperativo_txt/main.py):import os
ARQUIVO_PADRAO = "estoque.txt"
def carregar_estoque(caminho=ARQUIVO_PADRAO):
produtos = []
if not os.path.exists(caminho):
return produtos
with open(caminho, "r", encoding="utf-8") as f:
for linha in f:
linha = linha.strip()
if linha:
partes = linha.split(",")
if len(partes) >= 3:
try:
produtos.append({"nome": partes[0], "preco": float(partes[1]), "quantidade": int(partes[2])})
except ValueError:
continue
return produtos
def salvar_estoque(produtos, caminho=ARQUIVO_PADRAO):
with open(caminho, "w", encoding="utf-8") as f:
for p in produtos:
f.write(f"{p['nome']},{p['preco']:.2f},{p['quantidade']}\n")
def cadastrar_produto(produtos, nome, preco, quantidade):
nome = nome.strip()
if not nome or preco <= 0 or quantidade < 0:
return False
produtos.append({"nome": nome, "preco": preco, "quantidade": quantidade})
return True
def registrar_venda(produtos, nome, quantidade_vendida):
for p in produtos:
if p["nome"].lower() == nome.lower():
if p["quantidade"] < quantidade_vendida:
print(f"[ERRO] Estoque insuficiente! Disponível: {p['quantidade']} un.")
return False
p["quantidade"] -= quantidade_vendida
print(f"[OK] Venda registrada! Total: R$ {p['preco'] * quantidade_vendida:.2f}")
return True
return False
degrau_1_imperativo_txt/test_degrau_1.py):"""
Testes Unitários para o Degrau 1 do Projeto 03 (Imperativo & Arquivo TXT)
"""
import os
import unittest
from main import (
cadastrar_produto,
carregar_estoque,
registrar_venda,
repor_estoque,
salvar_estoque
)
ARQUIVO_TESTE = "teste_estoque.txt"
class TestDegrau1Estoque(unittest.TestCase):
def setUp(self):
if os.path.exists(ARQUIVO_TESTE):
os.remove(ARQUIVO_TESTE)
def tearDown(self):
if os.path.exists(ARQUIVO_TESTE):
os.remove(ARQUIVO_TESTE)
def test_cadastrar_e_persistir_produtos(self):
produtos = []
cadastrar_produto(produtos, "Coxinha de Frango", 6.50, 20)
cadastrar_produto(produtos, "Suco de Uva 300ml", 4.00, 15)
self.assertEqual(len(produtos), 2)
self.assertEqual(produtos[0]["preco"], 6.50)
salvar_estoque(produtos, caminho=ARQUIVO_TESTE)
carregados = carregar_estoque(caminho=ARQUIVO_TESTE)
self.assertEqual(len(carregados), 2)
self.assertEqual(carregados[0]["nome"], "Coxinha de Frango")
self.assertEqual(carregados[1]["quantidade"], 15)
def test_regras_de_venda_e_reposicao(self):
produtos = [{"nome": "Empada", "preco": 7.00, "quantidade": 5}]
# Venda com sucesso
venda_ok = registrar_venda(produtos, "Empada", 3)
self.assertTrue(venda_ok)
self.assertEqual(produtos[0]["quantidade"], 2)
# Tentativa de venda sem estoque suficiente
venda_falha = registrar_venda(produtos, "Empada", 10)
self.assertFalse(venda_falha)
self.assertEqual(produtos[0]["quantidade"], 2)
# Reposição de estoque
repor_ok = repor_estoque(produtos, "Empada", 8)
self.assertTrue(repor_ok)
self.assertEqual(produtos[0]["quantidade"], 10)
if __name__ == "__main__":
unittest.main()
Produto)No segundo degrau, a classe Produto reúne os atributos e as regras de negócio em métodos específicos como vender(), repor() e esta_baixo_estoque().
degrau_2_primeira_classe/main.py):class Produto:
def __init__(self, nome, preco, quantidade=0):
self.nome = nome
self.preco = float(preco)
self.quantidade = int(quantidade)
def vender(self, qtd):
if qtd <= 0:
return False, "Quantidade inválida."
if self.quantidade < qtd:
return False, f"Estoque insuficiente ({self.quantidade} un disponíveis)."
self.quantidade -= qtd
return True, f"Venda de {qtd} un realizada! Total: R$ {self.preco * qtd:.2f}."
def repor(self, qtd):
if qtd > 0:
self.quantidade += qtd
return True, f"Estoque reposto (+{qtd} un). Total: {self.quantidade} un."
return False, "Quantidade inválida."
def esta_baixo_estoque(self, limite=5):
return self.quantidade < limite
degrau_2_primeira_classe/test_degrau_2.py):"""
Testes Unitários para o Degrau 2 do Projeto 03 (Primeira Classe POO)
"""
import os
import unittest
from main import Produto, carregar_produtos, salvar_produtos
ARQUIVO_TESTE = "teste_estoque_poo.txt"
class TestDegrau2POO(unittest.TestCase):
def setUp(self):
if os.path.exists(ARQUIVO_TESTE):
os.remove(ARQUIVO_TESTE)
def tearDown(self):
if os.path.exists(ARQUIVO_TESTE):
os.remove(ARQUIVO_TESTE)
def test_classe_produto_metodos(self):
p = Produto("Suco de Laranja", 5.00, 10)
self.assertFalse(p.esta_baixo_estoque())
sucesso, _ = p.vender(7)
self.assertTrue(sucesso)
self.assertEqual(p.quantidade, 3)
self.assertTrue(p.esta_baixo_estoque()) # 3 < 5
# Tentativa de venda sem estoque
falha, _ = p.vender(5)
self.assertFalse(falha)
self.assertEqual(p.quantidade, 3)
# Reposição
p.repor(12)
self.assertEqual(p.quantidade, 15)
self.assertFalse(p.esta_baixo_estoque())
def test_persistencia_objetos(self):
p1 = Produto("Pastel de Carne", 7.50, 8)
p2 = Produto("Refrigerante Lata", 5.00, 2)
salvar_produtos([p1, p2], caminho=ARQUIVO_TESTE)
carregados = carregar_produtos(caminho=ARQUIVO_TESTE)
self.assertEqual(len(carregados), 2)
self.assertEqual(carregados[0].nome, "Pastel de Carne")
self.assertEqual(carregados[1].quantidade, 2)
self.assertTrue(carregados[1].esta_baixo_estoque())
if __name__ == "__main__":
unittest.main()
No terceiro degrau, criamos a tabela produtos no SQLite e executamos atualizações atômicas de estoque (UPDATE produtos SET quantidade = quantidade - ? WHERE id = ?).
degrau_3_sqlite/main.py):import sqlite3
BANCO_PADRAO = "estoque.db"
def inicializar_tabela(caminho=BANCO_PADRAO):
conn = sqlite3.connect(caminho)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS produtos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nome TEXT NOT NULL UNIQUE,
preco REAL NOT NULL,
quantidade INTEGER NOT NULL DEFAULT 0
)
""")
conn.commit()
conn.close()
def registrar_venda(produto_id, quantidade_vendida, caminho=BANCO_PADRAO):
conn = sqlite3.connect(caminho)
cursor = conn.cursor()
cursor.execute("SELECT preco, quantidade FROM produtos WHERE id = ?", (produto_id,))
prod = cursor.fetchone()
if not prod or prod[1] < quantidade_vendida:
conn.close()
return False
cursor.execute("UPDATE produtos SET quantidade = quantidade - ? WHERE id = ?", (quantidade_vendida, produto_id))
conn.commit()
conn.close()
return True
degrau_3_sqlite/test_degrau_3.py):"""
Testes Unitários para o Degrau 3 do Projeto 03 (SQLite3 Nativo)
"""
import os
import unittest
from main import (
cadastrar_produto,
inicializar_tabela,
listar_produtos,
registrar_venda,
repor_estoque
)
BANCO_TESTE = "teste_estoque_degrau3.db"
class TestDegrau3SQLite(unittest.TestCase):
def setUp(self):
if os.path.exists(BANCO_TESTE):
os.remove(BANCO_TESTE)
inicializar_tabela(caminho=BANCO_TESTE)
def tearDown(self):
if os.path.exists(BANCO_TESTE):
os.remove(BANCO_TESTE)
def test_crud_e_regras_estoque_sqlite(self):
p_id = cadastrar_produto("Misto Quente", 8.00, 10, caminho=BANCO_TESTE)
self.assertIsNotNone(p_id)
# Venda
venda_ok = registrar_venda(p_id, 4, caminho=BANCO_TESTE)
self.assertTrue(venda_ok)
# Validação saldo restante
prods = listar_produtos(caminho=BANCO_TESTE)
self.assertEqual(prods[0][3], 6)
# Reposição
repor_ok = repor_estoque(p_id, 5, caminho=BANCO_TESTE)
self.assertTrue(repor_ok)
prods_depois = listar_produtos(caminho=BANCO_TESTE)
self.assertEqual(prods_depois[0][3], 11)
def test_filtro_estoque_critico(self):
cadastrar_produto("Agua Mineral 500ml", 3.00, 2, caminho=BANCO_TESTE)
cadastrar_produto("Pao de Queijo", 4.50, 20, caminho=BANCO_TESTE)
criticos = listar_produtos(apenas_criticos=True, caminho=BANCO_TESTE)
self.assertEqual(len(criticos), 1)
self.assertEqual(criticos[0][1], "Agua Mineral 500ml")
if __name__ == "__main__":
unittest.main()
No quarto degrau, criamos uma aplicação visual para cantina com formulário de cadastro, tabela interativa com ttk.Treeview, cards informativos no topo e tags de cor vermelha para destacar itens com estoque crítico (< 5 unidades).
degrau_4_interface_customtkinter/main.py):import sqlite3
import tkinter as tk
from tkinter import messagebox, ttk
class AppEstoqueCantina:
def __init__(self, root):
self.root = root
self.root.title("🍔 Controle de Estoque da Cantina (Degrau 4)")
self.root.geometry("640x580")
self.criar_widgets()
self.recarregar_dados()
def recarregar_dados(self):
for item in self.tree.get_children():
self.tree.delete(item)
produtos = obter_todos_produtos()
for p_id, nome, preco, qtd in produtos:
tag = "critico" if qtd < 5 else "normal"
status = "⚠️ Crítico" if qtd < 5 else "✅ Normal"
self.tree.insert("", "end", values=(f"#{p_id:02d}", nome, f"R$ {preco:.2f}", f"{qtd} un", status), tags=(tag,))
main.py)O arquivo main.py na raiz do projeto funciona como um painel de controle: permite executar qualquer um dos 4 degraus (ou toda a suíte de testes) a partir de um único menu no terminal, sem precisar navegar manualmente entre as pastas.
main.py):"""
Hub Central / Launcher — Miniprojeto 03: Controle de Estoque da Cantina Escolar
Permite executar de forma autoguiada qualquer um dos 4 degraus pedagógicos ou rodar a suíte de testes.
"""
import os
import subprocess
import sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def rodar_script(caminho_relativo):
caminho_absoluto = os.path.join(BASE_DIR, caminho_relativo)
diretorio = os.path.dirname(caminho_absoluto)
print(f"\n🚀 Executando: {caminho_relativo}...\n")
try:
subprocess.run([sys.executable, caminho_absoluto], cwd=diretorio, check=True)
except KeyboardInterrupt:
print("\n\n⏹️ Execucao interrompida pelo usuario.")
except Exception as e:
print(f"\n❌ Erro durante a execucao: {e}")
def rodar_todos_os_testes():
print("\n🧪 ========================================================")
print(" RODANDO A SUÍTE DE TESTES DOS DEGRAUS 1, 2 E 3")
print("========================================================\n")
testes = [
("Degrau 1 (Imperativo & Arquivo TXT)", "degrau_1_imperativo_txt/test_degrau_1.py"),
("Degrau 2 (Primeira Classe POO)", "degrau_2_primeira_classe/test_degrau_2.py"),
("Degrau 3 (Banco SQLite3 Nativo)", "degrau_3_sqlite/test_degrau_3.py"),
]
for nome, arq in testes:
print(f"▶️ Testando {nome}...")
caminho_abs = os.path.join(BASE_DIR, arq)
diretorio = os.path.dirname(caminho_abs)
res = subprocess.run([sys.executable, caminho_abs], cwd=diretorio)
if res.returncode == 0:
print(f"✅ {nome}: PASSOU!\n")
else:
print(f"❌ {nome}: FALHOU!\n")
def menu_hub():
while True:
print("\n========================================================")
print("🍔 MINIPROJETO 03: CONTROLE DE ESTOQUE DA CANTINA (HUB)")
print("========================================================")
print("1. 🪜 Executar Degrau 1 (Imperativo & Arquivo .txt)")
print("2. 🪜 Executar Degrau 2 (Primeira Classe POO: class Produto)")
print("3. 🪜 Executar Degrau 3 (Banco de Dados SQLite3 Nativo)")
print("4. 🪜 Executar Degrau 4 (Interface Desktop Visual)")
print("5. 🧪 Rodar Todos os Testes Automatizados (Degraus 1, 2 e 3)")
print("6. 🚪 Sair")
print("========================================================")
opcao = input("Escolha o degrau que deseja executar (1-6): ").strip()
if opcao == "1":
rodar_script("degrau_1_imperativo_txt/main.py")
elif opcao == "2":
rodar_script("degrau_2_primeira_classe/main.py")
elif opcao == "3":
rodar_script("degrau_3_sqlite/main.py")
elif opcao == "4":
rodar_script("degrau_4_interface_customtkinter/main.py")
elif opcao == "5":
rodar_todos_os_testes()
elif opcao == "6":
print("👋 Encerrando o launcher. Bons estudos!")
break
else:
print("❌ Opcao invalida. Digite de 1 a 6.")
if __name__ == "__main__":
menu_hub()
# Executar a suíte de testes do Miniprojeto 03:
python -c "import subprocess, sys; [subprocess.run([sys.executable, p], check=True) for p in ['catalogo_projetos/sistemas_python_intro_03_estoque_cantina/degrau_1_imperativo_txt/test_degrau_1.py', 'catalogo_projetos/sistemas_python_intro_03_estoque_cantina/degrau_2_primeira_classe/test_degrau_2.py', 'catalogo_projetos/sistemas_python_intro_03_estoque_cantina/degrau_3_sqlite/test_degrau_3.py']]"
# Ou, de forma ainda mais simples, use o Hub Central (opção 5 do menu):
python main.py
Resultado esperado:
Ran 2 tests in 0.006s OK (Degrau 1)
Ran 2 tests in 0.005s OK (Degrau 2)
Ran 2 tests in 0.085s OK (Degrau 3)
A imagem abaixo ilustra a esteira evolutiva dos 4 degraus didáticos do miniprojeto:
flowchart TD
subgraph S1["🪜 Degrau 1: Script Imperativo"]
A1["Menu Terminal while"] --> A2["Arquivo de Texto/JSON"]
end
subgraph S2["🪜 Degrau 2: Orientação a Objetos"]
B1["Modelo de Domínio POO"] --> B2["Encapsulamento de Regras"]
end
subgraph S3["🪜 Degrau 3: Banco Relacional"]
C1["SQLite3 Local"] --> C2["Transações ACID & Agregações"]
end
subgraph S4["🪜 Degrau 4: Interface Visual"]
D1["Aplicação Desktop / Web"] --> D2["UX Interativa Final"]
end
S1 --> S2
S2 --> S3
S3 --> S4
style S1 fill:#fffde7,stroke:#fbc02d,stroke-width:1.5px
style S2 fill:#e3f2fd,stroke:#1976d2,stroke-width:1.5px
style S3 fill:#e8f5e9,stroke:#388e3c,stroke-width:1.5px
style S4 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:1.5px
| ID | Persona | História de Usuário | Critério de Aceite | Prioridade |
|---|---|---|---|---|
| US-01 | Atendente da Cantina | Desejo cadastrar lanches e bebidas com quantidade e preço no terminal | Persistir registros com layout delimitado em estoque.txt | Alta |
| US-02 | Gerente da Cantina | Desejo manipular produtos com métodos de baixa e reposição de estoque | Classe Produto com validação de estoque mínimo e custo total | Alta |
| US-03 | Administrador Escolar | Desejo banco SQLite com alertas para itens abaixo do ponto de pedido | Queries SQL identificando produtos críticos via cláusulas WHERE | Alta |
| US-04 | Operador de Balcão | Desejo interface desktop moderna com tema escuro e botões estilizados | Janela CustomTkinter responsiva com cards visuais de produtos | Alta |
| US-05 | Auditor de Inventário | Desejo suíte de testes que assegure prevenção de estoque negativo | Validações de integridade nos métodos de saída de mercadorias | Média |
| ID | Descrição do Requisito | Criticidade |
|---|---|---|
| RF-01 | Leitura e escrita de estoque físico com controle de delimitadores em arquivo texto | Alta |
| RF-02 | Classe Produto com cálculo de margem de lucro e reposição de quantidade | Alta |
| RF-03 | Modelagem da tabela produtos com constraints NOT NULL e CHECK no SQLite3 | Alta |
| RF-04 | Interface visual moderna CustomTkinter com modo Dark/Light e feedback tátil | Alta |
| RF-05 | Launcher central e scripts de teste unitário para cada fase de evolução | Média |
| ID | Especificação Arquitetural | Criticidade |
|---|---|---|
| RNF-01 | Consistência de inventário atômica em transações concorrentes de venda | Alta |
| RNF-02 | Compatibilidade de renderização gráfica CustomTkinter em Windows e Linux | Alta |
| RNF-03 | Tempo de inicialização de UI inferior a 1 segundo | Média |
| RNF-04 | Código limpo e documentado com tipagem semântica em Python 3.8+ | Média |
| RNF-05 | Isolamento integral de dependências por degrau instrucional | Alta |
| User Story | Requisito Funcional | Requisito Não-Funcional | Módulo / Artefato de Código | Status de Homologação |
|---|---|---|---|---|
| US-01 | RF-01 |
RNF-01, RNF-04 |
degrau_1_imperativo_txt/main.py |
Testado |
| US-02 | RF-02 |
RNF-01, RNF-04 |
degrau_2_primeira_classe/main.py |
Testado |
| US-03 | RF-03 |
RNF-01, RNF-05 |
degrau_3_sqlite/main.py |
Testado |
| US-04 | RF-04 |
RNF-02, RNF-03 |
degrau_4_interface_customtkinter/main.py |
Testado |
| US-05 | RF-05 |
RNF-01, RNF-05 |
main.py & test_degrau_*.py |
Testado |
| Checkpoint | Escopo da Validação | Artefato Verificado | Status |
|---|---|---|---|
| CP-01 | Script Imperativo & Persistência Inicial | degrau_1_* |
PASS ✅ |
| CP-02 | Modelagem Orientada a Objetos | degrau_2_* |
PASS ✅ |
| CP-03 | Integridade Relacional no SQLite | degrau_3_* |
PASS ✅ |
| CP-04 | Interface Visual & Usabilidade | degrau_4_* |
PASS ✅ |
| CP-05 | Hub Launcher Central & Suíte de Testes | main.py + test_*.py |
PASS ✅ |