Skip to main content

Introduction

The best way to learn programming is by building real projects. This module showcases complete Python applications from the bootcamp, demonstrating how to apply Python fundamentals and OOP concepts to solve real problems.

Project Overview

Project Type

Console Application

Language

Python 3.7+

Concepts

OOP, File I/O, JSON

Tests

22 Unit Tests
This project implements a complete Contact Management System that allows users to register, search, edit, and delete contacts through a command-line interface. It demonstrates object-oriented programming, data persistence, and proper software engineering practices.
Project Status: Tests License

Key Features

  • CRUD Operations: Complete Create, Read, Update, Delete functionality
  • Flexible Search: Search by name (partial match) or phone (exact match)
  • Data Validation: Input validation with descriptive error messages
  • JSON Persistence: Automatic save/load of contacts
  • User-Friendly Interface: Interactive menu-driven CLI
  • Unit Tests: Comprehensive test coverage with unittest

Project Architecture

File Structure

Sistema-Gestion-Contactos/

├── main.py                 # Entry point - CLI interface
├── contact.py              # Contact class (data model)
├── contact_manager.py      # ContactManager class (business logic)
├── test_contact_system.py  # Unit tests

├── README.md               # Project documentation
├── contactos.json          # Data file (auto-generated)
└── ejemplos_uso.py         # Usage examples
The project follows a clean architecture:
  1. Data Layer (contact.py): Defines the Contact model
  2. Business Logic Layer (contact_manager.py): Handles operations and persistence
  3. Presentation Layer (main.py): Manages user interaction
This separation makes the code:
  • Easier to test
  • More maintainable
  • Reusable in different contexts

Core Components

The Contact class represents a single contact with encapsulation:
class Contact:
    """
    Clase que representa un contacto.
    
    Atributos:
        nombre (str): Nombre del contacto
        telefono (str): Teléfono del contacto
        email (str): Email del contacto
        direccion (str): Dirección del contacto
    """
    
    def __init__(self, nombre: str, telefono: str, email: str, direccion: str):
        self._nombre = nombre
        self._telefono = telefono
        self._email = email
        self._direccion = direccion
    
    @property
    def nombre(self) -> str:
        return self._nombre
    
    @nombre.setter
    def nombre(self, valor: str):
        if not valor.strip():
            raise ValueError("El nombre no puede estar vacío")
        self._nombre = valor
    
    def to_dict(self) -> dict:
        """Convert contact to dictionary for JSON serialization"""
        return {
            "nombre": self._nombre,
            "telefono": self._telefono,
            "email": self._email,
            "direccion": self._direccion,
        }
    
    @staticmethod
    def from_dict(data: dict) -> "Contact":
        """Create contact from dictionary"""
        return Contact(
            data["nombre"],
            data["telefono"],
            data["email"],
            data["direccion"],
        )

Implementation Details

Data Persistence with JSON

The application uses JSON for data persistence, making it human-readable and easy to debug:
def _guardar_contactos(self):
    """Guarda los contactos en el archivo JSON"""
    try:
        # Convert Contact objects to dictionaries
        datos = [c.to_dict() for c in self._contactos]
        
        # Write to file with proper encoding
        with open(self._archivo_datos, "w", encoding="utf-8") as archivo:
            json.dump(datos, archivo, indent=2, ensure_ascii=False)
    except Exception as e:
        raise Exception(f"Error al guardar contactos: {e}")

Search Functionality

The system implements two types of search:
def buscar_por_nombre(self, nombre: str) -> list[Contact]:
    """Busca contactos por nombre (búsqueda parcial)"""
    nombre_busqueda = nombre.lower()
    return [
        c for c in self._contactos 
        if nombre_busqueda in c.nombre.lower()
    ]
Features:
  • Case-insensitive search
  • Partial matches (“Juan” finds “Juan García”)
  • Returns list of all matching contacts

Edit and Delete Operations

def editar_contacto(gestor: ContactManager):
    """Edita un contacto existente"""
    try:
        telefono = input("\nTeléfono del contacto a editar: ").strip()
        contacto = gestor.buscar_por_telefono(telefono)
        
        if not contacto:
            print(f"❌ No se encontró contacto con el teléfono '{telefono}'")
            return
        
        print(f"\nContacto actual:\n{contacto}")
        print("\nDejar en blanco para mantener el valor actual")
        
        nombre = input("Nuevo nombre: ").strip() or contacto.nombre
        email = input("Nuevo correo: ").strip() or contacto.email
        direccion = input("Nueva dirección: ").strip() or contacto.direccion
        
        gestor.editar_contacto(telefono, nombre, email, direccion)
        print("✅ Contacto editado correctamente")
    except Exception as e:
        print(f"❌ Error al editar contacto: {e}")

Testing

Unit Tests

The project includes comprehensive unit tests covering all functionality:
import unittest
import os
from contact import Contact
from contact_manager import ContactManager

class TestContact(unittest.TestCase):
    def setUp(self):
        self.contacto = Contact(
            "Juan García",
            "+56912345678",
            "[email protected]",
            "Calle Principal 123",
        )
    
    def test_crear_contacto(self):
        self.assertEqual(self.contacto.nombre, "Juan García")
        self.assertEqual(self.contacto.telefono, "+56912345678")
    
    def test_actualizar_nombre(self):
        self.contacto.nombre = "María García"
        self.assertEqual(self.contacto.nombre, "María García")
    
    def test_nombre_vacio_invalido(self):
        with self.assertRaises(ValueError):
            self.contacto.nombre = ""

class TestContactManager(unittest.TestCase):
    def setUp(self):
        self.archivo_pruebas = "test_contactos.json"
        if os.path.exists(self.archivo_pruebas):
            os.remove(self.archivo_pruebas)
        self.gestor = ContactManager(self.archivo_pruebas)
    
    def tearDown(self):
        if os.path.exists(self.archivo_pruebas):
            os.remove(self.archivo_pruebas)
    
    def test_agregar_contacto(self):
        self.gestor.agregar_contacto(
            "Ana Silva",
            "+56912345678",
            "[email protected]",
            "Paseo del Mar 101",
        )
        self.assertEqual(self.gestor.obtener_cantidad_contactos(), 1)
    
    def test_persistencia_datos(self):
        self.gestor.agregar_contacto(
            "Test",
            "+56912345678",
            "[email protected]",
            "Test Address",
        )
        # Create new manager instance - data should persist
        gestor2 = ContactManager(self.archivo_pruebas)
        contactos = gestor2.obtener_todos_contactos()
        self.assertEqual(len(contactos), 1)

if __name__ == "__main__":
    unittest.main(verbosity=2)
Test Coverage: The test suite includes 22 tests covering:
  • Contact creation and validation
  • Property getters and setters
  • CRUD operations
  • Search functionality
  • Data persistence
  • Error handling

Running Tests

# Run all tests
python test_contact_system.py

# Run with verbose output
python test_contact_system.py -v

# Run specific test class
python -m unittest test_contact_system.TestContact

Usage Examples

Basic Usage

from contact_manager import ContactManager

# Initialize the manager
gestor = ContactManager("contactos.json")

# Add contacts
gestor.agregar_contacto(
    "Juan García",
    "+56912345678",
    "[email protected]",
    "Calle Principal 123"
)

# Search by name
resultados = gestor.buscar_por_nombre("Juan")
for contacto in resultados:
    print(contacto)

# Search by phone
contacto = gestor.buscar_por_telefono("+56912345678")
if contacto:
    print(f"Found: {contacto.nombre}")

# Edit contact
gestor.editar_contacto(
    "+56912345678",
    email="[email protected]"
)

# Delete contact
gestor.eliminar_contacto("+56912345678")

# Get statistics
total = gestor.obtener_cantidad_contactos()
print(f"Total contacts: {total}")

Project Benefits

Clean Architecture

Separation of concerns between data, business logic, and presentation layers

Data Validation

Input validation with descriptive error messages prevents invalid data

Persistent Storage

JSON-based storage ensures data survives between sessions

Test Coverage

Comprehensive unit tests ensure code reliability and catch regressions

User-Friendly

Interactive CLI with clear prompts and visual feedback (✅/❌)

Type Safety

Type hints throughout improve code clarity and IDE support

Technologies Used

  • Python 3.7+: Core programming language
  • json: Built-in module for data persistence
  • os: File system operations
  • unittest: Built-in testing framework
  • typing: Type hints for better code quality
No External Dependencies: The project uses only Python’s standard library, making it easy to run in any Python 3.7+ environment.

Key Takeaways

This project demonstrates several important software development concepts:
  1. Object-Oriented Design: Clean class structure with encapsulation
  2. CRUD Operations: Complete Create, Read, Update, Delete functionality
  3. Data Persistence: Loading and saving data with JSON
  4. Error Handling: Try-except blocks and validation
  5. Testing: Unit tests with setUp/tearDown
  6. User Experience: Clear interface with visual feedback
  7. Code Organization: Separation of concerns across modules

Practice Exercises

Add a method to export contacts to CSV format:
def exportar_csv(self, filename: str):
    """Export contacts to CSV file"""
    # Your implementation here
Hint: Use Python’s csv module.
Extend the Contact class to include a category field (family, work, friends) and add filtering by category.Hint: Update the __init__ method and add a new search method.
Implement email format validation in the Contact class using regular expressions.Hint: Use the re module and validate in the email setter.

Next Steps

Python Basics

Review fundamental Python concepts

Object-Oriented Programming

Deep dive into OOP principles

Build docs developers (and LLMs) love