14-Tutorial Paso a Paso SIMPLIFICADO - Gestor de Tareas (Sin Edición)-api

 

Tutorial Paso a Paso - Gestor de Tareas con API (Versión Súper Simplificada)

Perfecto, voy a crear una versión ultra simplificada que incluye la conexión a la API y mantiene solo el CSS Grid esencial. El alumno podrá completarlo en 1 hora.

🚀 Paso 1: Configuración Inicial (5 minutos)

bash
# 1.1 Crear proyecto React
npx create-react-app gestor-tareas-api
cd gestor-tareas-api

# 1.2 Eliminar archivos innecesarios
rm src/App.test.js src/logo.svg src/reportWebVitals.js src/setupTests.js

# 1.3 Iniciar el proyecto
npm start

📁 Paso 2: Crear Estructura de Carpetas (2 minutos)

text
src/
├── components/
│   ├── TareaList.js
│   ├── TareaForm.js
│   └── TareaItem.js
├── services/
│   └── tareaService.js
├── App.js
└── index.js

📄 Paso 3: index.js (1 minuto)

jsx
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

📦 Paso 4: App.js inicial (2 minutos)

jsx
// src/App.js
import React from 'react';
import TareaList from './components/TareaList';

function App() {
  return (
    <div>
      <h1>📝 Gestor de Tareas con API</h1>
      <TareaList />
    </div>
  );
}

export default App;

✅ Verificación: Verás el título en el navegador

🔌 Paso 5: Crear el Servicio de API (10 minutos)

jsx
// src/services/tareaService.js

// URL de la API Laravel (PUERTO 8000)
const API_URL = 'http://localhost:8000/api/tareas';

// Servicio para manejar todas las operaciones con la API
const tareaService = {
  // 1. Obtener todas las tareas
  async obtenerTodas() {
    try {
      console.log('Obteniendo tareas desde:', API_URL);
      const respuesta = await fetch(API_URL);
      const datos = await respuesta.json();
      return datos.data || datos;
    } catch (error) {
      console.error('Error al obtener tareas:', error);
      throw error;
    }
  },

  // 2. Crear nueva tarea
  async crearTarea(tarea) {
    try {
      const respuesta = await fetch(API_URL, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(tarea),
      });
      return await respuesta.json();
    } catch (error) {
      console.error('Error al crear tarea:', error);
      throw error;
    }
  },

  // 3. Actualizar tarea (para marcar como completada)
  async actualizarTarea(id, datos) {
    try {
      const respuesta = await fetch(`${API_URL}/${id}`, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(datos),
      });
      return await respuesta.json();
    } catch (error) {
      console.error('Error al actualizar tarea:', error);
      throw error;
    }
  },

  // 4. Eliminar tarea
  async eliminarTarea(id) {
    try {
      const respuesta = await fetch(`${API_URL}/${id}`, {
        method: 'DELETE',
      });
      return await respuesta.json();
    } catch (error) {
      console.error('Error al eliminar tarea:', error);
      throw error;
    }
  },
};

export default tareaService;

📋 Paso 6: TareaList.js con CSS Grid (15 minutos)

jsx
// src/components/TareaList.js
import React, { useState, useEffect } from 'react';
import TareaForm from './TareaForm';
import TareaItem from './TareaItem';
import tareaService from '../services/tareaService';

function TareaList() {
  const [tareas, setTareas] = useState([]);
  const [cargando, setCargando] = useState(true);
  const [error, setError] = useState('');

  // CSS Grid para estructura básica
  const estiloContenedor = {
    display: 'grid',
    gridTemplateColumns: '300px 1fr', // 2 columnas
    gap: '20px',
    padding: '20px'
  };

  const estiloColumna = {
    border: '2px solid #333',
    padding: '15px'
  };

  // Cargar tareas al iniciar
  useEffect(() => {
    cargarTareas();
  }, []);

  // Función para cargar tareas desde la API
  const cargarTareas = async () => {
    try {
      setCargando(true);
      setError('');
      const datos = await tareaService.obtenerTodas();
      setTareas(datos);
    } catch (error) {
      setError('❌ Error: No se pudo conectar a la API. ¿Está corriendo Laravel en puerto 8000?');
    } finally {
      setCargando(false);
    }
  };

  // Función para crear tarea
  const manejarCrearTarea = async (nuevaTarea) => {
    try {
      await tareaService.crearTarea(nuevaTarea);
      cargarTareas(); // Recargar la lista
    } catch (error) {
      alert('Error al crear la tarea');
    }
  };

  // Función para eliminar tarea
  const manejarEliminarTarea = async (id) => {
    if (window.confirm('¿Eliminar esta tarea?')) {
      try {
        await tareaService.eliminarTarea(id);
        cargarTareas();
      } catch (error) {
        alert('Error al eliminar la tarea');
      }
    }
  };

  // Función para marcar como completada
  const manejarCompletarTarea = async (id, completada) => {
    try {
      await tareaService.actualizarTarea(id, { completada });
      cargarTareas();
    } catch (error) {
      alert('Error al actualizar la tarea');
    }
  };

  return (
    <div style={estiloContenedor}>
      {/* COLUMNA IZQUIERDA: Formulario */}
      <div style={estiloColumna}>
        <h2>➕ Nueva Tarea</h2>
        <TareaForm onSubmit={manejarCrearTarea} />
        
        <div style={{ marginTop: '20px' }}>
          <button onClick={cargarTareas} style={{ width: '100%', padding: '10px' }}>
            🔄 Recargar Tareas
          </button>
        </div>
      </div>

      {/* COLUMNA DERECHA: Lista de tareas */}
      <div style={estiloColumna}>
        <h2>📋 Lista de Tareas</h2>
        
        {error && (
          <div style={{ background: '#ffebee', color: '#c62828', padding: '10px', marginBottom: '15px' }}>
            {error}
          </div>
        )}
        
        {cargando ? (
          <p>Cargando tareas desde API...</p>
        ) : tareas.length === 0 ? (
          <p>No hay tareas. ¡Crea tu primera tarea!</p>
        ) : (
          <>
            <p>Total: {tareas.length} tareas</p>
            {tareas.map((tarea) => (
              <TareaItem
                key={tarea.id}
                tarea={tarea}
                onEliminar={manejarEliminarTarea}
                onCompletar={manejarCompletarTarea}
              />
            ))}
          </>
        )}
      </div>
    </div>
  );
}

export default TareaList;

✏️ Paso 7: TareaForm.js (10 minutos)

jsx
// src/components/TareaForm.js
import React, { useState } from 'react';

function TareaForm({ onSubmit }) {
  const [formData, setFormData] = useState({
    titulo: '',
    descripcion: '',
    completada: false
  });

  const [error, setError] = useState('');

  const manejarCambio = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData({
      ...formData,
      [name]: type === 'checkbox' ? checked : value
    });
  };

  const manejarSubmit = (e) => {
    e.preventDefault();
    
    if (!formData.titulo.trim()) {
      setError('El título es obligatorio');
      return;
    }
    
    onSubmit(formData);
    setFormData({ titulo: '', descripcion: '', completada: false });
    setError('');
  };

  return (
    <form onSubmit={manejarSubmit}>
      {error && <p style={{ color: 'red' }}>{error}</p>}
      
      <div style={{ marginBottom: '15px' }}>
        <label style={{ display: 'block', marginBottom: '5px' }}>
          Título:
        </label>
        <input
          type="text"
          name="titulo"
          value={formData.titulo}
          onChange={manejarCambio}
          placeholder="Ej: Comprar leche"
          style={{ width: '100%', padding: '8px' }}
        />
      </div>
      
      <div style={{ marginBottom: '15px' }}>
        <label style={{ display: 'block', marginBottom: '5px' }}>
          Descripción:
        </label>
        <textarea
          name="descripcion"
          value={formData.descripcion}
          onChange={manejarCambio}
          placeholder="Ej: Ir al supermercado"
          rows="3"
          style={{ width: '100%', padding: '8px' }}
        />
      </div>
      
      <div style={{ marginBottom: '15px' }}>
        <label>
          <input
            type="checkbox"
            name="completada"
            checked={formData.completada}
            onChange={manejarCambio}
          />
          <span style={{ marginLeft: '5px' }}>Marcar como completada</span>
        </label>
      </div>
      
      <button 
        type="submit" 
        style={{ 
          width: '100%', 
          padding: '10px',
          backgroundColor: '#4CAF50',
          color: 'white',
          border: 'none'
        }}
      >
        ➕ Crear Tarea
      </button>
    </form>
  );
}

export default TareaForm;

📦 Paso 8: TareaItem.js (10 minutos)

jsx
// src/components/TareaItem.js
import React from 'react';

function TareaItem({ tarea, onEliminar, onCompletar }) {
  const estiloTarea = {
    border: '1px solid #ccc',
    padding: '15px',
    marginBottom: '10px',
    backgroundColor: tarea.completada ? '#e8f5e9' : '#fff'
  };

  const estiloBotones = {
    display: 'grid',
    gridTemplateColumns: '1fr 1fr', // CSS Grid para 2 botones
    gap: '10px',
    marginTop: '10px'
  };

  return (
    <div style={estiloTarea}>
      <h3 style={{ marginTop: 0 }}>
        {tarea.completada ? '✅ ' : '⏳ '}
        {tarea.titulo}
      </h3>
      
      <p>{tarea.descripcion}</p>
      
      <div style={{ color: '#666', fontSize: '14px', marginBottom: '10px' }}>
        ID: {tarea.id} | 
        Estado: {tarea.completada ? 'Completada' : 'Pendiente'}
      </div>
      
      <div style={estiloBotones}>
        <button 
          onClick={() => onCompletar(tarea.id, !tarea.completada)}
          style={{
            padding: '8px',
            backgroundColor: tarea.completada ? '#ff9800' : '#4CAF50',
            color: 'white',
            border: 'none'
          }}
        >
          {tarea.completada ? '↩️ Desmarcar' : '✅ Completar'}
        </button>
        
        <button 
          onClick={() => onEliminar(tarea.id)}
          style={{
            padding: '8px',
            backgroundColor: '#f44336',
            color: 'white',
            border: 'none'
          }}
        >
          🗑️ Eliminar
        </button>
      </div>
    </div>
  );
}

export default TareaItem;

🎯 Paso 9: Verificación Final (5 minutos)

Para probar la aplicación:

  1. Asegúrate que la API Laravel esté corriendo:

bash
# En la terminal de Laravel:
php artisan serve --port=8000
  1. Ejecuta la app React:

bash
# En la terminal de React:
npm start
  1. Verifica en el navegador (http://localhost:3000):

text
📝 Gestor de Tareas con API

┌──────────────────────────────────────┬──────────────────────────────────────┐
│                                      │                                      │
│  ➕ NUEVA TAREA                       │  📋 LISTA DE TAREAS                  │
│                                      │                                      │
│  [Título] _______________            │  Total: X tareas                     │
│                                      │                                      │
│  [Descripción] _________             │  • ✅ Comprar leche                  │
│                     ______           │     Ir al supermercado               │
│                                      │     ID: 1 | Estado: Pendiente        │
│  [ ] Marcar como completada          │     [✅ Completar] [🗑️ Eliminar]    │
│                                      │                                      │
│  [➕ Crear Tarea]                     │  • ✅ Estudiar React                 │
│                                      │     Hacer ejercicios                 │
│  [🔄 Recargar Tareas]                │     ID: 2 | Estado: Completada       │
│                                      │     [↩️ Desmarcar] [🗑️ Eliminar]   │
└──────────────────────────────────────┴──────────────────────────────────────┘

📊 Resumen de Tiempo (60 minutos total)

PasoTiempoDescripción
15 minConfiguración inicial del proyecto
22 minCrear estructura de carpetas
31 minArchivo index.js
42 minApp.js básico
510 minServicio de API (tareaService.js)
615 minComponente principal con CSS Grid
710 minFormulario de creación
810 minComponente de tarea individual
95 minVerificación y pruebas

✅ Lo que el alumno logró en 1 hora:

  1. Proyecto React funcional con estructura organizada

  2. Conexión a API Laravel usando Fetch API nativo

  3. Layout con CSS Grid de 2 columnas

  4. Operaciones CRUD completas:

    • ✅ Listar tareas desde API

    • ✅ Crear nuevas tareas

    • ✅ Marcar como completadas

    • ✅ Eliminar tareas

  5. Manejo de estados (loading, error, data)

  6. Componentes reutilizables

🎯 Características clave de esta versión:

Simplificado:

  • Solo CSS Grid para estructura (sin estilos complejos)

  • Sin funcionalidad de edición (solo crear/eliminar/marcar)

  • Fetch API nativo (sin axios u otras dependencias)

Funcional:

  • Conexión real a API Laravel

  • Manejo de errores básico

  • Feedback visual para el usuario

Educativo:

  • Cada componente tiene una responsabilidad clara

  • Código comentado y fácil de seguir

  • Aprende Fetch API paso a paso

🔧 Si hay problemas de CORS:

Agrega esto al package.json:

json
{
  "name": "gestor-tareas-api",
  "version": "0.1.0",
  "private": true,
  "proxy": "http://localhost:8000",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }
}

Y actualiza el servicio:

javascript
// En tareaService.js
const API_URL = '/api/tareas'; // Usa ruta relativa con proxy

¡Perfecto! El alumno ahora tiene un gestor de tareas completo que se conecta a una API real, usando solo tecnologías nativas (React, Fetch API, CSS Grid), y puede explicar cada parte del código en menos de 1 hora.

Comentarios

Entradas más populares de este blog

0-Sistema de Tareas

13-CRUD en Laravel para Web y API con Rutas Normales (No Resource)

10-Introducción a Blade en Laravel con un Ejemplo Práctico