feat: send FCM push notification on service status change + update docs

- services.module: import NotificationsModule
- services.service: inject NotificationsService, notify the other party
  (user or professional) when service status changes to accepted/denied/
  cancelled/active/completed
- ARQUITECTURA.md: updated to reflect current migration state (all migrated)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-06-18 12:49:27 -05:00
co-authored by Claude Sonnet 4.6
parent 130e52ddcd
commit 6e4cc7436d
3 changed files with 165 additions and 2 deletions
+2 -1
View File
@@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
import { ServicesService } from './services.service';
import { ServicesController } from './services.controller';
import { AuthModule } from '../auth/auth.module';
import { NotificationsModule } from '../notifications/notifications.module';
@Module({
imports: [AuthModule],
imports: [AuthModule, NotificationsModule],
providers: [ServicesService],
controllers: [ServicesController],
exports: [ServicesService],
+27 -1
View File
@@ -1,11 +1,21 @@
import { Injectable, NotFoundException, BadRequestException, ForbiddenException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { NotificationsService } from '../notifications/notifications.service';
import { ServiceStatus, VALID_TRANSITIONS } from './dto/service.dto';
const STATUS_MESSAGES: Partial<Record<ServiceStatus, { title: string; body: string }>> = {
[ServiceStatus.ACCEPTED]: { title: 'Servicio aceptado', body: 'El profesional aceptó tu solicitud.' },
[ServiceStatus.DENIED]: { title: 'Servicio rechazado', body: 'El profesional rechazó tu solicitud.' },
[ServiceStatus.CANCELLED]:{ title: 'Servicio cancelado', body: 'El servicio fue cancelado.' },
[ServiceStatus.ACTIVE]: { title: 'Servicio iniciado', body: 'El profesional inició el servicio.' },
[ServiceStatus.COMPLETED]:{ title: 'Servicio completado', body: '¡El servicio ha finalizado! Puedes dejar tu puntuación.' },
};
@Injectable()
export class ServicesService {
constructor(
private prisma: PrismaService,
private notifications: NotificationsService,
) {}
async create(data: {
@@ -74,10 +84,26 @@ export class ServicesService {
}
}
return this.prisma.services.update({
const updated = await this.prisma.services.update({
where: { id: serviceId },
data: { status: newStatus as any },
});
// Notify the other party
const msg = STATUS_MESSAGES[newStatus];
if (msg) {
const targetUserId = userId === service.user_id
? (await this.prisma.professionals.findUnique({ where: { id: service.professional_id } }))?.user_id
: service.user_id;
if (targetUserId) {
const target = await this.prisma.users.findUnique({ where: { id: targetUserId }, select: { fcm_token: true } });
if (target?.fcm_token) {
this.notifications.send(target.fcm_token, msg.title, msg.body).catch(() => {});
}
}
}
return updated;
}
async findByUser(userId: string, page = 1, limit = 20) {