Proyecto: Aplicación de lista de tareas
Introducción
En este artículo vamos a desarrollar una aplicación práctica de lista de tareas utilizando Java. Este proyecto nos permitirá aplicar muchos de los conceptos que hemos aprendido hasta ahora, como la programación orientada a objetos, el manejo de colecciones, la entrada/salida por consola y el manejo de archivos. Una aplicación de lista de tareas es útil para organizar actividades pendientes y es un ejemplo perfecto de una herramienta que podrías necesitar en tu día a día.
A través de este proyecto, consolidarás tus conocimientos teóricos mediante la implementación de una aplicación funcional que podrás utilizar y mejorar según tus necesidades. Al finalizar, tendrás una aplicación de consola que te permitirá crear, ver, actualizar y eliminar tareas, así como guardarlas para futuras sesiones.
Planificación del proyecto
Antes de comenzar a programar, es importante planificar nuestra aplicación. Definiremos los requisitos y funcionalidades que debe tener nuestro gestor de tareas:
Requisitos funcionales
- Añadir nuevas tareas con título, descripción y fecha límite
- Mostrar la lista de todas las tareas
- Marcar tareas como completadas
- Eliminar tareas
- Editar tareas existentes
- Guardar tareas en un archivo
- Cargar tareas desde un archivo al iniciar la aplicación
Estructura del proyecto
Organizaremos nuestro proyecto en varios paquetes:
modelo
: Contendrá las clases que representan los datosservicio
: Lógica de negocio y operaciones con las tareasutil
: Clases utilitariasui
: Interfaz de usuario por consolaapp
: Clase principal para iniciar la aplicación
Implementación paso a paso
1. Creación de la clase Tarea
Primero, vamos a crear la clase que representará cada tarea individual:
package modelo;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class Tarea {
private int id;
private String titulo;
private String descripcion;
private LocalDate fechaLimite;
private boolean completada;
private static int contadorId = 1;
// Constructor
public Tarea(String titulo, String descripcion, LocalDate fechaLimite) {
this.id = contadorId++;
this.titulo = titulo;
this.descripcion = descripcion;
this.fechaLimite = fechaLimite;
this.completada = false;
}
// Constructor alternativo con ID predefinido (útil para cargar desde archivo)
public Tarea(int id, String titulo, String descripcion, LocalDate fechaLimite, boolean completada) {
this.id = id;
this.titulo = titulo;
this.descripcion = descripcion;
this.fechaLimite = fechaLimite;
this.completada = completada;
// Actualizamos el contador si el ID proporcionado es mayor
if (id >= contadorId) {
contadorId = id + 1;
}
}
// Getters y setters
public int getId() {
return id;
}
public String getTitulo() {
return titulo;
}
public void setTitulo(String titulo) {
this.titulo = titulo;
}
public String getDescripcion() {
return descripcion;
}
public void setDescripcion(String descripcion) {
this.descripcion = descripcion;
}
public LocalDate getFechaLimite() {
return fechaLimite;
}
public void setFechaLimite(LocalDate fechaLimite) {
this.fechaLimite = fechaLimite;
}
public boolean isCompletada() {
return completada;
}
public void setCompletada(boolean completada) {
this.completada = completada;
}
@Override
public String toString() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String estado = completada ? "Completada" : "Pendiente";
return String.format("[%d] %s - %s - Fecha límite: %s - Estado: %s",
id, titulo, descripcion, fechaLimite.format(formatter), estado);
}
// Método para convertir la tarea a formato CSV
public String toCSV() {
return String.format("%d,%s,%s,%s,%b",
id, titulo, descripcion, fechaLimite, completada);
}
}
2. Servicio para gestionar las tareas
Ahora implementaremos la lógica de negocio que gestionará las operaciones con tareas:
package servicio;
import modelo.Tarea;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class ServicioTareas {
private List<Tarea> tareas;
public ServicioTareas() {
this.tareas = new ArrayList<>();
}
public Tarea crearTarea(String titulo, String descripcion, LocalDate fechaLimite) {
Tarea nuevaTarea = new Tarea(titulo, descripcion, fechaLimite);
tareas.add(nuevaTarea);
return nuevaTarea;
}
public boolean eliminarTarea(int id) {
return tareas.removeIf(tarea -> tarea.getId() == id);
}
public Optional<Tarea> buscarTarea(int id) {
return tareas.stream()
.filter(tarea -> tarea.getId() == id)
.findFirst();
}
public boolean marcarComoCompletada(int id) {
Optional<Tarea> tarea = buscarTarea(id);
if (tarea.isPresent()) {
tarea.get().setCompletada(true);
return true;
}
return false;
}
public boolean editarTarea(int id, String nuevoTitulo, String nuevaDescripcion, LocalDate nuevaFechaLimite) {
Optional<Tarea> tarea = buscarTarea(id);
if (tarea.isPresent()) {
Tarea tareaActual = tarea.get();
if (nuevoTitulo != null && !nuevoTitulo.trim().isEmpty()) {
tareaActual.setTitulo(nuevoTitulo);
}
if (nuevaDescripcion != null) {
tareaActual.setDescripcion(nuevaDescripcion);
}
if (nuevaFechaLimite != null) {
tareaActual.setFechaLimite(nuevaFechaLimite);
}
return true;
}
return false;
}
public List<Tarea> listarTareas() {
return new ArrayList<>(tareas);
}
public List<Tarea> listarTareasPendientes() {
List<Tarea> tareasPendientes = new ArrayList<>();
for (Tarea tarea : tareas) {
if (!tarea.isCompletada()) {
tareasPendientes.add(tarea);
}
}
return tareasPendientes;
}
public List<Tarea> listarTareasCompletadas() {
List<Tarea> tareasCompletadas = new ArrayList<>();
for (Tarea tarea : tareas) {
if (tarea.isCompletada()) {
tareasCompletadas.add(tarea);
}
}
return tareasCompletadas;
}
public void setTareas(List<Tarea> tareas) {
this.tareas = tareas;
}
}
3. Servicio para la persistencia de datos
Implementaremos un servicio que nos permita guardar y cargar las tareas desde un archivo:
package servicio;
import modelo.Tarea;
import java.io.*;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
public class ServicioPersistencia {
private static final String NOMBRE_ARCHIVO = "tareas.csv";
public void guardarTareas(List<Tarea> tareas) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(NOMBRE_ARCHIVO))) {
for (Tarea tarea : tareas) {
writer.write(tarea.toCSV());
writer.newLine();
}
}
}
public List<Tarea> cargarTareas() throws IOException {
List<Tarea> tareas = new ArrayList<>();
File archivo = new File(NOMBRE_ARCHIVO);
if (!archivo.exists()) {
return tareas;
}
try (BufferedReader reader = new BufferedReader(new FileReader(archivo))) {
String linea;
while ((linea = reader.readLine()) != null) {
String[] partes = linea.split(",");
if (partes.length >= 5) {
int id = Integer.parseInt(partes[0]);
String titulo = partes[1];
String descripcion = partes[2];
LocalDate fechaLimite = LocalDate.parse(partes[3]);
boolean completada = Boolean.parseBoolean(partes[4]);
Tarea tarea = new Tarea(id, titulo, descripcion, fechaLimite, completada);
tareas.add(tarea);
}
}
}
return tareas;
}
}
4. Interfaz de usuario por consola
Ahora implementaremos la interfaz de usuario que interactuará con el usuario mediante la consola:
package ui;
import modelo.Tarea;
import servicio.ServicioTareas;
import servicio.ServicioPersistencia;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.Optional;
import java.util.Scanner;
public class ConsolaUI {
private ServicioTareas servicioTareas;
private ServicioPersistencia servicioPersistencia;
private Scanner scanner;
private boolean ejecutando;
public ConsolaUI() {
this.servicioTareas = new ServicioTareas();
this.servicioPersistencia = new ServicioPersistencia();
this.scanner = new Scanner(System.in);
this.ejecutando = true;
}
public void iniciar() {
cargarTareas();
while (ejecutando) {
mostrarMenu();
int opcion = leerOpcion();
procesarOpcion(opcion);
}
scanner.close();
}
private void cargarTareas() {
try {
List<Tarea> tareas = servicioPersistencia.cargarTareas();
servicioTareas.setTareas(tareas);
System.out.println("Se han cargado " + tareas.size() + " tareas.");
} catch (IOException e) {
System.out.println("Error al cargar las tareas: " + e.getMessage());
}
}
private void guardarTareas() {
try {
servicioPersistencia.guardarTareas(servicioTareas.listarTareas());
System.out.println("Tareas guardadas correctamente.");
} catch (IOException e) {
System.out.println("Error al guardar las tareas: " + e.getMessage());
}
}
private void mostrarMenu() {
System.out.println("\n==== GESTOR DE TAREAS ====");
System.out.println("1. Ver todas las tareas");
System.out.println("2. Ver tareas pendientes");
System.out.println("3. Ver tareas completadas");
System.out.println("4. Añadir nueva tarea");
System.out.println("5. Marcar tarea como completada");
System.out.println("6. Editar tarea");
System.out.println("7. Eliminar tarea");
System.out.println("8. Guardar tareas");
System.out.println("9. Salir");
System.out.print("Seleccione una opción: ");
}
private int leerOpcion() {
try {
return Integer.parseInt(scanner.nextLine());
} catch (NumberFormatException e) {
return 0;
}
}
private void procesarOpcion(int opcion) {
switch (opcion) {
case 1:
mostrarTodasLasTareas();
break;
case 2:
mostrarTareasPendientes();
break;
case 3:
mostrarTareasCompletadas();
break;
case 4:
añadirTarea();
break;
case 5:
marcarTareaComoCompletada();
break;
case 6:
editarTarea();
break;
case 7:
eliminarTarea();
break;
case 8:
guardarTareas();
break;
case 9:
guardarTareas();
ejecutando = false;
System.out.println("¡Hasta pronto!");
break;
default:
System.out.println("Opción no válida. Inténtelo de nuevo.");
}
}
private void mostrarTodasLasTareas() {
List<Tarea> tareas = servicioTareas.listarTareas();
if (tareas.isEmpty()) {
System.out.println("No hay tareas registradas.");
} else {
System.out.println("\n--- TODAS LAS TAREAS ---");
for (Tarea tarea : tareas) {
System.out.println(tarea);
}
}
}
private void mostrarTareasPendientes() {
List<Tarea> tareas = servicioTareas.listarTareasPendientes();
if (tareas.isEmpty()) {
System.out.println("No hay tareas pendientes.");
} else {
System.out.println("\n--- TAREAS PENDIENTES ---");
for (Tarea tarea : tareas) {
System.out.println(tarea);
}
}
}
private void mostrarTareasCompletadas() {
List<Tarea> tareas = servicioTareas.listarTareasCompletadas();
if (tareas.isEmpty()) {
System.out.println("No hay tareas completadas.");
} else {
System.out.println("\n--- TAREAS COMPLETADAS ---");
for (Tarea tarea : tareas) {
System.out.println(tarea);
}
}
}
private void añadirTarea() {
System.out.println("\n--- AÑADIR NUEVA TAREA ---");
System.out.print("Título: ");
String titulo = scanner.nextLine();
System.out.print("Descripción: ");
String descripcion = scanner.nextLine();
LocalDate fechaLimite = leerFecha();
if (fechaLimite != null) {
Tarea nuevaTarea = servicioTareas.crearTarea(titulo, descripcion, fechaLimite);
System.out.println("Tarea creada con éxito: " + nuevaTarea);
}
}
private LocalDate leerFecha() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
while (true) {
System.out.print("Fecha límite (dd/MM/yyyy): ");
String fechaStr = scanner.nextLine();
try {
return LocalDate.parse(fechaStr, formatter);
} catch (DateTimeParseException e) {
System.out.println("Formato de fecha incorrecto. Utilice el formato dd/MM/yyyy");
System.out.print("¿Desea intentarlo de nuevo? (s/n): ");
if (!scanner.nextLine().equalsIgnoreCase("s")) {
return null;
}
}
}
}
private void marcarTareaComoCompletada() {
System.out.println("\n--- MARCAR TAREA COMO COMPLETADA ---");
System.out.print("Introduzca el ID de la tarea: ");
try {
int id = Integer.parseInt(scanner.nextLine());
boolean resultado = servicioTareas.marcarComoCompletada(id);
if (resultado) {
System.out.println("Tarea marcada como completada.");
} else {
System.out.println("No se encontró ninguna tarea con ese ID.");
}
} catch (NumberFormatException e) {
System.out.println("El ID debe ser un número entero.");
}
}
private void editarTarea() {
System.out.println("\n--- EDITAR TAREA ---");
System.out.print("Introduzca el ID de la tarea a editar: ");
try {
int id = Integer.parseInt(scanner.nextLine());
Optional<Tarea> tareaOpt = servicioTareas.buscarTarea(id);
if (tareaOpt.isPresent()) {
Tarea tarea = tareaOpt.get();
System.out.println("Tarea actual: " + tarea);
System.out.println("Deje en blanco para mantener el valor actual");
System.out.print("Nuevo título [" + tarea.getTitulo() + "]: ");
String nuevoTitulo = scanner.nextLine();
if (nuevoTitulo.trim().isEmpty()) {
nuevoTitulo = null;
}
System.out.print("Nueva descripción [" + tarea.getDescripcion() + "]: ");
String nuevaDescripcion = scanner.nextLine();
if (nuevaDescripcion.trim().isEmpty()) {
nuevaDescripcion = null;
}
LocalDate nuevaFechaLimite = null;
System.out.print("¿Desea modificar la fecha límite? (s/n): ");
if (scanner.nextLine().equalsIgnoreCase("s")) {
nuevaFechaLimite = leerFecha();
}
boolean resultado = servicioTareas.editarTarea(id, nuevoTitulo, nuevaDescripcion, nuevaFechaLimite);
if (resultado) {
System.out.println("Tarea actualizada correctamente.");
} else {
System.out.println("Error al actualizar la tarea.");
}
} else {
System.out.println("No se encontró ninguna tarea con ese ID.");
}
} catch (NumberFormatException e) {
System.out.println("El ID debe ser un número entero.");
}
}
private void eliminarTarea() {
System.out.println("\n--- ELIMINAR TAREA ---");
System.out.print("Introduzca el ID de la tarea a eliminar: ");
try {
int id = Integer.parseInt(scanner.nextLine());
System.out.print("¿Está seguro de que desea eliminar la tarea? (s/n): ");
if (scanner.nextLine().equalsIgnoreCase("s")) {
boolean resultado = servicioTareas.eliminarTarea(id);
if (resultado) {
System.out.println("Tarea eliminada correctamente.");
} else {
System.out.println("No se encontró ninguna tarea con ese ID.");
}
} else {
System.out.println("Operación cancelada.");
}
} catch (NumberFormatException e) {
System.out.println("El ID debe ser un número entero.");
}
}
}
5. Clase principal de la aplicación
Finalmente, implementaremos la clase principal que inicia la aplicación:
package app;
import ui.ConsolaUI;
public class GestorTareasApp {
public static void main(String[] args) {
ConsolaUI ui = new ConsolaUI();
ui.iniciar();
}
}
Ejecutando la aplicación
Una vez que has implementado todas las clases, puedes ejecutar la aplicación mediante el método principal en la clase GestorTareasApp
. Asegúrate de que todas las clases estén en los paquetes correspondientes y que la estructura del proyecto sea la correcta.
Para ejecutar el programa:
-
Compila todas las clases:
javac -d bin app/*.java modelo/*.java servicio/*.java ui/*.java
-
Ejecuta la aplicación:
java -cp bin app.GestorTareasApp
Al iniciar la aplicación, se cargarán las tareas guardadas previamente (si existen) y se mostrará el menú principal. Desde allí, podrás gestionar tus tareas fácilmente mediante las opciones disponibles.
Posibles mejoras
Nuestra aplicación de gestor de tareas funciona correctamente, pero hay varias mejoras que podríamos implementar:
- Interfaz gráfica: Sustituir la interfaz de consola por una interfaz gráfica utilizando JavaFX o Swing.
- Filtros adicionales: Implementar la posibilidad de filtrar tareas por fecha o por texto.
- Categorías o etiquetas: Añadir la posibilidad de asignar categorías a las tareas para organizarlas mejor.
- Notificaciones: Implementar un sistema de alertas para tareas próximas a vencer.
- Base de datos: Reemplazar el almacenamiento en archivos CSV por una base de datos SQL.
- Sincronización: Permitir sincronizar las tareas con servicios en la nube.
Resumen
En este artículo hemos desarrollado una aplicación completa de gestión de tareas utilizando Java. Hemos aplicado conceptos fundamentales como la programación orientada a objetos, el manejo de colecciones, la entrada/salida por consola y el manejo de archivos para persistencia de datos.
La aplicación permite realizar operaciones básicas como añadir, visualizar, marcar como completadas, editar y eliminar tareas, además de guardar y cargar los datos en un archivo para mantener la persistencia entre sesiones. Este proyecto es un excelente ejemplo de cómo los conocimientos teóricos de Java se pueden aplicar para desarrollar aplicaciones prácticas y útiles en el mundo real.
A partir de aquí, puedes seguir mejorando la aplicación añadiendo nuevas funcionalidades o refactorizando el código para hacerlo más robusto y escalable. ¡La programación es un proceso continuo de aprendizaje y mejora!