feat(chat): adiciona encerramento automatico por inatividade (12h)

- Sessoes de chat sao encerradas apos 12 horas sem atividade
- Criterios de encerramento automatico:
  1. Maquina offline (5 min sem heartbeat)
  2. Chat inativo (12 horas sem atividade) - NOVO
  3. Ticket orfao (sem maquina vinculada)
- Log detalhado com contagem por motivo de encerramento
- Evento no timeline com reason "inatividade_chat"

Isso evita acumular sessoes abertas indefinidamente
quando usuario esquece de encerrar o chat.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rever-tecnologia 2025-12-15 13:06:24 -03:00
parent 29fbbfaa26
commit 86f818c6f3

View file

@ -763,15 +763,22 @@ export const getTicketChatHistory = query({
// Timeout de maquina offline: 5 minutos sem heartbeat
const MACHINE_OFFLINE_TIMEOUT_MS = 5 * 60 * 1000
// Mutation interna para encerrar sessões de máquinas offline (chamada pelo cron)
// Nova lógica: só encerra se a MÁQUINA estiver offline, não por inatividade de chat
// Isso permite que usuários mantenham o chat aberto sem precisar enviar mensagens
// Timeout de inatividade do chat: 12 horas sem atividade
// Isso evita acumular sessoes abertas indefinidamente quando usuario esquece de encerrar
const CHAT_INACTIVITY_TIMEOUT_MS = 12 * 60 * 60 * 1000
// Mutation interna para encerrar sessões inativas (chamada pelo cron)
// Critérios de encerramento:
// 1. Máquina offline (5 min sem heartbeat)
// 2. Chat inativo (12 horas sem atividade) - mesmo se máquina online
// 3. Ticket órfão (sem máquina vinculada)
export const autoEndInactiveSessions = mutation({
args: {},
handler: async (ctx) => {
console.log("cron: autoEndInactiveSessions iniciado (verificando maquinas offline)")
console.log("cron: autoEndInactiveSessions iniciado")
const now = Date.now()
const offlineCutoff = now - MACHINE_OFFLINE_TIMEOUT_MS
const inactivityCutoff = now - CHAT_INACTIVITY_TIMEOUT_MS
// Limitar a 50 sessões por execução para evitar timeout do cron (30s)
const maxSessionsPerRun = 50
@ -784,6 +791,7 @@ export const autoEndInactiveSessions = mutation({
let endedCount = 0
let checkedCount = 0
const reasons: Record<string, number> = {}
for (const session of activeSessions) {
checkedCount++
@ -812,6 +820,36 @@ export const autoEndInactiveSessions = mutation({
createdAt: now,
})
endedCount++
reasons["ticket_sem_maquina"] = (reasons["ticket_sem_maquina"] ?? 0) + 1
continue
}
// Verificar inatividade do chat (12 horas sem atividade)
// Isso tem prioridade sobre o status da máquina
const chatIsInactive = session.lastActivityAt < inactivityCutoff
if (chatIsInactive) {
await ctx.db.patch(session._id, {
status: "ENDED",
endedAt: now,
})
await ctx.db.insert("ticketEvents", {
ticketId: session.ticketId,
type: "LIVE_CHAT_ENDED",
payload: {
sessionId: session._id,
agentId: session.agentId,
agentName: session.agentSnapshot?.name ?? "Sistema",
durationMs: now - session.startedAt,
startedAt: session.startedAt,
endedAt: now,
autoEnded: true,
reason: "inatividade_chat",
inactiveForMs: now - session.lastActivityAt,
},
createdAt: now,
})
endedCount++
reasons["inatividade_chat"] = (reasons["inatividade_chat"] ?? 0) + 1
continue
}
@ -819,7 +857,7 @@ export const autoEndInactiveSessions = mutation({
const lastHeartbeatAt = await getLastHeartbeatAt(ctx, ticket.machineId)
const machineIsOnline = lastHeartbeatAt !== null && lastHeartbeatAt > offlineCutoff
// Se máquina está online, manter sessão ativa
// Se máquina está online e chat está ativo, manter sessão
if (machineIsOnline) {
continue
}
@ -849,10 +887,12 @@ export const autoEndInactiveSessions = mutation({
})
endedCount++
reasons["maquina_offline"] = (reasons["maquina_offline"] ?? 0) + 1
}
console.log(`cron: verificadas ${checkedCount} sessoes, encerradas ${endedCount} (maquinas offline)`)
return { endedCount, checkedCount, hasMore: activeSessions.length === maxSessionsPerRun }
const reasonsSummary = Object.entries(reasons).map(([r, c]) => `${r}=${c}`).join(", ")
console.log(`cron: verificadas ${checkedCount} sessoes, encerradas ${endedCount} (${reasonsSummary || "nenhuma"})`)
return { endedCount, checkedCount, reasons, hasMore: activeSessions.length === maxSessionsPerRun }
},
})