BorisTech
Explorar
Java 29 de abril de 2026 64

Cómo Crear un Gestor de Tareas Full Stack con Java y React: Tutorial Completo para Principiantes

En la actualidad, el desarrollo Full Stack es una habilidad muy demandada en el mundo de la programación. Combinar un backend robusto con Java y Spring Boot junto con un frontend interactivo en React permite crear aplicaciones web modernas y escalables.

En este tutorial práctico, te guiaré paso a paso para que construyas desde cero un gestor de tareas completo, funcional y preparado para ser mejorado con nuevas funcionalidades. No necesitarás conocimientos avanzados, pues te explicaré todo con detalle para que puedas seguirlo siendo principiante.

Proyecto completo que vas a crear

Este proyecto consiste en construir un Gestor de Tareas Full Stack. En el backend utilizaremos Java con Spring Boot para crear una API REST que gestione las tareas, permitiendo operaciones CRUD (crear, leer, actualizar y eliminar). Para el frontend, usaremos React, que consumirá la API para mostrar las tareas y permitir la interacción del usuario.

El proyecto tendrá estas funcionalidades: añadir tareas, listar tareas, marcar tareas como completadas, editar y eliminar tareas. Aprenderás a manejar la comunicación cliente-servidor, estructurar un proyecto Full Stack y configurar un entorno de desarrollo integrado de Java y JavaScript.

Además, el tutorial cubre buenas prácticas para desarrollar aplicaciones robustas, cómo probar cada parte y conceptos básicos de REST y desarrollo frontend con React.

Estructura de archivos

backend/src/main/java/com/boristech/taskmanager/TaskManagerApplication.java
backend/src/main/java/com/boristech/taskmanager/controller/TaskController.java
backend/src/main/java/com/boristech/taskmanager/model/Task.java
backend/src/main/java/com/boristech/taskmanager/repository/TaskRepository.java
frontend/src/App.js

Cómo crear y probar el proyecto

  1. Crear la carpeta raíz del proyecto, por ejemplo 'task-manager-fullstack'.
  2. Dentro de la carpeta raíz, crear dos carpetas llamadas 'backend' y 'frontend'.
  3. En la carpeta backend, inicializar un proyecto Spring Boot (se puede usar Spring Initializr o IDE como IntelliJ o Eclipse).
  4. Agregar las clases Java según los archivos proporcionados en backend/src/main/java/com/boristech/taskmanager/.
  5. En la carpeta frontend, inicializar un proyecto React usando 'npx create-react-app frontend' o similar.
  6. Reemplazar el archivo App.js del frontend con el código proporcionado.
  7. Configurar y ejecutar la base de datos H2 (en memoria) incluida en Spring Boot para pruebas.
  8. Ejecutar el backend con 'mvn spring-boot:run' o desde el IDE.
  9. Ejecutar el frontend con 'npm start' dentro de la carpeta frontend.
  10. Abrir el navegador y probar la aplicación en http://localhost:3000

Introducción al proyecto

Antes de comenzar a programar, es importante entender la estructura y los requerimientos del gestor de tareas que vamos a crear. Nuestro backend usará Java con Spring Boot para exponer una API REST que permitirá realizar operaciones básicas sobre las tareas. El frontend en React funcionará como una interfaz amigable para el usuario.

Configuración del Backend con Spring Boot

Spring Boot nos facilita la creación de aplicaciones Java con poco esfuerzo de configuración. Usaremos una base de datos en memoria H2 para simplificar el entorno de pruebas.

1. Crear la entidad Task

La entidad representa una tarea con campos típicos: id, descripción, estado completado y fecha de creación.

2. Crear el repositorio TaskRepository

Este repositorio extiende JpaRepository para gestionar la persistencia de la entidad Task sin escribir código SQL.

3. Crear el controlador REST TaskController

El controlador gestionará las rutas HTTP para permitir crear, listar, actualizar y eliminar tareas. Usaremos anotaciones de Spring MVC para definir los endpoints.

Configuración del Frontend con React

React nos permitirá construir una interfaz dinámica y reactiva. Usaremos fetch para consumir la API REST que creamos en el backend.

1. Estructura básica de App.js

En App.js definiremos los estados para almacenar las tareas, funciones para consumir la API y la interfaz para mostrar y modificar la lista.

2. Funciones principales

  • Obtener tareas: fetch para traer la lista del backend.
  • Crear tarea: formulario para añadir nuevas tareas.
  • Actualizar estado: marcar como completada o no.
  • Eliminar tarea: eliminar un ítem.

Buenas prácticas y errores comunes

Al programar una aplicación Full Stack es común encontrarse con errores como CORS, mal manejo de estados, o problemas de sincronización entre frontend y backend. Es fundamental:

  • Verificar que el backend permita peticiones desde el origen del frontend (configurar CORS en Spring Boot).
  • Manejar correctamente los estados en React para evitar inconsistencias.
  • Testear cada parte por separado antes de integrar.
  • Usar mensajes de error claros y logs para depurar.

Pruebas y ejecución del proyecto

Una vez configurado el backend y el frontend, debemos ejecutar ambos servidores. Accediendo a la URL del frontend podremos manipular las tareas y ver la comunicación con el backend en tiempo real. ¡Felicidades! Has creado tu primera aplicación Full Stack con Java y React.

Código completo para copiar y pegar

Clase principal de Spring Boot

backend/src/main/java/com/boristech/taskmanager/TaskManagerApplication.java

Punto de entrada de la aplicación Spring Boot para iniciar el servidor.

package com.boristech.taskmanager;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TaskManagerApplication {
    public static void main(String[] args) {
        SpringApplication.run(TaskManagerApplication.class, args);
    }
}

Entidad Task

backend/src/main/java/com/boristech/taskmanager/model/Task.java

Modelo JPA que representa una tarea con id, descripción y estado.

package com.boristech.taskmanager.model;

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "tasks")
public class Task {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String description;

    @Column(nullable = false)
    private boolean completed = false;

    @Column(name = "created_at")
    private LocalDateTime createdAt = LocalDateTime.now();

    public Task() {}

    public Task(String description) {
        this.description = description;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public boolean isCompleted() {
        return completed;
    }

    public void setCompleted(boolean completed) {
        this.completed = completed;
    }

    public LocalDateTime getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(LocalDateTime createdAt) {
        this.createdAt = createdAt;
    }
}

Repositorio JPA para Task

backend/src/main/java/com/boristech/taskmanager/repository/TaskRepository.java

Interfaz que extiende JpaRepository para operaciones CRUD sobre Task.

package com.boristech.taskmanager.repository;

import com.boristech.taskmanager.model.Task;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface TaskRepository extends JpaRepository<Task, Long> {

}

Controlador REST para tareas

backend/src/main/java/com/boristech/taskmanager/controller/TaskController.java

Controlador Spring Boot con endpoints para CRUD de tareas y configuración de CORS.

package com.boristech.taskmanager.controller;

import com.boristech.taskmanager.model.Task;
import com.boristech.taskmanager.repository.TaskRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/api/tasks")
@CrossOrigin(origins = "http://localhost:3000") // Permite peticiones desde React
public class TaskController {

    @Autowired
    private TaskRepository taskRepository;

    @GetMapping
    public List<Task> getAllTasks() {
        return taskRepository.findAll();
    }

    @PostMapping
    public Task createTask(@RequestBody Task task) {
        return taskRepository.save(task);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Task> updateTask(@PathVariable Long id, @RequestBody Task taskDetails) {
        Optional<Task> optionalTask = taskRepository.findById(id);
        if (!optionalTask.isPresent()) {
            return ResponseEntity.notFound().build();
        }
        Task task = optionalTask.get();
        task.setDescription(taskDetails.getDescription());
        task.setCompleted(taskDetails.isCompleted());
        Task updatedTask = taskRepository.save(task);
        return ResponseEntity.ok(updatedTask);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteTask(@PathVariable Long id) {
        Optional<Task> optionalTask = taskRepository.findById(id);
        if (!optionalTask.isPresent()) {
            return ResponseEntity.notFound().build();
        }
        taskRepository.delete(optionalTask.get());
        return ResponseEntity.noContent().build();
    }
}

Componente principal React

frontend/src/App.js

Frontend React que consume la API REST para administrar tareas.

import React, { useState, useEffect } from 'react';

function App() {
  const [tasks, setTasks] = useState([]);
  const [newDescription, setNewDescription] = useState('');
  const API_URL = 'http://localhost:8080/api/tasks';

  // Obtener todas las tareas al cargar
  useEffect(() => {
    fetchTasks();
  }, []);

  const fetchTasks = async () => {
    try {
      const response = await fetch(API_URL);
      const data = await response.json();
      setTasks(data);
    } catch (error) {
      console.error('Error al obtener tareas:', error);
    }
  };

  const handleAddTask = async (e) => {
    e.preventDefault();
    if (newDescription.trim() === '') return;

    try {
      const response = await fetch(API_URL, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ description: newDescription })
      });
      if (response.ok) {
        setNewDescription('');
        fetchTasks();
      } else {
        console.error('Error al agregar tarea');
      }
    } catch (error) {
      console.error('Error al agregar tarea:', error);
    }
  };

  const toggleComplete = async (task) => {
    try {
      const response = await fetch(`${API_URL}/${task.id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...task, completed: !task.completed })
      });
      if (response.ok) {
        fetchTasks();
      } else {
        console.error('Error al actualizar tarea');
      }
    } catch (error) {
      console.error('Error al actualizar tarea:', error);
    }
  };

  const handleDelete = async (id) => {
    try {
      const response = await fetch(`${API_URL}/${id}`, {
        method: 'DELETE'
      });
      if (response.ok) {
        fetchTasks();
      } else {
        console.error('Error al eliminar tarea');
      }
    } catch (error) {
      console.error('Error al eliminar tarea:', error);
    }
  };

  return (
    <div style={{ maxWidth: '600px', margin: '20px auto', fontFamily: 'Arial, sans-serif' }}>
      <h2>Gestor de Tareas</h2>
      <form onSubmit={handleAddTask} style={{ marginBottom: '20px' }}>
        <input
          type="text"
          placeholder="Nueva tarea..."
          value={newDescription}
          onChange={(e) => setNewDescription(e.target.value)}
          style={{ padding: '8px', width: '80%' }}
        />
        <button type="submit" style={{ padding: '8px 12px', marginLeft: '10px' }}>
          Añadir
        </button>
      </form>
      {tasks.length === 0 ? (
        <p>No hay tareas cargadas.</p>
      ) : (
        <ul style={{ listStyle: 'none', padding: 0 }}>
          {tasks.map((task) => (
            <li key={task.id} style={{ marginBottom: '12px', display: 'flex', alignItems: 'center' }}>
              <input
                type="checkbox"
                checked={task.completed}
                onChange={() => toggleComplete(task)}
                style={{ marginRight: '10px' }}
              />
              <span style={{ textDecoration: task.completed ? 'line-through' : 'none', flexGrow: 1 }}>
                {task.description}
              </span>
              <button onClick={() => handleDelete(task.id)} style={{ marginLeft: '10px' }}>
                Eliminar
              </button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default App;

En este tutorial has construido un gestor de tareas completo y funcional utilizando Java con Spring Boot para el backend y React para el frontend. Ahora cuentas con una base sólida para seguir aprendiendo desarrollo Full Stack, integrando nuevas características, mejorando la UI o usando bases de datos externas.

Te recomiendo explorar temas como seguridad con JWT, pruebas unitarias y despliegue en la nube para llevar tu proyecto al siguiente nivel. ¡Sigue practicando y creando tus propias aplicaciones!