UNPKG

skailan-conversations

Version:

Servicio de conversaciones y mensajería para Skailan

295 lines 12.9 kB
export class ConversationStateService { conversationRepository; stateTransitions = [ { from: "open", to: "assigned", allowed: true, requiresAssignment: true }, { from: "open", to: "closed", allowed: true }, { from: "open", to: "pending", allowed: true }, { from: "assigned", to: "open", allowed: true }, { from: "assigned", to: "closed", allowed: true }, { from: "assigned", to: "resolved", allowed: true }, { from: "pending", to: "open", allowed: true }, { from: "pending", to: "assigned", allowed: true, requiresAssignment: true, }, { from: "pending", to: "closed", allowed: true }, { from: "resolved", to: "closed", allowed: true }, { from: "resolved", to: "open", allowed: true }, { from: "closed", to: "open", allowed: false }, // No se puede reabrir una conversación cerrada ]; constructor(conversationRepository) { this.conversationRepository = conversationRepository; } /** * Cambia el estado de una conversación */ async changeConversationState(conversationId, organizationId, newStatus, assignedToUserId) { try { console.log(`🔄 Cambiando estado de conversación ${conversationId} a: ${newStatus}`); const conversation = await this.conversationRepository.findById(conversationId, organizationId); if (!conversation) { throw new Error("Conversación no encontrada"); } // Verificar si la transición es válida const isValidTransition = this.isValidTransition(conversation.status, newStatus); if (!isValidTransition) { throw new Error(`Transición de estado inválida: ${conversation.status}${newStatus}`); } // Verificar si se requiere asignación const transition = this.getTransition(conversation.status, newStatus); if (transition?.requiresAssignment && !assignedToUserId) { throw new Error("Se requiere asignar un usuario para este cambio de estado"); } // Actualizar estado y asignación conversation.status = newStatus; if (assignedToUserId) { conversation.assignedToUserId = assignedToUserId; } conversation.updatedAt = new Date(); const updatedConversation = await this.conversationRepository.save(conversation); console.log(`✅ Estado de conversación actualizado: ${conversation.status}${newStatus}`); return updatedConversation; } catch (error) { console.error("❌ Error cambiando estado de conversación:", error); throw error; } } /** * Asigna una conversación a un usuario */ async assignConversation(conversationId, organizationId, userId) { try { console.log(`👤 Asignando conversación ${conversationId} a usuario: ${userId}`); const conversation = await this.conversationRepository.findById(conversationId, organizationId); if (!conversation) { throw new Error("Conversación no encontrada"); } // Cambiar estado a 'assigned' si está abierta if (conversation.status === "open") { return this.changeConversationState(conversationId, organizationId, "assigned", userId); } // Solo actualizar asignación si el estado lo permite if (["open", "assigned", "pending"].includes(conversation.status)) { conversation.assignedToUserId = userId; conversation.updatedAt = new Date(); return await this.conversationRepository.save(conversation); } throw new Error(`No se puede asignar una conversación en estado: ${conversation.status}`); } catch (error) { console.error("❌ Error asignando conversación:", error); throw error; } } /** * Desasigna una conversación */ async unassignConversation(conversationId, organizationId) { try { console.log(`👤 Desasignando conversación: ${conversationId}`); const conversation = await this.conversationRepository.findById(conversationId, organizationId); if (!conversation) { throw new Error("Conversación no encontrada"); } // Cambiar estado a 'open' si está asignada if (conversation.status === "assigned") { return this.changeConversationState(conversationId, organizationId, "open"); } // Solo quitar asignación si el estado lo permite if (["open", "pending"].includes(conversation.status)) { conversation.assignedToUserId = null; conversation.updatedAt = new Date(); return await this.conversationRepository.save(conversation); } throw new Error(`No se puede desasignar una conversación en estado: ${conversation.status}`); } catch (error) { console.error("❌ Error desasignando conversación:", error); throw error; } } /** * Marca una conversación como resuelta */ async resolveConversation(conversationId, organizationId) { try { console.log(`✅ Marcando conversación como resuelta: ${conversationId}`); const conversation = await this.conversationRepository.findById(conversationId, organizationId); if (!conversation) { throw new Error("Conversación no encontrada"); } // Solo se puede resolver si está asignada if (conversation.status !== "assigned") { throw new Error(`No se puede resolver una conversación en estado: ${conversation.status}`); } return this.changeConversationState(conversationId, organizationId, "resolved"); } catch (error) { console.error("❌ Error resolviendo conversación:", error); throw error; } } /** * Cierra una conversación */ async closeConversation(conversationId, organizationId) { try { console.log(`🔒 Cerrando conversación: ${conversationId}`); const conversation = await this.conversationRepository.findById(conversationId, organizationId); if (!conversation) { throw new Error("Conversación no encontrada"); } // Se puede cerrar desde cualquier estado excepto 'closed' if (conversation.status === "closed") { throw new Error("La conversación ya está cerrada"); } return this.changeConversationState(conversationId, organizationId, "closed"); } catch (error) { console.error("❌ Error cerrando conversación:", error); throw error; } } /** * Reabre una conversación cerrada */ async reopenConversation(conversationId, organizationId) { try { console.log(`🔓 Reabriendo conversación: ${conversationId}`); const conversation = await this.conversationRepository.findById(conversationId, organizationId); if (!conversation) { throw new Error("Conversación no encontrada"); } // Solo se puede reabrir si está cerrada if (conversation.status !== "closed") { throw new Error(`No se puede reabrir una conversación en estado: ${conversation.status}`); } return this.changeConversationState(conversationId, organizationId, "open"); } catch (error) { console.error("❌ Error reabriendo conversación:", error); throw error; } } /** * Pone una conversación en espera */ async putConversationOnHold(conversationId, organizationId) { try { console.log(`⏸️ Poniendo conversación en espera: ${conversationId}`); const conversation = await this.conversationRepository.findById(conversationId, organizationId); if (!conversation) { throw new Error("Conversación no encontrada"); } // Solo se puede poner en espera si está abierta o asignada if (!["open", "assigned"].includes(conversation.status)) { throw new Error(`No se puede poner en espera una conversación en estado: ${conversation.status}`); } return this.changeConversationState(conversationId, organizationId, "pending"); } catch (error) { console.error("❌ Error poniendo conversación en espera:", error); throw error; } } /** * Obtiene conversaciones por estado */ async getConversationsByStatus(organizationId, status) { try { console.log(`🔍 Obteniendo conversaciones con estado: ${status}`); const allConversations = await this.conversationRepository.findAll(organizationId); const filteredConversations = allConversations.filter((conv) => conv.status === status); console.log(`✅ Encontradas ${filteredConversations.length} conversaciones con estado: ${status}`); return filteredConversations; } catch (error) { console.error("❌ Error obteniendo conversaciones por estado:", error); throw error; } } /** * Obtiene conversaciones asignadas a un usuario */ async getConversationsByUser(organizationId, userId) { try { console.log(`🔍 Obteniendo conversaciones asignadas a usuario: ${userId}`); const allConversations = await this.conversationRepository.findAll(organizationId); const userConversations = allConversations.filter((conv) => conv.assignedToUserId === userId); console.log(`✅ Encontradas ${userConversations.length} conversaciones asignadas al usuario: ${userId}`); return userConversations; } catch (error) { console.error("❌ Error obteniendo conversaciones por usuario:", error); throw error; } } /** * Obtiene estadísticas de conversaciones */ async getConversationStats(organizationId) { try { console.log(`📊 Obteniendo estadísticas de conversaciones para organización: ${organizationId}`); const allConversations = await this.conversationRepository.findAll(organizationId); const stats = { total: allConversations.length, open: allConversations.filter((c) => c.status === "open").length, assigned: allConversations.filter((c) => c.status === "assigned") .length, pending: allConversations.filter((c) => c.status === "pending").length, resolved: allConversations.filter((c) => c.status === "resolved") .length, closed: allConversations.filter((c) => c.status === "closed").length, }; console.log(`✅ Estadísticas obtenidas:`, stats); return stats; } catch (error) { console.error("❌ Error obteniendo estadísticas de conversaciones:", error); throw error; } } /** * Verifica si una transición de estado es válida */ isValidTransition(fromStatus, toStatus) { const transition = this.getTransition(fromStatus, toStatus); return transition?.allowed || false; } /** * Obtiene la configuración de transición entre dos estados */ getTransition(fromStatus, toStatus) { return this.stateTransitions.find((t) => t.from === fromStatus && t.to === toStatus); } /** * Obtiene todos los estados disponibles para una conversación */ getAvailableStates(currentStatus) { return this.stateTransitions .filter((t) => t.from === currentStatus && t.allowed) .map((t) => t.to); } /** * Verifica si una conversación puede ser asignada */ canBeAssigned(status) { return ["open", "pending"].includes(status); } /** * Verifica si una conversación puede ser cerrada */ canBeClosed(status) { return status !== "closed"; } /** * Verifica si una conversación puede ser resuelta */ canBeResolved(status) { return status === "assigned"; } } //# sourceMappingURL=ConversationStateService.js.map