skailan-conversations
Version:
Servicio de conversaciones y mensajería para Skailan
295 lines • 12.9 kB
JavaScript
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