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:
co-authored by
Claude Sonnet 4.6
parent
130e52ddcd
commit
6e4cc7436d
+136
@@ -0,0 +1,136 @@
|
||||
# ProsApp — Arquitectura del Proyecto
|
||||
|
||||
```
|
||||
📁 ~/Documents/GitHub/
|
||||
```
|
||||
|
||||
## Mapa de Componentes
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ USUARIOS (Web) │
|
||||
│ │
|
||||
│ ┌──────────────────────┐ ┌──────────────────────┐ │
|
||||
│ │ prosapp_landing_page │ │ prosappweb │ │
|
||||
│ │ (también prosapp_2026)│ │ Flutter Web │ │
|
||||
│ │ HTML + Tailwind CDN │ │ Provider + REST API │ │
|
||||
│ │ ✅ COMPLETO │ │ ✅ MIGRADO │ │
|
||||
│ └──────────┬───────────┘ └──────────┬───────────┘ │
|
||||
│ │ │ │
|
||||
│ ┌──────────▼──────────────────────────▼───────────────────┐ │
|
||||
│ │ prosapp-migration/admin │ │
|
||||
│ │ Next.js 16 + shadcn + Tailwind │ │
|
||||
│ │ Panel administrador │ │
|
||||
│ │ ✅ COMPLETO (95%) │ │
|
||||
│ └──────────┬──────────────────────────────────────────────┘ │
|
||||
└─────────────┼─────────────────────────────────────────────────┘
|
||||
│
|
||||
│ HTTP / JWT
|
||||
│
|
||||
┌─────────────▼─────────────────────────────────────────────────┐
|
||||
│ prosapp-migration/backend │
|
||||
│ NestJS + Prisma + PostgreSQL │
|
||||
│ ✅ COMPLETO — Deployado en Coolify │
|
||||
│ │
|
||||
│ Módulos: auth, users, professionals, services, professions, │
|
||||
│ locations, chat (Socket.IO), comments, │
|
||||
│ notifications (FCM), storage, settings │
|
||||
│ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ PostgreSQL │ (servidor Coolify) │
|
||||
│ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ HTTP / JWT
|
||||
│
|
||||
┌─────────────▼─────────────────────────────────────────────────┐
|
||||
│ USUARIOS (Mobile) │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ prosappco │ │
|
||||
│ │ Flutter iOS/Android │ │
|
||||
│ │ Bloc + 8 repos packages — ✅ MIGRADO │ │
|
||||
│ │ • user_repository • professional_repository │ │
|
||||
│ │ • chat_repository • score_repository │ │
|
||||
│ │ • city_repository • service_repository │ │
|
||||
│ │ • profession_repository • setting_repository │ │
|
||||
│ │ Push notifications via FCM + firebase_messaging │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Estado por Proyecto
|
||||
|
||||
| Carpeta | App | Tech | Estado |
|
||||
|---|---|---|---|
|
||||
| `prosapp-migration/backend` | API Central | NestJS + Prisma + PostgreSQL | ✅ **100%** — Deployado. Notificaciones FCM integradas en cambios de estado de servicio |
|
||||
| `prosapp-migration/admin` | Panel Admin | Next.js 16 + shadcn | ✅ **95%** — Pendiente: test post-deploy |
|
||||
| `prosappweb` | App Web (Flutter) | Flutter Web + Provider | ✅ **95%** — Migrado. Pendiente: testing manual |
|
||||
| `prosappco` | App Mobile (iOS/Android) | Flutter + Bloc + 8 repos | ✅ **95%** — Migrado + FCM. Pendiente: testing manual |
|
||||
| `prosapp_landing_page` | Landing Page | HTML + Tailwind CDN | ✅ **100%** — Estática, completa |
|
||||
|
||||
---
|
||||
|
||||
## Lo que está hecho
|
||||
|
||||
### Backend
|
||||
- Auth: registro, login, JWT, perfil (`/auth/*`)
|
||||
- Users: CRUD, foto, FCM token (`/users/*`)
|
||||
- Professionals: perfil, horarios, aprobación (`/professionals/*`)
|
||||
- Services: crear, listar (por rol), calendario, estado (`/services/*`)
|
||||
- Validación de transiciones de estado
|
||||
- Notificación FCM automática al cambiar estado
|
||||
- Comments/Scores: crear, listar por usuario/profesional, reputación
|
||||
- Chat: Socket.IO + REST (`/chat/*`)
|
||||
- Storage: upload a S3/local (`/storage/upload`)
|
||||
- Settings: CRUD global
|
||||
- Professions + Locations: catálogos
|
||||
- Notifications: envío FCM vía `POST /notifications/send`
|
||||
|
||||
### prosappweb (Flutter Web)
|
||||
- Auth migrada: login, registro, `GET /auth/me`
|
||||
- Todos los providers/models apuntan a REST API
|
||||
- Rutas correctas: `/services/me`, `/services/professional`, `/services/professional/requests`, `/services/professional/calendar`, `/services/public-calendar/:id`, `/services/:id/status`
|
||||
- DTOs correctos: snake_case, `rate`, `range1_hour1`, `location_preference`
|
||||
- Status en inglés en todos los archivos
|
||||
- Chat: `GET /chat/my`, `POST /chat/start/:id`
|
||||
- Horario: `PATCH /professionals/me/schedules` con formato array
|
||||
- Score: `POST /comments` con DTO correcto
|
||||
- `user.proState` (int) en lugar del antiguo `user.state` (string español)
|
||||
|
||||
### prosappco (Flutter Mobile)
|
||||
- 8 packages: `api_*_repository.dart` implementados (Firebase eliminado)
|
||||
- `firebase_messaging` + `firebase_core` para push notifications
|
||||
- FCM token registrado en backend al hacer login (`PATCH /users/me/fcm-token`)
|
||||
- Notificaciones en foreground via `flutter_local_notifications`
|
||||
- Background handler registrado
|
||||
- Status conversion maps: int ↔ string
|
||||
- `Timestamp` → `String` en todas las entidades
|
||||
- Professional UUID vs user_id: fallback resuelto en backend y Flutter
|
||||
|
||||
---
|
||||
|
||||
## Pendiente (Fase 3 — Testing)
|
||||
|
||||
| # | Tarea | Quién |
|
||||
|---|---|---|
|
||||
| T1 | Testear login/registro en prosappweb y prosappco | Manual |
|
||||
| T2 | Testear flujo completo: crear → aceptar → iniciar → terminar → puntuar | Manual |
|
||||
| T3 | Verificar notificaciones push llegan al dispositivo | Manual |
|
||||
| T4 | Probar chat en tiempo real (Socket.IO) | Manual |
|
||||
| T5 | Verificar WebSockets en producción | Manual |
|
||||
| T6 | Probar CRUDs desde admin (Profesiones, Ciudades, Settings) | Manual |
|
||||
| T7 | Verificar flujo aprobación/rechazo de profesionales desde admin | Manual |
|
||||
|
||||
---
|
||||
|
||||
## Variables de Entorno Requeridas (Coolify)
|
||||
|
||||
```env
|
||||
DATABASE_URL=postgresql://...
|
||||
JWT_SECRET=...
|
||||
FCM_SERVER_KEY=... # Firebase Cloud Messaging server key
|
||||
STORAGE_PATH=... # o S3 config si aplica
|
||||
```
|
||||
@@ -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],
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user