diff --git a/app/Console/Commands/ImportarExcelLimpio.php b/app/Console/Commands/ImportarExcelLimpio.php
new file mode 100644
index 0000000..ef2e364
--- /dev/null
+++ b/app/Console/Commands/ImportarExcelLimpio.php
@@ -0,0 +1,83 @@
+info("Iniciando importación del Excel limpio...");
+
+ // 1. Asegurarnos de que existe el proveedor comodín (para evitar el error de la llave foránea)
+ Supplier::firstOrCreate(
+ ['id' => 1],
+ [
+ 'name' => 'Proveedor General Lauck',
+ 'phone' => '0000000000'
+ ]
+ );
+
+ $rutaCompleta = storage_path('app/imports/catalogo_limpio.csv');
+
+ if (!file_exists($rutaCompleta)) {
+ $this->error("No se encontró el archivo en: " . $rutaCompleta);
+ return;
+ }
+ $file = fopen($rutaCompleta, 'r');
+
+ // Saltamos la primera fila (porque son los títulos: tipo, descripcion, costo, precio)
+ fgetcsv($file);
+
+ $contador = 0;
+
+ while (($fila = fgetcsv($file, 1000, ',')) !== false) {
+
+ // Asignamos las columnas del Excel (0 a 3)
+ $tipoRaw = strtolower(trim($fila[0] ?? ''));
+ $descripcion = trim($fila[1] ?? '');
+ $costoRaw = trim($fila[2] ?? '');
+ $precioRaw = trim($fila[3] ?? '');
+
+ // Si la descripción está vacía, saltamos la fila
+ if (empty($descripcion)) {
+ continue;
+ }
+
+ // --- Lógica de limpieza de Tipos ---
+ // Si en el Excel dice "bicicleta", lo guardamos como "bike" para mantener el estándar.
+ $tipoFinal = ($tipoRaw === 'bicicleta') ? 'bike' : $tipoRaw;
+
+ // --- Lógica de limpieza de Precios ("$ 7,200" -> 7200.0) ---
+ // Borramos el signo peso, los espacios y las comas de los miles
+ $costoLimpio = (float) str_replace(['$', ' ', ','], '', $costoRaw);
+ $precioLimpio = (float) str_replace(['$', ' ', ','], '', $precioRaw);
+
+ // Guardamos en la base de datos
+ Product::updateOrCreate(
+ ['name' => $descripcion], // Buscamos por descripción
+ [
+ 'type' => $tipoFinal,
+ 'cost' => $costoLimpio,
+ 'price' => $precioLimpio,
+ 'stock_quantity' => 20, // Al ser lista de precios, entra con stock 0
+ 'min_stock_alert' => 5,
+ 'suppliers_id' => 1,
+ ]
+ );
+
+ $contador++;
+ }
+
+ fclose($file);
+ $this->info("¡Éxito! Se importaron/actualizaron {$contador} productos.");
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/CatalogoController.php b/app/Http/Controllers/CatalogoController.php
index cc7ca16..e72942b 100644
--- a/app/Http/Controllers/CatalogoController.php
+++ b/app/Http/Controllers/CatalogoController.php
@@ -8,39 +8,39 @@ use Illuminate\Http\Request;
class CatalogoController extends Controller
{
/**
- * Muestra los registros de ventas con buscador y paginación.
+ * Muestra el catálogo público de productos.
*/
public function index(Request $request)
{
- // --- 1. CONSULTA PARA LA GRILLA (PAGINADA) ---
- $query = Product::query();
+ // 1. Atrapamos lo que el usuario escribió o seleccionó
+ $query = $request->input('search');
+ $type = $request->input('type');
- // Lógica del Buscador tradicional (por si seguís usando un input de texto además de Select2)
- if ($request->has('search')) {
- $searchTerm = $request->input('search');
+ // --- CONSULTA PARA LA GRILLA (PAGINADA) ---
+ $productsQuery = Product::query();
- $query->where(function($q) use ($searchTerm) {
- $q->where('name', 'like', "%{$searchTerm}%")
- ->orWhere('sku', 'like', "%{$searchTerm}%");
- });
+ $productsQuery->when($query, function ($q) use ($query) {
+ return $q->where('name', 'like', "%{$query}%");
+ });
+
+ if ($type) {
+ $productsQuery->where('type', $type);
+ } else {
+ $productsQuery->where('type', '!=', 'service');
}
- // Filtros base para la grilla
- $query->whereIn('type', ['bike', 'accessory', 'clothing', 'spare']);
- $query->where('stock_quantity', '>', 0); // Solo mostrar si tiene stock
+ //mostrar si tiene stock mayor a 0
+ $productsQuery->where('stock_quantity', '>', 0);
// Resultados paginados
- $products = $query->paginate(12)->withQueryString();
+ $products = $productsQuery->orderBy('name', 'asc')->paginate(12)->withQueryString();
- // --- 2. CONSULTA PARA EL BUSCADOR SELECT2 (TODOS) ---
- // Traemos todos los productos válidos para llenar el desplegable de búsqueda rápida.
- // Hacemos la misma validación de stock y tipo para no mostrar cosas agotadas.
- $allProducts = Product::whereIn('type', ['bike', 'accessory', 'clothing', 'spare'])
+ $allProducts = Product::where('type', '!=', 'service')
->where('stock_quantity', '>', 0)
- ->orderBy('name') // Ordenados alfabéticamente
+ ->orderBy('name', 'asc')
->get();
- // Devuelve la vista pasando ambas variables
+ // Devuelve la vista
return view('catalogo.index', compact('products', 'allProducts'));
}
@@ -48,4 +48,4 @@ class CatalogoController extends Controller
{
return view('catalogo.show', compact('product'));
}
-}
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/ProductosController.php b/app/Http/Controllers/ProductosController.php
index 3d78047..6ddeded 100644
--- a/app/Http/Controllers/ProductosController.php
+++ b/app/Http/Controllers/ProductosController.php
@@ -16,12 +16,15 @@ class ProductosController extends Controller
{
$query = $request->input('search');
$status = $request->input('stock_status');
+ $type = $request->input('type');
$products = Product::query()
->when($query, function ($q) use ($query) {
- // CORREGIDO: Filtramos solo por description
return $q->where('name', 'like', "%{$query}%");
})
+ ->when($type, function ($q) use ($type) {
+ return $q->where('type', $type);
+ })
->when($status, function ($q) use ($status) {
if ($status === 'low') {
return $q->whereColumn('stock_quantity', '<', 'min_stock_alert')
@@ -29,17 +32,17 @@ class ProductosController extends Controller
}
elseif ($status === 'medium') {
return $q->whereColumn('stock_quantity', '>=', 'min_stock_alert')
- ->whereRaw('stock_quantity <= (min_stock_alert + 1)')
+ ->whereRaw('stock_quantity <= (min_stock_alert + 5)')
->where('type', '!=', 'service');
}
elseif ($status === 'ok') {
- return $q->whereRaw('stock_quantity > (min_stock_alert + 1)')
+ return $q->whereRaw('stock_quantity > (min_stock_alert + 5)')
->where('type', '!=', 'service');
}
})
- ->orderBy('stock_quantity', 'asc')
- ->paginate(10)
- ->withQueryString();
+ ->orderBy('stock_quantity', 'asc') // Ordena primero los que tienen poco stock
+ ->paginate(10) // Muestra de a 10 productos
+ ->withQueryString(); // Mantiene los filtros al pasar a la página 2, 3, etc.
return view('productos.index', compact('products'));
}
@@ -64,7 +67,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,clothing,spare,service', // CORREGIDO: agregados nuevos tipos
+ 'type' => 'required|in:bike,accessory,clothing,spare,service,children,skate,rollers,other',
'serial_number' => 'nullable|string|max:100',
'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048',
]);
@@ -110,7 +113,7 @@ class ProductosController extends Controller
{
$validated = $request->validate([
'name' => 'required|string|max:255', // CORREGIDO
- 'type' => 'required|in:bike,accessory,clothing,spare,service', // CORREGIDO
+ 'type' => 'required|in:bike,accessory,clothing,spare,service,children,skate,rollers,other', // CORREGIDO
'price' => 'required|numeric|min:0',
'cost' => 'nullable|numeric|min:0',
'stock_quantity' => 'required|integer|min:0',
diff --git a/app/Http/Controllers/TallerController.php b/app/Http/Controllers/TallerController.php
index 2ff9ab0..07fa2b3 100644
--- a/app/Http/Controllers/TallerController.php
+++ b/app/Http/Controllers/TallerController.php
@@ -35,9 +35,9 @@ class TallerController extends Controller
{
$clients = Client::orderBy('name')->get();
- $products = Product::whereIn('type', ['accessory','spare'])
+ $products = Product::whereIn('type', ['accessory','spare', 'service'])
->where('stock_quantity', '>', 0)
- ->get(['id', 'name', 'price', 'sku', 'stock_quantity']); // Solo campos necesarios
+ ->get(['id', 'name', 'price', 'stock_quantity']); // Solo campos necesarios
return view('taller.create', compact('clients', 'products'));
}
diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php
index c72f08a..5690a35 100644
--- a/app/Models/Supplier.php
+++ b/app/Models/Supplier.php
@@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Model;
class Supplier extends Model
{
- protected $fillable = [ 'name', 'phone', 'email' ];
+ protected $fillable = [ 'name', 'phone'];
public function products()
{
diff --git a/database/migrations/2025_12_06_182356_create_suppliers_table.php b/database/migrations/2025_12_06_182356_create_suppliers_table.php
index ed43605..4e9f590 100644
--- a/database/migrations/2025_12_06_182356_create_suppliers_table.php
+++ b/database/migrations/2025_12_06_182356_create_suppliers_table.php
@@ -15,7 +15,7 @@ return new class extends Migration
$table->id();
$table->string('name');
$table->string('phone');
- $table->string('email');
+ $table->string('email')->nullable();
$table->timestamps();
});
}
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 51a2f95..fe306b5 100644
--- a/database/migrations/2025_12_06_182358_create_products_table.php
+++ b/database/migrations/2025_12_06_182358_create_products_table.php
@@ -13,7 +13,7 @@ return new class extends Migration
{
Schema::create('products', function (Blueprint $table) {
$table->id();
- $table->enum('type', ['bike', 'accessory', 'clothing', 'spare']); // Tipo de producto
+ $table->enum('type', ['bike', 'accessory', 'clothing', 'spare', 'children', 'skate', 'rollers','other']); // Tipo de producto
//$table->string('name');
//$table->string('sku')->unique()->nullable(); // Código de barras o interno
$table->text('name')->nullable();
diff --git a/resources/views/catalogo/index.blade.php b/resources/views/catalogo/index.blade.php
index 486a4b7..7096600 100644
--- a/resources/views/catalogo/index.blade.php
+++ b/resources/views/catalogo/index.blade.php
@@ -35,24 +35,64 @@
@endpush
-
No hay productos disponibles.
+No se encontraron productos con esos filtros.
@endif