📚 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 04 (Código Completo em 4 Degraus)
Profissionais autônomos como eletricistas, encanadores, diaristas, pedreiros e jardineiros enfrentam grandes dificuldades para separar o dinheiro que entra dos gastos com materiais, transporte e alimentação.
Neste projeto, o estudante aprende como:
Lancamento e LivroCaixa para calcular automaticamente o saldo líquido e totais por tipo.lancamentos do SQLite3 com restrições CHECK(tipo IN ('RECEITA', 'DESPESA')) e funções agregadoras SQL (SUM).flowchart LR
D1["🪜 DEGRAU 1\nImperativo & JSON\nMenu while + caixa.json\ncom cálculo de saldo"] --> D2["🪜 DEGRAU 2\nPrimeira Classe POO\nclass Lancamento &\nclass LivroCaixa"]
D2 --> D3["🪜 DEGRAU 3\nSQLite Nativo\nTabela lancamentos com\nfunções agregadoras SUM"]
D3 --> D4["🪜 DEGRAU 4\nInterface Web Streamlit\nCards de saldo, filtros\ne gráfico por categoria"]
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_04_caixa_autonomo/
├── degrau_1_imperativo_json/
│ ├── main.py # Script imperativo com menu e arquivo caixa.json
│ └── test_degrau_1.py # Testes automatizados de cálculo financeiro
├── degrau_2_primeira_classe/
│ ├── main.py # Classes POO (class Lancamento e class LivroCaixa)
│ └── test_degrau_2.py # Testes unitários de saldo e serialização
├── degrau_3_sqlite/
│ ├── main.py # Banco relacional SQLite3 com agregação SUM
│ └── test_degrau_3.py # Testes unitários de consultas SQL
├── degrau_4_interface_streamlit/
│ ├── app.py # Painel Web em Python puro com Streamlit
│ └── main.py # Launcher do servidor web
├── 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_04_caixa_autonomo
# Executar o Hub Central (Menu interativo com todos os degraus):
python main.py
# Ou executar os degraus de terminal (1 a 3):
python degrau_1_imperativo_json/main.py
python degrau_2_primeira_classe/main.py
python degrau_3_sqlite/main.py
# Executar a aplicação Web (Degrau 4 com Streamlit):
streamlit run degrau_4_interface_streamlit/app.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_04_caixa_autonomo/main.py
.json)No primeiro degrau, gravamos cada lançamento financeiro em caixa.json, calculando dinamicamente o total de receitas, total de despesas e saldo líquido.
degrau_1_imperativo_json/main.py):import json
import os
ARQUIVO_PADRAO = "caixa.json"
def carregar_caixa(caminho=ARQUIVO_PADRAO):
if not os.path.exists(caminho):
return []
try:
with open(caminho, "r", encoding="utf-8") as f:
return json.load(f)
except json.JSONDecodeError:
return []
def salvar_caixa(lancamentos, caminho=ARQUIVO_PADRAO):
with open(caminho, "w", encoding="utf-8") as f:
json.dump(lancamentos, f, indent=4, ensure_ascii=False)
def adicionar_lancamento(lancamentos, tipo, descricao, valor, categoria="Geral"):
tipo = tipo.upper().strip()
if tipo not in ["RECEITA", "DESPESA"] or not descricao.strip() or valor <= 0:
return False
lancamentos.append({"tipo": tipo, "descricao": descricao.strip(), "valor": valor, "categoria": categoria.strip()})
return True
def calcular_totais(lancamentos):
total_rec = sum(i["valor"] for i in lancamentos if i["tipo"] == "RECEITA")
total_desp = sum(i["valor"] for i in lancamentos if i["tipo"] == "DESPESA")
return total_rec, total_desp, (total_rec - total_desp)
degrau_1_imperativo_json/test_degrau_1.py):"""
Testes Unitários para o Degrau 1 do Projeto 04 (Imperativo + JSON)
"""
import os
import unittest
from main import (
adicionar_lancamento,
calcular_totais,
carregar_caixa,
salvar_caixa
)
ARQUIVO_TESTE = "teste_caixa.json"
class TestDegrau1Caixa(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_adicionar_e_calcular_saldo(self):
lancamentos = []
adicionar_lancamento(lancamentos, "RECEITA", "Reparo hidraulico", 250.00, "Servico")
adicionar_lancamento(lancamentos, "RECEITA", "Troca de torneira", 80.00, "Servico")
adicionar_lancamento(lancamentos, "DESPESA", "Compra de cano PVC", 45.00, "Material")
self.assertEqual(len(lancamentos), 3)
rec, desp, saldo = calcular_totais(lancamentos)
self.assertEqual(rec, 330.00)
self.assertEqual(desp, 45.00)
self.assertEqual(saldo, 285.00)
# Salvamento JSON
salvar_caixa(lancamentos, caminho=ARQUIVO_TESTE)
carregados = carregar_caixa(caminho=ARQUIVO_TESTE)
self.assertEqual(len(carregados), 3)
self.assertEqual(carregados[0]["descricao"], "Reparo hidraulico")
if __name__ == "__main__":
unittest.main()
Lancamento e LivroCaixa)No segundo degrau, a classe Lancamento encapsula a movimentação individual e a classe LivroCaixa orquestra a lista de lançamentos e cálculos de saldo.
degrau_2_primeira_classe/main.py):class Lancamento:
def __init__(self, tipo, descricao, valor, categoria="Geral"):
self.tipo = tipo.upper().strip()
self.descricao = descricao.strip()
self.valor = float(valor)
self.categoria = categoria.strip() if categoria else "Geral"
def eh_receita(self):
return self.tipo == "RECEITA"
def para_dicionario(self):
return {"tipo": self.tipo, "descricao": self.descricao, "valor": self.valor, "categoria": self.categoria}
class LivroCaixa:
def __init__(self):
self.lancamentos = []
def adicionar(self, lancamento):
if lancamento.valor > 0:
self.lancamentos.append(lancamento)
return True
return False
def total_receitas(self):
return sum(l.valor for l in self.lancamentos if l.eh_receita())
def total_despesas(self):
return sum(l.valor for l in self.lancamentos if not l.eh_receita())
def saldo_liquido(self):
return self.total_receitas() - self.total_despesas()
degrau_2_primeira_classe/test_degrau_2.py):"""
Testes Unitários para o Degrau 2 do Projeto 04 (Primeira Classe POO)
"""
import os
import unittest
from main import Lancamento, LivroCaixa
ARQUIVO_TESTE = "teste_caixa_poo.json"
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_lancamento_e_calculos_livro_caixa(self):
caixa = LivroCaixa()
l1 = Lancamento("RECEITA", "Conserto de ar condicionado", 350.00, "Servico")
l2 = Lancamento("DESPESA", "Gas refrigerante", 90.00, "Material")
l3 = Lancamento("DESPESA", "Almoco no cliente", 30.00, "Alimentacao")
caixa.adicionar(l1)
caixa.adicionar(l2)
caixa.adicionar(l3)
self.assertEqual(len(caixa.lancamentos), 3)
self.assertEqual(caixa.total_receitas(), 350.00)
self.assertEqual(caixa.total_despesas(), 120.00)
self.assertEqual(caixa.saldo_liquido(), 230.00)
def test_persistencia_livro_caixa(self):
caixa1 = LivroCaixa()
caixa1.adicionar(Lancamento("RECEITA", "Diaria de pintura", 200.00))
caixa1.salvar(caminho=ARQUIVO_TESTE)
caixa2 = LivroCaixa()
caixa2.carregar(caminho=ARQUIVO_TESTE)
self.assertEqual(len(caixa2.lancamentos), 1)
self.assertEqual(caixa2.lancamentos[0].descricao, "Diaria de pintura")
self.assertEqual(caixa2.saldo_liquido(), 200.00)
if __name__ == "__main__":
unittest.main()
No terceiro degrau, persistimos os lançamentos na tabela lancamentos e calculamos os totais usando a função agregadora SUM() do SQL.
degrau_3_sqlite/main.py):import sqlite3
BANCO_PADRAO = "caixa.db"
def inicializar_tabela(caminho=BANCO_PADRAO):
conn = sqlite3.connect(caminho)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS lancamentos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tipo TEXT NOT NULL CHECK(tipo IN ('RECEITA', 'DESPESA')),
descricao TEXT NOT NULL,
valor REAL NOT NULL,
categoria TEXT DEFAULT 'Geral',
data_registro TEXT DEFAULT (date('now', 'localtime'))
)
""")
conn.commit()
conn.close()
def obter_totais_financeiros(caminho=BANCO_PADRAO):
conn = sqlite3.connect(caminho)
cursor = conn.cursor()
cursor.execute("SELECT COALESCE(SUM(valor), 0.0) FROM lancamentos WHERE tipo = 'RECEITA'")
rec = cursor.fetchone()[0]
cursor.execute("SELECT COALESCE(SUM(valor), 0.0) FROM lancamentos WHERE tipo = 'DESPESA'")
desp = cursor.fetchone()[0]
conn.close()
return float(rec), float(desp), float(rec - desp)
degrau_3_sqlite/test_degrau_3.py):"""
Testes Unitários para o Degrau 3 do Projeto 04 (SQLite3 Nativo)
"""
import os
import unittest
from main import (
inicializar_tabela,
inserir_lancamento,
listar_extrato,
obter_totais_financeiros
)
BANCO_TESTE = "teste_caixa_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_insert_e_calculo_totais_sql(self):
l1 = inserir_lancamento("RECEITA", "Instalacao de chuveiro", 120.00, "Servico", caminho=BANCO_TESTE)
l2 = inserir_lancamento("RECEITA", "Troca de fiacao", 450.00, "Servico", caminho=BANCO_TESTE)
l3 = inserir_lancamento("DESPESA", "Rolo de fio 2.5mm", 180.00, "Material", caminho=BANCO_TESTE)
self.assertIsNotNone(l1)
self.assertIsNotNone(l2)
self.assertIsNotNone(l3)
rec, desp, saldo = obter_totais_financeiros(caminho=BANCO_TESTE)
self.assertEqual(rec, 570.00)
self.assertEqual(desp, 180.00)
self.assertEqual(saldo, 390.00)
extrato = listar_extrato(caminho=BANCO_TESTE)
self.assertEqual(len(extrato), 3)
if __name__ == "__main__":
unittest.main()
No quarto degrau, o aluno cria um painel no navegador com cards de resumo financeiro (com destaque para saldo positivo/negativo), formulário lateral com seleção de categoria e gráfico de distribuição de gastos.
degrau_4_interface_streamlit/app.py):import sqlite3
import streamlit as st
def main():
st.set_page_config(page_title="Livro Caixa do Autônomo", page_icon="💰", layout="wide")
st.title("💰 Livro Caixa de Prestador de Serviço Autônomo")
# Métricas no Topo
rec, desp, saldo = obter_totais()
c1, c2, c3 = st.columns(3)
c1.metric("💵 Saldo Líquido", f"R$ {saldo:.2f}", delta=f"R$ {saldo:.2f}", delta_color="normal" if saldo >= 0 else "inverse")
c2.metric("📈 Receitas", f"R${rec:.2f}") c3.metric("📉 Despesas", f"R$ {desp:.2f}")
degrau_4_interface_streamlit/main.py):"""
Launcher para o Degrau 4 (Streamlit Web)
Verifica se o pacote streamlit está instalado e inicia a aplicação no navegador.
"""
import os
import subprocess
import sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
APP_FILE = os.path.join(BASE_DIR, "app.py")
def main():
print("\n========================================================")
print("🌐 INICIANDO LIVRO CAIXA NA WEB (STREAMLIT)")
print("========================================================\n")
try:
import streamlit # noqa: F401
print("✅ Streamlit detectado! Abrindo a aplicacao no seu navegador padrao...\n")
cmd = [sys.executable, "-m", "streamlit", "run", APP_FILE]
subprocess.run(cmd, cwd=BASE_DIR)
except ImportError:
print("⚠️ O pacote 'streamlit' nao foi encontrado no ambiente Python atual.")
print("\nPara instalar e rodar, execute no seu terminal:")
print(" pip install streamlit")
print(f" streamlit run \"{APP_FILE}\"\n")
if __name__ == "__main__":
main()
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 04: Livro Caixa de Prestador de Serviço Autônomo
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 JSON)", "degrau_1_imperativo_json/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 04: LIVRO CAIXA AUTÔNOMO (HUB)")
print("========================================================")
print("1. 🪜 Executar Degrau 1 (Imperativo & Arquivo .json)")
print("2. 🪜 Executar Degrau 2 (Primeira Classe POO: class Lancamento)")
print("3. 🪜 Executar Degrau 3 (Banco de Dados SQLite3 Nativo)")
print("4. 🪜 Executar Degrau 4 (Interface Web com Streamlit)")
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_json/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_streamlit/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 04:
python -c "import subprocess, sys; [subprocess.run([sys.executable, p], check=True) for p in ['catalogo_projetos/sistemas_python_intro_04_caixa_autonomo/degrau_1_imperativo_json/test_degrau_1.py', 'catalogo_projetos/sistemas_python_intro_04_caixa_autonomo/degrau_2_primeira_classe/test_degrau_2.py', 'catalogo_projetos/sistemas_python_intro_04_caixa_autonomo/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 obtido:
Ran 1 test in 0.003s OK (Degrau 1)
Ran 2 tests in 0.004s OK (Degrau 2)
Ran 1 test in 0.029s 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 | Profissional Autônomo | Desejo registrar entradas de serviços e saídas de despesas em JSON | Registrar data, categoria, descrição e valor com saldo em tempo real | Alta |
| US-02 | Prestador de Serviço | Desejo estruturar receitas e despesas com validações em classe POO | Classe Lancamento validando tipos ‘RECEITA’ e ‘DESPESA’ | Alta |
| US-03 | Contador Pessoal | Desejo armazenar movimentações no SQLite para apuração mensal de lucro | Consultas SQL com filtros de data e agrupamento por categoria | Alta |
| US-04 | Usuário Web | Desejo acessar fluxo de caixa e gráficos financeiros via navegador web | Aplicação Streamlit com visualização de métricas e balanço financeiro | Alta |
| US-05 | Analista Fiscal | Desejo garantir que relatórios fiscais coincidam com as somas reais | Testes automatizados cobrindo cálculo de saldo inicial, entradas e saídas | Média |
| ID | Descrição do Requisito | Criticidade |
|---|---|---|
| RF-01 | Persistência estruturada de lançamentos financeiros em arquivo JSON delimitado | Alta |
| RF-02 | Regra de negócio POO para diferenciação estrita entre créditos e débitos | Alta |
| RF-03 | Persistência SQLite com índices por data e categorias pré-definidas | Alta |
| RF-04 | Dashboard Web Streamlit com cartões de receita, despesa e saldo líquido | Alta |
| RF-05 | Launcher de execução e testes unitários de regras contábeis básicas | Média |
| ID | Especificação Arquitetural | Criticidade |
|---|---|---|
| RNF-01 | Precisão decimal em todas as operações contábeis para evitar dízimas | Alta |
| RNF-02 | Carregamento e reatividade imediata no painel Streamlit sem travamentos | Alta |
| RNF-03 | Portabilidade local sem necessidade de servidor externo ou conexão com a nuvem | Média |
| RNF-04 | Tratamento de exceções em formatos de data incorretos no input do usuário | Média |
| RNF-05 | Arquitetura limpa e desacoplada em 4 níveis progressivos | 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_json/main.py |
Testado |
| US-02 | RF-02 |
RNF-01, RNF-05 |
degrau_2_primeira_classe/main.py |
Testado |
| US-03 | RF-03 |
RNF-01, RNF-03 |
degrau_3_sqlite/main.py |
Testado |
| US-04 | RF-04 |
RNF-02, RNF-03 |
degrau_4_interface_streamlit/app.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 ✅ |