From 46367238f5bc7ee0a874969451109949ce2a1df2 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Wed, 14 Jan 2026 15:08:54 -0600 Subject: [PATCH] add: login con username --- .../Controllers/System/LoginController.php | 18 ++-- app/Http/Requests/Auth/LoginRequest.php | 15 ++- app/Http/Requests/Users/UserStoreRequest.php | 22 ++++- app/Models/User.php | 96 ++++++++++++++++++- ..._14_141403_add_username_to_users_table.php | 30 ++++++ 5 files changed, 167 insertions(+), 14 deletions(-) create mode 100644 database/migrations/2026_01_14_141403_add_username_to_users_table.php diff --git a/app/Http/Controllers/System/LoginController.php b/app/Http/Controllers/System/LoginController.php index 6b35c3c..6471d9b 100644 --- a/app/Http/Controllers/System/LoginController.php +++ b/app/Http/Controllers/System/LoginController.php @@ -16,23 +16,29 @@ /** * Controlador de sesiones - * + * * @author Moisés Cortés C - * + * * @version 1.0.0 */ class LoginController extends Controller { /** * Iniciar sesión + * Permite login con username O email */ public function login(LoginRequest $request) { - $user = User::where('email', $request->get('email'))->first(); + $credential = $request->get('username'); + + // Buscar por username o email + $user = User::where('username', $credential) + ->orWhere('email', $credential) + ->first(); if (!$user || !$user->validateForPassportPasswordGrant($request->get('password'))) { return ApiResponse::UNPROCESSABLE_CONTENT->response([ - 'email' => ['Usuario no valido'] + 'username' => ['Credenciales inválidas'] ]); } @@ -87,7 +93,7 @@ public function forgotPassword(ForgotRequest $request) public function resetPassword(ResetPasswordRequest $request) { $data = $request->validated(); - + $model = ResetPassword::with('user')->where('token', $data['token'])->first(); if(!$model){ @@ -142,4 +148,4 @@ private function deleteToken($token) { ResetPassword::where('token', $token)->delete(); } -} \ No newline at end of file +} diff --git a/app/Http/Requests/Auth/LoginRequest.php b/app/Http/Requests/Auth/LoginRequest.php index 6fc38ea..8a742b8 100644 --- a/app/Http/Requests/Auth/LoginRequest.php +++ b/app/Http/Requests/Auth/LoginRequest.php @@ -7,9 +7,9 @@ /** * Solicitud de login - * + * * @author Moisés Cortés C. - * + * * @version 1.0.0 */ class LoginRequest extends FormRequest @@ -30,8 +30,17 @@ public function authorize(): bool public function rules(): array { return [ - 'email' => ['required', 'email'], + 'username' => ['required', 'string'], // Acepta username o email 'password' => ['required', 'min:8'], ]; } + + public function messages(): array + { + return [ + 'username.required' => 'El usuario o email es requerido', + 'password.required' => 'La contraseña es requerida', + 'password.min' => 'La contraseña debe tener al menos 8 caracteres', + ]; + } } diff --git a/app/Http/Requests/Users/UserStoreRequest.php b/app/Http/Requests/Users/UserStoreRequest.php index 7e7c1ff..b61ae94 100644 --- a/app/Http/Requests/Users/UserStoreRequest.php +++ b/app/Http/Requests/Users/UserStoreRequest.php @@ -3,13 +3,14 @@ * @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All rights reserved */ +use App\Models\User; use Illuminate\Foundation\Http\FormRequest; /** * Almacenar usuario - * + * * @author Moisés Cortés C - * + * * @version 1.0.0 */ class UserStoreRequest extends FormRequest @@ -39,4 +40,21 @@ public function rules(): array 'roles' => ['nullable', 'array'] ]; } + + /** + * Preparar datos antes de la validación + * Genera el username automáticamente + */ + protected function prepareForValidation(): void + { + if ($this->has('name') && $this->has('paternal')) { + $this->merge([ + 'username' => User::generateUsername( + $this->input('name'), + $this->input('paternal'), + $this->input('maternal') + ) + ]); + } + } } diff --git a/app/Models/User.php b/app/Models/User.php index d8cbed0..ee0c17f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -1,4 +1,7 @@ - $this->name . ' ' . $this->paternal . ' ' . $this->maternal, + get: fn() => $this->name . ' ' . $this->paternal . ' ' . $this->maternal, ); } @@ -109,7 +113,7 @@ public function fullName(): Attribute public function lastName(): Attribute { return Attribute::make( - get: fn () => $this->paternal . ' ' . $this->maternal, + get: fn() => $this->paternal . ' ' . $this->maternal, ); } @@ -141,4 +145,90 @@ public function responsibleModule() { return $this->hasOne(Module::class, 'responsible_id'); } + + /** + * Generar username automático al crear usuario + * Formato: inicial nombre + inicial segundo nombre + apellido paterno + inicial materno + */ + public static function generateUsername(?string $name, ?string $paternal, ?string $maternal = null): string + { + // Validar que al menos tengamos nombre y apellido paterno + if (empty($name) || empty($paternal)) { + return self::ensureUniqueUsername('user'); + } + + $name = self::normalizeString($name); + $paternal = self::normalizeString($paternal); + $maternal = $maternal ? self::normalizeString($maternal) : ''; + + // Separar nombres y obtener iniciales + $nameParts = preg_split('/\s+/', trim($name)); + $firstInitial = !empty($nameParts[0]) ? substr($nameParts[0], 0, 1) : ''; + $secondInitial = isset($nameParts[1]) && !empty($nameParts[1]) ? substr($nameParts[1], 0, 1) : ''; + $maternalInitial = !empty($maternal) ? substr($maternal, 0, 1) : ''; + + // Construir username + $baseUsername = $firstInitial . $secondInitial . $paternal . $maternalInitial; + + // Si el username queda vacío, usar fallback + if (empty($baseUsername)) { + $baseUsername = 'user'; + } + + return self::ensureUniqueUsername($baseUsername); + } + + + /** + * Normalizar string: quitar acentos, convertir a minúsculas, solo letras + */ + private static function normalizeString(string $string): string + { + // Convertir a minúsculas + $string = mb_strtolower($string); + + // Reemplazar caracteres acentuados + $replacements = [ + 'á' => 'a', + 'é' => 'e', + 'í' => 'i', + 'ó' => 'o', + 'ú' => 'u', + 'ä' => 'a', + 'ë' => 'e', + 'ï' => 'i', + 'ö' => 'o', + 'ü' => 'u', + 'à' => 'a', + 'è' => 'e', + 'ì' => 'i', + 'ò' => 'o', + 'ù' => 'u', + 'ñ' => 'n', + 'ç' => 'c', + ]; + + $string = strtr($string, $replacements); + + // Eliminar cualquier caracter que no sea letra o espacio + $string = preg_replace('/[^a-z\s]/', '', $string); + + return $string; + } + + /** + * Asegurar que el username sea único, agregando número si es necesario + */ + private static function ensureUniqueUsername(string $baseUsername): string + { + $username = $baseUsername; + $counter = 1; + + while (self::where('username', $username)->exists()) { + $counter++; + $username = $baseUsername . $counter; + } + + return $username; + } } diff --git a/database/migrations/2026_01_14_141403_add_username_to_users_table.php b/database/migrations/2026_01_14_141403_add_username_to_users_table.php new file mode 100644 index 0000000..43f69cc --- /dev/null +++ b/database/migrations/2026_01_14_141403_add_username_to_users_table.php @@ -0,0 +1,30 @@ +string('username')->nullable()->unique()->after('maternal'); + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('username'); + }); + } +};