HEX
Server: Apache/2.4.58 (Ubuntu)
System: Linux Bradford-Sitios 6.14.0-1017-azure #17~24.04.1-Ubuntu SMP Mon Dec 1 20:10:50 UTC 2025 x86_64
User: www-data (33)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /var/www/api_matriculas/app/Services/AuthService.php
<?php

namespace App\Services;

use App\Models\ResetPassword;
use App\Models\User;
use App\Repositories\ParentRepository;
use App\Repositories\ResetPasswordRepository;
use App\Repositories\UserRepository;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;

class AuthService
{
    public function login(Request $request, $origin = 'web')
    {
        $errors = $this->validateLogin($request);
        if (!empty($errors)) {
            throw new Exception($errors, 400);
        }

        $rut = $this->resolveIdentifier($request, false);

        $userRepository = new UserRepository();
        $register = $userRepository->getByUsername($rut);
        if (!$register) {
            throw new Exception("Usuario y/o contraseña incorrecta", 401);
        }
        if (!$register->status) {
            throw new Exception("Usuario ha sido deshabilitado. Pónganse en contacto con el administrador de su empresa", 403);
        }

        // Bloquear acceso de apoderados si el sitio está en mantenimiento
        if (!config('app.open_to_parents') && $register->profile && $register->profile->code === 'parent') {
            throw new Exception("El sitio se encuentra en mantenimiento. Intente nuevamente más tarde.", 503);
        }

        $credentials = ['username' => $rut, 'password' => $request->password, 'deleted' => false];
        if (!Auth::attempt($credentials)) {
            throw new Exception("Usuario y/o contraseña incorrecta", 401);
        }

        // Generar token y guardarlo en la BD
        $token = bin2hex(random_bytes(40));
        $register->api_token = $token;
        $register->save();

        return [
            'token' => $token,
            'user' => [
                'id' => $register->id,
                'name' => $register->name,
                'username' => $register->username,
                'email' => $register->email,
                'role' => $register->profile->code,
                'roleName' => $register->profile->profile,
                'permissions_users' => $register->getFrontPermissions(),
            ]
        ];
    }


    public function validateSession(Request $request)
    {
        $user = $request->user();

        // Fallback: buscar por Bearer token
        if (!$user) {
            $bearer = $request->bearerToken();
            if ($bearer) {
                $user = User::where('api_token', $bearer)->first();
            }
        }

        if (!$user) {
            throw new Exception('Acceso no autorizado', 401);
        }
        if (!$user->status) {
            throw new Exception('Usuario ha sido deshabilitado. Pónganse en contacto con el administrador de su empresa', 403);
        }

        return [
            'id' => $user->id,
            'name' => $user->name,
            'username' => $user->username,
            'email' => $user->email,
            'role' => $user->profile->code,
            'roleName' => $user->profile->profile,
            'permissions_users' => $user->getFrontPermissions(),
        ];
    }

    public function getUser($id)
    {
        $user = User::find($id);
        if (!$user->status) {
            $msg = 'Usuario ha sido deshabilitado. Pónganse en contacto con el administrador de su empresa';
            $code = 403;
            throw new Exception($msg,  $code);
        }

        return [
            'id' => $user->id,
            'name' => $user->name,
            'username' => $user->username,
            'email' => $user->email,
            'roleName' => $user->profile->profile,
            'permissions_users' => $user->getFrontPermissions(),
        ];
    }

    public function logout(Request $request)
    {
        $user = $request->user() ?? User::where('api_token', $request->bearerToken())->first();

        if ($user) {
            $user->api_token = null;
            $user->save();
        }

        return null;
    }


    public function sendRecoveryCode(Request $request)
    {
        $errors = null;
        $rules = [
            'rut' => 'required',
        ];

        $messages = [
            'rut.required' => 'Rut es Requerido',
        ];

        // Crear el validador manualmente
        $validator = Validator::make($request->all(), $rules, $messages);

        if ($validator->fails()) {
            $errors = implode(', ', $validator->errors()->all());
        }
        if (!empty($errors)) {
            throw new Exception(json_encode($errors), 400);
        }

        $rut = $this->resolveIdentifier($request);
        $userRepository = new UserRepository();
        $userData = $userRepository->getByUsername($rut);
        if (!$userData) {
            throw new Exception("Documento ingresado no existe en base de datos", 404);
        }

        if (!$userData->status) {
            throw new Exception("Su usuario ha sido deshabilitado. Pónganse en contacto con el administrador del sistema.", 403);
        }

        $resetPasswordRepository = new ResetPasswordRepository();

        $resetPassword = $resetPasswordRepository->getByUserId($userData->id);
        $currentDate = now();
        if ($resetPassword) {
            $expirationDate = $resetPassword->expiration_date;
            if (strtotime($expirationDate) >= strtotime($currentDate)) {
                throw new Exception(
                    'Ya existe una solicitud activa para restablecer tu contraseña, la cual expira el ' .
                        sort_datetimeHuman($resetPassword->expiration_date) .
                        '. Podrás generar una nueva solicitud una vez que esta haya expirado.',
                    400
                );
            }
        }
        $code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);

        ResetPassword::where('user_id', $userData->id)
            ->where('status', 1)->where('code', '=!', $code)
            ->update(['status' => 0]);

        try {
            DB::beginTransaction();
            $resetPasswordRepository->create((object)[
                'user_id' => $userData->id,
                'username' => strLower($userData->username),
                'code' => $code,
                'change_date' => now(),
                'expiration_date' => now()->addMinutes(15),
            ]);
            if (!$resetPasswordRepository) {
                throw new Exception("Error al crear usuario.", 500);
            }
            $mailService = new MailService();
            $sendMail = $mailService->recoveryPassword((object)[
                'name' => strUpper($userData->name),
                'email' => strLower($userData->email),
                'code' => $code,
            ]);
            // if (!$sendMail) {
            //     throw new Exception("Error al enviar correo. Contácte a soporte");
            // }
            DB::commit();

            return [
                'name' => $userData->name,
                'email' => obfuscateEmail(strLower($userData->email)),
            ];
        } catch (Exception $e) {
            if (DB::transactionLevel() > 0) {
                DB::rollBack();
            }
            Log::error("Error al recuperar contraseña: {$e->getMessage()} | {$e->getFile()}:{$e->getLine()}");
            throw new Exception("Ocurrió un problema al enviar el correo de recuperación. Recargue e intente nuevamente.", 500);
        }
    }


    public function validateCode(Request $request)
    {

        $errors = null;
        $rules = [
            'rut' => 'required',
            'code' => 'required|string|min:6',
        ];

        $messages = [
            'rut.required' => 'Rut es Requerido',
            'code.required' => 'Código es Requerido',
            'code.min' => 'Código debe tener 6 caracteres',
        ];

        // Crear el validador manualmente
        $validator = Validator::make($request->all(), $rules, $messages);

        if ($validator->fails()) {
            $errors = implode(', ', $validator->errors()->all());
        }

        if (!empty($errors)) {
            throw new Exception(json_encode($errors), 400);
        }

        $request->validate([
            'rut' => 'required|string',
            'code' => 'required|string|min:6',
        ]);


        $rut = $this->resolveIdentifier($request);
        $userRepository = new UserRepository();
        $userData = $userRepository->getByUsername($rut);
        if (!$userData) {
            throw new Exception("Lo Sentimos, su solicitud ha expirado.", 404);
        }


        $resetPasswordRepository = new ResetPasswordRepository();
        $resetPassword = $resetPasswordRepository->getByCode($request->code);
        $currentDate = now();
        if ($resetPassword) {
            $expirationDate = $resetPassword->expiration_date;
            if (strtotime($expirationDate) < strtotime($currentDate)) {
                throw new Exception(
                    'Lo Sentimos, su solicitud ha expirado. Genere otra y vuelva a intentarlo.',
                    400
                );
            }
        }

        return [
            'success' => true,
            'message' => 'Código validado correctamente'
        ];
    }


    // Restablecer contraseña
    public function resetPassword(Request $request)
    {


        $errors = null;
        $rules = [
            'rut' => 'required',
            'code' => 'required|string|min:6',
            'password' => [
                'required',
                'string',
                'min:8',
                'regex:/[a-z]/',
                'regex:/[A-Z]/',
                'regex:/[0-9]/',
                // 'regex:/[\W_]/'
            ],
            'password_confirm' => [
                'required',
                'string',
                'min:8',
                'regex:/[a-z]/',
                'regex:/[A-Z]/',
                'regex:/[0-9]/',
                // 'regex:/[\W_]/'
            ],
        ];

        $messages = [
            'rut.required' => 'Rut es Requerido',
            'code.required' => 'Código es Requerido',
            'code.min' => 'Código debe tener 6 caracteres',

            'password.required' => 'Contraseña requerida',
            'password.string' => 'Contraseña debe ser una cadena de texto',
            'password.min' => 'Contraseña debe tener al menos 8 caracteres',
            // 'password.regex' => 'Contraseña debe contener al menos una letra mayúscula, una letra minúscula, un número y un carácter especial',
            'password.regex' => 'Contraseña debe contener al menos una letra mayúscula, una letra minúscula y un número.',

            'password_confirm.required' => 'Confirmar Contraseña requerida',
            'password_confirm.string' => 'Confirmar Contraseña debe ser una cadena de texto',
            'password_confirm.min' => 'Confirmar Contraseña debe tener al menos 8 caracteres',
            'password_confirm.regex' => 'Contraseña debe contener al menos una letra mayúscula, una letra minúscula y un número',

        ];

        // Crear el validador manualmente
        $validator = Validator::make($request->all(), $rules, $messages);

        if ($validator->fails()) {
            $errors = implode(', ', $validator->errors()->all());
        }

        if (!empty($errors)) {
            throw new Exception(json_encode($errors), 400);
        }

        $rut = $this->resolveIdentifier($request);
        $userRepository = new UserRepository();
        $userData = $userRepository->getByUsername($rut);
        if (!$userData) {
            throw new Exception("Lo Sentimos, su solicitud ha expirado.", 404);
        }

        $resetPasswordRepository = new ResetPasswordRepository();
        $resetPassword = $resetPasswordRepository->getByCode($request->code);
        $currentDate = now();
        if ($resetPassword) {
            $expirationDate = $resetPassword->expiration_date;
            if (strtotime($expirationDate) < strtotime($currentDate)) {
                throw new Exception(
                    'Lo Sentimos, su solicitud ha expirado. Genere otra y vuelva a intentarlo.',
                    400
                );
            }
        }

        $userData->password = bcrypt($request->password);
        $userData->updated_at = now();
        if (!$userData->save()) {
            throw new Exception("Ocurrió un problema al actualizar contraseña. Intentelo nuevamente.", 404);
        }
        $resetPassword->status = false;
        $resetPassword->save();
        $mailService = new MailService();
        $sendMail = $mailService->confirmPassword((object)[
            'name' => strUpper($userData->name),
            'email' => strLower($userData->email),
        ]);
        return [
            'success' => true,
            'message' => 'Contraseña restablecida correctamente'
        ];
    }



    private function resolveIdentifier(Request $request, bool $withDots = true): string
    {
        $isPassport = strtoupper($request->document_type ?? 'RUT') === 'PASSPORT';
        if ($isPassport) {
            return strUpper($request->rut);
        }
        return formatterRut($request->rut);
    }

    private function validateLogin($request)
    {
        $errors = null;
        $rules = [
            'rut' => 'required',
            'password' => 'required',
        ];

        $messages = [
            'rut.required' => 'Rut es Requerido',
            'password.required' => 'Contraseña es Requerida',

        ];

        // Crear el validador manualmente
        $validator = Validator::make($request->all(), $rules, $messages);

        if ($validator->fails()) {
            $errors = implode(', ', $validator->errors()->all());
        }
        return $errors;
    }

    public function activateAccount(Request $request)
    {
        $errors = null;
        $rules = [
            'rut' => 'required',
        ];

        $messages = [
            'rut.required' => 'RUT es requerido',
        ];

        $validator = Validator::make($request->all(), $rules, $messages);

        if ($validator->fails()) {
            $errors = implode(', ', $validator->errors()->all());
        }
        if (!empty($errors)) {
            throw new Exception(json_encode($errors), 400);
        }

        $rut = $this->resolveIdentifier($request);

        $parentRepository = new ParentRepository();
        $parentData = $parentRepository->getByRut($rut);
        if (!$parentData) {
            throw new Exception("El documento ingresado no pertenece a ningún apoderado registrado.", 404);
        }

        $userRepository = new UserRepository();
        if ($parentData->user_id < 1) {
            $profileId = $parentRepository->getProfile();
            $isParentPassport = ($parentData->document_type ?? 'RUT') === 'PASSPORT';
            $parentUsername = $isParentPassport ? $parentData->rut : (!empty($parentData->rut) ? formatterRut($parentData->rut, false) : null);

            $user = $userRepository->create([
                'name'              => strUpper($parentData->first_name . ' ' . $parentData->last_name),
                'username'          => $parentUsername,
                'rut'               => $parentUsername,
                'email'             => strLower($parentData->email),
                'account_confirmed' => false,
                'status'            => true,
                'profile_id'        => $profileId,
                'created_at'        => now(),
                'updated_at'        => now(),
            ]);

            if (!$user) {
                throw new Exception("Ocurrió un error al activar la cuenta. Recarga e intenta nuevamente.", 500);
            }

            $parentData->user_id = $user->id;
            $parentData->save();
        }

        $userData = $userRepository->getByUsername($rut);
        if (!$userData) {
            throw new Exception("Documento ingresado no existe en base de datos", 404);
        }

        if (!$userData->status) {
            throw new Exception("Su usuario ha sido deshabilitado. Póngase en contacto con el administrador del sistema.", 403);
        }

        if ($userData->account_confirmed) {
            throw new Exception("Su cuenta ya se encuentra activa. Inicie sesión para continuar con el proceso de matrícula.", 400);
        }

        $currentDate = now();
        $expirationDate = $userData->account_confirmed_at;

        // 🔹 Calcular segundos restantes si la solicitud aún no expira
        if ($expirationDate && strtotime($expirationDate) >= strtotime($currentDate)) {
            $secondsLeft = strtotime($expirationDate) - strtotime($currentDate);

            throw new Exception(
                'Ya existe una solicitud para activar tu cuenta. <br>'
                    . '<b>El código actual expira en (' . $secondsLeft . 's)</b>. '
                    . 'Podrás solicitar un nuevo código una vez que este expire.',
                400
            );
        }

        // 🔹 Generar nuevo código de activación (numérico)
        $code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
        $userData->activation_token = $code;
        $userData->account_confirmed_at = now()->addMinutes(1);
        $userData->save();

        try {
            $mailService = new MailService();
            $sendMail = $mailService->activeAccount((object)[
                'name'  => strUpper($userData->name),
                'email' => strLower($userData->email),
                'code'  => $code,
            ]);

            if (!$sendMail) {
                throw new Exception("Error al enviar correo. Contacte a soporte.", 500);
            }

            return [
                'name'   => $userData->name,
                'email'  => obfuscateEmail(strLower($userData->email)),
                'expire' => $userData->account_confirmed_at,
                'ttl'    => 60, // segundos de validez total
            ];
        } catch (Exception $e) {
            Log::error("Error al activar cuenta: {$e->getMessage()} | {$e->getFile()}:{$e->getLine()}");
            throw new Exception("Ocurrió un problema al enviar el correo de activación. Recargue e intente nuevamente.", 500);
        }
    }

    public function validateCodeActivation(Request $request)
    {

        $errors = null;
        $rules = [
            'rut' => 'required',
            'code' => 'required|string|min:6',
        ];

        $messages = [
            'rut.required' => 'Rut es Requerido',
            'code.required' => 'Código es Requerido',
            'code.min' => 'Código debe tener 6 caracteres',
        ];

        // Crear el validador manualmente
        $validator = Validator::make($request->all(), $rules, $messages);

        if ($validator->fails()) {
            $errors = implode(', ', $validator->errors()->all());
        }

        if (!empty($errors)) {
            throw new Exception(json_encode($errors), 400);
        }

        $request->validate([
            'rut' => 'required|string',
            'code' => 'required|string|min:6',
        ]);


        $rut = $this->resolveIdentifier($request);
        $userRepository = new UserRepository();
        $userData = $userRepository->getByUsername($rut);
        if (!$userData) {
            throw new Exception("Lo Sentimos, su solicitud ha expirado.", 404);
        }

        if ($userData->account_confirmed) {
            throw new Exception("Su cuenta ya se encuentra activa. Inicie sesión para continuar con el proceso de matrícula.", 400);
        }

        if ($userData->activation_token !=  $request->code) {
            throw new Exception("Lo Sentimos, su solicitud ha expirado. Genere otra y vuelva a intentarlo.", 404);
        }


        return [
            'success' => true,
            'message' => 'Código validado correctamente'
        ];
    }

    public function activatePasswordWithCode(Request $request)
    {


        $errors = null;
        $rules = [
            'rut' => 'required',
            'code' => 'required|string|min:6',
            'password' => [
                'required',
                'string',
                'min:8',
                'regex:/[a-z]/',
                'regex:/[A-Z]/',
                'regex:/[0-9]/',
                // 'regex:/[\W_]/'
            ],
            'password_confirm' => [
                'required',
                'string',
                'min:8',
                'regex:/[a-z]/',
                'regex:/[A-Z]/',
                'regex:/[0-9]/',
                // 'regex:/[\W_]/'
            ],
        ];

        $messages = [
            'rut.required' => 'Rut es Requerido',
            'code.required' => 'Código es Requerido',
            'code.min' => 'Código debe tener 6 caracteres',

            'password.required' => 'Contraseña requerida',
            'password.string' => 'Contraseña debe ser una cadena de texto',
            'password.min' => 'Contraseña debe tener al menos 8 caracteres',
            // 'password.regex' => 'Contraseña debe contener al menos una letra mayúscula, una letra minúscula, un número y un carácter especial',
            'password.regex' => 'Contraseña debe contener al menos una letra mayúscula, una letra minúscula y un número.',

            'password_confirm.required' => 'Confirmar Contraseña requerida',
            'password_confirm.string' => 'Confirmar Contraseña debe ser una cadena de texto',
            'password_confirm.min' => 'Confirmar Contraseña debe tener al menos 8 caracteres',
            'password_confirm.regex' => 'Contraseña debe contener al menos una letra mayúscula, una letra minúscula y un número',

        ];

        // Crear el validador manualmente
        $validator = Validator::make($request->all(), $rules, $messages);

        if ($validator->fails()) {
            $errors = implode(', ', $validator->errors()->all());
        }

        if (!empty($errors)) {
            throw new Exception(json_encode($errors), 400);
        }

        $rut = $this->resolveIdentifier($request);
        $userRepository = new UserRepository();
        $userData = $userRepository->getByUsername($rut);
        if (!$userData) {
            throw new Exception("Lo Sentimos, su solicitud ha expirado.", 404);
        }

        if ($userData->activation_token != $request->code) {
            throw new Exception('Lo Sentimos, su solicitud ha expirado.', 400);
        }

        $userData->password = bcrypt($request->password);
        $userData->updated_at = now();
        $userData->activation_token = null;
        $userData->account_confirmed = 1;
        $userData->account_confirmed_at = now();
        if (!$userData->save()) {
            throw new Exception("Ocurrió un problema al activar cuenta. Intentelo nuevamente.", 404);
        }

        $mailService = new MailService();
        $sendMail = $mailService->confirmActiveAccount((object)[
            'name' => strUpper($userData->name),
            'email' => strLower($userData->email),
        ]);
        return [
            'success' => true,
            'message' => 'Cuenta activada correctamente'
        ];
    }
}