1
return redirect()
->route('pasteles.index')
->with('success', '¡Pastel restaurado exitosamente!');
<a href="{{ route('pasteles.create') }}" class="btn btn-primary">
<i class="fas fa-plus"></i> Nuevo Pastel
</a>
Introducción
Este tutorial está diseñado para enseñarte a crear un sistema CRUD (Create, Read, Update, Delete) completo en Laravel 12, utilizando las últimas características y mejores prácticas del framework. A diferencia del tutorial original (diseñado para Laravel 5), esta versión incorpora las novedades de Laravel 12 y un enfoque más moderno.
Índice
1-Configuración inicial
Antes de comenzar, asegúrate de tener instalado:
bash
# Verifica tu versión de PHP (mínimo 8.2 para Laravel 12)
php -v
# Verifica tu versión de Composer
composer -V
# Crea un nuevo proyecto Laravel
composer create-project laravel/laravel pasteles-app
cd pasteles-app
# Configura tu archivo .env con los datos de tu base de datos
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=pasteles_db
DB_USERNAME=root
DB_PASSWORD=
2-Creación de la migración
Crearemos la migración para la tabla pasteles:
bash
php artisan make:migration create_pasteles_table
Actualización para Laravel 12:
Laravel 12 usa Schema con nuevas características como json(), uuid(), y mejoras en enum().
Usamos foreignId() para relaciones futuras.
php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('pasteles', function (Blueprint $table) {
$table->id(); // Equivalente a increments('id') pero más moderno
$table->string('nombre', 100);
$table->enum('sabor', ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco']);
$table->decimal('precio', 8, 2)->nullable(); // Nuevo campo añadido
$table->boolean('disponible')->default(true);
$table->text('descripcion')->nullable();
$table->timestamps();
$table->softDeletes(); // Añadimos soft deletes para eliminación segura
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('pasteles');
}
};
Novedades de Laravel 12:
$table->id() es la forma moderna y recomendada.
softDeletes() permite recuperar registros eliminados.
Uso de tipos de datos mejorados.
Ejecución de las migraciones
Una vez definida la migración, estos son los comandos que usarás en el día a día:
⚡ Bonus combinado (muy útil)
Si además quieres poblar la tabla con datos de prueba (seeders) justo después de reiniciar:
bash
php artisan migrate:fresh --seed
bash
php artisan migrate:refresh --seed
Ambos hacen lo mismo que sus versiones sin --seed, pero además ejecutan los DatabaseSeeder y tus seeders personalizados para tener datos de prueba inmediatamente.
Si tu servicio PHP se llama app, entonces ejecuta:
text
docker compose exec app php artisan make:controller PastelController
Esto hace exactamente lo que querías, pero ejecuta PHP dentro del contenedor.
Si tu contenedor realmente se llama laravel-docker, puedes hacer:
text
docker exec -it laravel-docker /bin/bash
Una vez dentro, verás algo parecido a:
text
root@abc123:/var/www/html#
Entonces ya puedes ejecutar:
text
php artisan make:controller PastelController
Con esto ya tienes el flujo completo: crear la migración → ejecutarla → y saber cómo deshacer o reiniciar cuando sea necesario
3-Creación del seeder
bash
php artisan make:seeder PastelSeeder
Actualización para Laravel 12:
Uso de factory en lugar de inserciones manuales.
Mejores prácticas con Faker v3.
php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Pastel;
class PastelSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// Usamos el factory para crear registros
Pastel::factory(50)->create();
}
}
Crear el Factory:
bash
php artisan make:factory PastelFactory
php
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Pastel>
*/
class PastelFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
$sabores = ['chocolate', 'vainilla', 'cheesecake', 'fresa', 'coco'];
return [
'nombre' => 'Pastel ' . $this->faker->unique()->firstName() . ' ' . $this->faker->lastName(),
'sabor' => $this->faker->randomElement($sabores),
'precio' => $this->faker->randomFloat(2, 50, 500),
'disponible' => $this->faker->boolean(80), // 80% de probabilidad
'descripcion' => $this->faker->sentence(10),
'created_at' => $this->faker->dateTimeBetween('-1 year', 'now'),
'updated_at' => $this->faker->dateTimeBetween('-1 month', 'now'),
];
}
}
Registrar el seeder en DatabaseSeeder:
php
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
$this->call([
PastelSeeder::class,
]);
}
}
Ejecutar migraciones y seeders:
bash
php artisan migrate --seed
# O por separado:
php artisan migrate
php artisan db:seed
4-Creación del modelo
bash
php artisan make:model Pastel
Modelo básico para principiantes
app/Models/Pastel.php
php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Pastel extends Model
{
use HasFactory, SoftDeletes;
// Especificamos la tabla (opcional si sigue la convención)
protected $table = 'pasteles';
// Campos que pueden ser asignados masivamente
protected $fillable = [
'nombre',
'sabor',
'precio',
'disponible',
'descripcion'
];
}
Explicación para principiantes:
📌 Guardar commit del modelo
bash
git add app/Models/Pastel.php
git commit -m "feat: crear modelo Pastel con SoftDeletes y fillable"
¿Qué necesitas agregar más adelante?
A medida que avances, puedes ir agregando:
Pero por ahora, solo con $fillable es suficiente para que el CRUD funcione.
Actualización para Laravel 12:
Uso de $fillable o $guarded para protección masiva.
Definición de casts para tipos de datos.
Relaciones y scopes.
php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Pastel extends Model
{
use HasFactory, SoftDeletes;
// Especificamos la tabla (no necesario si sigue la convención)
protected $table = 'pasteles';
// Campos que pueden ser asignados masivamente
protected $fillable = [
'nombre',
'sabor',
'precio',
'disponible',
'descripcion'
];
// Casts para conversión automática de tipos
protected $casts = [
'precio' => 'decimal:2',
'disponible' => 'boolean',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
// Atributos que siempre deben ser incluidos en arrays/JSON
protected $visible = [
'id',
'nombre',
'sabor',
'precio',
'disponible',
'descripcion',
'created_at',
'updated_at'
];
// Scopes para consultas comunes
public function scopeDisponibles($query)
{
return $query->where('disponible', true);
}
public function scopeBySabor($query, $sabor)
{
return $query->where('sabor', $sabor);
}
// Accesors para formatear datos
public function getNombreCompletoAttribute(): string
{
return $this->nombre . ' (' . $this->sabor . ')';
}
// Mutators para modificar datos antes de guardar
public function setNombreAttribute($value): void
{
$this->attributes['nombre'] = ucwords(strtolower($value));
}
}
Novedades de Laravel 12:
SoftDeletes para eliminación lógica.
casts con tipos más precisos.
Scopes para consultas reutilizables.
Accesors y mutators para manipulación de atributos.
5-1-Crear el controlador
bash
php artisan make:controller PastelController --resource
--resource :: Crea todas las funciones crud:
ublic function index() { ... }
public function create() { ... }
public function store() { ... }
public function show() { ... }
public function edit() { ... }
public function update() { ... }
public function destroy() { ... }
class PastelController extends Controller
{
public function index()
{
return "Hola mundo";
}
}
Delo lo contrario el controlador se crea vacío:
php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PastelController extends Controller
{
// ¡No tiene ningún método!
}
si quiero usarlo en las rutas utilizo el namespace:
use App\Http\Controllers\PastelController;
Controlador básico sin FormRequest
app/Http/Controllers/PastelController.php
php
<?php
namespace App\Http\Controllers;
use App\Models\Pastel;
use Illuminate\Http\Request;
class PastelController extends Controller
{
/**
* Muestra la lista de pasteles.
*Route::get('/pasteles', [PastelController::class, 'index'])->name('pasteles.index');
*/
public function index()
{
$pasteles = Pastel::all(); // Obtiene todos los pasteles
return view('pasteles.index', compact('pasteles'));
}
/**
* Muestra el formulario para crear un nuevo pastel.
*Route::get('/pasteles/create', [PastelController::class, 'create'])->name('pasteles.create');
*
*/
public function create()
{
return view('pasteles.create');
}
/**
* Almacena un nuevo pastel en la base de datos.
*Route::post('/pasteles', [PastelController::class, 'store'])->name('pasteles.store');
*/
public function store(Request $request)
{
// 1. VALIDAR los datos
$validated = $request->validate([
'nombre' => 'required|string|max:100',
'sabor' => 'required|string|max:50',
'precio' => 'required|numeric|min:0',
'disponible' => 'nullable|boolean',
'descripcion' => 'nullable|string|max:1000',
]);
// 2. GUARDAR en la base de datos
Pastel::create($validated);
// 3. REDIRIGIR con mensaje de éxito
return redirect()
->route('pasteles.index')
->with('success', '¡Pastel creado exitosamente!');
}
/**
* Muestra un pastel específico.
*Route::get('/pasteles/{pastel}', [PastelController::class, 'show'])->name('pasteles.show');
*/
public function show(Pastel $pastel)
{
return view('pasteles.show', compact('pastel'));
}
/**
* Muestra el formulario para editar un pastel.
*Route::get('/pasteles/{pastel}/edit', [PastelController::class, 'edit'])->name('pasteles.edit');
*/
public function edit(Pastel $pastel)
{
return view('pasteles.edit', compact('pastel'));
}
/**
* Actualiza un pastel específico.
*Route::put('/pasteles/{pastel}', [PastelController::class, 'update'])->name('pasteles.update');
*/
public function update(Request $request, Pastel $pastel)
{
// 1. VALIDAR los datos
$validated = $request->validate([
'nombre' => 'required|string|max:100',
'sabor' => 'required|string|max:50',
'precio' => 'required|numeric|min:0',
'disponible' => 'nullable|boolean',
'descripcion' => 'nullable|string|max:1000',
]);
// 2. ACTUALIZAR en la base de datos
$pastel->update($validated);
// 3. REDIRIGIR con mensaje de éxito
return redirect()
->route('pasteles.index')
->with('success', '¡Pastel actualizado exitosamente!');
}
/**
* Elimina (eliminación lógica) un pastel.
*Route::delete('/pasteles/{pastel}', [PastelController::class, 'destroy'])->name('pasteles.destroy');
*/
public function destroy(Pastel $pastel)
{
$pastel->delete();
return redirect()
->route('pasteles.index')
->with('success', '¡Pastel eliminado exitosamente!');
}
}
📌 Guardar commit del controlador
bash
git add app/Http/Controllers/PastelController.php
git commit -m "feat: crear PastelController con validación básica"
Rutas para este controlador
routes/web.php
use App\Http\Controllers\PastelController;
Las rutas completas (sin comentarios)
php
use App\Http\Controllers\PastelController;
Route::get('/pasteles', [PastelController::class, 'index'])->name('pasteles.index');
Route::get('/pasteles/create', [PastelController::class, 'create'])->name('pasteles.create');
Route::post('/pasteles', [PastelController::class, 'store'])->name('pasteles.store');
Route::get('/pasteles/{pastel}', [PastelController::class, 'show'])->name('pasteles.show');
Route::get('/pasteles/{pastel}/edit', [PastelController::class, 'edit'])->name('pasteles.edit');
Route::put('/pasteles/{pastel}', [PastelController::class, 'update'])->name('pasteles.update');
Route::delete('/pasteles/{pastel}', [PastelController::class, 'destroy'])->name('pasteles.destroy');
🔄 Versión más corta con Route::resource()
php
use App\Http\Controllers\PastelController;
Route::resource('pasteles', PastelController::class);
¡Hace exactamente lo mismo
Comando para ver todas las rutas en la terminal
bash
php artisan route:list
Creación de vistas
Layout principal (resources/views/layouts/app.blade.php)
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Pastelería') }} - @yield('title', 'Panel de Control')</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- DataTables CSS -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
@stack('styles')
</head>
<body>
<!-- Contenido principal -->
<main class="py-4">
<div class="container">
@yield('content')
</div>
</main>
<!-- Scripts -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
@stack('scripts')
</body>
</html>
Vista Index (resources/views/pasteles/index.blade.php)
@extends('layouts.app')
@section('title', 'Lista de Pasteles')
@section('content')
<div class="d-flex justify-content-between align-items-center mb-4">
<h1><i class="fas fa-cake"></i> Lista de Pasteles</h1>
</div>
<div class="card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover" id="pasteles-table">
<thead>
<tr>
<th>ID</th>
<th>Nombre</th>
<th>Sabor</th>
<th>Precio</th>
<th>Disponible</th>
<th>Estado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
$(document).ready(function() {
$('#pasteles-table').DataTable({
language: {
url: '//cdn.datatables.net/plug-ins/1.13.6/i18n/es-ES.json'
},
order: [[0, 'desc']],
pageLength: 10,
lengthMenu: [[5, 10, 25, 50], [5, 10, 25, 50]]
});
});
</script>
@endpush
Comentarios
Publicar un comentario