7: Modelos y Eloquent ORM en Laravel 12 - Guía Práctica

 

Introducción

Eloquent es el ORM (Object-Relational Mapping) de Laravel que te permite interactuar con tu base de datos de manera elegante y expresiva. En lugar de escribir SQL directamente, trabajas con objetos PHP que representan tus tablas. En este tutorial, aprenderás todo sobre modelos y Eloquent en Laravel 12.

Índice

  1. ¿Qué es Eloquent?

  2. Creación de Modelos

  3. Convenciones de Eloquent

  4. Operaciones CRUD Básicas

  5. Scopes (Consultas Personalizadas)

  6. Relaciones entre Modelos

  7. Mass Assignment y Fillable

  8. Soft Deletes

  9. Consultas Avanzadas

  10. Ejercicio Práctico: Sistema de Pastelería


1. ¿Qué es Eloquent?

Eloquent es el ORM de Laravel que proporciona una implementación elegante y sencilla del patrón Active Record. Cada modelo representa una tabla en tu base de datos y te permite:

  • Realizar consultas sin escribir SQL

  • Definir relaciones entre tablas

  • Usar scopes para consultas reutilizables

  • Manejar eventos del ciclo de vida

  • Utilizar mutators y accessors

Ventajas de Eloquent

php
// ✅ Con Eloquent - Código limpio y legible
$pasteles = Pastel::where('sabor', 'chocolate')
    ->where('precio', '>', 100)
    ->orderBy('nombre')
    ->get();

// ❌ Sin Eloquent - SQL directo
$pasteles = DB::select("SELECT * FROM pasteles 
                        WHERE sabor = 'chocolate' 
                        AND precio > 100 
                        ORDER BY nombre");

// ✅ Con Eloquent - Inserción elegante
$pastel = new Pastel();
$pastel->nombre = 'Pastel de Chocolate';
$pastel->precio = 150;
$pastel->save();

// ❌ Sin Eloquent - Inserción manual
DB::insert("INSERT INTO pasteles (nombre, precio) VALUES (?, ?)", 
           ['Pastel de Chocolate', 150]);

2. Creación de Modelos

Comandos Artisan

bash
# Crear un modelo básico
php artisan make:model Pastel

# Crear modelo con migración
php artisan make:model Pastel -m

# Crear modelo con migración, factory y seeder
php artisan make:model Pastel -mf

# Crear modelo con todos los recursos
php artisan make:model Pastel -a

# Crear modelo en un subdirectorio
php artisan make:model Models/Pastel

Modelo básico

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Pastel extends Model
{
    // Especificar la tabla (opcional si sigue la convención)
    protected $table = 'pasteles';
    
    // Campos que pueden ser asignados masivamente
    protected $fillable = [
        'nombre', 'sabor', 'precio', 'disponible', 'descripcion'
    ];
    
    // Campos que no deben ser mostrados en arrays/JSON
    protected $hidden = [];
    
    // Campos que deben ser tratados como fechas
    protected $dates = ['created_at', 'updated_at'];
    
    // Casts de tipos de datos
    protected $casts = [
        'precio' => 'float',
        'disponible' => 'boolean',
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
    ];
}

Modelo con todos los recursos

bash
# Crear modelo con migración, factory y controlador
php artisan make:model Pastel -mfc

3. Convenciones de Eloquent

Convenciones de nombres

ElementoConvenciónEjemplo
ModeloSingular, UpperCamelCaseUser, Pastel, Categoria
TablaPlural, snake_caseusers, pasteles, categorias
Clave primariaidid
Clave foráneamodelo_idpastel_id, user_id
Tabla pivoteSingular alfabéticopastel_ingrediente
Timestampscreated_at, updated_at-
Soft Deletesdeleted_at-

Ejemplo de convenciones

php
// Modelo (singular, UpperCamelCase)
class Pastel extends Model
{
    // Tabla (plural, snake_case) - auto-detectada
    protected $table = 'pasteles';
    
    // Clave primaria - por defecto 'id'
    protected $primaryKey = 'id';
    
    // Timestamps - por defecto true
    public $timestamps = true;
}

// Relación (clave foránea: modelo_id)
public function ingrediente()
{
    return $this->belongsTo(Ingrediente::class, 'ingrediente_id');
}

Especificar la tabla manualmente

php
class Pastel extends Model
{
    // Forzar el nombre de la tabla (necesario en español)
    protected $table = 'pasteles';
    
    // Si la clave primaria no es 'id'
    protected $primaryKey = 'pastel_id';
    
    // Si la clave primaria no es autoincrementable
    public $incrementing = false;
    
    // Si la clave primaria no es un entero
    protected $keyType = 'string';
    
    // Deshabilitar timestamps
    public $timestamps = false;
}

4. Operaciones CRUD Básicas

CREATE - Crear registros

php
// Método 1: Usando new
$pastel = new Pastel();
$pastel->nombre = 'Pastel de Chocolate';
$pastel->sabor = 'chocolate';
$pastel->precio = 150.00;
$pastel->save();

// Método 2: Usando create (requiere $fillable)
$pastel = Pastel::create([
    'nombre' => 'Pastel de Vainilla',
    'sabor' => 'vainilla',
    'precio' => 120.00,
    'disponible' => true
]);

// Método 3: Usando firstOrCreate
$pastel = Pastel::firstOrCreate(
    ['nombre' => 'Pastel Especial'], // Buscar por estos campos
    ['sabor' => 'chocolate', 'precio' => 200] // Crear con estos datos
);

// Método 4: Usando firstOrNew
$pastel = Pastel::firstOrNew(
    ['nombre' => 'Pastel Nuevo'],
    ['sabor' => 'fresa', 'precio' => 180]
);
$pastel->save();

// Método 5: Usando updateOrCreate
$pastel = Pastel::updateOrCreate(
    ['nombre' => 'Pastel Especial'],
    ['sabor' => 'chocolate', 'precio' => 250]
);

READ - Leer registros

php
// Obtener todos los registros
$pasteles = Pastel::all();

// Obtener con condiciones
$pasteles = Pastel::where('sabor', 'chocolate')->get();

// Obtener el primer registro
$pastel = Pastel::where('sabor', 'chocolate')->first();

// Obtener por ID
$pastel = Pastel::find(1);

// Obtener por ID o lanzar excepción
$pastel = Pastel::findOrFail(1);

// Obtener con paginación
$pasteles = Pastel::paginate(10);

// Obtener solo algunos campos
$pasteles = Pastel::select('nombre', 'precio')->get();

// Obtener contando registros
$count = Pastel::where('sabor', 'chocolate')->count();

// Verificar si existe
$existe = Pastel::where('nombre', 'Pastel Especial')->exists();

// Obtener valores específicos
$nombres = Pastel::pluck('nombre');
$precios = Pastel::pluck('precio', 'nombre');

UPDATE - Actualizar registros

php
// Método 1: Encontrar y actualizar
$pastel = Pastel::find(1);
$pastel->precio = 180.00;
$pastel->save();

// Método 2: Actualizar directamente
Pastel::where('sabor', 'chocolate')
    ->update(['precio' => 200]);

// Método 3: Usando update en modelo existente
$pastel = Pastel::find(1);
$pastel->update([
    'precio' => 190,
    'disponible' => true
]);

// Método 4: Incrementar/Decrementar
Pastel::where('sabor', 'chocolate')
    ->increment('precio', 10);

Pastel::where('sabor', 'vainilla')
    ->decrement('precio', 5);

DELETE - Eliminar registros

php
// Método 1: Encontrar y eliminar
$pastel = Pastel::find(1);
$pastel->delete();

// Método 2: Eliminar por ID
Pastel::destroy(1);

// Método 3: Eliminar múltiples IDs
Pastel::destroy([1, 2, 3]);

// Método 4: Eliminar con condición
Pastel::where('precio', '<', 50)->delete();

// Método 5: Eliminar todos (¡cuidado!)
Pastel::truncate();

5. Scopes (Consultas Personalizadas)

Los scopes te permiten definir consultas reutilizables en tus modelos.

Scopes locales (locales)

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Pastel extends Model
{
    protected $table = 'pasteles';
    protected $fillable = ['nombre', 'sabor', 'precio', 'disponible'];

    // Scope para pasteles de chocolate
    public function scopeChocolate($query)
    {
        return $query->where('sabor', 'chocolate');
    }

    // Scope para pasteles de vainilla
    public function scopeVainilla($query)
    {
        return $query->where('sabor', 'vainilla');
    }

    // Scope para pasteles disponibles
    public function scopeDisponibles($query)
    {
        return $query->where('disponible', true);
    }

    // Scope dinámico (con parámetros)
    public function scopeSabor($query, $sabor)
    {
        return $query->where('sabor', $sabor);
    }

    // Scope con múltiples parámetros
    public function scopePrecioEntre($query, $min, $max)
    {
        return $query->whereBetween('precio', [$min, $max]);
    }

    // Scope con lógica compleja
    public function scopeRecomendados($query)
    {
        return $query->where('disponible', true)
                     ->where('precio', '>', 100)
                     ->orderBy('precio', 'desc')
                     ->limit(5);
    }

    // Scope con like
    public function scopeBuscar($query, $termino)
    {
        return $query->where('nombre', 'LIKE', "%{$termino}%")
                     ->orWhere('descripcion', 'LIKE', "%{$termino}%");
    }
}

Usar scopes

php
// Scope simple
$pasteles = Pastel::chocolate()->get();

// Múltiples scopes
$pasteles = Pastel::disponibles()->chocolate()->get();

// Scope con parámetros
$pasteles = Pastel::sabor('chocolate')->get();
$pasteles = Pastel::precioEntre(50, 200)->get();

// Scope con lógica compleja
$pasteles = Pastel::recomendados()->get();

// Scope con like
$pasteles = Pastel::buscar('chocolate')->get();

// Combinando scopes con otros métodos
$pasteles = Pastel::disponibles()
    ->sabor('chocolate')
    ->orderBy('precio', 'asc')
    ->take(10)
    ->get();

Scopes globales

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class Pastel extends Model
{
    // Scope global para siempre incluir pasteles disponibles
    protected static function booted()
    {
        static::addGlobalScope('disponibles', function (Builder $builder) {
            $builder->where('disponible', true);
        });
    }

    // Eliminar un scope global
    public static function withoutDisponibles()
    {
        return static::withoutGlobalScope('disponibles');
    }
}

// Uso
$pasteles = Pastel::all(); // Solo disponibles
$todos = Pastel::withoutGlobalScope('disponibles')->get();

6. Relaciones entre Modelos

Relaciones comunes

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Pastel extends Model
{
    // Relación belongsTo (pertenece a)
    public function categoria()
    {
        return $this->belongsTo(Categoria::class);
    }

    // Relación belongsTo con clave personalizada
    public function creador()
    {
        return $this->belongsTo(User::class, 'user_id');
    }

    // Relación hasMany (tiene muchos)
    public function ingredientes()
    {
        return $this->hasMany(Ingrediente::class);
    }

    // Relación hasMany con orden
    public function ingredientesOrdenados()
    {
        return $this->hasMany(Ingrediente::class)->orderBy('nombre');
    }

    // Relación belongsToMany (muchos a muchos)
    public function presentaciones()
    {
        return $this->belongsToMany(Presentacion::class, 'pastel_presentacion')
                    ->withPivot('cantidad', 'unidad')
                    ->withTimestamps();
    }

    // Relación hasOne (tiene uno)
    public function detalle()
    {
        return $this->hasOne(DetallePastel::class);
    }

    // Relación hasManyThrough (a través de)
    public function ventas()
    {
        return $this->hasManyThrough(Venta::class, DetalleVenta::class);
    }

    // Relación polimórfica
    public function imagenes()
    {
        return $this->morphMany(Imagen::class, 'imagenable');
    }
}

Ejemplos de relaciones inversas

php
// Modelo Categoria
class Categoria extends Model
{
    // Relación inversa de belongsTo
    public function pasteles()
    {
        return $this->hasMany(Pastel::class);
    }
}

// Modelo Ingrediente
class Ingrediente extends Model
{
    // Relación inversa de hasMany
    public function pastel()
    {
        return $this->belongsTo(Pastel::class);
    }
}

Eager Loading (Carga anticipada)

php
// Sin eager loading (N+1 problema)
$pasteles = Pastel::all();
foreach ($pasteles as $pastel) {
    echo $pastel->categoria->nombre; // Hace consulta adicional por cada pastel
}

// Con eager loading (una sola consulta adicional)
$pasteles = Pastel::with('categoria')->get();
foreach ($pasteles as $pastel) {
    echo $pastel->categoria->nombre; // No hay consultas adicionales
}

// Cargar múltiples relaciones
$pasteles = Pastel::with(['categoria', 'ingredientes'])->get();

// Cargar relaciones anidadas
$pasteles = Pastel::with('categoria.pasteles')->get();

// Cargar solo ciertas columnas
$pasteles = Pastel::with('categoria:id,nombre')->get();

// Cargar con condiciones
$pasteles = Pastel::with(['ingredientes' => function ($query) {
    $query->where('cantidad', '>', 10)->orderBy('nombre');
}])->get();

Lazy Eager Loading

php
$pasteles = Pastel::all();
$pasteles->load('categoria');
$pasteles->load(['ingredientes', 'presentaciones']);

// Cargar condicionalmente
if ($algo) {
    $pasteles->load('categoria');
}

7. Mass Assignment y Fillable

Protección contra asignación masiva

php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Pastel extends Model
{
    // Opción 1: Campos permitidos para asignación masiva
    protected $fillable = [
        'nombre',
        'sabor',
        'precio',
        'disponible',
        'descripcion',
        'categoria_id'
    ];

    // Opción 2: Campos protegidos (no permitidos)
    protected $guarded = [
        'id',
        'created_at',
        'updated_at'
    ];

    // Opción 3: Proteger todo (usar solo fillable)
    protected $guarded = [];

    // Opción 4: Permisos específicos
    protected $fillable = ['nombre', 'sabor'];
    protected $guarded = ['id'];
}

Ejemplos de asignación masiva

php
// Con fillable
$pastel = Pastel::create($request->all());

// Con guarded
$pastel = Pastel::create($request->except(['id', 'created_at']));

// Asignar y guardar
$pastel = new Pastel($request->all());
$pastel->save();

// Actualizar con asignación masiva
$pastel = Pastel::find(1);
$pastel->fill($request->all());
$pastel->save();

8. Soft Deletes

Habilitar Soft Deletes

php
// Migración
Schema::create('pasteles', function (Blueprint $table) {
    $table->id();
    $table->string('nombre');
    $table->string('sabor');
    $table->decimal('precio', 8, 2);
    $table->boolean('disponible')->default(true);
    $table->softDeletes(); // Añade columna deleted_at
    $table->timestamps();
});

// Modelo
use Illuminate\Database\Eloquent\SoftDeletes;

class Pastel extends Model
{
    use SoftDeletes;

    protected $dates = ['deleted_at'];
}

Operaciones con Soft Deletes

php
// Eliminar (soft delete)
$pastel->delete();

// Verificar si está eliminado
if ($pastel->trashed()) {
    echo 'El pastel está eliminado';
}

// Incluir eliminados en consultas
$pasteles = Pastel::withTrashed()->get();

// Solo eliminados
$pasteles = Pastel::onlyTrashed()->get();

// Restaurar
$pastel->restore();

// Eliminar permanentemente (hard delete)
$pastel->forceDelete();

// Forzar eliminación de varios
Pastel::where('disponible', false)->forceDelete();

9. Consultas Avanzadas

Condiciones complejas

php
// OR y AND combinados
$pasteles = Pastel::where('sabor', 'chocolate')
    ->orWhere('sabor', 'vainilla')
    ->where('precio', '>', 50)
    ->get();

// Subconsultas
$pasteles = Pastel::whereIn('categoria_id', function ($query) {
    $query->select('id')
          ->from('categorias')
          ->where('activa', true);
})->get();

// Exists
$pasteles = Pastel::whereHas('categoria', function ($query) {
    $query->where('nombre', 'Tortas');
})->get();

// DoesntHave
$pasteles = Pastel::doesntHave('ingredientes')->get();

// Where con JSON
$pasteles = Pastel::where('datos->color', 'rojo')->get();

Agregaciones y agrupamiento

php
// Count
$count = Pastel::where('sabor', 'chocolate')->count();

// Sum
$total = Pastel::where('sabor', 'chocolate')->sum('precio');

// Average
$promedio = Pastel::where('sabor', 'chocolate')->avg('precio');

// Max
$maximo = Pastel::where('sabor', 'chocolate')->max('precio');

// Min
$minimo = Pastel::where('sabor', 'chocolate')->min('precio');

// Group by
$pasteles = Pastel::select('sabor', Pastel::raw('count(*) as total'))
    ->groupBy('sabor')
    ->get();

// Having
$pasteles = Pastel::select('sabor', Pastel::raw('avg(precio) as promedio'))
    ->groupBy('sabor')
    ->having('promedio', '>', 100)
    ->get();

Subconsultas

php
// Subconsulta en select
$pasteles = Pastel::select('nombre', 'precio')
    ->selectSub(function ($query) {
        $query->select('precio')
              ->from('pasteles')
              ->whereColumn('sabor', 'pasteles.sabor')
              ->orderBy('precio', 'desc')
              ->limit(1);
    }, 'precio_maximo_sabor')
    ->get();

// Subconsulta en where
$pasteles = Pastel::where('precio', '>', function ($query) {
    $query->select(Pastel::raw('avg(precio)'))
          ->from('pasteles');
})->get();

Raw Queries

php
// Select raw
$pasteles = Pastel::select(
    Pastel::raw('nombre, precio, precio * 1.16 as precio_con_iva')
)->get();

// Where raw
$pasteles = Pastel::whereRaw('precio > ?', [100])->get();

// Order by raw
$pasteles = Pastel::orderByRaw('precio DESC, nombre ASC')->get();

// Raw con valores
$pasteles = Pastel::whereRaw('precio > :precio', ['precio' => 100])->get();

10. Ejercicio Práctico: Sistema de Pastelería

Paso 1: Crear modelos

bash
# Crear modelos con migraciones
php artisan make:model Pastel -m
php artisan make:model Categoria -m
php artisan make:model Ingrediente -m
php artisan make:model Venta -m

Paso 2: Configurar modelos

php
// app/Models/Pastel.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Pastel extends Model
{
    use SoftDeletes;

    protected $table = 'pasteles';
    
    protected $fillable = [
        'nombre', 'sabor', 'precio', 'disponible', 
        'descripcion', 'categoria_id'
    ];

    protected $casts = [
        'precio' => 'float',
        'disponible' => 'boolean',
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
        'deleted_at' => 'datetime'
    ];

    // Relaciones
    public function categoria()
    {
        return $this->belongsTo(Categoria::class);
    }

    public function ingredientes()
    {
        return $this->belongsToMany(Ingrediente::class, 'pastel_ingrediente')
                    ->withPivot('cantidad', 'unidad')
                    ->withTimestamps();
    }

    public function ventas()
    {
        return $this->hasMany(Venta::class);
    }

    // Scopes
    public function scopeDisponible($query)
    {
        return $query->where('disponible', true);
    }

    public function scopeSabor($query, $sabor)
    {
        return $query->where('sabor', $sabor);
    }

    public function scopePrecioEntre($query, $min, $max)
    {
        return $query->whereBetween('precio', [$min, $max]);
    }

    public function scopeBuscar($query, $termino)
    {
        return $query->where('nombre', 'LIKE', "%{$termino}%")
                     ->orWhere('descripcion', 'LIKE', "%{$termino}%");
    }

    // Accesors y Mutators
    public function getNombreCompletoAttribute()
    {
        return "{$this->nombre} - Sabor: {$this->sabor}";
    }

    public function getPrecioConIvaAttribute()
    {
        return $this->precio * 1.16;
    }

    public function setNombreAttribute($value)
    {
        $this->attributes['nombre'] = ucwords(strtolower($value));
    }
}

// app/Models/Categoria.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Categoria extends Model
{
    protected $fillable = ['nombre', 'descripcion'];

    public function pasteles()
    {
        return $this->hasMany(Pastel::class);
    }
}

// app/Models/Ingrediente.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Ingrediente extends Model
{
    protected $fillable = ['nombre', 'stock', 'unidad'];

    public function pasteles()
    {
        return $this->belongsToMany(Pastel::class, 'pastel_ingrediente')
                    ->withPivot('cantidad', 'unidad')
                    ->withTimestamps();
    }
}

// app/Models/Venta.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Venta extends Model
{
    protected $fillable = [
        'pastel_id', 'cantidad', 'precio_total', 
        'cliente_nombre', 'cliente_email', 'fecha_venta'
    ];

    public function pastel()
    {
        return $this->belongsTo(Pastel::class);
    }
}

Paso 3: Crear controlador para pruebas

bash
php artisan make:controller PastelPruebaController
php
<?php

namespace App\Http\Controllers;

use App\Models\Pastel;
use App\Models\Categoria;
use Illuminate\Http\Request;

class PastelPruebaController extends Controller
{
    public function index()
    {
        // 1. Crear categorías
        $categoria = Categoria::create([
            'nombre' => 'Tortas Especiales',
            'descripcion' => 'Tortas para ocasiones especiales'
        ]);

        // 2. Crear pasteles
        $pastel1 = Pastel::create([
            'nombre' => 'Pastel de Chocolate Especial',
            'sabor' => 'chocolate',
            'precio' => 250.00,
            'disponible' => true,
            'descripcion' => 'Delicioso pastel de chocolate con ganache',
            'categoria_id' => $categoria->id
        ]);

        $pastel2 = Pastel::create([
            'nombre' => 'Pastel de Vainilla',
            'sabor' => 'vainilla',
            'precio' => 180.00,
            'disponible' => true,
            'descripcion' => 'Pastel de vainilla con crema de mantequilla',
            'categoria_id' => $categoria->id
        ]);

        // 3. Consultas con scopes
        $pastelesDisponibles = Pastel::disponible()->get();
        $pastelesChocolate = Pastel::sabor('chocolate')->get();
        $pastelesCaros = Pastel::precioEntre(200, 500)->get();

        // 4. Buscar pasteles
        $resultadosBusqueda = Pastel::buscar('chocolate')->get();

        // 5. Actualizar pastel
        $pastel1->update([
            'precio' => 280.00
        ]);

        // 6. Eliminar pastel (soft delete)
        $pastel2->delete();

        // 7. Recuperar pasteles eliminados
        $eliminados = Pastel::onlyTrashed()->get();

        // 8. Restaurar pastel
        $pastel2->restore();

        // 9. Crear venta
        $venta = $pastel1->ventas()->create([
            'cantidad' => 2,
            'precio_total' => 560.00,
            'cliente_nombre' => 'Juan Pérez',
            'cliente_email' => 'juan@email.com',
            'fecha_venta' => now()
        ]);

        // 10. Usar accesors
        $nombreCompleto = $pastel1->nombre_completo;
        $precioConIva = $pastel1->precio_con_iva;

        // 11. Eager loading
        $pastelesConCategoria = Pastel::with('categoria')->get();

        // 12. Join de tabla pivot
        $pastelConIngredientes = Pastel::with('ingredientes')->find(1);

        return response()->json([
            'pasteles_disponibles' => $pastelesDisponibles,
            'pasteles_chocolate' => $pastelesChocolate,
            'pasteles_caros' => $pastelesCaros,
            'resultados_busqueda' => $resultadosBusqueda,
            'eliminados' => $eliminados,
            'venta' => $venta,
            'nombre_completo' => $nombreCompleto,
            'precio_con_iva' => $precioConIva,
            'pasteles_con_categoria' => $pastelesConCategoria,
            'pastel_con_ingredientes' => $pastelConIngredientes
        ]);
    }
}

Paso 4: Crear ruta de prueba

php
// routes/web.php
Route::get('/pruebas-pastel', [PastelPruebaController::class, 'index'])->name('pruebas.pastel');

Paso 5: Crear migraciones

php
// Crear migraciones para las tablas
php
// database/migrations/xxxx_create_categorias_table.php
Schema::create('categorias', function (Blueprint $table) {
    $table->id();
    $table->string('nombre', 100);
    $table->text('descripcion')->nullable();
    $table->timestamps();
});

// database/migrations/xxxx_create_pasteles_table.php
Schema::create('pasteles', function (Blueprint $table) {
    $table->id();
    $table->string('nombre', 100);
    $table->string('sabor', 50);
    $table->decimal('precio', 8, 2);
    $table->boolean('disponible')->default(true);
    $table->text('descripcion')->nullable();
    $table->foreignId('categoria_id')->constrained()->onDelete('cascade');
    $table->softDeletes();
    $table->timestamps();
});

// database/migrations/xxxx_create_ingredientes_table.php
Schema::create('ingredientes', function (Blueprint $table) {
    $table->id();
    $table->string('nombre', 100);
    $table->decimal('stock', 8, 2)->default(0);
    $table->string('unidad', 20);
    $table->timestamps();
});

// database/migrations/xxxx_create_pastel_ingrediente_table.php (pivot)
Schema::create('pastel_ingrediente', function (Blueprint $table) {
    $table->id();
    $table->foreignId('pastel_id')->constrained()->onDelete('cascade');
    $table->foreignId('ingrediente_id')->constrained()->onDelete('cascade');
    $table->decimal('cantidad', 8, 2);
    $table->string('unidad', 20);
    $table->timestamps();
});

// database/migrations/xxxx_create_ventas_table.php
Schema::create('ventas', function (Blueprint $table) {
    $table->id();
    $table->foreignId('pastel_id')->constrained()->onDelete('cascade');
    $table->integer('cantidad');
    $table->decimal('precio_total', 10, 2);
    $table->string('cliente_nombre', 100);
    $table->string('cliente_email', 100);
    $table->timestamp('fecha_venta');
    $table->timestamps();
});

Paso 6: Ejecutar y probar

bash
# Ejecutar migraciones
php artisan migrate

# Ir a la URL de prueba
http://localhost:8000/pruebas-pastel

Comandos Útiles

bash
# Crear modelo
php artisan make:model Pastel

# Crear modelo con migración
php artisan make:model Pastel -m

# Crear modelo con todos los recursos
php artisan make:model Pastel -a

# Crear modelo en subdirectorio
php artisan make:model Models/Pastel

# Crear controlador con modelo
php artisan make:controller PastelController --model=Pastel

# Ver consultas SQL ejecutadas
php artisan tinker
# Luego: DB::enableQueryLog(); Pastel::all(); DB::getQueryLog();

Resumen

ConceptoDescripciónEjemplo
ModeloRepresenta una tablaclass Pastel extends Model
FillableCampos asignables masivamenteprotected $fillable = ['nombre']
ScopesConsultas reutilizablespublic function scopeChocolate($query)
RelacionesConexiones entre modelosbelongsTo(), hasMany()
Eager LoadingCarga anticipada de relacioneswith('categoria')
Soft DeletesEliminación lógicause SoftDeletes
AccessorsTransformar datos al leergetNombreAttribute()
MutatorsTransformar datos al guardarsetNombreAttribute()

¡Con Eloquent puedes manejar tu base de datos de manera elegante y productiva en Laravel

Comentarios

Entradas más populares de este blog

Tutorial CRUD con Laravel 12-Pastel::factory(50)->create();

CRUD Completo con Laravel 12 - Guía Práctica Paso a Paso-productos

CRUD en Laravel 12 con Git - Guía para Principiantes