Add transactional emails: welcome, professional submission, status changes
- mail/mail.service.ts: MailService with HTML templates (welcome, submitted, approved, rejected, pending, deactivated). Silent if SMTP not configured. - mail/mail.module.ts: global module so all services can inject MailService - auth.service.ts: send welcome email on register() - professionals.service.ts: notify admin on new submission; notify professional on approve/deny/deactivate/setPending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
0a67f5d80a
commit
27f4a2d7ec
@@ -15,6 +15,7 @@ import { NotificationsModule } from './notifications/notifications.module';
|
|||||||
import { SmsModule } from './sms/sms.module';
|
import { SmsModule } from './sms/sms.module';
|
||||||
import { SuggestionsModule } from './suggestions/suggestions.module';
|
import { SuggestionsModule } from './suggestions/suggestions.module';
|
||||||
import { VerifikModule } from './verifik/verifik.module';
|
import { VerifikModule } from './verifik/verifik.module';
|
||||||
|
import { MailModule } from './mail/mail.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -34,6 +35,7 @@ import { VerifikModule } from './verifik/verifik.module';
|
|||||||
SmsModule,
|
SmsModule,
|
||||||
SuggestionsModule,
|
SuggestionsModule,
|
||||||
VerifikModule,
|
VerifikModule,
|
||||||
|
MailModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { JwtService } from '@nestjs/jwt';
|
|||||||
import * as bcrypt from 'bcryptjs';
|
import * as bcrypt from 'bcryptjs';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { SmsService } from '../sms/sms.service';
|
import { SmsService } from '../sms/sms.service';
|
||||||
|
import { MailService } from '../mail/mail.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
@@ -10,6 +11,7 @@ export class AuthService {
|
|||||||
private prisma: PrismaService,
|
private prisma: PrismaService,
|
||||||
private jwt: JwtService,
|
private jwt: JwtService,
|
||||||
private sms: SmsService,
|
private sms: SmsService,
|
||||||
|
private mail: MailService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async register(email: string, password: string, name: string) {
|
async register(email: string, password: string, name: string) {
|
||||||
@@ -21,6 +23,7 @@ export class AuthService {
|
|||||||
data: { email, password_hash, name },
|
data: { email, password_hash, name },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.mail.sendWelcome(name, email).catch(() => {});
|
||||||
return this.generateToken(user);
|
return this.generateToken(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module, Global } from '@nestjs/common';
|
||||||
|
import { MailService } from './mail.service';
|
||||||
|
import { PrismaModule } from '../prisma/prisma.module';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
providers: [MailService],
|
||||||
|
exports: [MailService],
|
||||||
|
})
|
||||||
|
export class MailModule {}
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import * as nodemailer from 'nodemailer';
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
|
||||||
|
const BRAND_COLOR = '#42A4EF';
|
||||||
|
const LOGO = 'https://prosapp.co/img/logo_prosapp.png';
|
||||||
|
const APP_URL = 'https://app.prosapp.co';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MailService {
|
||||||
|
private readonly logger = new Logger(MailService.name);
|
||||||
|
|
||||||
|
constructor(private prisma: PrismaService) {}
|
||||||
|
|
||||||
|
// ── transport ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private async createTransport(): Promise<{ transport: nodemailer.Transporter; from: string } | null> {
|
||||||
|
const dbConfig = await this.prisma.settings
|
||||||
|
.findUnique({ where: { key: 'smtp_config' } })
|
||||||
|
.then(r => r?.value as any)
|
||||||
|
.catch(() => null);
|
||||||
|
|
||||||
|
const host = dbConfig?.host || process.env.SMTP_HOST;
|
||||||
|
const port = parseInt(dbConfig?.port || process.env.SMTP_PORT || '587');
|
||||||
|
const user = dbConfig?.user || process.env.SMTP_USER;
|
||||||
|
const pass = dbConfig?.pass || process.env.SMTP_PASS;
|
||||||
|
const from = dbConfig?.from || process.env.EMAIL_FROM || user;
|
||||||
|
|
||||||
|
if (!host || !user || !pass) return null;
|
||||||
|
|
||||||
|
const transport = nodemailer.createTransport({
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
secure: port === 465,
|
||||||
|
auth: { user, pass },
|
||||||
|
});
|
||||||
|
return { transport, from: from || user };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Silent send — never throws, logs errors
|
||||||
|
async tryMail(to: string, subject: string, html: string) {
|
||||||
|
const ctx = await this.createTransport();
|
||||||
|
if (!ctx) return; // SMTP not configured — silently skip
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ctx.transport.sendMail({ from: `ProsApp <${ctx.from}>`, to, subject, html });
|
||||||
|
this.logger.log(`Mail enviado a ${to}: ${subject}`);
|
||||||
|
await this.prisma.message_logs.create({
|
||||||
|
data: { channel: 'email', recipient: to, body: subject, status: 'sent' },
|
||||||
|
}).catch(() => {});
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.warn(`Mail falló a ${to}: ${err}`);
|
||||||
|
await this.prisma.message_logs.create({
|
||||||
|
data: { channel: 'email', recipient: to, body: subject, status: 'error', error: String(err) },
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async adminEmail(): Promise<string | null> {
|
||||||
|
const g = await this.prisma.settings.findUnique({ where: { key: 'global' } }).then(r => r?.value as any).catch(() => null);
|
||||||
|
if (g?.admin_email) return g.admin_email;
|
||||||
|
const smtp = await this.prisma.settings.findUnique({ where: { key: 'smtp_config' } }).then(r => r?.value as any).catch(() => null);
|
||||||
|
return smtp?.user || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── base template ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private wrap(content: string, preheader = '') {
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>ProsApp</title>
|
||||||
|
<!--[if mso]><noscript><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript><![endif]-->
|
||||||
|
</head>
|
||||||
|
<body style="margin:0;padding:0;background:#F0F4F8;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
|
||||||
|
${preheader ? `<div style="display:none;max-height:0;overflow:hidden;color:#F0F4F8">${preheader}</div>` : ''}
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="background:#F0F4F8;padding:40px 16px">
|
||||||
|
<tr><td align="center">
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="max-width:560px">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<tr><td style="background:linear-gradient(135deg,${BRAND_COLOR},#1565C0);border-radius:16px 16px 0 0;padding:32px 40px;text-align:center">
|
||||||
|
<img src="${LOGO}" height="36" alt="ProsApp" style="display:block;margin:0 auto 16px">
|
||||||
|
<p style="margin:0;color:rgba(255,255,255,0.85);font-size:13px;letter-spacing:0.5px">ProsApp — Profesionales de salud</p>
|
||||||
|
</td></tr>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<tr><td style="background:#ffffff;padding:40px;border-left:1px solid #E2E8F0;border-right:1px solid #E2E8F0">
|
||||||
|
${content}
|
||||||
|
</td></tr>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<tr><td style="background:#F8FAFC;border:1px solid #E2E8F0;border-top:0;border-radius:0 0 16px 16px;padding:24px 40px;text-align:center">
|
||||||
|
<p style="margin:0;color:#94A3B8;font-size:12px">© ${new Date().getFullYear()} ProsApp · <a href="${APP_URL}" style="color:${BRAND_COLOR};text-decoration:none">app.prosapp.co</a></p>
|
||||||
|
<p style="margin:8px 0 0;color:#CBD5E1;font-size:11px">Si no esperabas este correo, puedes ignorarlo.</p>
|
||||||
|
</td></tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private badge(text: string, color: string, bg: string) {
|
||||||
|
return `<span style="display:inline-block;padding:4px 12px;background:${bg};color:${color};border-radius:20px;font-size:12px;font-weight:600;letter-spacing:0.3px">${text}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private btn(text: string, href: string) {
|
||||||
|
return `<a href="${href}" style="display:inline-block;padding:14px 32px;background:${BRAND_COLOR};color:#ffffff;border-radius:10px;font-size:15px;font-weight:600;text-decoration:none;margin-top:24px">${text}</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── templates ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async sendWelcome(name: string, email: string) {
|
||||||
|
const html = this.wrap(`
|
||||||
|
<h1 style="margin:0 0 8px;color:#1E293B;font-size:24px">¡Bienvenido a ProsApp, ${name}! 👋</h1>
|
||||||
|
<p style="margin:0 0 24px;color:#64748B;font-size:15px;line-height:1.6">
|
||||||
|
Nos alegra tenerte aquí. Ya puedes explorar profesionales de salud, agendar citas y mucho más.
|
||||||
|
</p>
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="background:#F0F8FF;border:1px solid #BAE3FF;border-radius:12px;padding:20px 24px;margin-bottom:24px">
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px 0">
|
||||||
|
<p style="margin:0;color:#1E293B;font-size:14px">✅ <strong>Cuenta creada</strong> con correo <strong>${email}</strong></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px 0;border-top:1px solid #E0F0FF">
|
||||||
|
<p style="margin:0;color:#475569;font-size:14px">📋 Completa tu perfil para una mejor experiencia</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px 0;border-top:1px solid #E0F0FF">
|
||||||
|
<p style="margin:0;color:#475569;font-size:14px">🔍 Encuentra y agenda profesionales de salud</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div style="text-align:center">${this.btn('Ir a ProsApp', APP_URL)}</div>
|
||||||
|
`, `Bienvenido a ProsApp, ${name}`);
|
||||||
|
|
||||||
|
await this.tryMail(email, `¡Bienvenido a ProsApp, ${name}!`, html);
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendProfessionalSubmitted(profName: string, profEmail: string, profession: string) {
|
||||||
|
const admin = await this.adminEmail();
|
||||||
|
if (!admin) return;
|
||||||
|
|
||||||
|
const html = this.wrap(`
|
||||||
|
<p style="margin:0 0 4px;color:#64748B;font-size:13px;text-transform:uppercase;letter-spacing:0.5px">Nueva solicitud pendiente</p>
|
||||||
|
<h1 style="margin:0 0 24px;color:#1E293B;font-size:22px">Solicitud de profesional recibida</h1>
|
||||||
|
<table width="100%" cellpadding="0" cellspacing="0" style="border:1px solid #E2E8F0;border-radius:12px;overflow:hidden;margin-bottom:24px">
|
||||||
|
<tr style="background:#F8FAFC">
|
||||||
|
<td style="padding:16px 20px;color:#64748B;font-size:13px;font-weight:600;width:40%">Nombre</td>
|
||||||
|
<td style="padding:16px 20px;color:#1E293B;font-size:14px;font-weight:500">${profName}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:16px 20px;color:#64748B;font-size:13px;font-weight:600;border-top:1px solid #E2E8F0">Correo</td>
|
||||||
|
<td style="padding:16px 20px;color:#1E293B;font-size:14px;border-top:1px solid #E2E8F0">${profEmail}</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="background:#F8FAFC">
|
||||||
|
<td style="padding:16px 20px;color:#64748B;font-size:13px;font-weight:600;border-top:1px solid #E2E8F0">Profesión</td>
|
||||||
|
<td style="padding:16px 20px;color:#1E293B;font-size:14px;font-weight:500;border-top:1px solid #E2E8F0">${profession || '—'}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:16px 20px;color:#64748B;font-size:13px;font-weight:600;border-top:1px solid #E2E8F0">Estado</td>
|
||||||
|
<td style="padding:16px 20px;border-top:1px solid #E2E8F0">${this.badge('En revisión', '#92400E', '#FEF3C7')}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p style="margin:0 0 24px;color:#64748B;font-size:14px;line-height:1.6">
|
||||||
|
Revisa los documentos adjuntos y aprueba o rechaza la solicitud desde el panel de administración.
|
||||||
|
</p>
|
||||||
|
<div style="text-align:center">${this.btn('Revisar en el panel', 'https://admin.prosapp.co/professionals')}</div>
|
||||||
|
`, `${profName} ha enviado una solicitud de profesional`);
|
||||||
|
|
||||||
|
await this.tryMail(admin, `Nueva solicitud de profesional — ${profName}`, html);
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendStatusChanged(
|
||||||
|
profName: string,
|
||||||
|
email: string,
|
||||||
|
status: 'approved' | 'rejected' | 'pending' | 'deactivated',
|
||||||
|
) {
|
||||||
|
const configs = {
|
||||||
|
approved: {
|
||||||
|
subject: '¡Tu solicitud fue aprobada! 🎉',
|
||||||
|
badge: this.badge('Aprobado', '#065F46', '#D1FAE5'),
|
||||||
|
title: '¡Felicidades, ya eres profesional en ProsApp!',
|
||||||
|
body: 'Tu solicitud fue revisada y <strong>aprobada</strong>. Ya puedes recibir clientes, gestionar tu agenda y ofrecer tus servicios de salud en la plataforma.',
|
||||||
|
cta: this.btn('Ver mi perfil profesional', APP_URL),
|
||||||
|
accent: '#10B981',
|
||||||
|
accentBg: '#D1FAE5',
|
||||||
|
icon: '✅',
|
||||||
|
},
|
||||||
|
rejected: {
|
||||||
|
subject: 'Actualización sobre tu solicitud de profesional',
|
||||||
|
badge: this.badge('No aprobada', '#991B1B', '#FEE2E2'),
|
||||||
|
title: 'Tu solicitud no fue aprobada',
|
||||||
|
body: 'Lamentablemente tu solicitud <strong>no fue aprobada</strong> en esta ocasión. Puedes corregir los documentos y volver a intentarlo desde la aplicación.',
|
||||||
|
cta: this.btn('Volver a intentarlo', APP_URL),
|
||||||
|
accent: '#EF4444',
|
||||||
|
accentBg: '#FEE2E2',
|
||||||
|
icon: '📋',
|
||||||
|
},
|
||||||
|
pending: {
|
||||||
|
subject: 'Tu solicitud está en revisión',
|
||||||
|
badge: this.badge('En revisión', '#92400E', '#FEF3C7'),
|
||||||
|
title: 'Tu solicitud volvió a revisión',
|
||||||
|
body: 'Un administrador ha puesto tu solicitud nuevamente <strong>en revisión</strong>. Te notificaremos cuando haya una actualización.',
|
||||||
|
cta: this.btn('Ver estado', APP_URL),
|
||||||
|
accent: '#F59E0B',
|
||||||
|
accentBg: '#FEF3C7',
|
||||||
|
icon: '🔍',
|
||||||
|
},
|
||||||
|
deactivated: {
|
||||||
|
subject: 'Tu cuenta profesional fue desactivada',
|
||||||
|
badge: this.badge('Desactivada', '#374151', '#F3F4F6'),
|
||||||
|
title: 'Tu cuenta profesional fue desactivada',
|
||||||
|
body: 'Tu perfil profesional ha sido <strong>desactivado</strong>. Si crees que es un error, comunícate con el soporte de ProsApp.',
|
||||||
|
cta: this.btn('Contactar soporte', APP_URL),
|
||||||
|
accent: '#6B7280',
|
||||||
|
accentBg: '#F3F4F6',
|
||||||
|
icon: '⚠️',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const c = configs[status];
|
||||||
|
const html = this.wrap(`
|
||||||
|
<div style="text-align:center;margin-bottom:28px">
|
||||||
|
<div style="display:inline-flex;align-items:center;justify-content:center;width:64px;height:64px;background:${c.accentBg};border-radius:50%;font-size:28px;margin-bottom:16px">${c.icon}</div>
|
||||||
|
<br>${c.badge}
|
||||||
|
</div>
|
||||||
|
<h1 style="margin:0 0 12px;color:#1E293B;font-size:22px;text-align:center">${c.title}</h1>
|
||||||
|
<p style="margin:0 0 28px;color:#64748B;font-size:15px;line-height:1.7;text-align:center">
|
||||||
|
Hola <strong>${profName}</strong>, ${c.body}
|
||||||
|
</p>
|
||||||
|
<div style="background:#F8FAFC;border-left:4px solid ${c.accent};border-radius:0 8px 8px 0;padding:16px 20px;margin-bottom:28px">
|
||||||
|
<p style="margin:0;color:#475569;font-size:13px;line-height:1.6">
|
||||||
|
Si tienes preguntas, escríbenos a través del soporte en la app o responde este correo.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="text-align:center">${c.cta}</div>
|
||||||
|
`, c.title);
|
||||||
|
|
||||||
|
await this.tryMail(email, c.subject, html);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Injectable, NotFoundException, BadRequestException, ForbiddenException } from '@nestjs/common';
|
import { Injectable, NotFoundException, BadRequestException, ForbiddenException } from '@nestjs/common';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { MailService } from '../mail/mail.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProfessionalsService {
|
export class ProfessionalsService {
|
||||||
constructor(private prisma: PrismaService) {}
|
constructor(private prisma: PrismaService, private mail: MailService) {}
|
||||||
|
|
||||||
async findAllActive(page = 1, limit = 20, city?: string) {
|
async findAllActive(page = 1, limit = 20, city?: string) {
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
@@ -72,16 +73,20 @@ export class ProfessionalsService {
|
|||||||
|
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
await this.prisma.users.update({ where: { id: userId }, data: { pro_state: 1 } });
|
await this.prisma.users.update({ where: { id: userId }, data: { pro_state: 1 } });
|
||||||
return this.prisma.professionals.create({ data: { user_id: userId, is_active: false, ...data } });
|
const prof = await this.prisma.professionals.create({ data: { user_id: userId, is_active: false, ...data } });
|
||||||
|
const u = await this.prisma.users.findUnique({ where: { id: userId }, select: { name: true, email: true } });
|
||||||
|
if (u?.email) this.mail.sendProfessionalSubmitted(u.name || 'Usuario', u.email, data.profession || '').catch(() => {});
|
||||||
|
return prof;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-submitting after rejection reset (pro_state=0): move back to pending
|
// Re-submitting after rejection reset (pro_state=0): move back to pending
|
||||||
const user = await this.prisma.users.findUnique({ where: { id: userId }, select: { pro_state: true } });
|
const user = await this.prisma.users.findUnique({ where: { id: userId }, select: { pro_state: true, name: true, email: true } });
|
||||||
if (user?.pro_state === 0) {
|
if (user?.pro_state === 0) {
|
||||||
await this.prisma.$transaction([
|
await this.prisma.$transaction([
|
||||||
this.prisma.professionals.update({ where: { user_id: userId }, data: { ...data, is_active: false } }),
|
this.prisma.professionals.update({ where: { user_id: userId }, data: { ...data, is_active: false } }),
|
||||||
this.prisma.users.update({ where: { id: userId }, data: { pro_state: 1 } }),
|
this.prisma.users.update({ where: { id: userId }, data: { pro_state: 1 } }),
|
||||||
]);
|
]);
|
||||||
|
if (user.email) this.mail.sendProfessionalSubmitted(user.name || 'Usuario', user.email, data.profession || '').catch(() => {});
|
||||||
return this.prisma.professionals.findUnique({ where: { user_id: userId } });
|
return this.prisma.professionals.findUnique({ where: { user_id: userId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,15 +160,11 @@ export class ProfessionalsService {
|
|||||||
if (!prof) throw new NotFoundException('Profesional no encontrado');
|
if (!prof) throw new NotFoundException('Profesional no encontrado');
|
||||||
|
|
||||||
await this.prisma.$transaction([
|
await this.prisma.$transaction([
|
||||||
this.prisma.professionals.update({
|
this.prisma.professionals.update({ where: { id: professionalId }, data: { is_active: true } }),
|
||||||
where: { id: professionalId },
|
this.prisma.users.update({ where: { id: prof.user_id }, data: { pro_state: 2 } }),
|
||||||
data: { is_active: true },
|
|
||||||
}),
|
|
||||||
this.prisma.users.update({
|
|
||||||
where: { id: prof.user_id },
|
|
||||||
data: { pro_state: 2 },
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
|
const u = await this.prisma.users.findUnique({ where: { id: prof.user_id }, select: { name: true, email: true } });
|
||||||
|
if (u?.email) this.mail.sendStatusChanged(u.name || 'Profesional', u.email, 'approved').catch(() => {});
|
||||||
return { message: 'Profesional aprobado' };
|
return { message: 'Profesional aprobado' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +176,8 @@ export class ProfessionalsService {
|
|||||||
this.prisma.professionals.update({ where: { id: professionalId }, data: { is_active: false, updated_at: new Date() } }),
|
this.prisma.professionals.update({ where: { id: professionalId }, data: { is_active: false, updated_at: new Date() } }),
|
||||||
this.prisma.users.update({ where: { id: prof.user_id }, data: { pro_state: 3 } }),
|
this.prisma.users.update({ where: { id: prof.user_id }, data: { pro_state: 3 } }),
|
||||||
]);
|
]);
|
||||||
|
const u = await this.prisma.users.findUnique({ where: { id: prof.user_id }, select: { name: true, email: true } });
|
||||||
|
if (u?.email) this.mail.sendStatusChanged(u.name || 'Profesional', u.email, 'rejected').catch(() => {});
|
||||||
return { message: 'Solicitud rechazada' };
|
return { message: 'Solicitud rechazada' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,6 +194,8 @@ export class ProfessionalsService {
|
|||||||
this.prisma.professionals.update({ where: { id }, data: { is_active: false } }),
|
this.prisma.professionals.update({ where: { id }, data: { is_active: false } }),
|
||||||
this.prisma.users.update({ where: { id: prof.user_id }, data: { pro_state: 3 } }),
|
this.prisma.users.update({ where: { id: prof.user_id }, data: { pro_state: 3 } }),
|
||||||
]);
|
]);
|
||||||
|
const u = await this.prisma.users.findUnique({ where: { id: prof.user_id }, select: { name: true, email: true } });
|
||||||
|
if (u?.email) this.mail.sendStatusChanged(u.name || 'Profesional', u.email, 'deactivated').catch(() => {});
|
||||||
return { message: 'Profesional desactivado' };
|
return { message: 'Profesional desactivado' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +206,8 @@ export class ProfessionalsService {
|
|||||||
this.prisma.professionals.update({ where: { id }, data: { is_active: false } }),
|
this.prisma.professionals.update({ where: { id }, data: { is_active: false } }),
|
||||||
this.prisma.users.update({ where: { id: prof.user_id }, data: { pro_state: 1 } }),
|
this.prisma.users.update({ where: { id: prof.user_id }, data: { pro_state: 1 } }),
|
||||||
]);
|
]);
|
||||||
|
const u = await this.prisma.users.findUnique({ where: { id: prof.user_id }, select: { name: true, email: true } });
|
||||||
|
if (u?.email) this.mail.sendStatusChanged(u.name || 'Profesional', u.email, 'pending').catch(() => {});
|
||||||
return { message: 'Profesional puesto en revisión' };
|
return { message: 'Profesional puesto en revisión' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user