📚 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 07: Orçamento de Oficina Mecânica

Guia Autoguiado Passo a Passo: Do Orçamento em JSON ao Sistema Web com Streamlit e Agregações no SQLite

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


🎯 Objetivo de Aprendizagem

Nas oficinas mecânicas de motos, carros e maquinários agrícolas de pequenas cidades, a composição de orçamentos somando peças substituídas com a mão de obra é essencial para transparência com o cliente.

Neste projeto, o estudante aprende como:

  1. Estruturar listas de itens com preços unitários e subtotais em arquivos JSON estruturados.
  2. Criar classes Peca e Orcamento encapsulando cálculos de subtotais, mão de obra e total geral.
  3. Modelar duas tabelas relacionais com Chave Estrangeira no SQLite3 e efetuar somas ponderadas diretamente com SUM(preco * quantidade).
  4. Construir uma interface Web moderna com Streamlit, gerando recibos e ordens de serviço formatadas para impressão ou aprovação do cliente.

🪜 O Caminho Didático dos 4 Degraus

flowchart LR
    D1["🪜 DEGRAU 1\nImperativo & JSON\nMenu while + orcamentos.json\ncom cálculo de peças + MO"] --> D2["🪜 DEGRAU 2\nPrimeira Classe POO\nClasses Peca e\nOrcamento (agregação)"]
    D2 --> D3["🪜 DEGRAU 3\nSQLite Relacional (1:N)\nTabelas orcamentos e\nitens com agregação SUM"]
    D3 --> D4["🪜 DEGRAU 4\nInterface Web Streamlit\nOrdem de serviço formatada,\nrecibo e aprovação"]

    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_07_oficina_mecanica/
├── degrau_1_imperativo_json/
│   ├── main.py              # Script imperativo com menu e arquivo orcamentos.json
│   └── test_degrau_1.py     # Testes automatizados de cálculo de orçamento
├── degrau_2_primeira_classe/
│   ├── main.py              # Classes POO (class Peca e class Orcamento)
│   └── test_degrau_2.py     # Testes unitários de agregação e cálculos
├── degrau_3_sqlite/
│   ├── main.py              # SQLite3 relacional: 2 tabelas e agregação ponderada SUM
│   └── test_degrau_3.py     # Testes unitários de cálculo SQL e aprovação
├── degrau_4_interface_streamlit/
│   ├── app.py               # Painel Web em Python puro com Streamlit (Ordem de Serviço)
│   └── main.py              # Launcher do servidor Web Streamlit
├── 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_07_oficina_mecanica

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_07_oficina_mecanica/main.py


📖 Roteiro Passo a Passo & Gabarito Completo


🪜 Degrau 1: Programação Imperativa com Arquivo JSON (.json)

No primeiro degrau, armazenamos os orçamentos e suas respectivas listas de peças em orcamentos.json.

Código-Fonte Completo (degrau_1_imperativo_json/main.py):

import json
import os

ARQUIVO_PADRAO = "orcamentos.json"

def criar_orcamento(orcamentos, cliente, veiculo, mao_de_obra=0.0):
    if not cliente.strip() or not veiculo.strip():
        return None
    novo = {
        "id": len(orcamentos) + 1,
        "cliente": cliente.strip(),
        "veiculo": veiculo.strip(),
        "mao_de_obra": float(mao_de_obra),
        "pecas": [],
        "status": "ABERTO"
    }
    orcamentos.append(novo)
    return novo

def adicionar_peca(orcamento, nome_peca, preco_unitario, quantidade=1):
    orcamento["pecas"].append({
        "nome": nome_peca.strip(),
        "preco_unitario": float(preco_unitario),
        "quantidade": int(quantidade),
        "subtotal": float(preco_unitario * quantidade)
    })
    return True

def calcular_total_orcamento(orcamento):
    tot_pecas = sum(p["subtotal"] for p in orcamento["pecas"])
    return tot_pecas, orcamento["mao_de_obra"], (tot_pecas + orcamento["mao_de_obra"])

Testes Automatizados (degrau_1_imperativo_json/test_degrau_1.py):

"""
Testes Unitários para o Degrau 1 do Projeto 07 (Imperativo & JSON)
"""

import os
import unittest
from main import (
    adicionar_peca,
    calcular_total_orcamento,
    carregar_orcamentos,
    criar_orcamento,
    salvar_orcamentos
)

ARQUIVO_TESTE = "teste_orcamentos.json"


class TestDegrau1Oficina(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_criar_orcamento_e_calculos(self):
        orcamentos = []
        orc = criar_orcamento(orcamentos, "Marcos Silva", "Gol 1.6 / ABC-1234", mao_de_obra=150.00)
        self.assertIsNotNone(orc)
        self.assertEqual(len(orcamentos), 1)

        # Adicionar peças
        adicionar_peca(orc, "Amortecedor Dianteiro", 220.00, 2)
        adicionar_peca(orc, "Bieleta", 45.00, 2)

        tot_p, mo, tot_g = calcular_total_orcamento(orc)
        self.assertEqual(tot_p, 530.00)  # 2*220 + 2*45 = 440 + 90 = 530
        self.assertEqual(mo, 150.00)
        self.assertEqual(tot_g, 680.00)  # 530 + 150 = 680

        # Salvamento JSON
        salvar_orcamentos(orcamentos, caminho=ARQUIVO_TESTE)
        carregados = carregar_orcamentos(caminho=ARQUIVO_TESTE)
        self.assertEqual(len(carregados), 1)
        self.assertEqual(carregados[0]["cliente"], "Marcos Silva")
        self.assertEqual(len(carregados[0]["pecas"]), 2)


if __name__ == "__main__":
    unittest.main()

🪜 Degrau 2: Primeiro Contato com POO (Classes Peca e Orcamento)

No segundo degrau, a classe Peca calcula seu próprio subtotal e a classe Orcamento reúne as peças calculando o valor final com mão de obra.

Código-Fonte Completo (degrau_2_primeira_classe/main.py):

class Peca:
    def __init__(self, nome, preco_unitario, quantidade=1):
        self.nome = nome.strip()
        self.preco_unitario = float(preco_unitario)
        self.quantidade = int(quantidade)

    def subtotal(self):
        return self.preco_unitario * self.quantidade

class Orcamento:
    def __init__(self, id_orc, cliente, veiculo, mao_de_obra=0.0, status="ABERTO"):
        self.id = int(id_orc)
        self.cliente = cliente.strip()
        self.veiculo = veiculo.strip()
        self.mao_de_obra = float(mao_de_obra)
        self.status = status
        self.pecas = []

    def adicionar_peca(self, peca):
        self.pecas.append(peca)
        return True

    def total_pecas(self):
        return sum(p.subtotal() for p in self.pecas)

    def total_geral(self):
        return self.total_pecas() + self.mao_de_obra

Testes Automatizados (degrau_2_primeira_classe/test_degrau_2.py):

"""
Testes Unitários para o Degrau 2 do Projeto 07 (Primeira Classe POO)
"""

import os
import unittest
from main import GerenciadorOrcamentos, Orcamento, Peca

ARQUIVO_TESTE = "teste_orcamentos_poo.json"


class TestDegrau2Oficina(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_peca_e_orcamento(self):
        p1 = Peca("Disco de Freio", 180.00, 2)
        self.assertEqual(p1.subtotal(), 360.00)

        p2 = Peca("Fluido de Freio DOT4", 35.00, 1)
        self.assertEqual(p2.subtotal(), 35.00)

        orc = Orcamento(1, "Renata Souza", "Civic 2.0 / XYZ-9988", mao_de_obra=120.00)
        orc.adicionar_peca(p1)
        orc.adicionar_peca(p2)

        self.assertEqual(orc.total_pecas(), 395.00)
        self.assertEqual(orc.total_geral(), 515.00)
        self.assertEqual(orc.status, "ABERTO")

        orc.aprovar()
        self.assertEqual(orc.status, "APROVADO")

    def test_gerenciador_persistencia(self):
        ger1 = GerenciadorOrcamentos()
        o = ger1.criar_orcamento("Paulo Sergio", "Uno Mille", 80.00)
        o.adicionar_peca(Peca("Cabo de Vela", 90.00, 1))
        ger1.salvar(caminho=ARQUIVO_TESTE)

        ger2 = GerenciadorOrcamentos()
        ger2.carregar(caminho=ARQUIVO_TESTE)
        self.assertEqual(len(ger2.orcamentos), 1)
        self.assertEqual(ger2.orcamentos[0].cliente, "Paulo Sergio")
        self.assertEqual(ger2.orcamentos[0].total_geral(), 170.00)


if __name__ == "__main__":
    unittest.main()

🪜 Degrau 3: Banco de Dados Relacional com SQLite3 Nativo (1:N)

No terceiro degrau, criamos as tabelas relacionais orcamentos e itens_orcamento calculando os valores diretamente no SQL.

Código-Fonte Completo (degrau_3_sqlite/main.py):

import sqlite3

def inicializar_tabelas(caminho="oficina.db"):
    conn = sqlite3.connect(caminho)
    conn.execute("PRAGMA foreign_keys = ON;")
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS orcamentos (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            cliente TEXT NOT NULL,
            veiculo TEXT NOT NULL,
            mao_de_obra REAL DEFAULT 0.0,
            data_criacao TEXT DEFAULT (date('now', 'localtime')),
            status TEXT DEFAULT 'ABERTO' CHECK(status IN ('ABERTO', 'APROVADO', 'REJEITADO'))
        )
    """)
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS itens_orcamento (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            orcamento_id INTEGER NOT NULL,
            nome_peca TEXT NOT NULL,
            preco_unitario REAL NOT NULL,
            quantidade INTEGER NOT NULL DEFAULT 1,
            FOREIGN KEY (orcamento_id) REFERENCES orcamentos(id) ON DELETE CASCADE
        )
    """)
    conn.commit()
    conn.close()

def listar_orcamentos_com_totais(caminho="oficina.db"):
    conn = sqlite3.connect(caminho)
    cursor = conn.cursor()
    cursor.execute("""
        SELECT 
            o.id, o.cliente, o.veiculo, o.mao_de_obra, o.status, o.data_criacao,
            COALESCE(SUM(i.preco_unitario * i.quantidade), 0.0) AS total_pecas,
            (o.mao_de_obra + COALESCE(SUM(i.preco_unitario * i.quantidade), 0.0)) AS total_geral
        FROM orcamentos o
        LEFT JOIN itens_orcamento i ON o.id = i.orcamento_id
        GROUP BY o.id, o.cliente, o.veiculo, o.mao_de_obra, o.status, o.data_criacao
        ORDER BY o.id ASC
    """)
    linhas = cursor.fetchall()
    conn.close()
    return linhas

Testes Automatizados (degrau_3_sqlite/test_degrau_3.py):

"""
Testes Unitários para o Degrau 3 do Projeto 07 (SQLite3 Relacional com Agregações)
"""

import os
import unittest
from main import (
    adicionar_item,
    aprovar_orcamento,
    criar_orcamento,
    inicializar_tabelas,
    listar_orcamentos_com_totais
)

BANCO_TESTE = "teste_oficina_degrau3.db"


class TestDegrau3SQLite(unittest.TestCase):

    def setUp(self):
        if os.path.exists(BANCO_TESTE):
            os.remove(BANCO_TESTE)
        inicializar_tabelas(caminho=BANCO_TESTE)

    def tearDown(self):
        if os.path.exists(BANCO_TESTE):
            os.remove(BANCO_TESTE)

    def test_fluxo_orcamento_e_calculo_sql(self):
        # 1. Cria orçamento com mão de obra de R$ 150.00
        orc_id = criar_orcamento("Roberto Carlos", "Corolla 2018 / ABC-1234", 150.00, caminho=BANCO_TESTE)
        self.assertIsNotNone(orc_id)

        # 2. Adiciona peças
        adicionar_item(orc_id, "Jogo de Velas Iridium", 120.00, 1, caminho=BANCO_TESTE)
        adicionar_item(orc_id, "Filtro de Ar", 40.00, 2, caminho=BANCO_TESTE)

        # 3. Valida cálculo SQL (120 + 2*40 = 200 de peças + 150 MO = 350 total)
        lista = listar_orcamentos_com_totais(caminho=BANCO_TESTE)
        self.assertEqual(len(lista), 1)
        self.assertEqual(lista[0][6], 200.00)  # total_pecas
        self.assertEqual(lista[0][7], 350.00)  # total_geral
        self.assertEqual(lista[0][4], "ABERTO")

        # 4. Aprovação
        aprovado = aprovar_orcamento(orc_id, caminho=BANCO_TESTE)
        self.assertTrue(aprovado)

        lista_depois = listar_orcamentos_com_totais(caminho=BANCO_TESTE)
        self.assertEqual(lista_depois[0][4], "APROVADO")


if __name__ == "__main__":
    unittest.main()

🪜 Degrau 4: Interface Web com Streamlit (Ordem de Serviço e Recibo)

No quarto degrau, o aluno cria uma aplicação web interativa com formulário de cadastro, inclusão dinâmica de peças e visualização de recibo com botões de aprovação.

Código-Fonte Completo (degrau_4_interface_streamlit/app.py):

import streamlit as st

def main():
    st.set_page_config(page_title="Orçamentos Oficina Mecânica", page_icon="🔧", layout="wide")
    st.title("🔧 Gestão de Orçamentos — Oficina Mecânica")

    # Métricas
    col1, col2, col3 = st.columns(3)
    col1.metric("📋 Total de Orçamentos", "8")
    col2.metric("✅ Orçamentos Aprovados", "6/8")
    col3.metric("💰 Faturamento Aprovado", "R$ 4.250,00")

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 SISTEMA DE ORÇAMENTOS 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 07: Orçamento de Oficina Mecânica
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 Relacional)", "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 07: OFICINA MECÂNICA (HUB)")
        print("========================================================")
        print("1. 🪜 Executar Degrau 1 (Imperativo & Arquivo .json)")
        print("2. 🪜 Executar Degrau 2 (Primeira Classe POO: Peca e Orcamento)")
        print("3. 🪜 Executar Degrau 3 (Banco de Dados SQLite3 Relacional)")
        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 07:
python -c "import subprocess, sys; [subprocess.run([sys.executable, p], check=True) for p in ['catalogo_projetos/sistemas_python_intro_07_oficina_mecanica/degrau_1_imperativo_json/test_degrau_1.py', 'catalogo_projetos/sistemas_python_intro_07_oficina_mecanica/degrau_2_primeira_classe/test_degrau_2.py', 'catalogo_projetos/sistemas_python_intro_07_oficina_mecanica/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.005s OK (Degrau 2)
Ran 1 test in 0.065s 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 Mecânico Responsável Desejo cadastrar veículo, defeito relatado e peças em arquivo JSON Gravar orçamento inicial com status de aprovação em orcamentos.json Alta
US-02 Chefe de Oficina Desejo calcular custo de peças e mão de obra através de classe POO Classe OrdemServico com cálculo de total, desconto e comissão Alta
US-03 Proprietário da Oficina Desejo persistir ordens no SQLite para acompanhar faturamento mensal Tabelas de clientes, veículos e itens de serviço com joins SQL Alta
US-04 Consultor Técnico Desejo painel web Streamlit para emitir orçamentos e aprovar serviços Interface web com formulário de itens, cálculo instantâneo e impressão Alta
US-05 Auditor de Processos Desejo testes que garantam a consistência das somas de peças e horas Testes automatizados cobrindo cenários com e sem desconto Média

⚙️ Requisitos Funcionais (RF)

ID Descrição do Requisito Criticidade
RF-01 Estruturação de ordens de serviço com listas aninhadas de itens em JSON Alta
RF-02 Encapsulamento de regras tributárias e cálculos de mão de obra em POO Alta
RF-03 Persistência relacional em SQLite com suporte a múltiplas peças por ordem Alta
RF-04 Aplicação web Streamlit com painel de indicadores e emissão de OS Alta
RF-05 Launcher central e rotinas automatizadas de teste em ambiente isolado Média

🛡️ Requisitos Não-Funcionais (RNF)

ID Especificação Arquitetural Criticidade
RNF-01 Rigor aritmético com duas casas decimais em todos os montantes financeiros Alta
RNF-02 Carregamento instantâneo de orçamentos históricos no dashboard Média
RNF-03 Interface clara e autoexplicativa com validações em tempo real Média
RNF-04 Independência de servidores de banco de dados externos pesados Alta
RNF-05 Separação pedagógica estrita entre os 4 degraus instrucionais 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-04 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 ✅