Sistema NEXUS v3.4 migrado con: Estructura principal: - core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles) - core/catalog: Catalogo de funcionalidades reutilizables - shared/knowledge-base: Base de conocimiento compartida - devtools/scripts: Herramientas de desarrollo - control-plane/registries: Control de servicios y CI/CD - orchestration/: Configuracion de orquestacion de agentes Proyectos incluidos (11): - gamilit (submodule -> GitHub) - trading-platform (OrbiquanTIA) - erp-suite con 5 verticales: - erp-core, construccion, vidrio-templado - mecanicas-diesel, retail, clinicas - betting-analytics - inmobiliaria-analytics - platform_marketing_content - pos-micro, erp-basico Configuracion: - .gitignore completo para Node.js/Python/Docker - gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git) - Sistema de puertos estandarizado (3005-3199) Generated with NEXUS v3.4 Migration System EPIC-010: Configuracion Git y Repositorios
233 lines
6.1 KiB
TypeScript
233 lines
6.1 KiB
TypeScript
/**
|
|
* NOTIFICATION SERVICE - REFERENCE IMPLEMENTATION
|
|
*
|
|
* @description Servicio de notificaciones multi-canal.
|
|
* Soporta notificaciones in-app, email y push.
|
|
*
|
|
* @usage Copiar y adaptar según necesidades del proyecto.
|
|
* @origin gamilit/apps/backend/src/modules/notifications/services/notifications.service.ts
|
|
*/
|
|
|
|
import { Injectable, Logger } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
|
|
// Adaptar imports según proyecto
|
|
// import { Notification, NotificationPreference, UserDevice } from '../entities';
|
|
|
|
/**
|
|
* Tipos de notificación
|
|
*/
|
|
export enum NotificationTypeEnum {
|
|
INFO = 'info',
|
|
SUCCESS = 'success',
|
|
WARNING = 'warning',
|
|
ERROR = 'error',
|
|
}
|
|
|
|
/**
|
|
* Canales de notificación
|
|
*/
|
|
export enum NotificationChannelEnum {
|
|
IN_APP = 'in_app',
|
|
EMAIL = 'email',
|
|
PUSH = 'push',
|
|
}
|
|
|
|
@Injectable()
|
|
export class NotificationService {
|
|
private readonly logger = new Logger(NotificationService.name);
|
|
|
|
constructor(
|
|
@InjectRepository(Notification, 'notifications')
|
|
private readonly notificationRepo: Repository<Notification>,
|
|
|
|
@InjectRepository(NotificationPreference, 'notifications')
|
|
private readonly preferenceRepo: Repository<NotificationPreference>,
|
|
|
|
// Inyectar servicios de email y push si están disponibles
|
|
// private readonly emailService: EmailService,
|
|
// private readonly pushService: PushNotificationService,
|
|
) {}
|
|
|
|
/**
|
|
* Enviar notificación a un usuario
|
|
*
|
|
* @param dto - Datos de la notificación
|
|
*/
|
|
async send(dto: CreateNotificationDto): Promise<Notification> {
|
|
// 1. Obtener preferencias del usuario
|
|
const preferences = await this.getUserPreferences(dto.userId);
|
|
|
|
// 2. Crear notificación en BD (siempre in-app)
|
|
const notification = this.notificationRepo.create({
|
|
user_id: dto.userId,
|
|
type: dto.type,
|
|
title: dto.title,
|
|
message: dto.message,
|
|
data: dto.data,
|
|
is_read: false,
|
|
});
|
|
await this.notificationRepo.save(notification);
|
|
|
|
// 3. Enviar por canales adicionales según preferencias
|
|
if (preferences.email_enabled && dto.sendEmail !== false) {
|
|
await this.sendEmail(dto);
|
|
}
|
|
|
|
if (preferences.push_enabled && dto.sendPush !== false) {
|
|
await this.sendPush(dto);
|
|
}
|
|
|
|
return notification;
|
|
}
|
|
|
|
/**
|
|
* Obtener notificaciones de un usuario
|
|
*/
|
|
async getByUser(
|
|
userId: string,
|
|
options?: {
|
|
unreadOnly?: boolean;
|
|
limit?: number;
|
|
offset?: number;
|
|
},
|
|
): Promise<{ notifications: Notification[]; total: number }> {
|
|
const query = this.notificationRepo
|
|
.createQueryBuilder('n')
|
|
.where('n.user_id = :userId', { userId })
|
|
.orderBy('n.created_at', 'DESC');
|
|
|
|
if (options?.unreadOnly) {
|
|
query.andWhere('n.is_read = false');
|
|
}
|
|
|
|
const total = await query.getCount();
|
|
|
|
if (options?.limit) {
|
|
query.limit(options.limit);
|
|
}
|
|
if (options?.offset) {
|
|
query.offset(options.offset);
|
|
}
|
|
|
|
const notifications = await query.getMany();
|
|
|
|
return { notifications, total };
|
|
}
|
|
|
|
/**
|
|
* Marcar notificación como leída
|
|
*/
|
|
async markAsRead(notificationId: string, userId: string): Promise<void> {
|
|
await this.notificationRepo.update(
|
|
{ id: notificationId, user_id: userId },
|
|
{ is_read: true, read_at: new Date() },
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Marcar todas las notificaciones como leídas
|
|
*/
|
|
async markAllAsRead(userId: string): Promise<number> {
|
|
const result = await this.notificationRepo.update(
|
|
{ user_id: userId, is_read: false },
|
|
{ is_read: true, read_at: new Date() },
|
|
);
|
|
return result.affected || 0;
|
|
}
|
|
|
|
/**
|
|
* Obtener conteo de notificaciones no leídas
|
|
*/
|
|
async getUnreadCount(userId: string): Promise<number> {
|
|
return this.notificationRepo.count({
|
|
where: { user_id: userId, is_read: false },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Eliminar notificación
|
|
*/
|
|
async delete(notificationId: string, userId: string): Promise<void> {
|
|
await this.notificationRepo.delete({
|
|
id: notificationId,
|
|
user_id: userId,
|
|
});
|
|
}
|
|
|
|
// ============ HELPERS PRIVADOS ============
|
|
|
|
private async getUserPreferences(userId: string): Promise<UserPreferences> {
|
|
const prefs = await this.preferenceRepo.findOne({
|
|
where: { user_id: userId },
|
|
});
|
|
|
|
// Retornar defaults si no tiene preferencias
|
|
return prefs || {
|
|
email_enabled: true,
|
|
push_enabled: true,
|
|
in_app_enabled: true,
|
|
};
|
|
}
|
|
|
|
private async sendEmail(dto: CreateNotificationDto): Promise<void> {
|
|
try {
|
|
// Implementar envío de email
|
|
// await this.emailService.send({
|
|
// to: dto.userEmail,
|
|
// subject: dto.title,
|
|
// template: 'notification',
|
|
// context: { message: dto.message, data: dto.data },
|
|
// });
|
|
this.logger.log(`Email notification sent to user ${dto.userId}`);
|
|
} catch (error) {
|
|
this.logger.error(`Failed to send email notification: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
private async sendPush(dto: CreateNotificationDto): Promise<void> {
|
|
try {
|
|
// Implementar envío push
|
|
// await this.pushService.send(dto.userId, {
|
|
// title: dto.title,
|
|
// body: dto.message,
|
|
// data: dto.data,
|
|
// });
|
|
this.logger.log(`Push notification sent to user ${dto.userId}`);
|
|
} catch (error) {
|
|
this.logger.error(`Failed to send push notification: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============ TIPOS ============
|
|
|
|
interface CreateNotificationDto {
|
|
userId: string;
|
|
type: NotificationTypeEnum;
|
|
title: string;
|
|
message: string;
|
|
data?: Record<string, any>;
|
|
sendEmail?: boolean;
|
|
sendPush?: boolean;
|
|
}
|
|
|
|
interface UserPreferences {
|
|
email_enabled: boolean;
|
|
push_enabled: boolean;
|
|
in_app_enabled: boolean;
|
|
}
|
|
|
|
interface Notification {
|
|
id: string;
|
|
user_id: string;
|
|
type: string;
|
|
title: string;
|
|
message: string;
|
|
data?: Record<string, any>;
|
|
is_read: boolean;
|
|
read_at?: Date;
|
|
created_at: Date;
|
|
}
|