4-CRUD de Tareas en Laravel (Sencillo y Práctico)-blade-plantilla
Tareas en Laravel con Estructura Blade Completa
Voy a recrear el CRUD utilizando una plantilla base para mostrar mejor cómo funcionan @extends, @section y @yield en Blade.
1. Estructura de Plantillas
Primero, crearemos una estructura de plantillas más organizada:
Plantilla Base (resources/views/layouts/app.blade.php)
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title', 'CRUD de Tareas')</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
@stack('styles')
</head>
<body>
<!-- Header -->
<header class="bg-primary text-white py-3">
<div class="container">
<div class="d-flex justify-content-between align-items-center">
<h1>@yield('header', 'Gestión de Tareas')</h1>
@yield('header-actions')
</div>
</div>
</header>
<!-- Main Content -->
<main class="container py-4">
@yield('content')
</main>
<!-- Footer -->
<footer class="bg-light py-3 mt-4">
<div class="container text-center">
<p class="mb-0">CRUD de Tareas © {{ date('Y') }}</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
@stack('scripts')
</body>
</html>2. Vistas Específicas
Vista Index (resources/views/tasks/index.blade.php)
@extends('layouts.app')
@section('title', 'Lista de Tareas')
@section('header-actions')
<a href="{{ route('tasks.create') }}" class="btn btn-light">Nueva Tarea</a>
@endsection
@section('content')
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show">
{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
@endif
<div class="card">
<div class="card-body">
@if($tasks->isEmpty())
<div class="text-center py-4">
<h4>No hay tareas registradas</h4>
<a href="{{ route('tasks.create') }}" class="btn btn-primary mt-2">Crear Primera Tarea</a>
</div>
@else
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Nombre</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
@foreach($tasks as $task)
<tr>
<td>{{ $task->id }}</td>
<td>{{ $task->name }}</td>
<td>
<div class="d-flex gap-2">
<a href="{{ route('tasks.edit', $task->id) }}" class="btn btn-sm btn-warning">Editar</a>
<form action="{{ route('tasks.destroy', $task->id) }}" method="POST">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('¿Estás seguro?')">Eliminar</button>
</form>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>
</div>
@endsectionVista Create (resources/views/tasks/create.blade.php)
@extends('layouts.app')
@section('title', 'Crear Nueva Tarea')
@section('content')
<div class="card">
<div class="card-header">
<h5 class="card-title">Nueva Tarea</h5>
</div>
<div class="card-body">
<form action="{{ route('tasks.store') }}" method="POST">
@csrf
<div class="mb-3">
<label for="name" class="form-label">Nombre de la Tarea</label>
<input type="text" class="form-control @error('name') is-invalid @enderror" id="name" name="name" value="{{ old('name') }}" required>
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="d-flex justify-content-end gap-2">
<a href="{{ route('tasks.index') }}" class="btn btn-secondary">Cancelar</a>
<button type="submit" class="btn btn-primary">Guardar</button>
</div>
</form>
</div>
</div>
@endsectionVista Edit (resources/views/tasks/edit.blade.php)
@extends('layouts.app')
@section('title', 'Editar Tarea')
@section('content')
<div class="card">
<div class="card-header">
<h5 class="card-title">Editar Tarea #{{ $task->id }}</h5>
</div>
<div class="card-body">
<form action="{{ route('tasks.update', $task->id) }}" method="POST">
@csrf
@method('PUT')
<div class="mb-3">
<label for="name" class="form-label">Nombre de la Tarea</label>
<input type="text" class="form-control @error('name') is-invalid @enderror" id="name" name="name" value="{{ old('name', $task->name) }}" required>
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="d-flex justify-content-end gap-2">
<a href="{{ route('tasks.index') }}" class="btn btn-secondary">Cancelar</a>
<button type="submit" class="btn btn-primary">Actualizar</button>
</div>
</form>
</div>
</div>
@endsection3. Actualización del Controlador
Actualiza app/Http/Controllers/TaskController.php para incluir todos los métodos:
<?php
namespace App\Http\Controllers;
use App\Models\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
public function index()
{
$tasks = Task::latest()->paginate(10);
return view('tasks.index', compact('tasks'));
}
public function create()
{
return view('tasks.create');
}
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
]);
Task::create($validated);
return redirect()->route('tasks.index')
->with('success', 'Tarea creada exitosamente');
}
public function edit(Task $task)
{
return view('tasks.edit', compact('task'));
}
public function update(Request $request, Task $task)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
]);
$task->update($validated);
return redirect()->route('tasks.index')
->with('success', 'Tarea actualizada exitosamente');
}
public function destroy(Task $task)
{
$task->delete();
return redirect()->route('tasks.index')
->with('success', 'Tarea eliminada exitosamente');
}
}4. Rutas
Actualiza routes/web.php:
<?php
use App\Http\Controllers\TaskController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return redirect()->route('tasks.index');
});
Route::resource('tasks', TaskController::class);Explicación de la Estructura Blade
@extends('layouts.app'): Indica que la vista hereda de la plantilla base.@section('nombre')y@endsection: Define una sección que será inyectada en la plantilla base.@yield('nombre'): En la plantilla base, marca el lugar donde se insertará el contenido de la sección.@stack('nombre'): Permite agregar contenido (como CSS o JS) que será renderizado en ese lugar.@include('partials.alert')(no usado aquí pero útil): Para incluir sub-vistas.@error('campo'): Directiva de Blade para mostrar errores de validación.
Beneficios de esta estructura:
Organización: Separación clara entre layout y contenido.
Reutilización: El layout base se usa en todas las vistas.
Mantenibilidad: Cambios en el diseño general solo requieren modificar el layout.
Flexibilidad: Cada vista puede sobrescribir solo las secciones necesarias.
Claridad: El código es más legible y fácil de entender.
Este CRUD ahora incluye:
Diseño profesional con Bootstrap
Paginación
Validación de formularios
Mensajes de confirmación
Confirmación antes de eliminar
Manejo de errores
Estructura Blade bien organizad
Comentarios
Publicar un comentario