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

 

Tutorial Actualizado: CRUD en Laravel para Web y API con Rutas Normales (No Resource)

Te mostraré la implementación completa y detallada con rutas normales (no resource), perfecto para principiantes.

Estructura del Proyecto

text
gestor-tareas/
├── app/
│   ├── Http/
│   │   ├── Controllers/
│   │   │   ├── Api/
│   │   │   │   └── TareaController.php
│   │   │   └── Web/
│   │   │       └── TareaController.php
├── routes/
│   ├── api.php
│   └── web.php
└── resources/views/tareas/
    ├── index.blade.php
    ├── create.blade.php
    ├── show.blade.php
    └── edit.blade.php

Paso a Paso Detallado

1. Crear el Proyecto

bash
composer create-project laravel/laravel gestor-tareas
cd gestor-tareas

2. Configurar Base de Datos

Editar el archivo .env:

env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=gestor_tareas
DB_USERNAME=root
DB_PASSWORD=

3. Crear Modelo y Migración

bash
php artisan make:model Tarea -m

4. Configurar la Migración

Editar database/migrations/xxxx_create_tareas_table.php:

php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('tareas', function (Blueprint $table) {
            $table->id(); // ID automático
            $table->string('titulo'); // Título de la tarea
            $table->text('descripcion')->nullable(); // Descripción opcional
            $table->boolean('completada')->default(false); // Estado
            $table->timestamps(); // created_at y updated_at
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('tareas');
    }
};

5. Ejecutar la Migración

bash
php artisan migrate

6. Crear Controladores

Controlador Web:

bash
php artisan make:controller Web/TareaController

Controlador API:

bash
php artisan make:controller Api/TareaController

7. Configurar Modelo Tarea

Editar app/Models/Tarea.php:

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Tarea extends Model
{
    use HasFactory;

    // Campos que se pueden llenar masivamente
    protected $fillable = [
        'titulo',
        'descripcion',
        'completada'
    ];

    // Campos que deben ser convertidos a tipos específicos
    protected $casts = [
        'completada' => 'boolean'
    ];
}

8. Configurar Rutas Web (Normales)

Editar routes/web.php:

php
<?php

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

// Ruta para la página principal (lista de tareas)
Route::get('/tareas', [TareaController::class, 'index'])->name('tareas.index');

// Ruta para mostrar el formulario de creación
Route::get('/tareas/crear', [TareaController::class, 'create'])->name('tareas.create');

// Ruta para guardar una nueva tarea
Route::post('/tareas', [TareaController::class, 'store'])->name('tareas.store');

// Ruta para mostrar una tarea específica
Route::get('/tareas/{id}', [TareaController::class, 'show'])->name('tareas.show');

// Ruta para mostrar el formulario de edición
Route::get('/tareas/{id}/editar', [TareaController::class, 'edit'])->name('tareas.edit');

// Ruta para actualizar una tarea
Route::put('/tareas/{id}', [TareaController::class, 'update'])->name('tareas.update');

// Ruta para eliminar una tarea
Route::delete('/tareas/{id}', [TareaController::class, 'destroy'])->name('tareas.destroy');

9. Configurar Rutas API (Normales)

Editar routes/api.php:

php
<?php

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

// Prefijo automático: /api

// Listar todas las tareas
Route::get('/tareas', [TareaController::class, 'index']);

// Crear una nueva tarea
Route::post('/tareas', [TareaController::class, 'store']);

// Mostrar una tarea específica
Route::get('/tareas/{id}', [TareaController::class, 'show']);

// Actualizar una tarea
Route::put('/tareas/{id}', [TareaController::class, 'update']);

// Eliminar una tarea
Route::delete('/tareas/{id}', [TareaController::class, 'destroy']);

10. Implementar Controlador Web

Editar app/Http/Controllers/Web/TareaController.php:

php
<?php

namespace App\Http\Controllers\Web;

use App\Http\Controllers\Controller;
use App\Models\Tarea;
use Illuminate\Http\Request;

class TareaController extends Controller
{
    /**
     * Mostrar lista de todas las tareas
     */
    public function index()
    {
        // Obtener todas las tareas ordenadas por fecha de creación
        $tareas = Tarea::orderBy('created_at', 'desc')->get();
        
        // Pasar las tareas a la vista
        return view('tareas.index', [
            'tareas' => $tareas
        ]);
    }

    /**
     * Mostrar formulario para crear nueva tarea
     */
    public function create()
    {
        return view('tareas.create');
    }

    /**
     * Guardar nueva tarea en la base de datos
     */
    public function store(Request $request)
    {
        // Validar los datos del formulario
        $request->validate([
            'titulo' => 'required|min:3|max:255',
            'descripcion' => 'nullable|string',
        ]);

        // Crear la tarea
        Tarea::create([
            'titulo' => $request->titulo,
            'descripcion' => $request->descripcion,
            'completada' => $request->has('completada') ? true : false
        ]);

        // Redirigir a la lista con mensaje de éxito
        return redirect()->route('tareas.index')
            ->with('success', '¡Tarea creada correctamente!');
    }

    /**
     * Mostrar una tarea específica
     */
    public function show($id)
    {
        // Buscar la tarea por ID
        $tarea = Tarea::findOrFail($id);
        
        return view('tareas.show', [
            'tarea' => $tarea
        ]);
    }

    /**
     * Mostrar formulario para editar tarea
     */
    public function edit($id)
    {
        // Buscar la tarea por ID
        $tarea = Tarea::findOrFail($id);
        
        return view('tareas.edit', [
            'tarea' => $tarea
        ]);
    }

    /**
     * Actualizar una tarea existente
     */
    public function update(Request $request, $id)
    {
        // Validar los datos
        $request->validate([
            'titulo' => 'required|min:3|max:255',
            'descripcion' => 'nullable|string',
        ]);

        // Buscar la tarea por ID
        $tarea = Tarea::findOrFail($id);
        
        // Actualizar los datos
        $tarea->titulo = $request->titulo;
        $tarea->descripcion = $request->descripcion;
        $tarea->completada = $request->has('completada') ? true : false;
        $tarea->save();

        // Redirigir a la lista con mensaje de éxito
        return redirect()->route('tareas.index')
            ->with('success', '¡Tarea actualizada correctamente!');
    }

    /**
     * Eliminar una tarea
     */
    public function destroy($id)
    {
        // Buscar la tarea por ID
        $tarea = Tarea::findOrFail($id);
        
        // Eliminar la tarea
        $tarea->delete();

        // Redirigir a la lista con mensaje de éxito
        return redirect()->route('tareas.index')
            ->with('success', '¡Tarea eliminada correctamente!');
    }
}

11. Implementar Controlador API

Editar app/Http\Controllers\Api\TareaController.php:

php
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Tarea;
use Illuminate\Http\Request;

class TareaController extends Controller
{
    /**
     * Obtener todas las tareas (JSON)
     */
    public function index()
    {
        $tareas = Tarea::orderBy('created_at', 'desc')->get();
        
        return response()->json([
            'success' => true,
            'message' => 'Lista de tareas obtenida correctamente',
            'data' => $tareas,
            'count' => $tareas->count()
        ]);
    }

    /**
     * Crear una nueva tarea (JSON)
     */
    public function store(Request $request)
    {
        // Validar los datos
        $request->validate([
            'titulo' => 'required|min:3|max:255',
            'descripcion' => 'nullable|string',
            'completada' => 'boolean'
        ]);

        // Crear la tarea
        $tarea = Tarea::create([
            'titulo' => $request->titulo,
            'descripcion' => $request->descripcion,
            'completada' => $request->completada ?? false
        ]);

        return response()->json([
            'success' => true,
            'message' => 'Tarea creada correctamente',
            'data' => $tarea
        ], 201); // Código 201: Created
    }

    /**
     * Obtener una tarea específica (JSON)
     */
    public function show($id)
    {
        // Buscar la tarea
        $tarea = Tarea::find($id);
        
        // Si no existe, devolver error
        if (!$tarea) {
            return response()->json([
                'success' => false,
                'message' => 'Tarea no encontrada'
            ], 404); // Código 404: Not Found
        }

        return response()->json([
            'success' => true,
            'message' => 'Tarea obtenida correctamente',
            'data' => $tarea
        ]);
    }

    /**
     * Actualizar una tarea existente (JSON)
     */
    public function update(Request $request, $id)
    {
        // Buscar la tarea
        $tarea = Tarea::find($id);
        
        // Si no existe, devolver error
        if (!$tarea) {
            return response()->json([
                'success' => false,
                'message' => 'Tarea no encontrada'
            ], 404);
        }

        // Validar los datos
        $request->validate([
            'titulo' => 'sometimes|min:3|max:255',
            'descripcion' => 'nullable|string',
            'completada' => 'boolean'
        ]);

        // Actualizar solo los campos enviados
        if ($request->has('titulo')) {
            $tarea->titulo = $request->titulo;
        }
        if ($request->has('descripcion')) {
            $tarea->descripcion = $request->descripcion;
        }
        if ($request->has('completada')) {
            $tarea->completada = $request->completada;
        }
        
        $tarea->save();

        return response()->json([
            'success' => true,
            'message' => 'Tarea actualizada correctamente',
            'data' => $tarea
        ]);
    }

    /**
     * Eliminar una tarea (JSON)
     */
    public function destroy($id)
    {
        // Buscar la tarea
        $tarea = Tarea::find($id);
        
        // Si no existe, devolver error
        if (!$tarea) {
            return response()->json([
                'success' => false,
                'message' => 'Tarea no encontrada'
            ], 404);
        }

        // Eliminar la tarea
        $tarea->delete();

        return response()->json([
            'success' => true,
            'message' => 'Tarea eliminada correctamente'
        ]);
    }
}

12. Crear Layout Base

Crear resources/views/layouts/app.blade.php:

html
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title', 'Gestor de Tareas')</title>
    <!-- Bootstrap 5 CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- Bootstrap Icons -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
    <style>
        body {
            padding-top: 20px;
            background-color: #f8f9fa;
        }
        .navbar {
            margin-bottom: 20px;
        }
        .card {
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
    </style>
</head>
<body>
    <div class="container">
        <!-- Barra de navegación -->
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary rounded">
            <div class="container-fluid">
                <a class="navbar-brand" href="{{ route('tareas.index') }}">
                    <i class="bi bi-check2-circle"></i> Gestor de Tareas
                </a>
                <div class="navbar-nav">
                    <a class="nav-link text-white" href="{{ route('tareas.index') }}">
                        <i class="bi bi-list"></i> Lista
                    </a>
                    <a class="nav-link text-white" href="{{ route('tareas.create') }}">
                        <i class="bi bi-plus-circle"></i> Nueva
                    </a>
                </div>
            </div>
        </nav>

        <!-- Mensajes de sesión -->
        @if(session('success'))
        <div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
            <i class="bi bi-check-circle"></i> {{ session('success') }}
            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
        </div>
        @endif

        @if(session('error'))
        <div class="alert alert-danger alert-dismissible fade show mt-3" role="alert">
            <i class="bi bi-exclamation-triangle"></i> {{ session('error') }}
            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
        </div>
        @endif

        <!-- Contenido principal -->
        <div class="mt-4">
            @yield('content')
        </div>

        <!-- Información API -->
        <div class="card mt-5">
            <div class="card-header bg-info text-white">
                <i class="bi bi-code-slash"></i> Información de la API
            </div>
            <div class="card-body">
                <p class="mb-1"><strong>URL Base API:</strong> <code>http://localhost:8000/api/tareas</code></p>
                <p class="mb-1"><strong>Métodos disponibles:</strong></p>
                <ul class="mb-0">
                    <li><code>GET /api/tareas</code> - Listar tareas</li>
                    <li><code>POST /api/tareas</code> - Crear tarea</li>
                    <li><code>GET /api/tareas/{id}</code> - Ver tarea</li>
                    <li><code>PUT /api/tareas/{id}</code> - Actualizar tarea</li>
                    <li><code>DELETE /api/tareas/{id}</code> - Eliminar tarea</li>
                </ul>
            </div>
        </div>
    </div>

    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <!-- Scripts adicionales -->
    @stack('scripts')
</body>
</html>

13. Crear Vistas

Vista Index (resources/views/tareas/index.blade.php):

html
@extends('layouts.app')

@section('title', 'Lista de Tareas')

@section('content')
<div class="card">
    <div class="card-header bg-dark text-white">
        <h4 class="mb-0">
            <i class="bi bi-list-task"></i> Lista de Tareas
        </h4>
    </div>
    <div class="card-body">
        @if($tareas->isEmpty())
        <div class="alert alert-info">
            <i class="bi bi-info-circle"></i> No hay tareas registradas. 
            <a href="{{ route('tareas.create') }}" class="alert-link">Crea tu primera tarea</a>
        </div>
        @else
        <div class="table-responsive">
            <table class="table table-hover table-striped">
                <thead class="table-light">
                    <tr>
                        <th>ID</th>
                        <th>Título</th>
                        <th>Estado</th>
                        <th>Creado</th>
                        <th class="text-center">Acciones</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach($tareas as $tarea)
                    <tr>
                        <td>{{ $tarea->id }}</td>
                        <td>
                            <strong>{{ $tarea->titulo }}</strong>
                            @if($tarea->descripcion)
                            <br>
                            <small class="text-muted">{{ Str::limit($tarea->descripcion, 50) }}</small>
                            @endif
                        </td>
                        <td>
                            @if($tarea->completada)
                            <span class="badge bg-success">
                                <i class="bi bi-check-circle"></i> Completada
                            </span>
                            @else
                            <span class="badge bg-warning">
                                <i class="bi bi-clock"></i> Pendiente
                            </span>
                            @endif
                        </td>
                        <td>{{ $tarea->created_at->format('d/m/Y') }}</td>
                        <td>
                            <div class="btn-group" role="group">
                                <!-- Ver -->
                                <a href="{{ route('tareas.show', $tarea->id) }}" 
                                   class="btn btn-sm btn-outline-info" 
                                   title="Ver detalles">
                                    <i class="bi bi-eye"></i>
                                </a>
                                
                                <!-- Editar -->
                                <a href="{{ route('tareas.edit', $tarea->id) }}" 
                                   class="btn btn-sm btn-outline-warning" 
                                   title="Editar">
                                    <i class="bi bi-pencil"></i>
                                </a>
                                
                                <!-- Eliminar -->
                                <form action="{{ route('tareas.destroy', $tarea->id) }}" 
                                      method="POST" 
                                      class="d-inline"
                                      onsubmit="return confirm('¿Estás seguro de eliminar esta tarea?')">
                                    @csrf
                                    @method('DELETE')
                                    <button type="submit" 
                                            class="btn btn-sm btn-outline-danger" 
                                            title="Eliminar">
                                        <i class="bi bi-trash"></i>
                                    </button>
                                </form>
                            </div>
                        </td>
                    </tr>
                    @endforeach
                </tbody>
            </table>
        </div>
        @endif
        
        <!-- Botón para crear nueva tarea -->
        <div class="mt-4 text-center">
            <a href="{{ route('tareas.create') }}" class="btn btn-primary btn-lg">
                <i class="bi bi-plus-circle"></i> Crear Nueva Tarea
            </a>
        </div>
    </div>
</div>
@endsection

Vista Create (resources/views/tareas/create.blade.php):

html
@extends('layouts.app')

@section('title', 'Crear Nueva Tarea')

@section('content')
<div class="row justify-content-center">
    <div class="col-md-8">
        <div class="card">
            <div class="card-header bg-success text-white">
                <h4 class="mb-0">
                    <i class="bi bi-plus-circle"></i> Crear Nueva Tarea
                </h4>
            </div>
            <div class="card-body">
                <!-- Mostrar errores de validación -->
                @if($errors->any())
                <div class="alert alert-danger">
                    <h6>Por favor corrige los siguientes errores:</h6>
                    <ul class="mb-0">
                        @foreach($errors->all() as $error)
                        <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
                @endif

                <!-- Formulario -->
                <form action="{{ route('tareas.store') }}" method="POST">
                    @csrf
                    
                    <div class="mb-3">
                        <label for="titulo" class="form-label">
                            <strong>Título *</strong>
                        </label>
                        <input type="text" 
                               class="form-control @error('titulo') is-invalid @enderror" 
                               id="titulo" 
                               name="titulo" 
                               value="{{ old('titulo') }}" 
                               placeholder="Ej: Comprar víveres" 
                               required>
                        @error('titulo')
                        <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                        <div class="form-text">El título es obligatorio (3-255 caracteres).</div>
                    </div>
                    
                    <div class="mb-3">
                        <label for="descripcion" class="form-label">
                            <strong>Descripción</strong>
                        </label>
                        <textarea class="form-control @error('descripcion') is-invalid @enderror" 
                                  id="descripcion" 
                                  name="descripcion" 
                                  rows="4" 
                                  placeholder="Ej: Comprar leche, pan, huevos...">{{ old('descripcion') }}</textarea>
                        @error('descripcion')
                        <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                    </div>
                    
                    <div class="mb-3 form-check">
                        <input type="checkbox" 
                               class="form-check-input" 
                               id="completada" 
                               name="completada" 
                               value="1">
                        <label class="form-check-label" for="completada">
                            <strong>¿Tarea completada?</strong>
                        </label>
                        <div class="form-text">Marca esta casilla si la tarea ya está terminada.</div>
                    </div>
                    
                    <div class="d-grid gap-2 d-md-flex justify-content-md-end">
                        <a href="{{ route('tareas.index') }}" class="btn btn-secondary me-md-2">
                            <i class="bi bi-arrow-left"></i> Volver al Listado
                        </a>
                        <button type="submit" class="btn btn-success">
                            <i class="bi bi-save"></i> Guardar Tarea
                        </button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
@endsection

Vista Show (resources/views/tareas/show.blade.php):

html
@extends('layouts.app')

@section('title', 'Detalles de Tarea')

@section('content')
<div class="row justify-content-center">
    <div class="col-md-8">
        <div class="card">
            <div class="card-header bg-info text-white">
                <h4 class="mb-0">
                    <i class="bi bi-eye"></i> Detalles de Tarea #{{ $tarea->id }}
                </h4>
            </div>
            <div class="card-body">
                <!-- Información de la tarea -->
                <div class="row mb-3">
                    <div class="col-md-6">
                        <h5><strong>ID:</strong></h5>
                        <p>{{ $tarea->id }}</p>
                    </div>
                    <div class="col-md-6">
                        <h5><strong>Estado:</strong></h5>
                        @if($tarea->completada)
                        <span class="badge bg-success p-2">
                            <i class="bi bi-check-circle"></i> COMPLETADA
                        </span>
                        @else
                        <span class="badge bg-warning p-2">
                            <i class="bi bi-clock"></i> PENDIENTE
                        </span>
                        @endif
                    </div>
                </div>
                
                <div class="mb-4">
                    <h5><strong>Título:</strong></h5>
                    <p class="fs-5">{{ $tarea->titulo }}</p>
                </div>
                
                <div class="mb-4">
                    <h5><strong>Descripción:</strong></h5>
                    <div class="border rounded p-3 bg-light">
                        {{ $tarea->descripcion ?: 'No hay descripción' }}
                    </div>
                </div>
                
                <div class="row">
                    <div class="col-md-6">
                        <h5><strong>Creada:</strong></h5>
                        <p>{{ $tarea->created_at->format('d/m/Y H:i:s') }}</p>
                    </div>
                    <div class="col-md-6">
                        <h5><strong>Actualizada:</strong></h5>
                        <p>{{ $tarea->updated_at->format('d/m/Y H:i:s') }}</p>
                    </div>
                </div>
                
                <!-- Botones de acción -->
                <div class="d-grid gap-2 d-md-flex justify-content-md-center mt-4">
                    <a href="{{ route('tareas.edit', $tarea->id) }}" class="btn btn-warning me-md-2">
                        <i class="bi bi-pencil"></i> Editar Tarea
                    </a>
                    <form action="{{ route('tareas.destroy', $tarea->id) }}" method="POST" class="d-inline">
                        @csrf
                        @method('DELETE')
                        <button type="submit" 
                                class="btn btn-danger" 
                                onclick="return confirm('¿Estás seguro de eliminar esta tarea?')">
                            <i class="bi bi-trash"></i> Eliminar Tarea
                        </button>
                    </form>
                    <a href="{{ route('tareas.index') }}" class="btn btn-secondary ms-md-2">
                        <i class="bi bi-list"></i> Ver Todas
                    </a>
                </div>
            </div>
        </div>
        
        <!-- Sección JSON para desarrolladores -->
        <div class="card mt-4">
            <div class="card-header bg-dark text-white">
                <i class="bi bi-code"></i> Datos en Formato JSON (API)
            </div>
            <div class="card-body">
                <pre class="bg-light p-3 rounded" style="max-height: 200px; overflow-y: auto;"><code>{
    "id": {{ $tarea->id }},
    "titulo": "{{ $tarea->titulo }}",
    "descripcion": "{{ $tarea->descripcion }}",
    "completada": {{ $tarea->completada ? 'true' : 'false' }},
    "created_at": "{{ $tarea->created_at }}",
    "updated_at": "{{ $tarea->updated_at }}"
}</code></pre>
            </div>
        </div>
    </div>
</div>
@endsection

Vista Edit (resources/views/tareas/edit.blade.php):

html
@extends('layouts.app')

@section('title', 'Editar Tarea')

@section('content')
<div class="row justify-content-center">
    <div class="col-md-8">
        <div class="card">
            <div class="card-header bg-warning text-white">
                <div class="d-flex justify-content-between align-items-center">
                    <h4 class="mb-0">
                        <i class="bi bi-pencil"></i> Editar Tarea #{{ $tarea->id }}
                    </h4>
                    <a href="{{ route('tareas.show', $tarea->id) }}" class="btn btn-light btn-sm">
                        <i class="bi bi-eye"></i> Ver
                    </a>
                </div>
            </div>
            <div class="card-body">
                <!-- Mostrar errores de validación -->
                @if($errors->any())
                <div class="alert alert-danger">
                    <h6>Por favor corrige los siguientes errores:</h6>
                    <ul class="mb-0">
                        @foreach($errors->all() as $error)
                        <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
                @endif

                <!-- Formulario -->
                <form action="{{ route('tareas.update', $tarea->id) }}" method="POST">
                    @csrf
                    @method('PUT')
                    
                    <div class="mb-3">
                        <label for="titulo" class="form-label">
                            <strong>Título *</strong>
                        </label>
                        <input type="text" 
                               class="form-control @error('titulo') is-invalid @enderror" 
                               id="titulo" 
                               name="titulo" 
                               value="{{ old('titulo', $tarea->titulo) }}" 
                               required>
                        @error('titulo')
                        <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                    </div>
                    
                    <div class="mb-3">
                        <label for="descripcion" class="form-label">
                            <strong>Descripción</strong>
                        </label>
                        <textarea class="form-control @error('descripcion') is-invalid @enderror" 
                                  id="descripcion" 
                                  name="descripcion" 
                                  rows="4">{{ old('descripcion', $tarea->descripcion) }}</textarea>
                        @error('descripcion')
                        <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                    </div>
                    
                    <div class="mb-3 form-check">
                        <input type="checkbox" 
                               class="form-check-input" 
                               id="completada" 
                               name="completada" 
                               value="1"
                               {{ $tarea->completada ? 'checked' : '' }}>
                        <label class="form-check-label" for="completada">
                            <strong>¿Tarea completada?</strong>
                        </label>
                    </div>
                    
                    <div class="d-grid gap-2 d-md-flex justify-content-md-between mt-4">
                        <a href="{{ route('tareas.index') }}" class="btn btn-outline-secondary">
                            <i class="bi bi-x-circle"></i> Cancelar
                        </a>
                        
                        <div>
                            <button type="submit" class="btn btn-warning me-2">
                                <i class="bi bi-save"></i> Actualizar
                            </button>
                            <a href="{{ route('tareas.show', $tarea->id) }}" class="btn btn-info">
                                <i class="bi bi-eye"></i> Ver Detalles
                            </a>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
@endsection

14. Crear Carpeta de Vistas

bash
mkdir -p resources/views/tareas

15. Iniciar el Servidor

bash
php artisan serve

Acceso a la Aplicación

Interfaz Web:

API REST:

Pruebas con curl (API)

bash
# Listar tareas
curl http://localhost:8000/api/tareas

# Crear tarea
curl -X POST http://localhost:8000/api/tareas \
  -H "Content-Type: application/json" \
  -d '{"titulo":"Mi primera tarea","descripcion":"Descripción de prueba"}'

# Ver tarea específica
curl http://localhost:8000/api/tareas/1

# Actualizar tarea
curl -X PUT http://localhost:8000/api/tareas/1 \
  -H "Content-Type: application/json" \
  -d '{"titulo":"Título actualizado","completada":true}'

# Eliminar tarea
curl -X DELETE http://localhost:8000/api/tareas/1

Resumen de Archivos Creados

  1. Rutas:

    • routes/web.php - Rutas web normales

    • routes/api.php - Rutas API normales

  2. Controladores:

    • app/Http/Controllers/Web/TareaController.php - Controlador web

    • app/Http/Controllers/Api/TareaController.php - Controlador API

  3. Modelo:

    • app/Models/Tarea.php - Modelo de tarea

  4. Vistas:

    • resources/views/layouts/app.blade.php - Layout base

    • resources/views/tareas/index.blade.php - Lista de tareas

    • resources/views/tareas/create.blade.php - Crear tarea

    • resources/views/tareas/show.blade.php - Ver tarea

    • resources/views/tareas/edit.blade.php - Editar tarea

Este tutorial está completamente detallado y usa rutas normales en lugar de resource, haciéndolo más comprensible para principiantes. Cada paso está explicado y el código está bien comentado.

Comentarios

Entradas más populares de este blog

0-Sistema de Tareas

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