📚 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.

🌱 Miniprojeto 04: Livro Caixa de Prestador de Serviço Autônomo

Guia Autoguiado Passo a Passo: Do Controle em JSON à Aplicação Web Financeira com SQLite e Streamlit

Trilha de Especialização — Nível Introdutório (Curso 25: Sistemas Desktop com Python)


🎯 Objetivo de Aprendizagem

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:

  1. Estruturar movimentações financeiras de entradas e saídas em arquivos JSON estruturados.
  2. Criar as classes Lancamento e LivroCaixa para calcular automaticamente o saldo líquido e totais por tipo.
  3. Persistir dados na tabela lancamentos do SQLite3 com restrições CHECK(tipo IN ('RECEITA', 'DESPESA')) e funções agregadoras SQL (SUM).
  4. Construir uma interface Web moderna com Streamlit, exibindo métricas financeiras com indicador dinâmico de saldo positivo/negativo, filtros de extrato e gráficos por categoria.

🪜 O Caminho Didático dos 4 Degraus

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

📂 Estrutura de Pastas e Arquivos

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

🚀 Como Executar no Laboratório

1. Abra o terminal na pasta deste projeto

No VS Code / PyCharm, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:

cd sistemas_python_intro_04_caixa_autonomo

2. Execute o Hub Central ou um Degrau Específico

# 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


📖 Roteiro Passo a Passo & Gabarito Completo


🪜 Degrau 1: Programação Imperativa com Arquivo JSON (.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.

Código-Fonte Completo (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)

Testes Automatizados (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()

🪜 Degrau 2: Primeiro Contato com POO (Classes 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.

Código-Fonte Completo (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()

Testes Automatizados (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()

🪜 Degrau 3: Banco de Dados Relacional com SQLite3 Nativo

No terceiro degrau, persistimos os lançamentos na tabela lancamentos e calculamos os totais usando a função agregadora SUM() do SQL.

Código-Fonte Completo (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)

Testes Automatizados (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()

🪜 Degrau 4: Interface Web com Streamlit (Painel Financeiro)

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.

Código-Fonte Completo (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}")

Launcher do Servidor Web (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()

🎛️ Hub Central: Launcher Interativo (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.

Código-Fonte Completo (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()

🧪 Validação com Testes Automatizados

# 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)


🏛️ Arquitetura Visual & Engenharia do Sistema

A imagem abaixo ilustra a esteira evolutiva dos 4 degraus didáticos do miniprojeto:

Arquitetura da Aplicação em 4 Degraus

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

🎯 Engenharia de Requisitos & Modelagem Formal

👤 User Stories (Histórias de Usuário)

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

⚙️ Requisitos Funcionais (RF)

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

🛡️ Requisitos Não-Funcionais (RNF)

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

🔗 Matriz de Rastreabilidade (RTM)

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

🏁 Checkpoints de Qualidade & Homologação

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 ✅