diff --git a/app/Http/Controllers/CatalogoController.php b/app/Http/Controllers/CatalogoController.php index 8991f3e..82eecb7 100644 --- a/app/Http/Controllers/CatalogoController.php +++ b/app/Http/Controllers/CatalogoController.php @@ -20,11 +20,11 @@ class CatalogoController extends Controller $searchTerm = $request->input('search'); $query->where(function($q) use ($searchTerm) { - $q->where('name', 'like', "%{$searchTerm}%") // Buscar por nombre - ->orWhere('sku', 'like', "%{$searchTerm}%"); // O por código SKU + $q->where('name', 'like', "%{$searchTerm}%") + ->orWhere('sku', 'like', "%{$searchTerm}%"); }); } - $query->whereIn('type', ['bike', 'accessory']);// Filtros para el público + $query->whereIn('type', ['bike', 'accessory', 'clothing', 'spare']); $query->where('stock_quantity', '>', 0); // Solo mostrar si tiene stock // Resultados paginados diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index b456d43..da56e93 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -20,7 +20,6 @@ class ClientController extends Controller return $q->where('name', 'like', "%{$query}%"); }) ->orderBy('name', 'asc') - //->orderBy('create_at', 'asc') ->paginate(10) // Paginamos de a 10 ->withQueryString(); // Mantiene el filtro de búsqueda al cambiar de página @@ -53,13 +52,18 @@ class ClientController extends Controller // 2. Verificamos el origen if ($request->input('origin') === 'sales') { - // Si vino de ventas, volvemos a ventas - // Y pasamos el ID del nuevo cliente para auto-seleccionarlo + // Si vino de ventas, volvemos a ventas con el cliente nuevo return redirect()->route('sales.create', ['new_client_id' => $client->id]) ->with('success', 'Cliente creado. Ya puedes seleccionarlo.'); } + else if ($request->input('origin') === 'taller') { + + // Si vino de taller, volvemos a taller con el cliente nuevo + return redirect()->route('taller.create', ['new_client_id' => $client->id]) + ->with('success', 'Cliente creado. Ya puedes seleccionarlo.'); + } - // 3. Si no, comportamiento normal (volver al index de clientes) + // 3. Si no, vuelvo al index de clientes return redirect()->route('clients.index') ->with('success', 'Cliente creado correctamente.'); } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index fd970d4..24379cc 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -11,9 +11,9 @@ class HomeController extends Controller { // Si tuvieras un campo 'sales_count', podrías usar ->orderByDesc('sales_count') $destacados = Product::where('type', 'bike') - ->latest() // Las más nuevas - ->take(5) - ->get(); + ->latest() // Las más nuevas + ->take(5) + ->get(); // Si no hay bicis, traemos cualquier cosa para que no se rompa if ($destacados->isEmpty()) { diff --git a/app/Http/Controllers/LoginController.php b/app/Http/Controllers/LoginController.php index d198e28..3397c06 100644 --- a/app/Http/Controllers/LoginController.php +++ b/app/Http/Controllers/LoginController.php @@ -14,13 +14,13 @@ class LoginController extends Controller 'email' => ['required', 'email'], 'password' => ['required'], ]); - + if (Auth::attempt($credentials)) { $request->session()->regenerate(); - + return redirect()->intended('dashboard'); } - + return back()->withErrors([ 'email' => 'Los datos no coinciden.', ])->onlyInput('email'); diff --git a/app/Http/Controllers/ProductosController.php b/app/Http/Controllers/ProductosController.php index 92e1c74..c7e37e7 100644 --- a/app/Http/Controllers/ProductosController.php +++ b/app/Http/Controllers/ProductosController.php @@ -4,8 +4,8 @@ namespace App\Http\Controllers; use App\Models\Product; use Illuminate\Http\Request; -use Illuminate\Validation\Rule; // Necesario para validar unicidad al editar -use Illuminate\Support\Facades\Storage; // <--- IMPORTANTE: Agregar esto arriba +use Illuminate\Validation\Rule; +use Illuminate\Support\Facades\Storage; class ProductosController extends Controller { @@ -14,33 +14,30 @@ class ProductosController extends Controller */ public function index(Request $request) { - // Recuperamos lo que el usuario escribió en el buscador (si escribió algo) + // Recuperamos lo escrito en el buscador (si aplica) $query = $request->input('search'); $status = $request->input('stock_status'); // Construimos la consulta $products = Product::query() ->when($query, function ($q) use ($query) { - // Si hay búsqueda, filtra por nombre o SKU + // Filtra por nombre o SKU return $q->where('name', 'like', "%{$query}%") - ->orWhere('sku', 'like', "%{$query}%"); + ->orWhere('sku', 'like', "%{$query}%"); }) ->when($status, function ($q) use ($status) { if ($status === 'low') { // Rojo: Menor o igual a la alerta - return $q->whereColumn('stock_quantity', '<', 'min_stock_alert') - ->where('type', '!=', 'service'); // Ignoramos servicios + return $q->whereColumn('stock_quantity', '<', 'min_stock_alert'); } elseif ($status === 'medium') { - // Amarillo: Mayor a alerta PERO menor o igual a alerta + 2 (margen pequeño) + // Amarillo: Mayor al min y menor o igual al min + 5 return $q->whereColumn('stock_quantity', '>=', 'min_stock_alert') - ->whereRaw('stock_quantity <= (min_stock_alert + 1)') // Ajusta este "+ 5" según tu criterio de "amarillo" - ->where('type', '!=', 'service'); + ->whereRaw('stock_quantity <= (min_stock_alert + 5)'); } elseif ($status === 'ok') { // Verde: Stock saludable - return $q->whereRaw('stock_quantity > (min_stock_alert + 1)') - ->where('type', '!=', 'service'); + return $q->whereRaw('stock_quantity > (min_stock_alert + 5)'); } }) ->orderBy('stock_quantity', 'asc') // Ordenamos primero los que tienen poco stock (Alerta visual) @@ -72,7 +69,7 @@ class ProductosController extends Controller 'cost' => 'nullable|numeric|min:0', // Costo opcional 'stock_quantity' => 'required|integer|min:0', 'min_stock_alert' => 'required|integer|min:0', - 'type' => 'required|in:bike,accessory,service', // Solo permite estos 3 valores + 'type' => 'required|in:bike,accessory,clothing,spare', // Solo permite estos valores 'serial_number' => 'nullable|string|max:100', 'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048', ]); @@ -93,6 +90,7 @@ class ProductosController extends Controller // Para no romper la logica del supplier $validated['suppliers_id'] = 1; + // 3. Creamos el producto Product::create($validated); @@ -103,7 +101,6 @@ class ProductosController extends Controller /** * Muestra el detalle de un producto. - * Usamos Route Model Binding: Laravel busca el ID solo. */ public function show(Product $product) { @@ -132,7 +129,7 @@ class ProductosController extends Controller 'cost' => 'nullable|numeric|min:0', 'stock_quantity' => 'required|integer|min:0', 'min_stock_alert' => 'required|integer|min:0', - 'type' => 'required|in:bike,accessory,service', + 'type' => 'required|in:bike,accessory,clothing,spare', 'serial_number' => 'nullable|string|max:100', 'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048', diff --git a/app/Http/Controllers/RegisterController.php b/app/Http/Controllers/RegisterController.php index 39f4fe1..9185db0 100644 --- a/app/Http/Controllers/RegisterController.php +++ b/app/Http/Controllers/RegisterController.php @@ -18,7 +18,6 @@ class RegisterController extends Controller ]); $userData['password'] = bcrypt($userData['password']); - $user = User::create($userData); Auth::login($user); diff --git a/app/Http/Controllers/TallerController.php b/app/Http/Controllers/TallerController.php index fd4725a..2ff9ab0 100644 --- a/app/Http/Controllers/TallerController.php +++ b/app/Http/Controllers/TallerController.php @@ -35,7 +35,7 @@ class TallerController extends Controller { $clients = Client::orderBy('name')->get(); - $products = Product::whereIn('type', ['accessory']) + $products = Product::whereIn('type', ['accessory','spare']) ->where('stock_quantity', '>', 0) ->get(['id', 'name', 'price', 'sku', 'stock_quantity']); // Solo campos necesarios diff --git a/app/Models/Sale.php b/app/Models/Sale.php index 67f7dcb..7057337 100644 --- a/app/Models/Sale.php +++ b/app/Models/Sale.php @@ -8,22 +8,22 @@ class Sale extends Model { //use HasFactory; - // Permitimos asignación masiva para poder guardar rápido + // Permitimos asignación masiva protected $guarded = []; - // Relación 1: Una venta la realiza un Usuario (User) + // Rel 1: Una venta la hace un Usuario public function user() { return $this->belongsTo(User::class); } - // Relación 2: Una venta pertenece a un Cliente (User) + // Rel 2: Una venta pertenece a un Cliente public function client() { return $this->belongsTo(Client::class); } - // Relación 3: Una venta tiene muchos items o detalles + // Rel 3: Una venta tiene muchos items public function details() { return $this->hasMany(SaleDetail::class); diff --git a/app/Models/SaleDetail.php b/app/Models/SaleDetail.php index 597eff4..94dcac5 100644 --- a/app/Models/SaleDetail.php +++ b/app/Models/SaleDetail.php @@ -9,13 +9,13 @@ class SaleDetail extends Model //use HasFactory; protected $guarded = []; - // este detalle pertenece a una Venta específica + // Rel 1: Este detalle pertenece a una Venta específica public function sale() { return $this->belongsTo(Sale::class); } - // este detalle corresponde a un Producto + // Rel 2: Este detalle pertenece a un Producto public function product() { return $this->belongsTo(Product::class); diff --git a/config/app.php b/config/app.php index 423eed5..25f728f 100644 --- a/config/app.php +++ b/config/app.php @@ -78,7 +78,7 @@ return [ | */ - 'locale' => env('APP_LOCALE', 'en'), + 'locale' => env('APP_LOCALE', 'es'), 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), diff --git a/database/factories/ProductFactory.php b/database/factories/ProductFactory.php index 2ab688a..2ce0624 100644 --- a/database/factories/ProductFactory.php +++ b/database/factories/ProductFactory.php @@ -19,23 +19,24 @@ class ProductFactory extends Factory $brands = ['Shimano', 'Venzo', 'Trek', 'Specialized', 'Maxxis', 'Sram', 'Raleigh']; $bikeModels = ['Loki', 'Marlin 5', 'Chisel', 'Talon 3', 'Aspect 950']; $accessories = ['Guantes Grip', 'Calco Reflectora', 'Coderas', 'Rodilleras', 'Casco MTB', 'Luz Delantera USB', 'Cubierta Kevlar']; - // PARA DESPUES $spareParts = ['Manubrio', 'Pedales Aluminio', 'Cámara 29"', 'Rayos x50', 'Disco de Freno', 'Cable de Freno', 'Asiento Goma', 'Cadena 9v']; - - $type = $this->faker->randomElement(['bike', 'accessory', 'service']); + $cats = ['Pro', 'Basic', 'Comp', 'Elite']; + $type = $this->faker->randomElement(['bike', 'accessory', 'clothing', 'spare']); // Generar nombre según el tipo if ($type === 'bike') { $name = $this->faker->randomElement($brands) . ' ' . $this->faker->randomElement($bikeModels); } elseif ($type === 'accessory') { - $name = $this->faker->randomElement($accessories) . ' ' . $this->faker->randomElement(['Pro', 'Basic', 'Comp', 'Elite']); + $name = $this->faker->randomElement($accessories) .' '. $this->faker->randomElement($cats); + } elseif ($type === 'clothing') { + $name = $this->faker->randomElement(['Remera', 'Pantalon', 'Casco', 'Coderas', 'Guantes']).' '.$this->faker->randomElement($cats); } else { - $name = $this->faker->randomElement(['Service General', 'Ajuste Cambios', 'Centrado de Rueda', 'Lavado y Engrase']); + $name = $this->faker->randomElement($spareParts) .' '. $this->faker->randomElement($cats); } // Lógica de Precios - $price = $this->faker->numberBetween(5000, 800000); - $cost = $price * $this->faker->randomFloat(2, 0.5, 0.7); + $price = $this->faker->numberBetween(5000, 200000); + $cost = $price * $this->faker->randomFloat(2, 0.1, 0.8); return [ 'name' => $name, diff --git a/database/migrations/2025_12_06_063046_add_role_to_users_table.php b/database/migrations/2025_12_06_063046_add_role_to_users_table.php index 78352f6..de5bce5 100644 --- a/database/migrations/2025_12_06_063046_add_role_to_users_table.php +++ b/database/migrations/2025_12_06_063046_add_role_to_users_table.php @@ -12,7 +12,7 @@ return new class extends Migration public function up(): void { Schema::table('users', function (Blueprint $table) { - $table->enum('role', ['admin', 'employee'])->default('employee')->after('email'); + $table->enum('role', ['admin', 'employee', 'guest'])->default('guest')->after('email'); }); } diff --git a/database/migrations/2025_12_06_182358_create_products_table.php b/database/migrations/2025_12_06_182358_create_products_table.php index ea3aba5..69f735f 100644 --- a/database/migrations/2025_12_06_182358_create_products_table.php +++ b/database/migrations/2025_12_06_182358_create_products_table.php @@ -23,7 +23,7 @@ return new class extends Migration $table->integer('stock_quantity')->default(0); $table->integer('min_stock_alert'); // Alerta - $table->enum('type', ['bike', 'accessory', 'service']); + $table->enum('type', ['bike', 'accessory', 'clothing', 'spare']); // Tipo de producto $table->string('serial_number')->nullable(); // Solo para bicis $table->foreignId('suppliers_id')->constrained()->default(1); diff --git a/database/migrations/2026_01_23_135512_add_taller_fields_to_appointments_table.php b/database/migrations/2026_01_23_135512_add_taller_fields_to_appointments_table.php index 5f1690e..3239982 100644 --- a/database/migrations/2026_01_23_135512_add_taller_fields_to_appointments_table.php +++ b/database/migrations/2026_01_23_135512_add_taller_fields_to_appointments_table.php @@ -15,7 +15,7 @@ return new class extends Migration // Repuestos estimados (texto simple por ahora) $table->text('parts_needed')->nullable()->after('problem_description'); - // Costo Aproximado (Presupuesto) + // Costo (Presupuesto) $table->decimal('estimated_cost', 10, 2)->nullable()->after('parts_needed'); // Teléfono de contacto rápido (por si es distinto al del cliente registrado) diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 925158c..dc52494 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -16,7 +16,7 @@ class DatabaseSeeder extends Seeder { //Crear el Super Admin User::factory()->create([ - 'name' => 'Jose Admin', + 'name' => 'Sergio Lauck', 'email' => 'admin@lauck.com', 'password' => bcrypt('password'), // Cambiar en producción 'role' => 'admin', @@ -60,7 +60,7 @@ class DatabaseSeeder extends Seeder 'suppliers_id' => 1 ]); - // Generar 10 productos aleatorios más + // Generar 50 productos aleatorios más Product::factory(50)->create(); // Clientes y Turnos diff --git a/resources/views/catalogo/show.blade.php b/resources/views/catalogo/show.blade.php index a7834cb..4e94d0c 100644 --- a/resources/views/catalogo/show.blade.php +++ b/resources/views/catalogo/show.blade.php @@ -27,11 +27,17 @@
- Categoria: {{ $producto->Categoria }} + Categoria: {{ $producto->type }}
{{ $producto->content }} diff --git a/resources/views/taller/create.blade.php b/resources/views/taller/create.blade.php index 0b718dc..70fa74e 100644 --- a/resources/views/taller/create.blade.php +++ b/resources/views/taller/create.blade.php @@ -60,7 +60,7 @@
Sin repuestos seleccionados