Projeto Final: Gerenciamento de Notas Escolares (Parte 1)

Este é o projeto final do tutorial de Python. Em três lições, vamos construir um sistema completo de gerenciamento de notas escolares do zero. O projeto tem três camadas: camada de dados (persistência JSON), camada de lógica (processamento de notas) e camada de apresentação (interface de linha de comando). Esta lição constrói a base — requisitos e a camada de dados.


Visão Geral do Projeto

TEXT
Sistema de Gerenciamento de Notas Escolares
├── Lição 34: Camada de Dados — Análise de requisitos + Persistência JSON/CSV
├── Lição 35: Camada de Lógica — Estatísticas de notas + Ordenação + Tratamento de exceções
└── Lição 36: Camada de Apresentação — Interface de menu + Busca + Relatórios

Especificação de Requisitos

TEXT
1. Gerenciamento de Alunos
   ├── Adicionar aluno (nome, matrícula, turma)
   └── Remover aluno (por matrícula)

2. Gerenciamento de Notas
   ├── Inserir notas (por matrícula para múltiplas disciplinas)
   ├── Modificar notas
   └── Remover registros de notas

3. Estatísticas e Análise
   ├── Boletim individual
   ├── Ranking da turma
   ├── Médias por disciplina
   └── Distribuição de notas

4. Persistência de Dados
   ├── Armazenar dados em formato JSON em arquivo
   └── Carregamento automático na inicialização

Design da Estrutura de Dados

Exemplo: Estrutura de Dados JSON

PYTHON
# Dados armazenados em formato JSON
{
    "students": {
        "2024001": {
            "name": "João Silva",
            "class_name": "Turma 1",
            "scores": {
                "Português": 85,
                "Matemática": 92,
                "Inglês": 78
            }
        },
        "2024002": {
            "name": "Maria Souza",
            "class_name": "Turma 1",
            "scores": {
                "Português": 90,
                "Matemática": 88,
                "Inglês": 95
            }
        }
    },
    "subjects": ["Português", "Matemática", "Inglês"],
    "classes": ["Turma 1", "Turma 2"]
}
▶ Experimente
💡 Por que usar a matrícula como chave? Usar a matrícula (string) como chave do dicionário torna as buscas O(1) — quer haja 10 ou 10.000 alunos, a velocidade de busca é a mesma.


Implementação da Camada de Dados

Exemplo: Implementação e Teste da Camada de Dados

PYTHON
import json
import os

DATA_FILE = "grade_system.json"

# ====== Estrutura de Dados Padrão ======
DEFAULT_DATA = {
    "students": {},
    "subjects": ["Português", "Matemática", "Inglês"],
    "classes": []
}


# ====== Camada de Dados ======

def load_data():
    """Carrega dados do arquivo JSON"""
    if not os.path.exists(DATA_FILE):
        save_data(DEFAULT_DATA.copy())
        return DEFAULT_DATA.copy()
    with open(DATA_FILE, "r", encoding="utf-8") as f:
        return json.load(f)


def save_data(data):
    """Salva dados no arquivo JSON"""
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)


# ====== Gerenciamento de Alunos ======

def add_student(student_id, name, class_name):
    """Adiciona um aluno"""
    data = load_data()

    if student_id in data["students"]:
        return False, f"Matrícula {student_id} já existe!"

    data["students"][student_id] = {
        "name": name,
        "class_name": class_name,
        "scores": {}
    }

    # Atualiza lista de turmas
    if class_name not in data["classes"]:
        data["classes"].append(class_name)

    save_data(data)
    return True, f"Aluno adicionado: {name} ({student_id})"


def delete_student(student_id):
    """Remove um aluno"""
    data = load_data()

    if student_id not in data["students"]:
        return False, f"Matrícula {student_id} não existe!"

    name = data["students"][student_id]["name"]
    del data["students"][student_id]
    save_data(data)
    return True, f"Aluno removido: {name}"


def get_student(student_id):
    """Obtém informações de um aluno"""
    data = load_data()
    return data["students"].get(student_id)


def get_all_students():
    """Obtém todos os alunos"""
    data = load_data()
    return data["students"]


# ====== Gerenciamento de Notas ======

def add_score(student_id, subject, score):
    """Insere uma nota"""
    data = load_data()

    if student_id not in data["students"]:
        return False, f"Matrícula {student_id} não existe!"

    if subject not in data["subjects"]:
        return False, f"Disciplina {subject} não existe!"

    if not isinstance(score, (int, float)) or score < 0 or score > 100:
        return False, "A nota deve estar entre 0 e 100!"

    data["students"][student_id]["scores"][subject] = score
    save_data(data)
    return True, f"Inserida nota de {data['students'][student_id]['name']} em {subject}: {score}"


def update_score(student_id, subject, score):
    """Atualiza uma nota (mesma função que add_score)"""
    return add_score(student_id, subject, score)


def delete_score(student_id, subject):
    """Remove a nota de uma disciplina"""
    data = load_data()

    if student_id not in data["students"]:
        return False, f"Matrícula {student_id} não existe!"

    if subject not in data["students"][student_id]["scores"]:
        return False, f"{data['students'][student_id]['name']} não tem nota em {subject}"

    del data["students"][student_id]["scores"][subject]
    save_data(data)
    return True, f"Nota de {data['students'][student_id]['name']} em {subject} removida"


# ====== Código de Teste ======
if __name__ == "__main__":
    print("=== Testando Camada de Dados ===")
    print(add_student("2024001", "João Silva", "Turma 1"))
    print(add_student("2024002", "Maria Souza", "Turma 1"))
    print(add_student("2024003", "Pedro Santos", "Turma 2"))

    print(add_score("2024001", "Português", 85))
    print(add_score("2024001", "Matemática", 92))
    print(add_score("2024002", "Português", 90))

    student = get_student("2024001")
    print(f"\nInformações de João Silva: {student}")

    print(f"\nTotal de alunos: {len(get_all_students())}")
▶ Experimente

Destaques de Design


❓ Perguntas Frequentes

P: Por que usar a matrícula como chave do dicionário em vez do nome? R: Nomes podem se repetir (alunos com o mesmo nome), mas matrículas são identificadores únicos. Chaves de dicionário devem ser únicas; usar matrículas garante velocidade de busca O(1) e sem conflitos. P: É uma boa prática usar caminho de arquivo JSON fixo no código? R: Por simplicidade, o tutorial usa caminho fixo. Em projetos reais, use arquivos de configuração ou argumentos de linha de comando para especificar o caminho. Use os.path.join() para construir caminhos e evite valores fixos. P: Por que retornar uma tupla (sucesso, mensagem) em vez de levantar exceções? R: Este é o "padrão de código de status" — o chamador pode decidir o que fazer com base em sucesso, que é mais leve que try/except. É adequado para pequenas ferramentas CLI; para projetos maiores, classes de exceção personalizadas são recomendadas.


📖 Resumo


📝 Atividades

  1. Iniciante (Dificuldade ⭐): Execute o código da camada de dados acima, adicione 2-3 alunos com suas notas e verifique se o arquivo JSON foi gerado corretamente.

  2. Intermediário (Dificuldade ⭐⭐): Adicione uma função update_student(student_id, name=None, class_name=None) à camada de dados que atualiza apenas os campos fornecidos (deixando outros inalterados).

  3. Avançado (Dificuldade ⭐⭐⭐): Adicione funcionalidade de inserção em lote a add_score — aceite um dicionário como {"Português": 85, "Matemática": 92} para inserir múltiplas notas de uma vez. Requisito: se qualquer nota for inválida, reverta toda a operação em lote (não salve nenhum dado).

100%