Implementación y Refactorización de un Gestor de Tareas en C++

La creación de herramientas para la organización del trabajo diario es un excelente ejercicio para practicar la programación orientada a objetos y la manipulación de estructursa de datos. A continuación, se detalla el proceso de construcción de un administrador de actividades en C++, comenzando con una implementación básica y evolucionando hacia una versión más robusta con capacidades de filtrado y categorización.

Versión Inicial: Administrador Básico

En esta primera iteración, se establece la estructura fundamental para almacenar y manipular elementos pendientes. Se utiliza un vector de la STL (Standard Template Library) para mantener la colección en memoria.

#include <iostream>
#include <vector>
#include <string>

struct Assignment {
    std::string title;
    bool isDone;
};

class ActivityTracker {
private:
    std::vector<Assignment> items;

public:
    void insertActivity(const std::string& name) {
        items.push_back({name, false});
    }

    void removeActivity(size_t idx) {
        if (idx < items.size()) {
            items.erase(items.begin() + idx);
        } else {
            std::cerr << "Index out of bounds.\n";
        }
    }

    void displayAll() const {
        if (items.empty()) {
            std::cout << "No activities found.\n";
            return;
        }
        for (size_t i = 0; i < items.size(); ++i) {
            std::cout << (i + 1) << ". " << items[i].title 
                      << " [" << (items[i].isDone ? "Done" : "Pending") << "]\n";
        }
    }

    void setDone(size_t idx) {
        if (idx < items.size()) {
            items[idx].isDone = true;
        } else {
            std::cerr << "Invalid index.\n";
        }
    }
};

int main() {
    ActivityTracker tracker;
    tracker.insertActivity("Buy groceries");
    tracker.insertActivity("Finish report");
    tracker.displayAll();
    tracker.setDone(0);
    tracker.displayAll();
    tracker.removeActivity(1);
    tracker.displayAll();
    return 0;
}

Análisis de la Implementación Básica

  • Estructura de Datos: Se define Assignment para encapsular el título y el estado de finalización.
  • Gestión de Memoria: La clase ActivityTracker centraliza la lógica de negocio, utilizando std::vector para permitir un tamaño dinámico de la lista.
  • Operaciones CRUD: Se implementan métodos para insertar, eliminar, visualizar y actualizar el estado de los elementos, validando los índices para evitar errores de desbordamiento.

Expansión: Categorización y Niveles de Prioridad

Para mejorar la utilidad de la aplicación, es necesario incorporar metadatos adicionales que permitan clasificar y priorizar el trabajo. En esta refactorización, se introduce el uso de enumeraciones fuertemente tipadas (enum class) y se implementa un menú interactivo en la consola.

#include <iostream>
#include <vector>
#include <string>
#include <limits>

enum class Priority { Low, Medium, High };

std::string priorityToString(Priority p) {
    switch(p) {
        case Priority::Low: return "Low";
        case Priority::High: return "High";
        default: return "Medium";
    }
}

Priority stringToPriority(const std::string& str) {
    if (str == "High" || str == "high") return Priority::High;
    if (str == "Low" || str == "low") return Priority::Low;
    return Priority::Medium;
}

struct Assignment {
    std::string title;
    bool isDone;
    Priority level;
    std::string group;
};

class ActivityTracker {
private:
    std::vector<Assignment> items;

public:
    void insertActivity(const std::string& name, Priority lvl = Priority::Medium, const std::string& grp = "General") {
        items.push_back({name, false, lvl, grp});
    }

    void removeActivity(size_t idx) {
        if (idx < items.size()) {
            items.erase(items.begin() + idx);
        } else {
            std::cerr << "Index out of bounds.\n";
        }
    }

    void displayFiltered(const std::string& grpFilter = "", Priority lvlFilter = Priority::Medium, bool filterByPriority = false) const {
        bool found = false;
        for (size_t i = 0; i < items.size(); ++i) {
            bool matchGroup = grpFilter.empty() || items[i].group == grpFilter;
            bool matchPriority = !filterByPriority || items[i].level == lvlFilter;
            
            if (matchGroup && matchPriority) {
                std::cout << (i + 1) << ". " << items[i].title 
                          << " [Priority: " << priorityToString(items[i].level) 
                          << ", Group: " << items[i].group 
                          << ", Status: " << (items[i].isDone ? "Done" : "Pending") << "]\n";
                found = true;
            }
        }
        if (!found) std::cout << "No matching activities.\n";
    }

    void setDone(size_t idx) {
        if (idx < items.size()) {
            items[idx].isDone = true;
        } else {
            std::cerr << "Invalid index.\n";
        }
    }
};

void showMenu() {
    std::cout << "\n--- Activity Tracker ---\n"
              << "1. View Activities\n"
              << "2. Add Activity\n"
              << "3. Remove Activity\n"
              << "4. Mark as Done\n"
              << "5. Exit\n"
              << "Choice: ";
}

int main() {
    ActivityTracker tracker;
    int option;
    
    while (true) {
        showMenu();
        if (!(std::cin >> option)) break;
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

        if (option == 1) {
            std::string grp, prioStr;
            std::cout << "Filter by group (leave empty for all): ";
            std::getline(std::cin, grp);
            std::cout << "Filter by priority (High/Medium/Low, leave empty for all): ";
            std::getline(std::cin, prioStr);
            
            bool usePrio = !prioStr.empty();
            tracker.displayFiltered(grp, stringToPriority(prioStr), usePrio);
        } else if (option == 2) {
            std::string name, prioStr, grp;
            std::cout << "Activity title: ";
            std::getline(std::cin, name);
            std::cout << "Priority (High/Medium/Low): ";
            std::getline(std::cin, prioStr);
            std::cout << "Group: ";
            std::getline(std::cin, grp);
            tracker.insertActivity(name, stringToPriority(prioStr), grp.empty() ? "General" : grp);
        } else if (option == 3) {
            size_t idx;
            std::cout << "Index to remove: ";
            std::cin >> idx;
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            tracker.removeActivity(idx - 1);
        } else if (option == 4) {
            size_t idx;
            std::cout << "Index to mark done: ";
            std::cin >> idx;
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            tracker.setDone(idx - 1);
        } else if (option == 5) {
            break;
        } else {
            std::cout << "Invalid option.\n";
        }
    }
    return 0;
}

Mejoras Técnicas en la Refactorización

  • Seguridad de Tipos: La prioridad ya no se maneja como una cadena de texto libre, sino a través de enum class Priority, lo que previene errores de asignación y facilita las comparaciones lógicas.
  • Lógica de Filtrado: El método displayFiltered evalúa múltiples condiciones. Si los parámetros de filtro están vacíos o desactivados, muestra todos los registros; de lo contrario, aplica intersecciones lógicas para presentar solo los resultados relevantes.
  • Manejo de Entrada/Salida: Se optimiza la limpeiza del búfer de entrada utilizando std::numeric_limits<std::streamsize>::max() junto con std::cin.ignore(), garantizando que los saltos de línea residuales no interfieran con las lecturas subsequentes de std::getline.
  • Interfaz de Usuario: Se diseña un bucle principal controlado por un menú que permite al usuario interactuar de manera continua con el sistema hasta que decida finalizar la ejecución.

Etiquetas: C++ STL ObjectOrientedProgramming CommandLineInterface DataStructures

Publicado el 6-12 01:52