Aplicaci贸n de Tareas con Laravel, Inertia y Jetstream

Aplicaci贸n de Tareas con Laravel, Inertia y Jetstream

馃搵 Descripci贸n del Proyecto

Crearemos una aplicaci贸n de tareas sencilla que permita:

  • Agregar m煤ltiples tareas

  • Ver lista de tareas

  • Almacenar en un arreglo en la sesi贸n

  • Sin base de datos, sin tablas, sin modelos


馃殌 1. INSTALACI脫N COMPLETA DESDE CERO

1.1 Requisitos Previos

Antes de comenzar, aseg煤rate de tener instalado:

bash
# Verificar PHP (necesitas PHP 8.0 o superior)
php -v

# Verificar Composer
composer --version

# Verificar Node.js y NPM
node -v
npm -v

1.2 Crear el Proyecto Laravel

bash
# 1. Navegar al directorio de proyectos
cd C:\xampp\htdocs   # En Windows con XAMPP
# o
cd /var/www/html     # En Linux
# o
cd ~/Projects        # En Mac

# 2. Crear proyecto Laravel (versi贸n 10 para compatibilidad)
composer create-project laravel/laravel:^10.0 tareas-app

composer create-project laravel/laravel tareas-app

# 3. Entrar al directorio del proyecto cd tareas-app # 4. Verificar que todo est茅 bien php artisan --version # Deber铆as ver: Laravel Framework 10.x.x

1.3 Configurar Variables de Entorno

bash
# 1. Copiar archivo de entorno
copy .env.example .env    # En Windows
# o
cp .env.example .env      # En Linux/Mac

# 2. Generar clave de aplicaci贸n
php artisan key:generate

1.4 Configurar Base de Datos (M铆nimo necesario)

NO necesitamos base de datos para las tareas, pero Jetstream necesita una para los usuarios:

bash
# 1. Crear archivo SQLite
# En Windows:
type nul > database/database.sqlite
# En Linux/Mac:
touch database/database.sqlite

Editar .env:

env
DB_CONNECTION=sqlite
DB_DATABASE=C:\xampp\htdocs\tareas-app\database\database.sqlite
# O en Linux/Mac:
# DB_DATABASE=/ruta/absoluta/a/tareas-app/database/database.sqlite

1.5 Instalar Jetstream

bash
# 1. Instalar Jetstream
composer require laravel/jetstream

# 2. Instalar Jetstream con Inertia (SIN TEAMS - m谩s sencillo)
php artisan jetstream:install inertia

# 3. Migrar las tablas de usuarios
php artisan migrate

1.6 Instalar Dependencias Frontend

bash
# 1. Instalar paquetes NPM
npm install

# 2. Compilar assets para desarrollo
npm run dev

# 3. En otra terminal, mantener assets compil谩ndose
# npm run dev  (dejar corriendo)

馃搧 2. ESTRUCTURA DE ARCHIVOS

text
馃搧 tareas-app/
├── 馃搧 app/
│   └── 馃搧 Http/
│       └── 馃搧 Controllers/
│           └── TareaController.php
├── 馃搧 routes/
│   └── web.php
├── 馃搧 resources/
│   └── 馃搧 js/
│       └── 馃搧 Pages/
│           └── 馃搧 Tareas/
│               └── Index.vue
├── 馃搧 database/
│   └── database.sqlite
├── .env
├── composer.json
└── package.json

馃幆 3. CONFIGURAR RUTAS

routes/web.php

php
<?php

use App\Http\Controllers\TareaController;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;

// Ruta principal
Route::get('/', function () {
    return Inertia::render('Welcome');
});

// Ruta dashboard (ya viene con Jetstream)
Route::get('/dashboard', function () {
    return Inertia::render('Dashboard');
})->middleware(['auth:sanctum', 'verified'])->name('dashboard');

// RUTAS DE TAREAS (protegidas con autenticaci贸n)
Route::middleware(['auth:sanctum', 'verified'])->group(function () {
    // Ver todas las tareas
    Route::get('/tareas', [TareaController::class, 'index'])->name('tareas.index');
    
    // Agregar nueva tarea
    Route::post('/tareas', [TareaController::class, 'store'])->name('tareas.store');
    
    // Eliminar tarea
    Route::delete('/tareas/{id}', [TareaController::class, 'destroy'])->name('tareas.destroy');
    
    // Marcar/Desmarcar tarea completada
    Route::put('/tareas/{id}/toggle', [TareaController::class, 'toggle'])->name('tareas.toggle');
    
    // Eliminar todas las tareas
    Route::delete('/tareas/clear', [TareaController::class, 'clear'])->name('tareas.clear');
});

馃幃 4. CREAR CONTROLADOR

app/Http/Controllers/TareaController.php

php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;

class TareaController extends Controller
{
    // Obtener tareas del arreglo en sesi贸n
    private function getTareas()
    {
        if (!session()->has('tareas')) {
            session()->put('tareas', []);
        }
        return session()->get('tareas');
    }

    // Guardar tareas en sesi贸n
    private function saveTareas($tareas)
    {
        session()->put('tareas', $tareas);
    }

    // Mostrar todas las tareas
    public function index()
    {
        return Inertia::render('Tareas/Index', [
            'tareas' => $this->getTareas()
        ]);
    }

    // Guardar nueva tarea
    public function store(Request $request)
    {
        // Validar
        $request->validate([
            'tarea' => 'required|string|max:255'
        ]);

        // Obtener tareas actuales
        $tareas = $this->getTareas();
        
        // Agregar nueva tarea al arreglo
        $tareas[] = [
            'id' => uniqid(),                    // ID 煤nico
            'texto' => $request->tarea,           // Texto de la tarea
            'completada' => false,                // Estado
            'fecha' => date('d/m/Y H:i')          // Fecha de creaci贸n
        ];
        
        // Guardar el arreglo actualizado
        $this->saveTareas($tareas);
        
        // Redirigir (Inertia har谩 el resto)
        return redirect()->route('tareas.index');
    }

    // Eliminar una tarea espec铆fica
    public function destroy($id)
    {
        $tareas = $this->getTareas();
        
        // Filtrar para eliminar la tarea con el ID dado
        $tareas = array_values(array_filter($tareas, function($t) use ($id) {
            return $t['id'] != $id;
        }));
        
        $this->saveTareas($tareas);
        
        return redirect()->route('tareas.index');
    }

    // Marcar/Desmarcar tarea como completada
    public function toggle($id)
    {
        $tareas = $this->getTareas();
        
        // Buscar y cambiar estado
        foreach ($tareas as &$tarea) {
            if ($tarea['id'] == $id) {
                $tarea['completada'] = !$tarea['completada'];
                break;
            }
        }
        
        $this->saveTareas($tareas);
        
        return redirect()->route('tareas.index');
    }

    // Limpiar todas las tareas
    public function clear()
    {
        session()->forget('tareas');
        return redirect()->route('tareas.index');
    }
}

馃帹 5. CREAR COMPONENTE VUE

resources/js/Pages/Tareas/Index.vue

vue
<template>
    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg p-6">
                <!-- Encabezado -->
                <div class="flex justify-between items-center mb-6">
                    <h1 class="text-2xl font-bold">馃搵 Mis Tareas</h1>
                    <div class="flex gap-2">
                        <!-- Bot贸n limpiar todo -->
                        <button
                            v-if="tareas.length > 0"
                            @click="clearTareas"
                            class="px-3 py-1 text-sm text-red-500 hover:text-red-700 hover:bg-red-50 rounded transition"
                        >
                            馃棏️ Limpiar todo
                        </button>
                        <!-- Contador de tareas -->
                        <span class="px-3 py-1 text-sm bg-gray-100 rounded">
                            {{ tareas.length }} tareas
                        </span>
                    </div>
                </div>

                <!-- Formulario para agregar tarea -->
                <form @submit.prevent="guardarTarea" class="mb-6">
                    <div class="flex gap-2">
                        <input
                            type="text"
                            v-model="form.tarea"
                            placeholder="Escribe una nueva tarea..."
                            class="flex-1 px-4 py-2 rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
                            :disabled="form.processing"
                        />
                        <button
                            type="submit"
                            class="px-6 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition disabled:opacity-50 disabled:cursor-not-allowed"
                            :disabled="form.processing || !form.tarea.trim()"
                        >
                            <span v-if="form.processing">⏳ Guardando...</span>
                            <span v-else>➕ Agregar</span>
                        </button>
                    </div>
                    
                    <!-- Errores de validaci贸n -->
                    <div v-if="form.errors.tarea" class="text-red-500 text-sm mt-1">
                        {{ form.errors.tarea }}
                    </div>
                </form>

                <!-- Lista de tareas -->
                <div v-if="tareas.length > 0">
                    <!-- Estad铆sticas -->
                    <div class="grid grid-cols-3 gap-2 mb-4 text-sm">
                        <div class="bg-blue-50 p-2 rounded text-center">
                            <span class="text-blue-600 font-bold">{{ tareas.length }}</span>
                            <span class="text-gray-600 ml-1">Total</span>
                        </div>
                        <div class="bg-yellow-50 p-2 rounded text-center">
                            <span class="text-yellow-600 font-bold">{{ tareas.filter(t => !t.completada).length }}</span>
                            <span class="text-gray-600 ml-1">Pendientes</span>
                        </div>
                        <div class="bg-green-50 p-2 rounded text-center">
                            <span class="text-green-600 font-bold">{{ tareas.filter(t => t.completada).length }}</span>
                            <span class="text-gray-600 ml-1">Completadas</span>
                        </div>
                    </div>

                    <!-- Lista -->
                    <div class="space-y-3">
                        <div
                            v-for="tarea in tareas"
                            :key="tarea.id"
                            class="flex items-center justify-between p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition group"
                        >
                            <div class="flex items-center gap-3 flex-1 min-w-0">
                                <!-- Checkbox -->
                                <input
                                    type="checkbox"
                                    :checked="tarea.completada"
                                    @change="toggleTarea(tarea.id)"
                                    class="w-4 h-4 rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
                                    :disabled="form.processing"
                                />
                                
                                <!-- Texto de la tarea -->
                                <span
                                    class="flex-1 truncate"
                                    :class="{
                                        'line-through text-gray-500': tarea.completada,
                                        'text-gray-800': !tarea.completada
                                    }"
                                >
                                    {{ tarea.texto }}
                                </span>
                                
                                <!-- Fecha -->
                                <span class="text-xs text-gray-400 flex-shrink-0">
                                    馃晲 {{ tarea.fecha }}
                                </span>
                            </div>
                            
                            <!-- Bot贸n eliminar -->
                            <button
                                @click="eliminarTarea(tarea.id)"
                                class="ml-2 text-sm text-red-400 hover:text-red-700 opacity-0 group-hover:opacity-100 transition"
                                :disabled="form.processing"
                            ></button>
                        </div>
                    </div>
                </div>
                
                <!-- Mensaje vac铆o -->
                <div v-else class="text-center py-12">
                    <div class="text-6xl mb-4">馃幆</div>
                    <h3 class="text-lg font-medium text-gray-600 mb-2">¡No hay tareas a煤n!</h3>
                    <p class="text-gray-400">Agrega tu primera tarea usando el formulario de arriba.</p>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup>
import { useForm } from '@inertiajs/vue3';

// Props recibidas del controlador
const props = defineProps({
    tareas: {
        type: Array,
        default: () => []
    }
});

// Formulario para nueva tarea
const form = useForm({
    tarea: ''
});

// Guardar nueva tarea
function guardarTarea() {
    form.post('/tareas', {
        preserveState: true,
        onSuccess: () => {
            form.reset();
        }
    });
}

// Eliminar tarea
function eliminarTarea(id) {
    if (confirm('¿Est谩s seguro de eliminar esta tarea?')) {
        form.delete(`/tareas/${id}`, {
            preserveState: true
        });
    }
}

// Cambiar estado completada
function toggleTarea(id) {
    form.put(`/tareas/${id}/toggle`, {
        preserveState: true
    });
}

// Limpiar todas las tareas
function clearTareas() {
    if (confirm('¿Eliminar todas las tareas?')) {
        form.delete('/tareas/clear', {
            preserveState: true
        });
    }
}
</script>

馃彔 6. AGREGAR AL MEN脷 DE NAVEGACI脫N

Editar resources/js/Layouts/AppLayout.vue

Busca la secci贸n de navegaci贸n y agrega:

vue
<!-- Dentro de la navegaci贸n de Jetstream -->
<NavLink :href="route('tareas.index')" :active="route().current('tareas.*')">
    馃搵 Tareas
</NavLink>

Ejemplo completo de la secci贸n de navegaci贸n:

vue
<div class="hidden sm:flex sm:space-x-8 sm:ml-10">
    <NavLink :href="route('dashboard')" :active="route().current('dashboard')">
        Dashboard
    </NavLink>
    <NavLink :href="route('tareas.index')" :active="route().current('tareas.*')">
        馃搵 Tareas
    </NavLink>
</div>

馃殌 7. EJECUTAR LA APLICACI脫N

Terminal 1 - Servidor Laravel

bash
# Iniciar servidor de desarrollo
php artisan serve

# Ver谩s: Server running on http://localhost:8000

Terminal 2 - Compilar Assets

bash
# Compilar assets en modo desarrollo
npm run dev

# Dejar corriendo para detectar cambios

Terminal 3 - (Opcional) Base de datos

bash
# Si usas SQLite, no necesitas hacer nada m谩s

馃搳 8. VERIFICAR FUNCIONAMIENTO

Paso 1: Abrir el navegador

text
http://localhost:8000

Paso 2: Registrarse

  • Click en "Register"

  • Completar datos

  • Click en "Register"

Paso 3: Ir a Tareas

  • Click en "馃搵 Tareas" en el men煤

Paso 4: Probar funcionalidades

  1. Agregar tarea: Escribir y click en "Agregar"

  2. Ver lista: Se muestran todas las tareas

  3. Marcar completada: Click en checkbox

  4. Eliminar: Hover sobre tarea y click en ❌

  5. Limpiar todo: Click en "Limpiar todo"


馃幆 9. RESPUESTAS A POSIBLES ERRORES

Error: "Class 'Inertia\Inertia' not found"

bash
composer require inertiajs/inertia-laravel

Error: "Vite manifest not found"

bash
npm install
npm run build

Error: "Jetstream not found"

bash
composer require laravel/jetstream
php artisan jetstream:install inertia

Error: "Could not open input file: artisan"

bash
# Aseg煤rate de estar en el directorio correcto
cd tareas-app
dir artisan  # Verificar que existe

馃搵 10. COMANDOS 脷TILES

bash
# Verificar instalaci贸n
php artisan about

# Limpiar cache
php artisan cache:clear
php artisan config:clear
php artisan view:clear

# Recompilar assets
npm run build

# Reinstalar dependencias
composer install
npm install

# Migrar base de datos (si usas BD)
php artisan migrate

# Crear controlador
php artisan make:controller NombreController

# Crear middleware
php artisan make:middleware NombreMiddleware

✅ 11. RESUMEN DE FUNCIONALIDADES

Funci贸nM茅todo HTTPRutaAcci贸n
Ver tareasGET/tareasMuestra todas las tareas
AgregarPOST/tareasA帽ade nueva tarea
EliminarDELETE/tareas/{id}Elimina una tarea
CompletarPUT/tareas/{id}/toggleCambia estado completada
LimpiarDELETE/tareas/clearElimina todas

馃挕 12. VENTAJAS DE ESTE ENFOQUE

  • Sin base de datos - no necesitas configurar MySQL/PostgreSQL

  • Sin modelos - solo arreglos simples

  • R谩pido - sesi贸n en memoria

  • Portable - funciona en cualquier entorno

  • F谩cil - ideal para prototipos y pruebas

  • Autenticaci贸n - Jetstream maneja usuarios


馃帗 13. POSIBLES MEJORAS FUTURAS

  1. Persistencia - Migrar a base de datos

  2. Editar tareas - Agregar endpoint PUT

  3. Prioridades - Campo de prioridad

  4. Categor铆as - Etiquetas para organizar

  5. Fechas l铆mite - Recordatorios

  6. Compartir - Tareas con otros usuarios


馃帀 ¡FELICITACIONES!

Has creado una aplicaci贸n completa de tareas usando:

  • ✅ Laravel 10

  • ✅ Jetstream

  • ✅ Inertia.js

  • ✅ Vue 3

  • ✅ Solo arreglos (sin base de datos)

  • ✅ Autenticaci贸n de usuarios

  • ✅ Dise帽o moderno con Tailwind CSS

¡Todo desde cero!

Comentarios

Entradas m谩s populares de este blog

Tareas con Laravel + Inertia + Jetstream

HACER CAMBIOS-AL EJEMPLO POR DEFECTO

Laravel + Inertia - ejemplo