Projeto: Sistema de Gerenciamento de Biblioteca

Este é o projeto final da Fase 4. Ele reúne tudo das últimas lições — classes, herança, encapsulamento, métodos mágicos e operações de arquivo — para construir um sistema de gerenciamento de biblioteca realmente utilizável. Completar este projeto significa que você realmente dominou a programação orientada a objetos do Python.


Requisitos do Projeto

TEXT
1. Gerenciamento de Livros (CRUD)
   ├── Adicionar livro (título, autor, ISBN, ano de publicação)
   ├── Deletar livro (por ISBN)
   ├── Modificar informações do livro
   └── Buscar livros (busca difusa por título / busca por autor)

2. Gerenciamento de Empréstimos
   ├── Emprestar livro (registrar mutuário e data de empréstimo)
   ├── Devolver livro (verificar atraso)
   └── Visualizar registros de empréstimo

3. Estatísticas de Dados
   ├── Total de livros
   ├── Contagem de disponíveis/emprestados
   └── Contagem por autor

4. Persistência de Dados (arquivo CSV, carregamento automático na inicialização)

1. Código Completo

Exemplo: Sistema de Gerenciamento de Biblioteca

PYTHON
import csv
import os
from datetime import datetime, timedelta
from dataclasses import dataclass

# ====== Modelo de Dados ======

@dataclass
class Livro:
    """Classe de dados Livro"""
    titulo: str
    autor: str
    isbn: str
    ano: int
    emprestado: bool = False
    mutuario: str = ""
    data_emprestimo: str = ""

    def to_csv_row(self):
        """Converte para linha CSV"""
        return [self.titulo, self.autor, self.isbn, str(self.ano),
                str(self.emprestado), self.mutuario, self.data_emprestimo]

    @staticmethod
    def from_csv_row(row):
        """Cria objeto Livro a partir de linha CSV"""
        return Livro(
            titulo=row[0], autor=row[1], isbn=row[2],
            ano=int(row[3]),
            emprestado=row[4] == "True",
            mutuario=row[5], data_emprestimo=row[6]
        )


# ====== Camada de Lógica de Negócio ======

class Biblioteca:
    """Sistema de gerenciamento de biblioteca"""
    DATA_FILE = "library_data.csv"

    def __init__(self):
        self.livros = []
        self.load_data()

    # ---- Persistência ----

    def load_data(self):
        """Carrega dados do CSV"""
        if not os.path.exists(self.DATA_FILE):
            return
        with open(self.DATA_FILE, "r", encoding="utf-8") as f:
            reader = csv.reader(f)
            next(reader, None)  # Pular cabeçalho
            for row in reader:
                if row:
                    self.livros.append(Livro.from_csv_row(row))
        print(f"Carregados {len(self.livros)} livros.")

    def save_data(self):
        """Salva dados em CSV"""
        with open(self.DATA_FILE, "w", newline="", encoding="utf-8") as f:
            writer = csv.writer(f)
            writer.writerow(["Título", "Autor", "ISBN", "Ano",
                            "Emprestado", "Mutário", "Data Empréstimo"])
            for book in self.livros:
                writer.writerow(book.to_csv_row())

    # ---- Gerenciamento de Livros ----

    def adicionar_livro(self, titulo, autor, isbn, ano):
        """Adiciona um livro"""
        for book in self.livros:
            if book.isbn == isbn:
                return False, "ISBN já existe!"
        self.livros.append(Livro(titulo, autor, isbn, ano))
        self.save_data()
        return True, f"Adicionado '{titulo}'"

    def deletar_livro(self, isbn):
        """Deleta um livro"""
        for i, book in enumerate(self.livros):
            if book.isbn == isbn:
                removido = self.livros.pop(i)
                self.save_data()
                return True, f"Deletado '{removido.titulo}'"
        return False, "Livro com este ISBN não encontrado."

    def buscar_por_titulo(self, keyword):
        """Busca difusa por título"""
        return [b for b in self.livros if keyword.lower() in b.titulo.lower()]

    def buscar_por_autor(self, autor):
        """Busca por autor"""
        return [b for b in self.livros if autor.lower() in b.autor.lower()]

    def get_livro_por_isbn(self, isbn):
        """Encontra livro por ISBN"""
        for book in self.livros:
            if book.isbn == isbn:
                return book
        return None

    # ---- Gerenciamento de Empréstimos ----

    def emprestar_livro(self, isbn, mutuario):
        """Empresta um livro"""
        book = self.get_livro_por_isbn(isbn)
        if not book:
            return False, "Livro não encontrado."
        if book.emprestado:
            return False, f"'{book.titulo}' já está emprestado para {book.mutuario}."
        book.emprestado = True
        book.mutuario = mutuario
        book.data_emprestimo = datetime.now().strftime("%Y-%m-%d")
        self.save_data()
        return True, f"'{book.titulo}' foi emprestado para {mutuario}."

    def devolver_livro(self, isbn):
        """Devolve um livro"""
        book = self.get_livro_por_isbn(isbn)
        if not book:
            return False, "Livro não encontrado."
        if not book.emprestado:
            return False, f"'{book.titulo}' não está atualmente emprestado."
        # Verifica atraso (período de empréstimo de 30 dias)
        data_emp = datetime.strptime(book.data_emprestimo, "%Y-%m-%d")
        dias_emprestado = (datetime.now() - data_emp).days
        atrasado = dias_emprestado > 30

        book.emprestado = False
        book.mutuario = ""
        book.data_emprestimo = ""
        self.save_data()

        if atrasado:
            return True, f"'{book.titulo}' devolvido. Atrasado em {dias_emprestado - 30} dias."
        return True, f"'{book.titulo}' devolvido. Emprestado por {dias_emprestado} dias."

    # ---- Estatísticas ----

    def get_stats(self):
        """Obtém estatísticas"""
        total = len(self.livros)
        emprestados = sum(1 for b in self.livros if b.emprestado)
        disponiveis = total - emprestados

        # Contagem por autor
        autor_count = {}
        for book in self.livros:
            autor_count[book.autor] = autor_count.get(book.autor, 0) + 1

        return {
            "total": total,
            "disponiveis": disponiveis,
            "emprestados": emprestados,
            "por_autor": dict(sorted(autor_count.items(),
                                      key=lambda x: x[1], reverse=True))
        }


# ====== Camada de Apresentação ======

def mostrar_menu():
    print("\n" + "=" * 40)
    print("Sistema de Gerenciamento de Biblioteca")
    print("=" * 40)
    print("1. Adicionar Livro")
    print("2. Deletar Livro")
    print("3. Buscar Livros")
    print("4. Exibir Todos os Livros")
    print("5. Emprestar Livro")
    print("6. Devolver Livro")
    print("7. Estatísticas")
    print("8. Sair")
    print("=" * 40)


def exibir_livros(livros, titulo="Resultados da Busca"):
    """Exibe uma lista de livros"""
    if not livros:
        print(f"\n{titulo}: Nenhum registro.")
        return
    print(f"\n--- {titulo} ---")
    print(f"{'Título':<20}{'Autor':<12}{'ISBN':<15}{'Status':<8}")
    print("-" * 55)
    for b in livros:
        status = "Emprestado" if b.emprestado else "Disponível"
        print(f"{b.titulo:<20}{b.autor:<12}{b.isbn:<15}{status:<8}")


def main():
    library = Biblioteca()

    while True:
        mostrar_menu()
        choice = input("Selecione opção (1-8): ").strip()

        if choice == "1":
            titulo = input("Título: ").strip()
            autor = input("Autor: ").strip()
            isbn = input("ISBN: ").strip()
            ano = input("Ano: ").strip()
            success, msg = library.adicionar_livro(titulo, autor, isbn, int(ano))
            print(f"  {'OK' if success else 'Erro'}: {msg}")

        elif choice == "2":
            isbn = input("Digite o ISBN para deletar: ").strip()
            success, msg = library.deletar_livro(isbn)
            print(f"  {'OK' if success else 'Erro'}: {msg}")

        elif choice == "3":
            print("  1. Buscar por Título")
            print("  2. Buscar por Autor")
            opt = input("Escolha: ").strip()
            if opt == "1":
                keyword = input("Digite palavra-chave do título: ").strip()
                results = library.buscar_por_titulo(keyword)
            elif opt == "2":
                autor = input("Digite o nome do autor: ").strip()
                results = library.buscar_por_autor(autor)
            else:
                print("Escolha inválida")
                continue
            exibir_livros(results)

        elif choice == "4":
            exibir_livros(library.livros, "Todos os Livros")

        elif choice == "5":
            isbn = input("Digite o ISBN para emprestar: ").strip()
            mutuario = input("Nome do mutuário: ").strip()
            success, msg = library.emprestar_livro(isbn, mutuario)
            print(f"  {'OK' if success else 'Erro'}: {msg}")

        elif choice == "6":
            isbn = input("Digite o ISBN para devolver: ").strip()
            success, msg = library.devolver_livro(isbn)
            print(f"  {'OK' if success else 'Erro'}: {msg}")

        elif choice == "7":
            stats = library.get_stats()
            print(f"\n--- Estatísticas ---")
            print(f"Total de livros: {stats['total']}")
            print(f"Disponíveis: {stats['disponiveis']}  Emprestados: {stats['emprestados']}")
            print(f"\nPor Autor:")
            for autor, count in stats['por_autor'].items():
                print(f"  {autor}: {count} livros")

        elif choice == "8":
            print("Obrigado por usar! Até logo!")
            break

        else:
            print("Escolha inválida. Digite 1-8.")


if __name__ == "__main__":
    main()
▶ Experimente

2. Destaques de Design

Arquitetura em camadas: Modelo de dados (Livro), lógica de negócio (Biblioteca) e camada de apresentação (main) são separados, com responsabilidades claras para cada camada.

Persistência de dados: Armazenamento em arquivo CSV com carregamento automático na inicialização — dados sobrevivem a reinicializações do programa.

Lógica de empréstimo: Calcula automaticamente a duração do empréstimo; avisa sobre atraso se exceder 30 dias.

Simplificação com @dataclass: A classe Livro usa o decorador @dataclass, eliminando muito código boilerplate.


3. Direções de Extensão

Recurso Ideia de Implementação
Sistema de usuários Adicione uma classe Usuário, rastreie histórico de empréstimos, limite máximo de empréstimos
Multas por atraso Calcule multas em devolver_livro
Interface gráfica Use tkinter ou um framework web para interface gráfica
Armazenamento em banco de dados Substitua CSV por banco de dados SQLite
Busca avançada Suporte busca combinada com múltiplas condições (autor + faixa de ano)

❓ Perguntas Frequentes

P: Qual a diferença entre @dataclass e escrever __init__ manualmente? R: @dataclass gera automaticamente __init__, __repr__, __eq__ e outros métodos, economizando código boilerplate. Funcionalmente idêntico, apenas mais conciso. Quando uma classe armazena principalmente dados sem lógica complexa, prefira @dataclass. P: Por que usar CSV em vez de JSON para armazenamento de dados? R: Dados de livros são "tabulares" (um livro por linha, campos fixos) — CSV se ajusta bem a esta estrutura e pode ser aberto diretamente no Excel. JSON é mais adequado para estruturas aninhadas (como notas de alunos com sub-dicionários). Ambos funcionam; escolha com base na estrutura dos dados. P: Usar strings como "Emprestado"/"Disponível" para status pode causar problemas? R: É suficiente para projetos pequenos. Uma abordagem mais rigorosa usaria um Enum para definir estados, evitando erros de digitação em strings. No entanto, enums são um tópico avançado; strings funcionam bem aqui.


📖 Resumo


📝 Atividades

  1. Iniciante (Dificuldade ⭐): Adicione um campo categoria à classe Livro (Ficção, Ciência, História, etc.) e permita que o usuário selecione uma categoria ao adicionar um livro.

  2. Intermediário (Dificuldade ⭐⭐): Adicione um recurso de "histórico de empréstimos" — cada vez que um livro é devolvido, anexe o registro de empréstimo a um arquivo history.csv. Formato do registro: título do livro, mutuário, data de empréstimo, data de devolução, status de atraso.

  3. Avançado (Dificuldade ⭐⭐⭐): Sem consultar o código acima, construa um "Sistema de Gerenciamento de Membros" do zero. Recursos incluem: adicionar membro (nome, telefone, pontos), resgatar pontos, visualizar lista de membros, ordenar por pontos, salvar dados em CSV. Organize seu código usando classes.

100%