diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php new file mode 100644 index 0000000..fcf5db8 --- /dev/null +++ b/app/Http/Controllers/ClientController.php @@ -0,0 +1,64 @@ +', 0)->get(); + + $clients = \App\Models\Client::orderBy('nombre')->get(); + + return view('sales.create', compact('products', 'clients')); + } + + public function store(Request $request) + { + $request->validate([ + 'payment_method' => 'required|string', + 'client_id' => 'nullable|exists:clients,id', + 'items' => 'required|array', + 'items.*.product_id' => 'required|exists:products,id', + 'items.*.quantity' => 'required|integer|min:1', + ]); + + try { + DB::transaction(function () use ($request) { + $totalVenta = 0; + + foreach ($request->items as $item) { + $product = Product::find($item['product_id']); + $totalVenta += $product->precio * $item['quantity']; + + if ($product->stock < $item['quantity']) { + throw new \Exception("No hay suficiente stock de " . $product->nombre); + } + } + + //cabecera + $sale = Sale::create([ + //'user_id' => auth()->id(), //empleado logueado + 'client_id' => $request->client_id, + 'total' => $totalVenta, + 'payment_method' => $request->payment_method, + ]); + + foreach ($request->items as $item) { + $product = Product::find($item['product_id']); + + SaleDetail::create([ + 'sale_id' => $sale->id, + 'product_id' => $product->id, + 'cantidad' => $item['quantity'], + 'precio' => $product->precio, + ]); + $nuevoStock = $product->stock - $item['quantity']; + $product->stock = $nuevoStock; + $product->save(); + } + }); + + // 5. REDIRECCIÓN DE ÉXITO + // Redirigimos al usuario con un mensaje flash [cite: 668] + return redirect()->route('sales.create')->with('success', '¡Venta registrada correctamente!'); + + } catch (\Exception $e) { + // Si algo falló (ej: falta stock), volvemos atrás con el error + return back()->with('error', 'Error en la venta: ' . $e->getMessage()); + } + } +} diff --git a/app/Models/Client.php b/app/Models/Client.php index 236bd9b..6286e3f 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -8,9 +8,16 @@ use Illuminate\Database\Eloquent\Model; class Client extends Model { use HasFactory; - + protected $guarded = []; protected $fillable = ['name', 'phone', 'email', 'address']; + // Un usuario (cliente) realiza muchas compras (ventas) + public function sales() + { + return $this->hasMany(Sale::class); + } + + // Relación: Un cliente tiene muchos turnos public function appointments() { diff --git a/app/Models/Product.php b/app/Models/Product.php index bda64e3..25d29cf 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -8,21 +8,34 @@ use Illuminate\Database\Eloquent\Model; class Product extends Model { use HasFactory; + protected $guarded = []; - protected $fillable = [ - 'name', - 'sku', - 'description', - 'price', - 'cost', - 'stock_quantity', - 'min_stock_alert', - 'type', - 'serial_number' - ]; public function hasLowStock(): bool { + //cambiar return $this->stock_quantity <= $this->min_stock_alert; } + + public function supplier() + { + return $this->belongsTo(Suppliers::class); + } + + // Opción 1: Relación directa con los detalles (Renglones de ticket) + // Útil para saber cantidad total vendida: $product->saleDetails->sum('quantity') + public function saleDetails() + { + return $this->hasMany(SaleDetail::class); + } + + // Opción 2 (Pro Tip): Relación directa con las Ventas a través de los detalles + // Útil para saber EN QUÉ fechas se vendió: $product->sales + public function sales() + { + return $this->belongsToMany(Sale::class, 'sale_details'); + } } +/**$user->sales: Te da la lista de todas las compras de ese usuario. + +$product->saleDetails->count(): Te dice cuántas veces aparece ese producto en tickets.**/ \ No newline at end of file diff --git a/app/Models/Sale.php b/app/Models/Sale.php new file mode 100644 index 0000000..aeeca90 --- /dev/null +++ b/app/Models/Sale.php @@ -0,0 +1,25 @@ +belongsTo(User::class); + } + + // Relación 2: Una venta tiene muchos items o detalles + public function details() + { + return $this->hasMany(SaleDetail::class); + } +} diff --git a/app/Models/SaleDetail.php b/app/Models/SaleDetail.php new file mode 100644 index 0000000..597eff4 --- /dev/null +++ b/app/Models/SaleDetail.php @@ -0,0 +1,23 @@ +belongsTo(Sale::class); + } + + // este detalle corresponde a un Producto + public function product() + { + return $this->belongsTo(Product::class); + } +} \ No newline at end of file diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php new file mode 100644 index 0000000..8acf047 --- /dev/null +++ b/app/Models/Supplier.php @@ -0,0 +1,18 @@ +hasMany(Product::class); + } +} diff --git a/database/migrations/2025_12_06_182356_create_suppliers_table.php b/database/migrations/2025_12_06_182356_create_suppliers_table.php new file mode 100644 index 0000000..c5e0c12 --- /dev/null +++ b/database/migrations/2025_12_06_182356_create_suppliers_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('nombre'); + $table->string('telefono'); + $table->string('email'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('suppliers'); + } +}; diff --git a/database/migrations/2025_08_05_005156_create_products_table.php b/database/migrations/2025_12_06_182358_create_products_table.php similarity index 94% rename from database/migrations/2025_08_05_005156_create_products_table.php rename to database/migrations/2025_12_06_182358_create_products_table.php index d044a7f..5b743f7 100644 --- a/database/migrations/2025_08_05_005156_create_products_table.php +++ b/database/migrations/2025_12_06_182358_create_products_table.php @@ -26,6 +26,7 @@ return new class extends Migration $table->enum('type', ['bike', 'accessory', 'service']); $table->string('serial_number')->nullable(); // Solo para bicis + $table->foreignId('suppliers_id')->constrained(); $table->timestamps(); }); } @@ -37,4 +38,4 @@ return new class extends Migration { Schema::dropIfExists('products'); } -}; +}; \ No newline at end of file diff --git a/database/migrations/2025_12_06_184833_create_clients_table.php b/database/migrations/2025_12_06_184833_create_clients_table.php new file mode 100644 index 0000000..398e55b --- /dev/null +++ b/database/migrations/2025_12_06_184833_create_clients_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('nombre'); + $table->string('telefono'); + $table->string('email'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('clients'); + } +}; diff --git a/database/migrations/2025_12_06_193913_create_sales_table.php b/database/migrations/2025_12_06_193913_create_sales_table.php new file mode 100644 index 0000000..2125264 --- /dev/null +++ b/database/migrations/2025_12_06_193913_create_sales_table.php @@ -0,0 +1,32 @@ +id(); + //$table->foreignId('user_id')->constrained(); + // nullable para que no sea obligatrio + $table->foreignId('client_id')->nullable()->constrained(); + $table->decimal('total', 10, 2); + $table->string('payment_method'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sales'); + } +}; diff --git a/database/migrations/2025_12_06_201853_create_sale_details_table.php b/database/migrations/2025_12_06_201853_create_sale_details_table.php new file mode 100644 index 0000000..e357fa9 --- /dev/null +++ b/database/migrations/2025_12_06_201853_create_sale_details_table.php @@ -0,0 +1,31 @@ +id(); + $table->foreignId('sale_id')->constrained()->onDelete('cascade'); + $table->foreignId('product_id')->constrained(); + $table->integer('cantidad'); + $table->decimal('precio', 10, 2); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sale_details'); + } +}; diff --git a/database/migrations/2025_12_07_023045_create_appointments_table.php b/database/migrations/2025_12_07_023045_create_appointments_table.php new file mode 100644 index 0000000..3bac73b --- /dev/null +++ b/database/migrations/2025_12_07_023045_create_appointments_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('client_id')->constrained(); + $table->datetime('fecha_programada'); + $table->string('modelo_bici')->nullable(); + $table->string('descripcion')->nullable(); + $table->string('estado')->default('pendiente'); + $table->string('notas')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('appointments'); + } +}; diff --git a/resources/views/clients/create.blade.php b/resources/views/clients/create.blade.php new file mode 100644 index 0000000..6265ad0 --- /dev/null +++ b/resources/views/clients/create.blade.php @@ -0,0 +1,5 @@ +@extends('layouts.app') +@section('main') +

formulario de crear cliente

+ +@endsection \ No newline at end of file diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index 7bf6f93..22715e9 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -42,11 +42,10 @@
  • - - Ventas - -
  • + + Ventas + diff --git a/resources/views/sales/create.blade.php b/resources/views/sales/create.blade.php new file mode 100644 index 0000000..fd20768 --- /dev/null +++ b/resources/views/sales/create.blade.php @@ -0,0 +1,227 @@ +@extends('layouts.app') + +@section('title', 'Nueva Venta') + +@section('main') +
    +
    + +
    +
    +

    Nueva Venta en Mostrador

    +
    + + @if (session('success')) +
    + ¡Éxito! {{ session('success') }} +
    + @endif + + @if (session('error')) +
    + Error: {{ session('error') }} +
    + @endif + + @if ($errors->any()) +
    +
      + @foreach ($errors->all() as $error) +
    • • {{ $error }}
    • + @endforeach +
    +
    + @endif + + + +
    + @csrf + +
    + +
    + + + + + + +
    +
    + +
    +
    +
    + + +
    + +
    + + +
    + +
    + $0.00 +
    + +
    + +
    +
    +
    + +
    + +
    + +
    +
    + Total a Pagar: $0.00 +
    +
    + +
    +
    + + +
    + +
    + + Cancelar + + +
    +
    +
    +
    +
    +
    + +@push('scripts') + +@endpush +@endsection \ No newline at end of file diff --git a/resources/views/sales/index.blade.php b/resources/views/sales/index.blade.php new file mode 100644 index 0000000..8174db7 --- /dev/null +++ b/resources/views/sales/index.blade.php @@ -0,0 +1,12 @@ + + + + + + + Document + + +

    Gwssssssssssssssss

    + + \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 11f21bc..bd87c32 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,6 +2,8 @@ use App\Http\Controllers\AuthController; use App\Http\Controllers\CatalogoController; +use App\Http\Controllers\SaleController; +use App\Http\Controllers\ClientController; use Illuminate\Support\Facades\Route; use App\Http\Controllers\HomeController; use App\Http\Controllers\LoginController; @@ -41,10 +43,14 @@ Route::middleware(['auth'])->group(function () { Route::resource('productos', ProductosController::class)->parameters([ 'productos' => 'producto' ]); - Route::get('/productos',[ProductosController::class,'index'])->name('productos.index'); + Route::get('/productos/{id}', [ProductosController::class,'show'])->name('productos.show'); + Route::get('/productos/{id}/edit', [ProductosController::class,'edit'])->name('productos.edit'); Route::get('/checker', [ProductosController::class, 'checker'])->name('productos.checker'); - Route::get('/sales', [CatalogoController::class,'index'])->name('sales.index'); + //Route::get('/sales', [SaleController::class, 'index'])->name('sales.index'); + Route::get('/sales/create', [SaleController::class, 'create'])->name('sales.create'); + Route::resource('clients', ClientController::class); }); + Route::get('/catalogo', [CatalogoController::class,'catalogo'])->name('catalogo'); Route::get('/productos/{id}/vistaUsuario', [ProductosController::class,'vistaUsuario'])->name('productos.vistaUsuario'); \ No newline at end of file