import { Injectable, UnauthorizedException, ConflictException, BadRequestException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import * as bcrypt from 'bcryptjs'; import { PrismaService } from '../prisma/prisma.service'; import { SmsService } from '../sms/sms.service'; import { MailService } from '../mail/mail.service'; @Injectable() export class AuthService { constructor( private prisma: PrismaService, private jwt: JwtService, private sms: SmsService, private mail: MailService, ) {} async register(email: string, password: string, name: string) { const existing = await this.prisma.users.findUnique({ where: { email } }); if (existing) throw new ConflictException('Email ya registrado'); const password_hash = await bcrypt.hash(password, 10); const user = await this.prisma.users.create({ data: { email, password_hash, name }, }); this.mail.sendWelcome(name, email).catch(() => {}); return this.generateToken(user); } async login(email: string, password: string) { const user = await this.prisma.users.findUnique({ where: { email } }); if (!user || !user.password_hash) throw new UnauthorizedException('Credenciales inválidas'); const valid = await bcrypt.compare(password, user.password_hash); if (!valid) throw new UnauthorizedException('Credenciales inválidas'); if (user.is_active === false) throw new UnauthorizedException('Tu cuenta ha sido bloqueada'); return this.generateToken(user); } async sendPhoneOtp(phone: string): Promise { await this.sms.sendOtp(phone); } async loginOrCreateByPhone(phone: string, code: string, name?: string) { const valid = this.sms.verifyOtp(phone, code); if (!valid) throw new BadRequestException('Código OTP inválido o expirado'); let user = await this.prisma.users.findUnique({ where: { phone } }); if (!user) { user = await this.prisma.users.create({ data: { phone, name: name || phone, is_phone_verified: true }, }); } else { if (user.is_active === false) throw new UnauthorizedException('Tu cuenta ha sido bloqueada'); user = await this.prisma.users.update({ where: { id: user.id }, data: { is_phone_verified: true }, }); } return this.generateToken(user); } async verifyOtpAndLinkPhone(userId: string, phone: string, code: string) { const valid = this.sms.verifyOtp(phone, code); if (!valid) throw new BadRequestException('Código OTP inválido o expirado'); const existing = await this.prisma.users.findUnique({ where: { phone } }); if (existing && existing.id !== userId) { throw new ConflictException('Teléfono ya registrado por otro usuario'); } return this.prisma.users.update({ where: { id: userId }, data: { phone, is_phone_verified: true }, }); } async linkEmail(userId: string, email: string, password: string) { const existing = await this.prisma.users.findUnique({ where: { email } }); if (existing) throw new ConflictException('Email ya registrado'); const password_hash = await bcrypt.hash(password, 10); return this.prisma.users.update({ where: { id: userId }, data: { email, password_hash }, }); } async changePassword(userId: string, currentPassword: string, newPassword: string) { const user = await this.prisma.users.findUnique({ where: { id: userId } }); if (!user || !user.password_hash) throw new BadRequestException('El usuario no tiene contraseña configurada'); const valid = await bcrypt.compare(currentPassword, user.password_hash); if (!valid) throw new UnauthorizedException('Contraseña actual incorrecta'); const password_hash = await bcrypt.hash(newPassword, 10); await this.prisma.users.update({ where: { id: userId }, data: { password_hash } }); return { ok: true }; } async me(userId: string) { const user = await this.prisma.users.findUnique({ where: { id: userId }, include: { professionals: { include: { schedules: true, payment_methods: true, specializations: true }, }, reputations: true, }, }); if (!user) throw new UnauthorizedException('Usuario no encontrado'); if (user.is_active === false) throw new UnauthorizedException('Tu cuenta ha sido bloqueada'); return { ...user, professional_state: user.pro_state }; } async getProfessionalId(userId: string): Promise { const prof = await this.prisma.professionals.findUnique({ where: { user_id: userId } }); return prof?.id || null; } private generateToken(user: any) { const payload = { sub: user.id, email: user.email, phone: user.phone }; return { access_token: this.jwt.sign(payload), user: { id: user.id, email: user.email, phone: user.phone, name: user.name, is_phone_verified: user.is_phone_verified ?? false, }, }; } }