post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [ 'secret' => config('services.turnstile.secret_key'), 'response' => $token, 'remoteip' => request()->ip(), ]); return $response->successful() && $response->json('success'); } public function login(Request $request) { $tipo = $request->input('tipo'); if ($tipo === 'admin') { return $this->loginAdmin($request); } return $this->loginPlayer($request); } public function loginPlayer(Request $request) { if (!$this->verifyTurnstile($request->input('cf-turnstile-response'))) { return back()->with('login_error', 'Error de verificación de seguridad (Turnstile).')->with('login_tab', 'player'); } $dni = $request->input('dni'); $password = $request->input('password'); $jugador = Jugador::where('documento', $dni)->where('activo', true)->first(); if ($jugador && $jugador->password && Hash::check($password, $jugador->password)) { $request->session()->put('user_logged_in', true); $request->session()->put('user_tipo', 'jugador'); $request->session()->put('user_id', $jugador->id_jugador); $request->session()->put('user_name', $jugador->nombre . ' ' . $jugador->apellido); $request->session()->put('user_documento', $jugador->documento); $request->session()->put('user_ultimo_acceso', time()); return redirect()->intended('/'); } $aficionado = Aficionado::where('dni', $dni)->first(); if ($aficionado && $aficionado->password && Hash::check($password, $aficionado->password)) { $request->session()->put('user_logged_in', true); $request->session()->put('user_tipo', 'aficionado'); $request->session()->put('user_id', $aficionado->id_aficionado); $request->session()->put('user_name', $aficionado->nombre . ' ' . $aficionado->apellido); $request->session()->put('user_documento', $aficionado->dni); $request->session()->put('user_ultimo_acceso', time()); return redirect()->intended('/'); } return back()->with('login_error', 'DNI o contraseña incorrectos')->with('login_tab', 'player'); } public function loginAdmin(Request $request) { if (!$this->verifyTurnstile($request->input('cf-turnstile-response'))) { return back()->with('login_error', 'Error de verificación de seguridad (Turnstile).')->with('login_tab', 'admin'); } $username = $request->input('username'); $password = $request->input('password'); $admin = AdminUser::whereRaw('BINARY `username` = ?', [$username])->first(); if ($admin && Hash::check($password, $admin->password)) { $request->session()->put('admin_logged_in', true); $request->session()->put('admin_id', $admin->id); $request->session()->put('admin_username', $admin->username); $request->session()->put('admin_role', $admin->role); $request->session()->put('admin_id_club', $admin->id_club); if ($admin->id_club && $admin->club) { $request->session()->put('admin_club_nombre', $admin->club->nombre); } $request->session()->put('ultimo_acceso', time()); return redirect()->intended('/'); } return back()->with('login_error', 'Usuario o contraseña incorrectos')->with('login_tab', 'admin'); } public function logout(Request $request) { $isAdmin = $request->session()->get('admin_logged_in'); if ($isAdmin) { $request->session()->forget(['admin_logged_in', 'admin_id', 'admin_username', 'admin_role', 'ultimo_acceso']); $msg = 'Sesión de administrador cerrada correctamente.'; } else { $request->session()->forget(['user_logged_in', 'user_tipo', 'user_id', 'user_name', 'user_documento', 'user_ultimo_acceso']); $msg = 'Sesión cerrada correctamente.'; } return redirect('/?logout_msg=' . urlencode($msg)); } public function showLoginForm() { return view('welcome'); } public function recuperar(Request $request) { if (!$this->verifyTurnstile($request->input('cf-turnstile-response'))) { return back()->with('mensaje', '⚠️ Error de verificación de seguridad (Captcha).'); } $dni = trim($request->input('dni')); $email = trim($request->input('email')); if (empty($dni) || empty($email)) { return back()->with('mensaje', 'Debes ingresar tu DNI y correo electrónico.'); } $jugador = Jugador::where('documento', $dni)->where('email', $email)->first(); $aficionado = Aficionado::where('dni', $dni)->where('email', $email)->first(); $usuario = $jugador ?: $aficionado; if (!$usuario) { return back()->with('mensaje', 'No se encontró un usuario con ese DNI y correo.'); } $token = bin2hex(random_bytes(16)); $expires = now()->addHour(); if ($jugador) { $jugador->update([ 'reset_token' => $token, 'reset_expira' => $expires ]); } else { $aficionado->update([ 'reset_token' => $token, 'reset_expira' => $expires ]); } try { Mail::to($usuario->email)->send(new ResetPasswordMail($usuario, $token)); } catch (\Exception $e) { Log::error("Error enviando mail de recuperación: " . $e->getMessage()); } return back()->with('mensaje', '📩 Te enviamos un correo con las instrucciones para recuperar tu contraseña.'); } public function resetPasswordForm($token) { // Verificar que el token exista y no esté expirado $jugador = Jugador::where('reset_token', $token)->where('reset_expira', '>', now())->first(); $aficionado = Aficionado::where('reset_token', $token)->where('reset_expira', '>', now())->first(); if (!$jugador && !$aficionado) { return redirect()->route('recuperar')->with('mensaje', '❌ El enlace es inválido o ya expiró. Solicitá uno nuevo.'); } return view('auth.reset_password', compact('token')); } public function resetPassword(Request $request) { $request->validate([ 'token' => 'required|string', 'password' => 'required|confirmed|min:6', ]); $token = $request->input('token'); $jugador = Jugador::where('reset_token', $token)->where('reset_expira', '>', now())->first(); $aficionado = Aficionado::where('reset_token', $token)->where('reset_expira', '>', now())->first(); $usuario = $jugador ?: $aficionado; if (!$usuario) { return redirect()->route('recuperar')->with('mensaje', '❌ El enlace es inválido o ya expiró. Solicitá uno nuevo.'); } $usuario->update([ 'password' => bcrypt($request->input('password')), 'reset_token' => null, 'reset_expira' => null, ]); return redirect('/')->with('login_success', '✅ Contraseña cambiada correctamente. Ya podés iniciar sesión.'); } public function registroAficionado(Request $request) { if (!$this->verifyTurnstile($request->input('cf-turnstile-response'))) { return back()->with('registro_msg', '⚠️ Error de verificación de seguridad (Captcha).')->withInput(); } $data = $request->validate([ 'nombre' => 'required|string|max:100', 'apellido' => 'required|string|max:100', 'dni' => 'required|string|unique:aficionados,dni', 'email' => 'required|email|unique:aficionados,email', 'fecha_nacimiento' => 'nullable|date', 'telefono' => 'nullable|string|max:50', 'localidad' => 'nullable|string|max:100', 'password' => 'required|confirmed|min:6', ]); $data['password'] = bcrypt($data['password']); $data['fecha_registro'] = now(); $aficionado = Aficionado::create($data); try { Mail::to($aficionado->email)->send(new WelcomeMail($aficionado, 'aficionado')); } catch (\Exception $e) { Log::error("Error enviando mail de bienvenida a Aficionado: " . $e->getMessage()); } return redirect()->route('asociate')->with('mensaje', '✅ Te registraste correctamente. Ya podés iniciar sesión.'); } public function buscarJugador(Request $request) { $request->validate([ 'nombre' => 'required|string', 'apellido' => 'required|string', 'dni' => 'required|string', 'acepto' => 'required', ]); $dni = preg_replace('/[^0-9]/', '', $request->input('dni')); $nombre = strtoupper(trim($request->input('nombre'))); $apellido = strtoupper(trim($request->input('apellido'))); // Buscar jugador por DNI $jugador = Jugador::where('documento', $dni)->first(); if (!$jugador) { // Verificar si ya está registrado como aficionado $aficionado = Aficionado::where('dni', $dni)->first(); if ($aficionado) { return redirect()->route('asociate')->with('mensaje', '⚠️ Ya estás registrado como aficionado.'); } // No existe - se debe registrar como aficionado return redirect()->route('asociate')->with('mensaje', '⚠️ No encontramos tu registro como jugador. Podés registrarte como aficionado.'); } // --- Lógica Smart Match --- $nombreEnBD = $this->normalizeString($jugador->nombre ?? ''); $apellidoEnBD = $this->normalizeString($jugador->apellido ?? ''); $terminosBD = explode(' ', $nombreEnBD . ' ' . $apellidoEnBD); $palabrasNombreIn = explode(' ', $this->normalizeString($nombre)); $palabrasApellidoIn = explode(' ', $this->normalizeString($apellido)); $nombreCoincide = false; foreach ($palabrasNombreIn as $p) { if ($this->isApproxMatch($p, $terminosBD)) { $nombreCoincide = true; break; } } $apellidoCoincide = false; foreach ($palabrasApellidoIn as $p) { if ($this->isApproxMatch($p, $terminosBD)) { $apellidoCoincide = true; break; } } if (!$nombreCoincide || !$apellidoCoincide) { return redirect()->route('asociate')->with('mensaje', '⚠️ El DNI ingresado no coincide con el Nombre y Apellido proporcionados. Por favor, verifica tus datos.'); } // --- Fin Smart Match --- if ($jugador->activo) { return redirect()->route('asociate')->with('registro_msg', 'Este jugador ya está registrado en el sistema.'); } // Jugador encontrado e inactivo - mostrar formulario para completar $club = null; if ($jugador->id_club_actual) { $clubObj = \App\Models\Club::find($jugador->id_club_actual); $club = $clubObj ? $clubObj->nombre : null; } $jugador_encontrado = [ 'documento' => $jugador->documento, 'nombre' => $jugador->nombre, 'apellido' => $jugador->apellido, 'fecha_nacimiento' => $jugador->fecha_nacimiento, 'club' => $club, 'categoria' => $jugador->categoria, ]; return view('auth.asociate', compact('jugador_encontrado'))->with('tab', 'jugador'); } public function completarRegistroJugador(Request $request) { if (!$this->verifyTurnstile($request->input('cf-turnstile-response'))) { return back()->with('registro_msg', '⚠️ Error de verificación de seguridad (Captcha).')->withInput(); } $request->validate([ 'dni' => 'required|string', 'email' => 'required|email', 'telefono' => 'nullable|string', 'password' => 'required|confirmed|min:6', ]); $dni = preg_replace('/[^0-9]/', '', $request->input('dni')); $jugador = Jugador::where('documento', $dni)->first(); if (!$jugador) { return redirect()->route('asociate')->with('registro_msg', 'Jugador no encontrado.'); } if ($jugador->activo) { return redirect()->route('asociate')->with('registro_msg', 'Este jugador ya está registrado.'); } $jugador->update([ 'email' => $request->input('email'), 'telefono' => $request->input('telefono'), 'password' => bcrypt($request->input('password')), 'activo' => 1, 'fecha_registro' => now(), ]); try { Mail::to($jugador->email)->send(new WelcomeMail($jugador, 'jugador')); } catch (\Exception $e) { Log::error("Error enviando mail de bienvenida a Jugador: " . $e->getMessage()); } return redirect()->route('asociate')->with('mensaje', '✅ Registro completado exitosamente. Ya podés iniciar sesión.'); } /** * Normaliza un string para comparaciones (mayúsculas, sin acentos, sin espacios extras) */ private function normalizeString($str) { $unwanted_array = ['Š'=>'S', 'š'=>'s', 'Ž'=>'Z', 'ž'=>'z', 'À'=>'A', 'Á'=>'A', 'Â'=>'A', 'Ã'=>'A', 'Ä'=>'A', 'Å'=>'A', 'Æ'=>'A', 'Ç'=>'C', 'È'=>'E', 'É'=>'E', 'Ê'=>'E', 'Ë'=>'E', 'Ì'=>'I', 'Í'=>'I', 'Î'=>'I', 'Ï'=>'I', 'Ñ'=>'N', 'Ò'=>'O', 'Ó'=>'O', 'Ô'=>'O', 'Õ'=>'O', 'Ö'=>'O', 'Ø'=>'O', 'Ù'=>'U', 'Ú'=>'U', 'Û'=>'U', 'Ü'=>'U', 'Ý'=>'Y', 'Þ'=>'B', 'ß'=>'Ss', 'à'=>'a', 'á'=>'a', 'â'=>'a', 'ã'=>'a', 'ä'=>'a', 'å'=>'a', 'æ'=>'a', 'ç'=>'c', 'è'=>'e', 'é'=>'e', 'ê'=>'e', 'ë'=>'e', 'ì'=>'i', 'í'=>'i', 'î'=>'i', 'ï'=>'i', 'ð'=>'o', 'ñ'=>'n', 'ò'=>'o', 'ó'=>'o', 'ô'=>'o', 'õ'=>'o', 'ö'=>'o', 'ø'=>'o', 'ù'=>'u', 'ú'=>'u', 'û'=>'u', 'ý'=>'y', 'þ'=>'b', 'ÿ'=>'y']; $str = strtr($str, $unwanted_array); return strtoupper(trim(preg_replace('/\s+/', ' ', $str))); } /** * Comprueba si una palabra coincide aproximadamente con alguna de la lista */ private function isApproxMatch($word, $list) { if (strlen($word) < 3) return false; foreach ($list as $item) { if (strlen($item) < 3) continue; // Coincidencia exacta o contenida if ($word === $item || strpos($item, $word) !== false || strpos($word, $item) !== false) { return true; } // Levenshtein para errores de tipeo (máximo 1 de distancia) if (levenshtein($word, $item) <= 1) { return true; } } return false; } }