plicación Web CRUD de Películas con Laravel

 

plicación Web CRUD de Películas con Laravel

Para Principiantes - Sin Resumen, Paso a Paso


📋 TABLA DE CONTENIDO

  1. Prerrequisitos e Instalación

  2. Configuración del Proyecto

  3. Configuración de Base de Datos

  4. Modelo, Migración y Seeder

  5. Rutas y Controladores

  6. Layouts y Vistas

  7. Vistas CRUD

  8. CSS y Assets

  9. Pruebas y Despliegue


1. PRERREQUISITOS E INSTALACIÓN

1.1 Instalar Composer (si no lo tienes)

  • Descarga de: https://getcomposer.org/

  • En Windows, usa el instalador .exe

  • En Linux/Mac:

    bash
    php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
    php composer-setup.php
    php -r "unlink('composer-setup.php');"
    sudo mv composer.phar /usr/local/bin/composer

1.2 Instalar Laravel

bash
# Instalar globalmente el instalador de Laravel
composer global require laravel/installer

# Crear nuevo proyecto (llamémoslo 'peliculas-app')
laravel new peliculas-app
# O alternativamente
composer create-project laravel/laravel peliculas-app

1.3 Entrar al directorio del proyecto

bash
cd peliculas-app

1.4 Instalar dependencias adicionales (opcional pero recomendado)

bash
composer require laravel/ui

2. CONFIGURACIÓN DEL PROYECTO

2.1 Configurar variables de entorno

Abre el archivo .env y configura:

env
APP_NAME=PeliculasApp
APP_ENV=local
APP_KEY=base64:...
APP_DEBUG=true
APP_URL=http://localhost

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=peliculas_db
DB_USERNAME=root
DB_PASSWORD=

# Configuración de correo (puedes dejarlo así por ahora)
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

2.2 Generar clave de aplicación

bash
php artisan key:generate

2.3 Servir la aplicación localmente

bash
# En una terminal ejecuta:
php artisan serve

Abre tu navegador en: http://localhost:8000


3. CONFIGURACIÓN DE BASE DE DATOS

3.1 Crear la base de datos en MySQL

sql
-- Abre MySQL desde terminal o phpMyAdmin
CREATE DATABASE peliculas_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

3.2 Verificar conexión

bash
php artisan migrate:status

4. MODELO, MIGRACIÓN Y SEEDER

4.1 Crear Modelo con Migración

bash
php artisan make:model Movie -m

4.2 Editar la migración

Abre database/migrations/xxxx_xx_xx_xxxxxx_create_movies_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('movies', function (Blueprint $table) {
            $table->id();
            $table->string('title', 200);
            $table->text('description')->nullable();
            $table->integer('year');
            $table->integer('duration')->comment('Duración en minutos');
            $table->string('director', 100);
            $table->string('genre', 100);
            $table->decimal('rating', 3, 1)->default(0.0);
            $table->string('poster_url')->nullable();
            $table->boolean('available')->default(true);
            $table->timestamps(); // created_at y updated_at automáticos
        });
    }

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

4.3 Ejecutar migración

bash
php artisan migrate

4.4 Crear Seeder

bash
php artisan make:seeder MovieSeeder

4.5 Editar el Seeder

Abre database/seeders/MovieSeeder.php:

php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\Movie;
use Illuminate\Support\Facades\DB;

class MovieSeeder extends Seeder
{
    public function run(): void
    {
        // Limpiar tabla primero
        DB::table('movies')->truncate();
        
        $movies = [
            [
                'title' => 'El Padrino',
                'description' => 'La saga de la familia Corleone, una poderosa familia mafiosa de Nueva York.',
                'year' => 1972,
                'duration' => 175,
                'director' => 'Francis Ford Coppola',
                'genre' => 'Crimen, Drama',
                'rating' => 9.2,
                'poster_url' => 'https://image.tmdb.org/t/p/w500/rSPw7tgCH9c6NqICZef4kZjFOQ5.jpg',
                'available' => true,
            ],
            [
                'title' => 'Pulp Fiction',
                'description' => 'Las vidas de dos matones, un boxeador y una pareja de atracadores se entrelazan.',
                'year' => 1994,
                'duration' => 154,
                'director' => 'Quentin Tarantino',
                'genre' => 'Crimen, Drama',
                'rating' => 8.9,
                'poster_url' => 'https://image.tmdb.org/t/p/w500/d5iIlFn5s0ImszYzBPb8JPIfbXD.jpg',
                'available' => true,
            ],
            [
                'title' => 'El Señor de los Anillos: El Retorno del Rey',
                'description' => 'Gandalf y Aragorn lideran el mundo de los hombres contra Sauron.',
                'year' => 2003,
                'duration' => 201,
                'director' => 'Peter Jackson',
                'genre' => 'Aventura, Fantasía',
                'rating' => 9.0,
                'poster_url' => 'https://image.tmdb.org/t/p/w500/rCzpDGLbOoPwLjy3OAm5NUPOTrC.jpg',
                'available' => true,
            ],
            [
                'title' => 'Origen',
                'description' => 'Un ladrón que roba secretos mediante el uso de tecnología para compartir sueños.',
                'year' => 2010,
                'duration' => 148,
                'director' => 'Christopher Nolan',
                'genre' => 'Ciencia Ficción, Acción',
                'rating' => 8.8,
                'poster_url' => 'https://image.tmdb.org/t/p/w500/9gk7adHYeDvHkCSEqAvQNLV5Uge.jpg',
                'available' => false,
            ],
            [
                'title' => 'Parásitos',
                'description' => 'Una familia pobre que se infiltra en una familia adinerada.',
                'year' => 2019,
                'duration' => 132,
                'director' => 'Bong Joon Ho',
                'genre' => 'Comedia, Drama, Thriller',
                'rating' => 8.6,
                'poster_url' => 'https://image.tmdb.org/t/p/w500/7IiTTgloJzvGI1TAYymCfbfl3vT.jpg',
                'available' => true,
            ],
        ];
        
        foreach ($movies as $movie) {
            Movie::create($movie);
        }
        
        $this->command->info('¡Películas insertadas correctamente!');
    }
}

4.6 Actualizar DatabaseSeeder

Abre database/seeders/DatabaseSeeder.php:

php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            MovieSeeder::class,
        ]);
    }
}

4.7 Ejecutar el Seeder

bash
php artisan db:seed
# O para refrescar todo
php artisan migrate:fresh --seed

4.8 Editar el Modelo

Abre app/Models/Movie.php:

php
<?php

namespace App\Models;

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

class Movie extends Model
{
    use HasFactory;

    // Campos que se pueden llenar masivamente
    protected $fillable = [
        'title',
        'description',
        'year',
        'duration',
        'director',
        'genre',
        'rating',
        'poster_url',
        'available'
    ];

    // Campos que deben ser convertidos a tipos específicos
    protected $casts = [
        'rating' => 'float',
        'available' => 'boolean',
        'year' => 'integer',
        'duration' => 'integer'
    ];

    // Validación de datos (opcional pero útil)
    public static $rules = [
        'title' => 'required|string|max:200',
        'description' => 'nullable|string',
        'year' => 'required|integer|min:1888|max:' . (date('Y') + 5),
        'duration' => 'required|integer|min:1|max:500',
        'director' => 'required|string|max:100',
        'genre' => 'required|string|max:100',
        'rating' => 'numeric|min:0|max:10',
        'poster_url' => 'nullable|url',
        'available' => 'boolean'
    ];
}

5. RUTAS Y CONTROLADORES

5.1 Crear Controlador de Películas

bash
php artisan make:controller MovieController --resource

5.2 Editar el Controlador

Abre app/Http/Controllers/MovieController.php:

php
<?php

namespace App\Http\Controllers;

use App\Models\Movie;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class MovieController extends Controller
{
    /**
     * Mostrar lista de todas las películas
     */
    public function index()
    {
        $movies = Movie::orderBy('title')->get();
        return view('movies.index', compact('movies'));
    }

    /**
     * Mostrar formulario para crear nueva película
     */
    public function create()
    {
        return view('movies.create');
    }

    /**
     * Guardar nueva película en la base de datos
     */
    public function store(Request $request)
    {
        // Validar datos
        $validator = Validator::make($request->all(), Movie::$rules);
        
        if ($validator->fails()) {
            return redirect()->back()
                ->withErrors($validator)
                ->withInput();
        }
        
        // Crear película
        Movie::create($request->all());
        
        return redirect()->route('movies.index')
            ->with('success', 'Película creada exitosamente!');
    }

    /**
     * Mostrar detalles de una película específica
     */
    public function show($id)
    {
        $movie = Movie::findOrFail($id);
        return view('movies.show', compact('movie'));
    }

    /**
     * Mostrar formulario para editar una película
     */
    public function edit($id)
    {
        $movie = Movie::findOrFail($id);
        return view('movies.edit', compact('movie'));
    }

    /**
     * Actualizar película en la base de datos
     */
    public function update(Request $request, $id)
    {
        // Validar datos
        $validator = Validator::make($request->all(), Movie::$rules);
        
        if ($validator->fails()) {
            return redirect()->back()
                ->withErrors($validator)
                ->withInput();
        }
        
        // Actualizar película
        $movie = Movie::findOrFail($id);
        $movie->update($request->all());
        
        return redirect()->route('movies.show', $id)
            ->with('success', 'Película actualizada exitosamente!');
    }

    /**
     * Eliminar una película
     */
    public function destroy($id)
    {
        $movie = Movie::findOrFail($id);
        $movie->delete();
        
        return redirect()->route('movies.index')
            ->with('success', 'Película eliminada exitosamente!');
    }
}

5.3 Configurar las Rutas

Abre routes/web.php:

php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\MovieController;

// Ruta principal - redirige a lista de películas
Route::get('/', function () {
    return redirect()->route('movies.index');
});

// Rutas CRUD para películas
Route::resource('movies', MovieController::class);

// Ruta adicional para buscar películas
Route::get('/search', [MovieController::class, 'search'])->name('movies.search');

// Ruta para información sobre la aplicación
Route::get('/about', function () {
    return view('about');
})->name('about');

5.4 Crear Ruta para Búsqueda

Agrega este método al MovieController:

php
/**
 * Buscar películas
 */
public function search(Request $request)
{
    $search = $request->input('search');
    
    $movies = Movie::where('title', 'like', "%$search%")
        ->orWhere('director', 'like', "%$search%")
        ->orWhere('genre', 'like', "%$search%")
        ->orWhere('description', 'like', "%$search%")
        ->orderBy('title')
        ->get();
    
    return view('movies.index', compact('movies', 'search'));
}

6. LAYOUTS Y VISTAS

6.1 Crear Layout Principal

Crea el archivo 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') - Películas App</title>
    
    <!-- Bootstrap 5 CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- Font Awesome -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <!-- CSS Personalizado -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
    
    @stack('styles')
</head>
<body>
    <!-- Barra de Navegación -->
    <nav class="navbar navbar-expand-lg navbar-dark bg-primary shadow">
        <div class="container">
            <a class="navbar-brand" href="{{ route('movies.index') }}">
                <i class="fas fa-film"></i> Películas App
            </a>
            
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                <span class="navbar-toggler-icon"></span>
            </button>
            
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item">
                        <a class="nav-link {{ request()->routeIs('movies.index') ? 'active' : '' }}" 
                           href="{{ route('movies.index') }}">
                            <i class="fas fa-home"></i> Inicio
                        </a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link {{ request()->routeIs('movies.create') ? 'active' : '' }}" 
                           href="{{ route('movies.create') }}">
                            <i class="fas fa-plus-circle"></i> Nueva Película
                        </a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link {{ request()->routeIs('about') ? 'active' : '' }}" 
                           href="{{ route('about') }}">
                            <i class="fas fa-info-circle"></i> Acerca de
                        </a>
                    </li>
                </ul>
                
                <!-- Buscador -->
                <form class="d-flex ms-3" action="{{ route('movies.search') }}" method="GET">
                    <div class="input-group">
                        <input type="text" class="form-control" name="search" 
                               placeholder="Buscar películas..." value="{{ request('search') ?? '' }}">
                        <button class="btn btn-light" type="submit">
                            <i class="fas fa-search"></i>
                        </button>
                    </div>
                </form>
            </div>
        </div>
    </nav>

    <!-- Contenido Principal -->
    <main class="py-4">
        <div class="container">
            <!-- Mensajes Flash -->
            @if(session('success'))
                <div class="alert alert-success alert-dismissible fade show" role="alert">
                    {{ 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" role="alert">
                    {{ session('error') }}
                    <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                </div>
            @endif
            
            @if($errors->any())
                <div class="alert alert-danger alert-dismissible fade show" role="alert">
                    <ul class="mb-0">
                        @foreach($errors->all() as $error)
                            <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                    <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                </div>
            @endif
            
            <!-- Contenido de la Página -->
            @yield('content')
        </div>
    </main>

    <!-- Footer -->
    <footer class="bg-dark text-white py-4 mt-5">
        <div class="container">
            <div class="row">
                <div class="col-md-6">
                    <h5><i class="fas fa-film"></i> Películas App</h5>
                    <p>Gestiona tu colección de películas favoritas</p>
                </div>
                <div class="col-md-6 text-md-end">
                    <p class="mb-0">
                        &copy; {{ date('Y') }} Películas App. Todos los derechos reservados.
                    </p>
                    <p class="mb-0">
                        Desarrollado con Laravel <i class="fas fa-heart text-danger"></i>
                    </p>
                </div>
            </div>
        </div>
    </footer>

    <!-- Bootstrap JS y Dependencias -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
    <!-- Scripts Personalizados -->
    <script src="{{ asset('js/app.js') }}"></script>
    
    @stack('scripts')
</body>
</html>

6.2 Crear Vista de Página Principal

Crea resources/views/home.blade.php:

html
@extends('layouts.app')

@section('title', 'Inicio')

@section('content')
<div class="row mb-4">
    <div class="col-12">
        <div class="jumbotron bg-light p-5 rounded">
            <h1 class="display-4"><i class="fas fa-film text-primary"></i> Bienvenido a Películas App</h1>
            <p class="lead">Gestiona tu colección de películas favoritas de manera sencilla y eficiente.</p>
            <hr class="my-4">
            <p>Desde aquí puedes ver todas las películas, agregar nuevas, editar existentes y mucho más.</p>
            <a class="btn btn-primary btn-lg" href="{{ route('movies.index') }}">
                <i class="fas fa-play-circle"></i> Ver Películas
            </a>
            <a class="btn btn-success btn-lg" href="{{ route('movies.create') }}">
                <i class="fas fa-plus"></i> Agregar Nueva
            </a>
        </div>
    </div>
</div>

<div class="row">
    <div class="col-md-4 mb-4">
        <div class="card h-100 text-center">
            <div class="card-body">
                <i class="fas fa-list fa-3x text-primary mb-3"></i>
                <h4 class="card-title">Ver Todas</h4>
                <p class="card-text">Consulta el listado completo de películas en tu colección.</p>
            </div>
            <div class="card-footer bg-transparent">
                <a href="{{ route('movies.index') }}" class="btn btn-outline-primary">Ir al Listado</a>
            </div>
        </div>
    </div>
    
    <div class="col-md-4 mb-4">
        <div class="card h-100 text-center">
            <div class="card-body">
                <i class="fas fa-plus-circle fa-3x text-success mb-3"></i>
                <h4 class="card-title">Agregar Nueva</h4>
                <p class="card-text">Añade nuevas películas a tu colección con todos sus detalles.</p>
            </div>
            <div class="card-footer bg-transparent">
                <a href="{{ route('movies.create') }}" class="btn btn-outline-success">Crear Película</a>
            </div>
        </div>
    </div>
    
    <div class="col-md-4 mb-4">
        <div class="card h-100 text-center">
            <div class="card-body">
                <i class="fas fa-search fa-3x text-info mb-3"></i>
                <h4 class="card-title">Buscar</h4>
                <p class="card-text">Encuentra películas por título, director, género o descripción.</p>
            </div>
            <div class="card-footer bg-transparent">
                <form action="{{ route('movies.search') }}" method="GET" class="input-group">
                    <input type="text" class="form-control" name="search" placeholder="Buscar...">
                    <button class="btn btn-outline-info" type="submit">
                        <i class="fas fa-search"></i>
                    </button>
                </form>
            </div>
        </div>
    </div>
</div>

<div class="row mt-5">
    <div class="col-12">
        <div class="card">
            <div class="card-header bg-info text-white">
                <h5 class="mb-0"><i class="fas fa-chart-line"></i> Estadísticas Rápidas</h5>
            </div>
            <div class="card-body">
                <div class="row text-center">
                    <div class="col-md-3">
                        <h3 class="text-primary">{{ \App\Models\Movie::count() }}</h3>
                        <p class="text-muted">Películas Totales</p>
                    </div>
                    <div class="col-md-3">
                        <h3 class="text-success">{{ \App\Models\Movie::where('available', true)->count() }}</h3>
                        <p class="text-muted">Disponibles</p>
                    </div>
                    <div class="col-md-3">
                        <h3 class="text-warning">{{ \App\Models\Movie::avg('rating') ? number_format(\App\Models\Movie::avg('rating'), 1) : '0.0' }}</h3>
                        <p class="text-muted">Rating Promedio</p>
                    </div>
                    <div class="col-md-3">
                        <h3 class="text-danger">{{ \App\Models\Movie::max('year') }}</h3>
                        <p class="text-muted">Año Más Reciente</p>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

6.3 Crear Vista About

Crea resources/views/about.blade.php:

html
@extends('layouts.app')

@section('title', 'Acerca de')

@section('content')
<div class="row justify-content-center">
    <div class="col-md-8">
        <div class="card">
            <div class="card-header bg-primary text-white">
                <h4 class="mb-0"><i class="fas fa-info-circle"></i> Acerca de Películas App</h4>
            </div>
            <div class="card-body">
                <h5 class="card-title">¿Qué es Películas App?</h5>
                <p class="card-text">
                    Películas App es una aplicación web desarrollada con Laravel que te permite 
                    gestionar tu colección personal de películas. Con esta aplicación puedes:
                </p>
                
                <ul class="list-group list-group-flush mb-4">
                    <li class="list-group-item">
                        <i class="fas fa-check text-success"></i> Ver todas tus películas en un listado organizado
                    </li>
                    <li class="list-group-item">
                        <i class="fas fa-check text-success"></i> Agregar nuevas películas con información detallada
                    </li>
                    <li class="list-group-item">
                        <i class="fas fa-check text-success"></i> Editar información de películas existentes
                    </li>
                    <li class="list-group-item">
                        <i class="fas fa-check text-success"></i> Eliminar películas de tu colección
                    </li>
                    <li class="list-group-item">
                        <i class="fas fa-check text-success"></i> Buscar películas por diferentes criterios
                    </li>
                    <li class="list-group-item">
                        <i class="fas fa-check text-success"></i> Ver detalles completos de cada película
                    </li>
                </ul>
                
                <h5 class="card-title">Características Técnicas</h5>
                <div class="row mt-3">
                    <div class="col-md-6">
                        <div class="card bg-light mb-3">
                            <div class="card-body">
                                <h6><i class="fas fa-code text-primary"></i> Tecnologías Utilizadas</h6>
                                <ul class="mb-0">
                                    <li>Laravel 10.x - Framework PHP</li>
                                    <li>Bootstrap 5 - Framework CSS</li>
                                    <li>MySQL - Base de Datos</li>
                                    <li>Blade - Motor de Plantillas</li>
                                </ul>
                            </div>
                        </div>
                    </div>
                    <div class="col-md-6">
                        <div class="card bg-light mb-3">
                            <div class="card-body">
                                <h6><i class="fas fa-cogs text-primary"></i> Características</h6>
                                <ul class="mb-0">
                                    <li>CRUD Completo</li>
                                    <li>Validación de Datos</li>
                                    <li>Diseño Responsivo</li>
                                    <li>Mensajes de Confirmación</li>
                                </ul>
                            </div>
                        </div>
                    </div>
                </div>
                
                <div class="alert alert-info mt-4">
                    <h6><i class="fas fa-lightbulb"></i> Para Principiantes</h6>
                    <p class="mb-0">
                        Esta aplicación está diseñada para ser un ejemplo educativo para quienes 
                        están comenzando con Laravel. Incluye todas las funcionalidades básicas 
                        de una aplicación web CRUD completa.
                    </p>
                </div>
                
                <div class="text-center mt-4">
                    <a href="{{ route('movies.index') }}" class="btn btn-primary">
                        <i class="fas fa-play"></i> Comenzar a Usar la App
                    </a>
                    <a href="{{ route('movies.create') }}" class="btn btn-success">
                        <i class="fas fa-plus"></i> Agregar Tu Primera Película
                    </a>
                </div>
            </div>
            <div class="card-footer text-muted text-center">
                Versión 1.0.0 | Desarrollado con <i class="fas fa-heart text-danger"></i> para la comunidad Laravel
            </div>
        </div>
    </div>
</div>
@endsection

7. VISTAS CRUD

7.1 Vista Index (Listado de Películas)

Crea resources/views/movies/index.blade.php:

html
@extends('layouts.app')

@section('title', 'Lista de Películas')

@section('content')
<div class="d-flex justify-content-between align-items-center mb-4">
    <h1><i class="fas fa-film text-primary"></i> Lista de Películas</h1>
    <a href="{{ route('movies.create') }}" class="btn btn-success">
        <i class="fas fa-plus-circle"></i> Nueva Película
    </a>
</div>

@if($movies->isEmpty())
    <div class="alert alert-info">
        <h4 class="alert-heading"><i class="fas fa-info-circle"></i> No hay películas registradas</h4>
        <p>Parece que aún no has agregado ninguna película a tu colección.</p>
        <hr>
        <a href="{{ route('movies.create') }}" class="btn btn-primary">
            <i class="fas fa-plus"></i> Agregar Mi Primera Película
        </a>
    </div>
@else
    <!-- Filtros -->
    <div class="card mb-4">
        <div class="card-body">
            <form action="{{ route('movies.search') }}" method="GET" class="row g-3">
                <div class="col-md-6">
                    <input type="text" class="form-control" name="search" 
                           placeholder="Buscar por título, director, género..." 
                           value="{{ request('search') ?? '' }}">
                </div>
                <div class="col-md-2">
                    <select class="form-select" name="genre">
                        <option value="">Todos los géneros</option>
                        @foreach($movies->pluck('genre')->unique() as $genre)
                            <option value="{{ $genre }}" {{ request('genre') == $genre ? 'selected' : '' }}>
                                {{ $genre }}
                            </option>
                        @endforeach
                    </select>
                </div>
                <div class="col-md-2">
                    <select class="form-select" name="available">
                        <option value="">Disponibilidad</option>
                        <option value="1" {{ request('available') == '1' ? 'selected' : '' }}>Disponible</option>
                        <option value="0" {{ request('available') == '0' ? 'selected' : '' }}>No Disponible</option>
                    </select>
                </div>
                <div class="col-md-2">
                    <button type="submit" class="btn btn-primary w-100">
                        <i class="fas fa-search"></i> Filtrar
                    </button>
                </div>
            </form>
            
            @if(request()->has('search') || request()->has('genre') || request()->has('available'))
                <div class="mt-3">
                    <a href="{{ route('movies.index') }}" class="btn btn-sm btn-outline-secondary">
                        <i class="fas fa-times"></i> Limpiar Filtros
                    </a>
                </div>
            @endif
        </div>
    </div>

    <!-- Tarjetas de Películas -->
    <div class="row">
        @foreach($movies as $movie)
            <div class="col-md-4 mb-4">
                <div class="card h-100 shadow-sm">
                    <!-- Badge de Disponibilidad -->
                    <div class="position-absolute top-0 end-0 m-2">
                        @if($movie->available)
                            <span class="badge bg-success">
                                <i class="fas fa-check-circle"></i> Disponible
                            </span>
                        @else
                            <span class="badge bg-danger">
                                <i class="fas fa-times-circle"></i> No Disponible
                            </span>
                        @endif
                    </div>
                    
                    <!-- Poster -->
                    <div class="text-center p-3">
                        @if($movie->poster_url)
                            <img src="{{ $movie->poster_url }}" 
                                 alt="{{ $movie->title }}" 
                                 class="img-fluid rounded" 
                                 style="max-height: 300px;">
                        @else
                            <div class="bg-light rounded d-flex align-items-center justify-content-center" 
                                 style="height: 300px;">
                                <i class="fas fa-film fa-5x text-muted"></i>
                            </div>
                        @endif
                    </div>
                    
                    <div class="card-body">
                        <h5 class="card-title">{{ $movie->title }}</h5>
                        <h6 class="card-subtitle mb-2 text-muted">
                            <i class="fas fa-user"></i> {{ $movie->director }} | 
                            <i class="fas fa-calendar"></i> {{ $movie->year }}
                        </h6>
                        
                        <p class="card-text">
                            @if(strlen($movie->description) > 100)
                                {{ substr($movie->description, 0, 100) }}...
                            @else
                                {{ $movie->description }}
                            @endif
                        </p>
                        
                        <div class="mb-3">
                            <span class="badge bg-info">
                                <i class="fas fa-tag"></i> {{ $movie->genre }}
                            </span>
                            <span class="badge bg-warning text-dark">
                                <i class="fas fa-clock"></i> {{ $movie->duration }} min
                            </span>
                            <span class="badge bg-primary">
                                <i class="fas fa-star"></i> {{ $movie->rating }}/10
                            </span>
                        </div>
                    </div>
                    
                    <div class="card-footer bg-transparent">
                        <div class="d-flex justify-content-between">
                            <a href="{{ route('movies.show', $movie->id) }}" 
                               class="btn btn-sm btn-outline-primary">
                                <i class="fas fa-eye"></i> Ver
                            </a>
                            <a href="{{ route('movies.edit', $movie->id) }}" 
                               class="btn btn-sm btn-outline-warning">
                                <i class="fas fa-edit"></i> Editar
                            </a>
                            <form action="{{ route('movies.destroy', $movie->id) }}" 
                                  method="POST" 
                                  class="d-inline"
                                  onsubmit="return confirm('¿Estás seguro de eliminar esta película?');">
                                @csrf
                                @method('DELETE')
                                <button type="submit" class="btn btn-sm btn-outline-danger">
                                    <i class="fas fa-trash"></i> Eliminar
                                </button>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        @endforeach
    </div>

    <!-- Paginación -->
    @if($movies->hasPages())
        <div class="d-flex justify-content-center mt-4">
            {{ $movies->links() }}
        </div>

Comentarios

Entradas más populares de este blog

Axios para Principiantes - Guía Paso a Paso

15-Tutorial: Crear Película en React con useState

Tutorial de React para Principiantes