import { WebSocketGateway, OnGatewayConnection, OnGatewayDisconnect, SubscribeMessage, MessageBody, ConnectedSocket, WebSocketServer, } from '@nestjs/websockets'; import { Socket, Server } from 'socket.io'; import axios, { AxiosResponse } from 'axios'; import { config } from 'dotenv'; import { OrderDeliveryDto, OrderDeliveryResponseDto, OrderCanceledDto, StatusOrderDto, CoordinatesDto, CashCutDto, ListenOrdersDto, } from './models'; import { VerifyExsistUserRoomService } from 'src/utils/verify-exsist-user-room.service'; import { ChatService } from '../chat/chat.service'; import { Message, MessageCliRep } from './models/chat-support.interface'; import { ChatCliRepService } from 'src/chat-cli-rep/chat-cli-rep.service'; import { CreateChatCliRep } from 'src/chat-cli-rep/interfaces/chatCliRep.interface'; import { WitchUserIsService } from 'src/utils/witchUserIs.service'; import { FirebaseService } from '../common/services/firebase.service'; import { ChangeStatusOrdersService } from 'src/utils/change-status-orders.service'; import { SendPushNotificationService } from 'src/utils/send-push-notification.service'; import { User } from './models/user.interface'; import { ChatCliRepDocument } from 'src/chat-cli-rep/schema/chat-cli-rep.schema'; import { SocketsLogsService } from 'src/sockets-logs/sockets-logs.service'; import { DeliveryStatusService } from 'src/delivery-status/delivery-status.service'; import { DeliveryLocationService } from 'src/delivery-location/delivery-location.service'; config(); @WebSocketGateway({ cors: { origin: ['*'], methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', // Added OPTION credentials: true, allowedHeaders: ['Content-Type', 'Authorization'], }, transports: ['websocket', 'polling'], pingTimeout: 60000, pingInterval: 25000, cookie: false, connectionStateRecovery: { maxDisconnectionDuration: 30000, skipMiddlewares: true, }, }) export class SubitoDeliveryGateway implements OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; private connectedUsers = new Map(); // userId -> User private userSockets = new Map(); // socketId -> userId private soporteUser = new Map(); // socketId -> userId private connectedUsersSoporte = new Map(); // socketId -> userId private callCenterUser = new Map(); // socketId -> userId private connectedUsersCallCenter = new Map(); // socketId -> userId private Establishment = new Map(); // socketId -> userId private connectedUsersEstablishment = new Map(); // socketId -> userId private Repartidor = new Map(); // socketId -> userId private connectedUsersRepartidor = new Map(); // socketId -> userId private readonly TIME_NOTIFY = 600000; private readonly timeouts: { [key: string]: NodeJS.Timeout } = {}; private readonly timeoutsNotification: { [key: string]: NodeJS.Timeout } = {}; private readonly endpoint: string = process.env.HOST_ENDPOINT!; private readonly marketplace: string = process.env.HOST_MARKETPLACE!; constructor( private verifyExsistUserRoomService: VerifyExsistUserRoomService, private chatService: ChatService, private chatCliRepService: ChatCliRepService, private witchUserIsService: WitchUserIsService, private firebaseService: FirebaseService, private changeStatusOrdersService: ChangeStatusOrdersService, private sendPushNotificationService: SendPushNotificationService, private socketsLogsService: SocketsLogsService, private deliveryStatusService: DeliveryStatusService, private deliveryLocationService: DeliveryLocationService, ) { // Ejecutar la tarea cada 12 segundos // setInterval(() => this.sendRequestToAPI(), 12000); // Limpiar caché de órdenes antiguas cada hora setInterval(() => this.firebaseService.cleanOldOrders(24), 60 * 60 * 1000); } handleConnection(client: Socket) { console.log('Cliente conectado:', client.id); // Unir el socket a la sala 'panel' // client.join('panel-admin'); } handleDisconnect(client: Socket) { console.log('Cliente desconectado:', client.id); // Limpiar de soporteUser si existe const userIdSoporte = this.soporteUser.get(client.id); if (userIdSoporte) { this.soporteUser.delete(client.id); console.log(`Usuario ${userIdSoporte} removido de soporte`); } // Limpiar de connectedUsersSoporte si existe const userIdCallCenter = this.connectedUsersSoporte.get(client.id); if (userIdCallCenter) { this.connectedUsersSoporte.delete(client.id); console.log(`Usuario ${userIdCallCenter} removido de call-center`); } // Limpiar de callCenterUser si existe const userIdCallCenterUser = this.callCenterUser.get(client.id); if (userIdCallCenterUser) { this.callCenterUser.delete(client.id); console.log( `Usuario ${userIdCallCenterUser} removido de call-center user`, ); } // Limpiar de connectedUsersCallCenter si existe const userIdConnectedCallCenter = this.connectedUsersCallCenter.get( client.id, ); if (userIdConnectedCallCenter) { this.connectedUsersCallCenter.delete(client.id); console.log( `Usuario ${userIdConnectedCallCenter} removido de connected call-center`, ); } // Limpiar de connectedUsers (chat) si existe const userId = this.userSockets.get(client.id); if (userId) { this.connectedUsers.delete(userId); this.userSockets.delete(client.id); // Notificar a otros usuarios que este usuario se desconectó this.server.emit('userDisconnected', { userId }); console.log(`User ${userId} disconnected`); } } //MUERTA @SubscribeMessage('orderDelivery') handleOrderDelivery( @MessageBody() orderData: OrderDeliveryDto, @ConnectedSocket() client: Socket, ) { const { orderId, establishmentId, total, reason } = orderData; if (!establishmentId) { return; } console.log( `Orden recibida: OrderID - ${orderId}, EstablishmentID - ${establishmentId}`, ); axios .post(this.endpoint + '/api/apprisa-panel/sendToEstablishment', { id: establishmentId, }) .then((response) => { console.log( 'Se ha notificado al establecimiento: ' + establishmentId + ' de la orden: ' + orderId, ); }) .catch((error) => { console.error({ Message: 'Ocurrio un error al generar la notificación', Error: error.data, }); }); // Cancelación automática deshabilitada console.log( `No se recibió respuesta para la orden ${orderId}. Enviando notificación al establecimiento.`, ); client.broadcast.to('establishment').emit('orderDelivery', { orderId: orderId, establishmentId: establishmentId, }); axios .post(this.endpoint + '/api/apprisa-panel/sendToEstablishment', { id: establishmentId, }) .then((response) => { console.log( 'Se ha notificado al establecimiento: ' + establishmentId + ' de la orden: ' + orderId, ); }) .catch((error) => { console.error({ Message: 'Ocurrio un error al generar la notificación', Error: error.data, }); }); // Emitir el evento 'orderDelivery' a todos los clientes en la sala 'panel' client.to('establishment').emit('orderDelivery', { orderId: orderId, establishmentId: establishmentId, }); console.log( `Reemitiendo 'orderDelivery' a la sala 'panel' con los datos:`, { orderId, establishmentId, }, ); } @SubscribeMessage('messageSup') async handleMessageSupport( @MessageBody() data: Message, @ConnectedSocket() client: Socket, ) { try { const message: Message = { id: this.generateId(), senderId: data.senderId, senderUsername: data.senderUsername, receiverId: data.receiverId, content: data.content, type: data.type, fileName: data.fileName, fileUrl: data.fileUrl, fileSize: data.fileSize, mimeType: data.mimeType, timestamp: new Date(), chatId: data.chatId, user: data.user, }; console.log('mensaje en el back', message); // Guardar mensaje en MongoDB try { // Asegurar que el chat existe antes de agregar el mensaje await this.chatService.findOrCreateChat( data.chatId, data.senderUsername, 'Chat de Soporte', ); const chatMessage = { sender: this.determineSender(data.senderId, data.receiverId), content: data.content || '', type: this.mapMessageType(data.type), timestamp: new Date(), metadata: { fileName: data.fileName, fileUrl: data.fileUrl, fileSize: data.fileSize, mimeType: data.mimeType || this.getMimeType(data.fileName), }, }; await this.chatService.addMessage(data.chatId, chatMessage); if (data.user != 1 || data.user == undefined) { await this.sendPushNotificationService.sendPushNotification(Number(data.receiverId), 'Chat', data.content || 'imagen') } console.log('Mensaje guardado en MongoDB exitosamente'); } catch (dbError) { this.socketsLogsService.create({ event: 'messageSup', type: 'ERROR', message: 'El mensaje no se guardó en la base de datos de mongo.', payload: message, socketId: '', namespace: '', userId: 'El usuario tiene el id:' + message.senderId.toString(), }); console.error('Error al guardar mensaje en MongoDB:', dbError); // Continuar con el flujo normal aunque falle el guardado } // Enviar mensaje a todos en el chat EXCEPTO al emisor client.to(`chat_${data.chatId}`).emit('messageReceived', message); // Enviar confirmación solo al emisor client.emit('messageSent', message); return { status: 'success', message }; } catch (error) { return { status: 'error', message: error.message }; } } // Maneja unirse a una sala @SubscribeMessage('join-room') handleJoinRoom( client: Socket, payload: { room: string; UserId: number; username: string }, ): void { console.log('payload', payload); const { room, UserId, username } = payload; if (room === 'soporte') { console.log('usuarios en soporte', this.soporteUser); this.server .to('call-center') .emit('user-connected', { userId: UserId, username }); this.verifyExsistUserRoomService.verifyExistsUserInRoom( room, UserId, this.soporteUser, client.id, ); if (!isAlreadyInSupport) { this.server.to('call-center').emit('user-connected', { userId: UserId, username }); await this.sendPushNotificationService.sendPushNotificationToPanelUsersContro(`Chat de soporte`, `El usuario ${username} de marktplace necesita ayuda`); } } else if (room === 'call-center') { console.log('usuarios en call-cennter', this.callCenterUser); this.verifyExsistUserRoomService.verifyExistsUserInRoom( room, UserId, this.connectedUsersSoporte, client.id, ); } else if (room === 'establishment') { console.log('usuarios en establishment', this.Establishment); this.verifyExsistUserRoomService.verifyExistsUserInRoom( room, UserId, this.connectedUsersEstablishment, client.id, ); } else if (room === 'repartidor') { console.log('usuarios en repartidor', this.Repartidor); this.verifyExsistUserRoomService.verifyExistsUserInRoom( room, UserId, this.connectedUsersRepartidor, client.id, ); } else { this.socketsLogsService.create({ event: 'join-room', type: 'ERROR', message: 'El usuario intenta unirse a una room que no existe:' + payload.room, payload: payload, socketId: '', namespace: '', userId: 'El usuario tiene el id:' + payload.UserId.toString(), }); } client.join(room); console.log('Usuario unido a la sala:', room); client.emit('joined-room', `Te uniste a la sala ${room}`); } // Nuevo método para unirse a una sala de chat específica @SubscribeMessage('join-chat') async handleJoinChat( @MessageBody() data: { chatId: string; userId: number; username: string }, @ConnectedSocket() client: Socket, ): Promise { try { const { chatId, userId, username } = data; console.log( `Usuario ${username} (${userId}) intentando unirse al chat ${chatId}`, ); // Verificar que el chat existe en la base de datos const chat = await this.chatService.findOne(chatId); if (!chat) { this.socketsLogsService.create({ event: 'join-chat', type: 'ERROR', message: 'El usuario no puede unirse al chat porque no existe', payload: data, socketId: '', namespace: '', userId: 'El usuario tiene el id:' + userId.toString(), }); console.error(`Chat ${chatId} no encontrado`); client.emit('join-chat-error', { message: 'Chat no encontrado' }); return; } // Verificar que el chat está activo if (chat.status !== 'active' && chat.status !== 'pending') { console.log(`Chat ${chatId} no está activo (status: ${chat.status})`); client.emit('join-chat-error', { message: 'El chat no está activo' }); this.socketsLogsService.create({ event: 'join-chat', type: 'ERROR', message: 'El usuario intenta conectarse al chat que ya no está activo.', payload: data, socketId: '', namespace: '', userId: 'El usuario tiene el id:' + userId.toString(), }); return; } // Unir al cliente a la sala específica del chat const roomName = `chat_${chatId}`; client.join(roomName); console.log( `Usuario ${username} unido exitosamente a la sala ${roomName}`, ); // Confirmar al cliente que se unió exitosamente client.emit('joined-chat', { chatId, roomName, message: `Te has unido al chat ${chatId}`, }); // Notificar a otros usuarios en la sala que alguien se reconectó (opcional) client.to(roomName).emit('user-reconnected', { userId, username, chatId, }); } catch (error) { this.socketsLogsService.create({ event: 'join-chat', type: 'ERROR', message: 'El usuario intenta conectarse al chat que ya no está activo.', payload: data, socketId: '', namespace: '', userId: 'El usuario tiene el id:' + data.userId.toString(), }); console.error('Error al unirse al chat:', error); client.emit('join-chat-error', { message: 'Error interno del servidor al unirse al chat', }); } } // Método para obtener el historial de mensajes de un chat @SubscribeMessage('get-chat-history') async handleGetChatHistory( @MessageBody() data: { chatId: string }, @ConnectedSocket() client: Socket, ): Promise { try { const { chatId } = data; // Obtener el chat con sus mensajes const chat = await this.chatService.findOne(chatId); if (!chat) { this.socketsLogsService.create({ event: 'get-chat-history', type: 'ERROR', message: 'El usuario intenta obtener el chat de soporte y no puede porque no existe el chatid', payload: data, socketId: '', namespace: '', userId: '', }); client.emit('chat-history-error', { message: 'Chat no encontrado' }); return; } // Enviar el historial de mensajes al cliente client.emit('chat-history', { chatId, messages: chat.messages || [], chatInfo: { clientName: chat.clientName, subject: chat.subject, status: chat.status, agent: chat.agent, createdAt: (chat as any).createdAt, updatedAt: (chat as any).updatedAt, }, }); } catch (error) { console.error('Error al obtener historial del chat:', error); client.emit('chat-history-error', { message: 'Error al obtener el historial del chat', }); } } @SubscribeMessage('atender-chat') handleAtenderChat( @MessageBody() data: { chatId: string; agentName: string; agentId: number; clientId: number; }, @ConnectedSocket() client: Socket, ) { // Buscar el socket ID del cliente usando su ID de usuario // console.log('clientId original:', data.clientId, 'tipo:', typeof data.clientId); // Convertir clientId a número en caso de que llegue como string const clientIdNumber = typeof data.clientId === 'string' ? parseInt(data.clientId, 10) : data.clientId; // console.log('clientId convertido:', clientIdNumber, 'tipo:', typeof clientIdNumber); // console.log('usuarios en soporte:', this.soporteUser); const clientSocketId = this.verifyExsistUserRoomService.findSocketIdByUserId( clientIdNumber, this.soporteUser, ); console.log('clienteSocketID', clientSocketId); if (clientSocketId) { // Obtener el socket del cliente y sacarlo de "support" const clientSocket = this.server.sockets.sockets.get(clientSocketId); // console.log('clienteSocket', clientSocket); if (clientSocket) { clientSocket.leave('soporte'); console.log(`Cliente ${data.clientId} removido de la sala soporte`); // console.log('clienteSocket', clientSocket); clientSocket.join(`chat_${data.chatId}`); client.join(`chat_${data.chatId}`); // 4. Unir a ambos (cliente y agente) a una sala privada clientSocket.emit('chat_accepted', { agentId: data.agentId, chatId: data.chatId, agentName: data.agentName, }); this.sendPushNotificationService.sendPushNotification( data.clientId, 'Chat Atendido', `El agente ${data.agentName} ha atendido tu chat`, ); client.emit('joined_chat', { message: 'Unido al chat #', chatId: data.chatId, }); } } else { this.socketsLogsService.create({ event: 'atender-chat', type: 'ERROR', message: 'El agente de call center no puede atender el chat del cliente de Marketplace', payload: data, socketId: '', namespace: '', userId: '', }); } console.log('Chat atendido:', data); this.server.to('call-center').emit('chat-atendido', data); } @SubscribeMessage('chat-finalizado') handleChatFinalizado( @MessageBody() data: { chatId: string; agentName: string; client: string; clientId: number; }, @ConnectedSocket() client: Socket, ) { console.log('Chat finalizado:', data); this.server.to('call-center').emit('chat-terminado', data); if (data.client) { this.server.to(`chat_${data.chatId}`).emit('chat-terminado', data); this.sendPushNotificationService.sendPushNotification( data.clientId, 'Chat Finalizado', `El agente ${data.agentName} ha finalizado tu chat`, ); } else { this.socketsLogsService.create({ event: 'chat-finalizado', type: 'ERROR', message: 'El agente de call center no puede finalizar el chat del cliente de Marketplace', payload: data, socketId: '', namespace: '', userId: '', }); } } @SubscribeMessage('typing') handleTyping( @MessageBody() data: { senderId: string; receiverId: string; isTyping: boolean; }, ) { this.server.to(`chat_${data.receiverId}`).emit('userTyping', { userId: data.senderId, isTyping: data.isTyping, }); } //////repartidor cliente @SubscribeMessage('join-chat-cli-rep') async handleJoinChatCliRep( @MessageBody() data: CreateChatCliRep, @ConnectedSocket() client: Socket, ): Promise { const { orderId, clientCli, repartidor } = data; console.log('este es la data', data); // Validar que orderId existe y es válido if (!orderId && !repartidor) { this.socketsLogsService.create({ event: 'join-chat-cli-rep', type: 'ERROR', message: 'orderId es requerido pero no fue proporcionado y los datos del repartidor no pueden ir vacios', payload: data, socketId: '', namespace: '', userId: '', }); console.error( 'orderId es requerido pero no fue proporcionado y los datos del repartidor no pueden ir vacios', ); client.emit('join-chat-error', { message: 'orderId es requerido' }); return; } const orderIdNumber = typeof orderId === 'string' ? parseInt(orderId, 10) : orderId; console.log( `Procesando orderId: ${orderIdNumber} (tipo: ${typeof orderIdNumber})`, ); // Validar que orderIdNumber no sea NaN if (isNaN(orderIdNumber)) { this.socketsLogsService.create({ event: 'join-chat-cli-rep', type: 'ERROR', message: `orderId inválido: ${orderId} resultó en NaN`, payload: data, socketId: '', namespace: '', userId: '', }); console.error(`orderId inválido: ${orderId} resultó en NaN`); client.emit('join-chat-error', { message: 'orderId inválido' }); return; } const order: ChatCliRepDocument | null = await this.chatCliRepService.findOne(orderIdNumber.toString()); if (!order) { console.error( `Orden ${orderIdNumber} no encontrado, intentando crear un chat entre ${clientCli?.clientName} y ${repartidor?.name}`, ); // No hay chat previo; se creará uno nuevo this.chatCliRepService.create({ orderId: orderIdNumber.toString(), // Pasar como string para consistencia con el esquema clientName: clientCli?.clientName!, clientId: clientCli?.userId?.toString()!, messages: [ { sender: repartidor?.name!, senderId: Number(repartidor?._id), content: 'Hola yo seré el repartidor de tu pedido, me llamo ' + repartidor?.name, type: 'text', timestamp: new Date(), }, ], repartidor: repartidor!, status: 'active', }); this.server.to('order_' + orderIdNumber).emit('rep_asing', { message: 'repartidor asignado', repartidor, }); // client.emit('join-chat-error', { message: 'Orden no encontrado' }); } else { // Verificar que la orden está activo if (order.status !== 'active' && order.status !== 'pending') { console.log( `la orden ${orderIdNumber} no está activa (status: ${order.status})`, ); client.emit('join-chat-error', { message: 'La orden no está activo' }); return; } // Si el chat ya existe y el repartidor cambió, actualizar sus datos try { if (repartidor) { const orderRepIdStr = order.repartidor?._id ? order.repartidor._id.toString() : null; const newRepIdStr = repartidor._id ? repartidor._id.toString() : null; if (!orderRepIdStr || orderRepIdStr !== newRepIdStr) { await this.chatCliRepService.update(orderIdNumber.toString(), { 'repartidor._id': repartidor._id as any, 'repartidor.name': repartidor.name, 'messages.0.sender': repartidor.name, 'messages.0.senderId': repartidor._id, 'messages.0.content': 'Hola yo seré el repartidor de tu pedido, me llamo ' + repartidor.name, 'messages.0.timestamp': new Date(), } as any); this.server.to('order_' + orderIdNumber).emit('rep_asing', { message: 'repartidor actualizado', repartidor, }); } } } catch (error) { this.socketsLogsService.create({ event: 'join-chat-cli-rep', type: 'ERROR', message: 'Error actualizando repartidor del chat existente (reasignación)', payload: data, socketId: '', namespace: '', userId: '', }); console.error( 'Error actualizando repartidor del chat existente:', error, ); } const roomName = `order_chat_${orderIdNumber}`; client.join(roomName); console.log(`Cliente unido exitosamente a la sala ${roomName}`); // Confirmar al cliente que se unió exitosamente client.emit('joined-chat', { orderId: orderIdNumber, roomName, message: `Te has unido al chat ${orderId}`, }); } } // Método para obtener el historial de mensajes de un chat @SubscribeMessage('get-chat-history-cli-rep') async handleGetChatHistoryClipRep( @MessageBody() data: { orderId: number }, @ConnectedSocket() client: Socket, ): Promise { try { const { orderId } = data; console.log('cliente unido al la sala del chat con la orden ', orderId); // Obtener el chat con sus mensajes const orden = await this.chatCliRepService.findOne(orderId.toString()); if (!orden) { this.socketsLogsService.create({ event: 'get-chat-history-cli-rep', type: 'ERROR', message: 'Orden no encontrado ', payload: data, socketId: '', namespace: '', userId: '', }); client.emit('order-history-error', { message: 'Orden no encontrado' }); return; } // Enviar el historial de mensajes al cliente client.emit('order-history', { orderId, messages: orden.messages || [], chatInfo: { clientName: orden.clientName, status: orden.status, repaartidor: orden.repartidor, createdAt: (orden as any).createdAt, updatedAt: (orden as any).updatedAt, }, }); } catch (error) { console.error('Error al obtener historial del chat:', error); client.emit('order-history-error', { message: 'Error al obtener el historial del chat', }); } } @SubscribeMessage('typing-cli-rep') handleTypingCliRep( @MessageBody() data: { senderId: number; receiverId: number; orderId: number; isTyping: boolean; }, ) { console.log('el usuario esta ecribiendo:', data); const roomName = `order_chat_${data.orderId}`; console.log('Emitiendo a la sala:', roomName); this.server.to(roomName).emit('userTypingCliRep', { userId: data.senderId, isTyping: data.isTyping, }); } @SubscribeMessage('messageCliRep') async handleMessageCliRep( @MessageBody() data: MessageCliRep, @ConnectedSocket() client: Socket, ) { try { const message: MessageCliRep = { id: this.generateId(), senderId: data.senderId, senderUsername: data.senderUsername, receiverId: data.receiverId, content: data.content, type: data.type, fileName: data.fileName, fileUrl: data.fileUrl, fileSize: data.fileSize, mimeType: data.mimeType, timestamp: new Date(), orderId: data.orderId, isClient: data.isClient, }; console.log('mensaje en el back', message); // Guardar mensaje en MongoDB try { // Asegurar que el chat existe antes de agregar el mensaje await this.chatCliRepService.findOrCreateChat( data.orderId, data.senderUsername, 'Orden ' + data.orderId, ); const chatMessage = { sender: data.senderUsername, senderId: data.senderId, content: data.content || '', type: this.mapMessageType(data.type), timestamp: new Date(), metadata: { fileName: data.fileName, fileUrl: data.fileUrl, fileSize: data.fileSize, mimeType: data.mimeType || this.getMimeType(data.fileName), }, }; await this.chatCliRepService .addMessage(data.orderId, chatMessage) .then(async () => { if (message.isClient) { console.log('cliente para enviar mensaje a repartidor'); await this.sendPushNotificationService.sendPushNotificationToRepartidor( data.receiverId!, data.content!, 'Nuevo mensaje de cliente', ); } else { await this.sendPushNotificationService.sendPushNotification( data.receiverId!, 'Nuevo mensaje de repartidor', data.content!, ); } }); } catch (dbError) { this.socketsLogsService.create({ event: 'messageCliRep', type: 'ERROR', message: 'Error al guardar mensaje en MongoDB:', payload: data, socketId: '', namespace: '', userId: '', }); console.error('Error al guardar mensaje en MongoDB:', dbError); // Continuar con el flujo normal aunque falle el guardado } // Enviar mensaje a todos en el chat EXCEPTO al emisor client .to(`order_chat_${data.orderId}`) .emit('messageReceivedCliRep', message); // Enviar confirmación solo al emisor client.emit('messageSentCliRep', message); return { status: 'success', message }; } catch (error) { return { status: 'error', message: error.message }; } } @SubscribeMessage('finalizeChstClieRep') async handleMessageFinalizeChatCliRep( @MessageBody() data: { orderId: number }, @ConnectedSocket() client: Socket, ) { try { const { orderId } = data; // Actualizar el estado de la orden a 'completed' await this.chatCliRepService.updateOrderStatus( orderId.toString(), 'finalized', ); // Emitir evento para notificar a todos los clientes que el chat ha finalizado this.server.to(`order_chat_${orderId}`).emit('chatFinalizedCliRep', { orderId, message: 'El chat ha finalizado', }); } catch (error) { this.socketsLogsService.create({ event: 'finalizeChstClieRep', type: 'ERROR', message: 'Error al finalizar el chat', payload: data, socketId: '', namespace: '', userId: '', }); console.error('Error al finalizar el chat:', error); } } @SubscribeMessage('joinOrderRoom') handleJoinOrderRoom( @MessageBody() data: { orderId: number }, @ConnectedSocket() client: Socket, ) { const { orderId } = data; const roomName = `order_${orderId}`; if (!client.rooms.has(roomName)) { client.join(roomName); console.log(`Cliente unido a la sala de la orden: ${roomName}`); client.emit('roomJoined', { orderId, success: true }); } else { this.socketsLogsService.create({ event: 'joinOrderRoom', type: 'ERROR', message: 'Error al unir al room de la orden', payload: data, socketId: '', namespace: '', userId: '', }); } } @SubscribeMessage('trackingOrder') async handleTrackingOrder( @MessageBody() data: { orderId: number; status: number; latitude: string; longitude: string; idRepartidor: number; userId: number; }, @ConnectedSocket() client: Socket, ) { // Validación básica if (!data.orderId || !data.idRepartidor) { this.socketsLogsService.create({ event: 'trackingOrder', type: 'ERROR', message: 'Error al actualizar el tracking de la orden', payload: data, socketId: '', namespace: '', userId: '', }); client.emit('error', { message: 'Datos incompletos' }); return; } const { orderId, status, latitude, longitude, idRepartidor, userId } = data; console.log( `Tracking orden ${orderId} - Repartidor: ${idRepartidor} - Status: ${status}`, ); console.log('data,', data); // Verificar si el status ha cambiado usando el servicio const hasStatusChanged = await this.deliveryStatusService.hasStatusChanged( orderId, status, idRepartidor, ); console.log('[hasStatusChanged],', hasStatusChanged); // Datos del tracking para emitir const trackingData = { status, latitude, longitude, orderId, idRepartidor, timestamp: new Date().toISOString(), }; // Unir al room solo si no está ya unido const roomName = `order_${orderId}`; if (!client.rooms.has(roomName)) { client.join(roomName); console.log(`Cliente unido al room: ${roomName}`); } // Siempre emitir el tracking para actualizaciones en tiempo real client.to(roomName).emit('orderTracking', trackingData); // Solo enviar push notification si el status cambió if (hasStatusChanged) { console.log(`Status cambió para orden ${orderId}: ${status}`); try { const statusNew = this.changeStatusOrdersService.GetTextStatus(status); await this.sendPushNotificationService.sendPushNotification( userId, 'Se ha actualizado el estado de la orden', statusNew, ); } catch (error) { this.socketsLogsService.create({ event: 'trackingOrder', type: 'ERROR', message: `Error enviando push notification para orden ${orderId}:`, payload: data, socketId: '', namespace: '', userId: '', }); console.error( `Error enviando push notification para orden ${orderId}:`, error, ); } } else { console.log(`Status sin cambios para orden ${orderId}: ${status}`); } // Confirmar al repartidor que se recibió client.emit('trackingConfirmed', { orderId, success: true, statusChanged: hasStatusChanged, }); } @SubscribeMessage('trackingGeneral') async handleTrackingGeneral( @MessageBody() data: { idRepartidor: number; latitude: string; longitude: string; velocity: number; }, @ConnectedSocket() client: Socket, ) { console.log('Datos recibidos del cliente:', JSON.stringify(data, null, 2)); const { idRepartidor, latitude, longitude, velocity } = data; await this.deliveryLocationService.updateLatestLocation( Number(idRepartidor), Number(latitude), Number(longitude), Number(velocity), ); const acquired = await this.deliveryLocationService.tryAcquireSaveLock( Number(idRepartidor), ); if (!acquired) { return; } const dataRequest = { id_delivery: Number(idRepartidor), latitude: Number(latitude), longitude: Number(longitude), velocity: Number(velocity), }; try { await axios.post( this.endpoint + '/api/apprisa-panel/geocoding_app_apprisa', dataRequest, ); // console.log(`Ubicación guardada para repartidor ${idRepartidor}`); } catch (error) { await this.socketsLogsService.create({ event: 'trackingGeneral', type: 'ERROR', message: `Error al actualizar el tracking general para repartidor ${idRepartidor}:`, payload: data, socketId: client.id ?? '', namespace: '/', userId: '', }); console.error('Error al hacer la solicitud: ', error); } // client.emit('listenTrackingGeneral', data); } private generateId(): string { return Math.random().toString(36).substr(2, 9); } @SubscribeMessage('leaveRoomOrderChat') handleLeaveRoomOrderChat( @MessageBody() data: { orderId: number }, @ConnectedSocket() client: Socket, ) { const { orderId } = data; const roomName = `order_${orderId}`; if (client.rooms.has(roomName)) { this.chatCliRepService.updateOrderStatus(orderId.toString(), 'finalized'); client.leave(roomName); client.leave('order_chat_' + orderId); console.log(`Cliente salido del room: ${roomName}`); this.server.to(roomName).emit('roomLeft', { orderId, success: true }); } this.chatCliRepService; } // Mapear tipos de mensaje del gateway al esquema de chat private mapMessageType( type: 'TEXT' | 'IMAGE' | 'DOCUMENT', ): 'text' | 'image' | 'file' | 'video' { switch (type) { case 'TEXT': return 'text'; case 'IMAGE': return 'image'; case 'DOCUMENT': return 'file'; default: return 'text'; } } // Obtener tipo MIME basado en la extensión del archivo private getMimeType(fileName?: string): string | undefined { if (!fileName) return undefined; const extension = fileName.split('.').pop()?.toLowerCase(); const mimeTypes: { [key: string]: string } = { jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', gif: 'image/gif', pdf: 'application/pdf', doc: 'application/msword', docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', txt: 'text/plain', mp4: 'video/mp4', avi: 'video/avi', }; return extension ? mimeTypes[extension] : undefined; } // Determinar si el mensaje es de cliente o agente private determineSender( senderId: number, receiverId?: number, ): 'client' | 'agent' { // Lógica para determinar si es cliente o agente // Puedes ajustar esta lógica según tu sistema // Por ejemplo, si los agentes tienen IDs específicos o roles // Si hay receiverId, significa que el sender es un agente enviando a un cliente // Si no hay receiverId específico, es un cliente enviando al soporte general return receiverId ? 'agent' : 'client'; } //////////////////////////////////////////////// @SubscribeMessage('orderDeliveryResponse') async handleOrderDeliveryResponse( @MessageBody() orderData: OrderDeliveryResponseDto, @ConnectedSocket() client: Socket, ) { const { userId, orderId, response, time, time_seconds, message, userOrden, } = orderData; console.log( `Reemitiendo 'orderDeliveryResponse' a la sala 'establishment' con los datos:`, { orderId, response, time, time_seconds, userOrden, }, ); // Cancelar el temporizador si existe if (this.timeouts[orderId]) { clearTimeout(this.timeouts[orderId]); delete this.timeouts[orderId]; console.log( `Respuesta recibida para la orden ${orderId}. Temporizador cancelado.`, ); } if (this.timeoutsNotification[orderId]) { clearInterval(this.timeoutsNotification[orderId]); delete this.timeoutsNotification[orderId]; console.log( `Respuesta recibida para la orden ${orderId}. Temporizador de notificación cancelado.`, ); } if (response == 1) { console.log( 'Respuesta buena de aceptar pedido: ' + 'id_order' + orderId, 'time_order' + time, ); console.log('Endpoint' + this.endpoint); axios .post(this.endpoint + '/api/apprisa-panel/mkpAcceptOrder', { id_order: orderId, time_order: time, }) .then(async (response) => { // Evento para mkp (sala panel) await this.sendPushNotificationService.sendPushNotification( Number(userOrden), 'Se aceptó tu pedido', '', ); console.log('id del usuario', userOrden); client.to('establishment').emit('orderDelivery', { orderId: orderId, response: response.data.status ? 1 : 0, message: message, userId: userId, }); // Evento para todos los clientes conectados (incluyendo el que hizo la orden) this.server.emit('orderDelivery', { orderId: orderId, response: response.data.status ? 1 : 0, message: response.data.message, userId: userId, }); // Evento para el panel client.emit('responseDelivery', { userId: userId, orderId: orderId, response: response.data.status ? response.data : { message: 'No se pudo procesar el pago', }, }); console.log( `Respuesta al aceptar pedido: `, response.data.message, ' order: ', orderId, ); if (!response.data.status) { this.socketsLogsService.create({ event: 'orderDeliveryResponse', type: 'ERROR', message: `Error al aceptar pedido: ${response.data.message}`, payload: orderData, socketId: client.id, namespace: 'establishment', userId: userId, }); console.log('Error al aceptar pedido: ', response.data.message); return; } }) .catch((error) => { console.error('Error al hacer la solicitud: ', error.response.data); }); } else { console.log('Respuesta mala de aceptar pedido: ' + 'id_order' + orderId); // Evento para sala panel client.to('establishment').emit('orderDelivery', { orderId: orderId, response: 0, message: message, userId: userId, }); // Evento para todos los clientes conectados (incluyendo el que hizo la orden) this.server.emit('orderDelivery', { orderId: orderId, response: 0, message: message, userId: userId, }); } } @SubscribeMessage('orderCanceled') handleOrderCanceled( @MessageBody() orderData: OrderCanceledDto, @ConnectedSocket() client: Socket, ) { const { userId, orderId, reason, message, total, userOrden } = orderData; console.log('cancelar orden', orderData); const cancelUrl = this.marketplace + '/api/delivery-drive/cancel_order'; const requestData = { order_id: Number(orderId), reason: reason, }; console.log('URL de cancelación:', cancelUrl); console.log('Datos a enviar:', requestData); axios .post(cancelUrl, requestData, { headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, timeout: 10000, }) .then(async (response) => { console.log('orden eliminada', response.data); await this.sendPushNotificationService.sendPushNotification( Number(userOrden), 'Se ha cancelado la orden', message, ); client.emit('orderCanceled', { status: 200, userId: userId, orderId: orderId, response: response.data, message: message, }); }) .catch((error) => { console.error('Error completo:', { message: error.message, status: error.response?.status, statusText: error.response?.statusText, data: error.response?.data, config: { url: error.config?.url, method: error.config?.method, data: error.config?.data, }, }); const errorMessage = error.response?.data?.message || error.message || 'Error desconocido'; const errorStatus = error.response?.status || 500; client.emit('orderCanceled', { status: errorStatus, userId: userId, orderId: orderId, response: error.response?.data || errorMessage, message: message, error: errorMessage, }); }); } @SubscribeMessage('checkOrders') handleCheckOrders(@ConnectedSocket() client: Socket) { axios .get(this.endpoint + '/api/apprisa-panel/comprobate_orders_apprisa') .then((response) => { response.data.forEach((item) => { client.emit('responseAutoAsign', { userId: item.userId, orderId: item.orderId, }); }); }) .catch((error) => { console.error('Error checkOrders:', error); }); } @SubscribeMessage('statusOrder') handleStatusOrder( @MessageBody() statusData: StatusOrderDto, @ConnectedSocket() client: Socket, ) { const { orderId, status } = statusData; console.log('datos recibidos', statusData); client.emit('statusOrder', { orderId: orderId, status: status, }); console.log('datos emitidos del socket statusOrder', statusData); } @SubscribeMessage('sendCordinates') handleSendCoordinates( @MessageBody() coordinatesData: CoordinatesDto, @ConnectedSocket() client: Socket, ) { const { orderId, latitude, longitude, status } = coordinatesData; console.log('datos recibidos', coordinatesData); // Emitir socket al front de pancho client.emit('sendLocation', { orderId: orderId, latitude: latitude, longitude: longitude, status: status, }); client.to('panel').emit('sendLocation', { orderId: orderId, latitude: latitude, longitude: longitude, status: status, }); console.log(`Emitiendo 'sendLocation' con los datos:`, { orderId, latitude, longitude, status, }); } @SubscribeMessage('cashCut') async handleCashCut( @MessageBody() data: CashCutDto, @ConnectedSocket() client: Socket, ) { const { idSupervisor, idDeposito, idRepartidor, turno } = data; console.log('Respuesta de cashCut ', data); this.server.emit('cashCut', idSupervisor, idDeposito, idRepartidor, turno); console.log('Emitiendo cashCut'); } @SubscribeMessage('message orders') async handleMessageOrders( @MessageBody() data: { msg: string; user: string; time: string; cht: string; origin?: string; content?: string; }, @ConnectedSocket() client: Socket, ) { const { msg, user, time, cht, origin = null, content = 'txt' } = data; const messageData = { chat: cht, message: msg, user: user, time_at: time, content: content, origin: origin, }; console.log('Respuesta de mi back: ', messageData); try { const response = await axios.post( this.marketplace + '/api/delivery-drive/message_order', messageData, ); console.log('Respuesta de mi api: ', response.data); } catch (error) { console.error('Error al hacer la solicitud: ', error); } this.server.emit('message orders', msg, user, time, cht, origin); console.log(`Nuevo mensaje: ${msg} por ${user}`); } @SubscribeMessage('listenOrders') handleListenOrders( @MessageBody() data: ListenOrdersDto, @ConnectedSocket() client: Socket, ) { console.log('escuchar evento listenOrders ' + JSON.stringify(data.message)); this.server.emit('listenOrders', data); } @SubscribeMessage('message') handleMessage( @MessageBody() message: string, @ConnectedSocket() client: Socket, ) { console.log(`Mensaje recibido de ${client.id}: ${message}`); // Enviar una respuesta al cliente client.emit('serverMessage', `Servidor: recibí tu mensaje - ${message}`); console.log( `Mensaje enviado al cliente ${client.id}: Servidor: recibí tu mensaje - ${message}`, ); } private async sendRequestToAPI(): Promise { try { const response = await axios.get( this.endpoint + '/api/apprisa-panel/changeStatus', ); console.log(`Respuesta de la API:`, response.data); } catch (error) { // console.error(`Error al enviar la solicitud:`, error.response.data); } } ///////////////////////////////////////////////////////////////////////////////////////////////////// }