diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 948ae5e..cb8e726 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -40,10 +40,12 @@ class ClientController extends Controller public function store(Request $request) { $validated = $request->validate([ - 'name' => 'required|string|max:255', + 'name' => ['required', 'string', 'max:255', 'not_regex:/[0-9]/'], 'phone' => 'nullable|string|max:50', 'email' => 'nullable|email|max:255|unique:clients,email', 'address' => 'nullable|string|max:255', + ], [ + 'name.not_regex' => 'El nombre del cliente no puede contener números.', ]); // 1. Guardamos el cliente en una variable para tener su ID @@ -88,10 +90,12 @@ class ClientController extends Controller public function update(Request $request, Client $client) { $validated = $request->validate([ - 'name' => 'required|string|max:255', + 'name' => ['required', 'string', 'max:255', 'not_regex:/[0-9]/'], 'phone' => 'nullable|string|max:50', 'email' => 'nullable|email|max:255|unique:clients,email,' . $client->id, // Ignorar email propio 'address' => 'nullable|string|max:255', + ], [ + 'name.not_regex' => 'El nombre del cliente no puede contener números.', ]); $client->update($validated); diff --git a/app/Http/Controllers/NewPasswordController.php b/app/Http/Controllers/NewPasswordController.php new file mode 100644 index 0000000..e7e01c4 --- /dev/null +++ b/app/Http/Controllers/NewPasswordController.php @@ -0,0 +1,45 @@ + $request, 'token' => $token]); + } + + public function store(Request $request) + { + $request->validate([ + 'token' => 'required', + 'email' => 'required|email', + 'password' => 'required|min:8|confirmed', + ]); + + $status = Password::reset( + $request->only('email', 'password', 'password_confirmation', 'token'), + function ($user, $password) { + $user->forceFill([ + 'password' => Hash::make($password) + ])->setRememberToken(Str::random(60)); + + $user->save(); + + event(new PasswordReset($user)); + } + ); + + if ($status == Password::PASSWORD_RESET) { + return redirect()->route('login')->with('status', __($status)); + } + + return back()->withErrors(['email' => [__($status)]]); + } +} diff --git a/app/Http/Controllers/PasswordResetLinkController.php b/app/Http/Controllers/PasswordResetLinkController.php new file mode 100644 index 0000000..cca2052 --- /dev/null +++ b/app/Http/Controllers/PasswordResetLinkController.php @@ -0,0 +1,31 @@ +validate([ + 'email' => 'required|email', + ]); + + $status = Password::sendResetLink( + $request->only('email') + ); + + if ($status == Password::RESET_LINK_SENT) { + return back()->with('status', __($status)); + } + + return back()->withErrors(['email' => __($status)]); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 5c0bb00..d806001 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -6,6 +6,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use App\Notifications\ResetPasswordNotification; class User extends Authenticatable { @@ -45,4 +46,15 @@ class User extends Authenticatable 'password' => 'hashed', ]; } + + /** + * Send the password reset notification. + * + * @param string $token + * @return void + */ + public function sendPasswordResetNotification($token) + { + $this->notify(new ResetPasswordNotification($token)); + } } diff --git a/app/Notifications/ResetPasswordNotification.php b/app/Notifications/ResetPasswordNotification.php new file mode 100644 index 0000000..d2bcfeb --- /dev/null +++ b/app/Notifications/ResetPasswordNotification.php @@ -0,0 +1,26 @@ + $this->token, + 'email' => $notifiable->getEmailForPasswordReset(), + ], false)); + + return (new MailMessage) + ->subject('Recuperación de Contraseña - Lauck') + ->view('emails.reset-password', [ + 'url' => $url, + ]); + } +} diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php new file mode 100644 index 0000000..3c6e665 --- /dev/null +++ b/resources/views/auth/forgot-password.blade.php @@ -0,0 +1,51 @@ + +
+ + +
+
+ ¿Olvidaste tu contraseña? No hay problema. Simplemente déjanos saber tu dirección de correo electrónico y te enviaremos un enlace para restablecer la contraseña que te permitirá elegir una nueva. +
+ + @if (session('status')) +
+ {{ session('status') }} +
+ @endif + +
+ @csrf + + @if ($errors->any()) +
+

+ {{ $errors->first() }} +

+
+ @endif + + +
+ + +
+ +
+ + Volver a inicio de sesión + + + +
+
+
+
+
diff --git a/resources/views/auth/reset-password.blade.php b/resources/views/auth/reset-password.blade.php new file mode 100644 index 0000000..7c7eb32 --- /dev/null +++ b/resources/views/auth/reset-password.blade.php @@ -0,0 +1,56 @@ + +
+ + +
+
+ @csrf + + + + + @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+ +
+
+
+
+
diff --git a/resources/views/catalogo/show.blade.php b/resources/views/catalogo/show.blade.php index cfc1485..4fa2ddf 100644 --- a/resources/views/catalogo/show.blade.php +++ b/resources/views/catalogo/show.blade.php @@ -102,7 +102,7 @@ {{-- Editar -
+ @csrf @method('DELETE')
+
+ +

¿Estás seguro de que quieres eliminar este registro?

+

Esta acción no se puede deshacer.

+ + +
+ + + + + diff --git a/resources/views/emails/reset-password.blade.php b/resources/views/emails/reset-password.blade.php new file mode 100644 index 0000000..c7795fa --- /dev/null +++ b/resources/views/emails/reset-password.blade.php @@ -0,0 +1,71 @@ + + + + + + Recuperar Contraseña + + + + + + +
+ + + + + + + + + + + + + + + + + +
+

+ Bicicletería Lauck +

+

Recuperación de cuenta

+
+

+ Hola, +

+

+ Estás recibiendo este correo porque recibimos una solicitud de restablecimiento de contraseña para tu cuenta en Lauck. +

+ + + + + +
+ + Restablecer Contraseña + +
+ +

+ Este enlace para restablecer la contraseña caducará en 60 minutos. +

+

+ Si no solicitaste un restablecimiento de contraseña, no es necesario realizar ninguna otra acción. +

+
+

+ Si tienes problemas para hacer clic en el botón "Restablecer Contraseña", copia y pega la siguiente URL en tu navegador web:
+ {{ $url }} +

+
+

+ © {{ date('Y') }} Bicicletería Lauck. Todos los derechos reservados. +

+
+ + diff --git a/resources/views/faq.blade.php b/resources/views/faq.blade.php new file mode 100644 index 0000000..e0cefe4 --- /dev/null +++ b/resources/views/faq.blade.php @@ -0,0 +1,123 @@ + + + +
+ + +
+
+

+ + + + Manual de Uso Rápido +

+

Conceptos básicos para navegar por el sistema Lauck.

+
+
+ +
+
1
+
+

Dashboard

+

El panel principal muestra un resumen de las métricas clave, accesos directos a tus módulos más importantes y un panorama general del negocio.

+
+
+ +
+
2
+
+

Stock y Productos

+

Registra bicicletas, accesorios y repuestos. Mantén las cantidades actualizadas para evitar quiebres de inventario mediante la gestión de tu stock.

+
+
+ +
+
3
+
+

Taller

+

Administra la agenda de reparaciones, asigna estados (pendiente, en proceso, listo) y mantén un registro de los servicios realizados a cada cliente.

+
+
+ +
+
4
+
+

Ventas y Clientes

+

Genera nuevas ventas, consulta el historial, y gestiona la base de datos de tus clientes para ofrecer un servicio más personalizado.

+
+
+
+
+ + +

+ + + + Preguntas Frecuentes (FAQ) +

+ +
+ +
+ + ¿Cómo agrego un nuevo producto al stock? + + + + +
+

Dirígete a la sección Stock desde el menú principal, haz clic en el botón superior derecho de agregar nuevo producto y completa el formulario con los detalles (SKU, nombre, precio, etc).

+
+
+ + +
+ + ¿Puedo registrar una venta si el cliente no está registrado? + + + + +
+

Sí, al crear una nueva venta desde la sección Ventas puedes seleccionar un cliente genérico ("Consumidor Final") o bien ingresar los datos de un cliente nuevo en el mismo momento para que quede registrado en tu base de datos.

+
+
+ + +
+ + ¿Qué pasa si olvido mi contraseña? + + + + +
+

En la pantalla de inicio de sesión, haz clic en el enlace "¿Olvidaste tu contraseña?". Te enviaremos un correo electrónico con instrucciones para restablecerla de forma segura. Si el problema persiste, contacta al administrador del sistema.

+
+
+ + +
+ + ¿Cómo cambiar entre modo claro y oscuro? + + + + +
+

En la barra de navegación superior (Navbar), encontrarás un icono de un sol o una luna (dependiendo del modo actual en el que te encuentres). Haz clic sobre el ícono para alternar entre el tema claro y el oscuro. El sistema recordará tu preferencia para futuras sesiones.

+
+
+
+ +
+

¿Necesitas más ayuda?

+

Si tienes alguna otra duda o experimentas problemas técnicos, nuestro equipo de soporte está disponible para ayudarte.

+ + Contactar a Soporte + +
+
+
diff --git a/resources/views/login.blade.php b/resources/views/login.blade.php index 9e0bc6b..ad06a6e 100644 --- a/resources/views/login.blade.php +++ b/resources/views/login.blade.php @@ -1,44 +1,65 @@ -
- -
- +
+ + +
+ @csrf + {{-- Bloque de errores --}} @if ($errors->any()) -
+

{{ $errors->first() }}

@endif + + @if (session('status')) +
+

+ {{ session('status') }} +

+
+ @endif
- Correo electrónico - + +
- Contraseña - + + + +
- +
-

- ¿No tienes una cuenta? Regístrate acá. -

+
+

+ ¿No tienes una cuenta? Regístrate acá. +

+
+ \ No newline at end of file diff --git a/resources/views/productos/create.blade.php b/resources/views/productos/create.blade.php index 2d626f0..fddc91e 100644 --- a/resources/views/productos/create.blade.php +++ b/resources/views/productos/create.blade.php @@ -53,11 +53,7 @@
- -
- - -
+
diff --git a/resources/views/productos/edit.blade.php b/resources/views/productos/edit.blade.php index c419ef2..a2e279f 100644 --- a/resources/views/productos/edit.blade.php +++ b/resources/views/productos/edit.blade.php @@ -52,11 +52,6 @@
- -
- - -
diff --git a/resources/views/productos/index.blade.php b/resources/views/productos/index.blade.php index 5340808..9ab6e84 100644 --- a/resources/views/productos/index.blade.php +++ b/resources/views/productos/index.blade.php @@ -2,7 +2,7 @@ - +
@@ -114,7 +114,7 @@ -
+ @csrf @method('DELETE')
-
- -
- {{ $product->description ?: 'No hay descripción cargada para este producto.' }} -
-
-
diff --git a/resources/views/sales.blade.php b/resources/views/sales.blade.php index 40a18ab..a892a17 100644 --- a/resources/views/sales.blade.php +++ b/resources/views/sales.blade.php @@ -2,7 +2,7 @@ - +
diff --git a/resources/views/sales/create.blade.php b/resources/views/sales/create.blade.php index 7a16985..4d0af5c 100644 --- a/resources/views/sales/create.blade.php +++ b/resources/views/sales/create.blade.php @@ -85,7 +85,7 @@ - + @if($errors->any())
diff --git a/resources/views/sales/show.blade.php b/resources/views/sales/show.blade.php index 9271fa9..cf0d76e 100644 --- a/resources/views/sales/show.blade.php +++ b/resources/views/sales/show.blade.php @@ -1,7 +1,7 @@ - +
diff --git a/resources/views/suppliers/index.blade.php b/resources/views/suppliers/index.blade.php index 7fd2443..a11018f 100644 --- a/resources/views/suppliers/index.blade.php +++ b/resources/views/suppliers/index.blade.php @@ -2,17 +2,8 @@ - - @if(session('success')) -
- {{ session('success') }} -
- @endif - + +
@@ -79,7 +70,7 @@ - + @csrf @method('DELETE') @@ -154,10 +154,10 @@ if (matches.length > 0) { matches.forEach(p => { html += ` -
${p.name} - $${p.price} + $${p.price}
`; }); } else { @@ -215,8 +215,8 @@ // Visual $container.append(` -
-
+
+
${p.name} ($${p.price})
diff --git a/resources/views/taller/index.blade.php b/resources/views/taller/index.blade.php index e786f4a..4da8218 100644 --- a/resources/views/taller/index.blade.php +++ b/resources/views/taller/index.blade.php @@ -2,11 +2,7 @@ - @if(session('success')) -
- {{ session('success') }} -
- @endif +
-
-
-

🔴 Pendientes / A Revisar

- {{ $pending->count() }} +
+
+

🔴 Pendientes / A Revisar

+ {{ $pending->count() }}
@foreach($pending as $job) @@ -36,10 +32,10 @@
-
-
-

🟡 En Reparación

- {{ $inProgress->count() }} +
+
+

🟡 En Reparación

+ {{ $inProgress->count() }}
@foreach($inProgress as $job) @@ -49,10 +45,10 @@
-
-
-

🟢 Listas para Retirar

- {{ $ready->count() }} +
+
+

🟢 Listas para Retirar

+ {{ $ready->count() }}
@foreach($ready as $job) diff --git a/routes/web.php b/routes/web.php index c7ba6f0..51f733a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -13,6 +13,8 @@ use App\Http\Controllers\TallerController; use App\Http\Controllers\AgendaController; use App\Http\Controllers\AppointmentController; use App\Http\Controllers\SupplierController; +use App\Http\Controllers\PasswordResetLinkController; +use App\Http\Controllers\NewPasswordController; use App\Models\Product; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Session; @@ -25,6 +27,20 @@ Route::resource('catalogo', CatalogoController::class)->only(['index', 'show'])- Route::get('login', function(){ return view('login'); })->name('login'); Route::post('login', LoginController::class)->middleware('throttle:5,1')->name('login.attempt'); +Route::middleware('guest')->group(function () { + Route::get('forgot-password', [PasswordResetLinkController::class, 'create']) + ->name('password.request'); + + Route::post('forgot-password', [PasswordResetLinkController::class, 'store']) + ->name('password.email'); + + Route::get('reset-password/{token}', [NewPasswordController::class, 'create']) + ->name('password.reset'); + + Route::post('reset-password', [NewPasswordController::class, 'store']) + ->name('password.store'); +}); + Route::view('register', 'register')->name('register'); Route::post('register', RegisterController::class)->name('register.store'); @@ -56,4 +72,5 @@ Route::middleware(['auth'])->group(function () { Route::get('/agenda', [AgendaController::class, 'index'])->name('agenda'); Route::get('/appointments/{appointment}', [AppointmentController::class, 'show']) ->name('appointments.show'); + Route::view('/faq', 'faq')->name('faq'); });