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
Assignmentpara encapsular el título y el estado de finalización. - Gestión de Memoria: La clase
ActivityTrackercentraliza la lógica de negocio, utilizandostd::vectorpara 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
displayFilteredevalú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 constd::cin.ignore(), garantizando que los saltos de línea residuales no interfieran con las lecturas subsequentes destd::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.