From 24b8eb6a14430ef4971a0653634238b2c1b7aecf Mon Sep 17 00:00:00 2001 From: deyvisonwanderley Date: Sun, 16 Nov 2025 14:19:01 -0300 Subject: [PATCH 1/4] feat: integrate Convex authentication across ticket management routes - Added useConvexWithAuth hook to ensure authenticated access for ticket-related pages. - Updated the perfil and central-chamados routes to include the authentication setup. - Enhanced user navigation by adding a new "Meus Chamados" tab in the perfil page for better access to ticket management. --- .../(dashboard)/abrir-chamado/+page.svelte | 6 +++++ .../routes/(dashboard)/perfil/+page.svelte | 24 +++++++++++++++++++ .../(dashboard)/perfil/chamados/+page.svelte | 5 ++++ .../ti/central-chamados/+page.svelte | 3 +++ 4 files changed, 38 insertions(+) diff --git a/apps/web/src/routes/(dashboard)/abrir-chamado/+page.svelte b/apps/web/src/routes/(dashboard)/abrir-chamado/+page.svelte index c445f2e..7180ddd 100644 --- a/apps/web/src/routes/(dashboard)/abrir-chamado/+page.svelte +++ b/apps/web/src/routes/(dashboard)/abrir-chamado/+page.svelte @@ -7,6 +7,7 @@ import TicketTimeline from "$lib/components/chamados/TicketTimeline.svelte"; import { chamadosStore } from "$lib/stores/chamados"; import { resolve } from "$app/paths"; + import { useConvexWithAuth } from "$lib/hooks/useConvexWithAuth"; type Ticket = Doc<"tickets">; type SlaConfig = Doc<"slaConfigs">; @@ -45,6 +46,11 @@ carregarSlaConfigs(); }); + $effect(() => { + // Garante que o cliente Convex use o token do usuário logado + useConvexWithAuth(); + }); + async function carregarSlaConfigs() { try { carregandoSla = true; diff --git a/apps/web/src/routes/(dashboard)/perfil/+page.svelte b/apps/web/src/routes/(dashboard)/perfil/+page.svelte index fa7e589..d23443b 100644 --- a/apps/web/src/routes/(dashboard)/perfil/+page.svelte +++ b/apps/web/src/routes/(dashboard)/perfil/+page.svelte @@ -1,6 +1,7 @@
@@ -313,21 +499,94 @@
{#if assignFeedback} -

{assignFeedback}

+
+ {assignFeedback} +
{/if} +
+ + +
+

Prorrogar prazo

+

Recurso exclusivo para a equipe de TI

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ {#if prorrogacaoFeedback} +
+ {prorrogacaoFeedback} +
+ {/if} +
@@ -336,56 +595,242 @@
-

Configuração de SLA

-

Defina tempos de resposta, conclusão e alertas.

+

Configuração de SLA por Prioridade

+

Configure SLAs separados para cada nível de prioridade

- {#if slaConfigsQuery?.data} - {#each slaConfigsQuery.data as sla (sla._id)} - +
+
+ + +
+ {#each ["baixa", "media", "alta", "critica"] as prioridade} + {@const slaAtual = slaConfigsPorPrioridade[prioridade]} +
+
+

{prioridade}

+ {#if slaAtual} + Configurado + {:else} + Não configurado + {/if} +
+ {#if slaAtual} +
+
+ Resposta: + {slaAtual.tempoRespostaHoras}h +
+
+ Conclusão: + {slaAtual.tempoConclusaoHoras}h +
+ {#if slaAtual.tempoEncerramentoHoras} +
+ Auto-encerramento: + {slaAtual.tempoEncerramentoHoras}h +
+ {/if} +
+
+ + +
+ {:else} + - {/each} + {/if} +
+ {/each} +
+ + +
+

+ {slaForm.slaId ? "Editar" : "Novo"} SLA - Prioridade {slaForm.prioridade.charAt(0).toUpperCase() + slaForm.prioridade.slice(1)} +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ {#if slaFeedback} +
+

{slaFeedback}

+
+ {/if} +
+ + {#if slaForm.slaId} + {/if}
+
-
-
- - + + {#if slaParaExcluir} + - {#if slaFeedback} -

{slaFeedback}

- {/if} - + {/if} + + +
+
+

Templates de Email - Chamados

+

Templates automáticos usados nas notificações de chamados.

+
+ +
+ {#if carregandoTemplates} +
+ +

Carregando templates...

+
+ {:else if templatesChamados.length === 0} +
+

Nenhum template encontrado

+

+ Os templates de chamados serão criados automaticamente quando o sistema for inicializado. + Clique no botão abaixo para criar os templates padrão agora. +

+ {#if templatesFeedback} +
+ {templatesFeedback} +
+ {/if} + +
+ {:else} + {#each templatesChamados as template (template._id)} +
+
+

{template.nome}

+ Sistema +
+

{template.titulo}

+ {#if template.variaveis && template.variaveis.length > 0} +
+

Variáveis:

+
+ {#each template.variaveis as variavel} + {{variavel}} + {/each} +
+
+ {/if} +
+

Código: {template.codigo || "N/A"}

+
+
+ {/each} + {:else} +
+

Carregando templates...

+
+ {/if} +
diff --git a/packages/backend/convex/chamados.ts b/packages/backend/convex/chamados.ts index 0d2a3cf..c98497e 100644 --- a/packages/backend/convex/chamados.ts +++ b/packages/backend/convex/chamados.ts @@ -100,14 +100,13 @@ function montarTimeline(base: number, prazos: ReturnType) return timeline; } -async function selecionarSlaConfig(ctx: Parameters[0], slaConfigId?: Id<"slaConfigs">) { - if (slaConfigId) { - return await ctx.db.get(slaConfigId); - } - +async function selecionarSlaConfig( + ctx: Parameters[0], + prioridade: "baixa" | "media" | "alta" | "critica" +): Promise | null> { return await ctx.db .query("slaConfigs") - .withIndex("by_ativo", (q) => q.eq("ativo", true)) + .withIndex("by_prioridade", (q) => q.eq("prioridade", prioridade).eq("ativo", true)) .first(); } @@ -122,6 +121,7 @@ async function registrarNotificacoes( ) { const { ticket, titulo, mensagem, usuarioEvento } = params; + // Notificar solicitante if (ticket.solicitanteEmail) { await ctx.runMutation(api.email.enfileirarEmail, { destinatario: ticket.solicitanteEmail, @@ -142,6 +142,31 @@ async function registrarNotificacoes( lida: false, criadaEm: Date.now(), }); + + // Notificar responsável (se houver) + if (ticket.responsavelId && ticket.responsavelId !== ticket.solicitanteId) { + const responsavel = await ctx.db.get(ticket.responsavelId); + if (responsavel?.email) { + await ctx.runMutation(api.email.enfileirarEmail, { + destinatario: responsavel.email, + destinatarioId: ticket.responsavelId, + assunto: `${titulo} - Chamado ${ticket.numero}`, + corpo: `${mensagem}\n\n---\nCentral de Chamados SGSE`, + enviadoPor: usuarioEvento, + }); + } + + await ctx.db.insert("notificacoes", { + usuarioId: ticket.responsavelId, + tipo: "nova_mensagem", + ...(ticket.conversaId ? { conversaId: ticket.conversaId } : {}), + remetenteId: usuarioEvento, + titulo, + descricao: mensagem.length > 120 ? `${mensagem.slice(0, 117)}...` : mensagem, + lida: false, + criadaEm: Date.now(), + }); + } } async function registrarInteracao( @@ -185,7 +210,6 @@ export const abrirChamado = mutation({ categoria: v.optional(v.string()), prioridade: prioridadeValidator, anexos: v.optional(v.array(arquivoValidator)), - slaConfigId: v.optional(v.id("slaConfigs")), canalOrigem: v.optional(v.string()), }, returns: v.object({ @@ -195,7 +219,7 @@ export const abrirChamado = mutation({ handler: async (ctx, args) => { const usuario = await assertAuth(ctx); const agora = Date.now(); - const sla = await selecionarSlaConfig(ctx, args.slaConfigId); + const sla = await selecionarSlaConfig(ctx, args.prioridade); const prazos = calcularPrazos(agora, sla); const timeline = montarTimeline(agora, prazos); @@ -486,7 +510,7 @@ export const salvarSlaConfig = mutation({ slaId: v.optional(v.id("slaConfigs")), nome: v.string(), descricao: v.optional(v.string()), - setores: v.optional(v.array(v.string())), + prioridade: prioridadeValidator, tempoRespostaHoras: v.number(), tempoConclusaoHoras: v.number(), tempoEncerramentoHoras: v.optional(v.number()), @@ -501,7 +525,7 @@ export const salvarSlaConfig = mutation({ await ctx.db.patch(args.slaId, { nome: args.nome, descricao: args.descricao, - setores: args.setores, + prioridade: args.prioridade, tempoRespostaHoras: args.tempoRespostaHoras, tempoConclusaoHoras: args.tempoConclusaoHoras, tempoEncerramentoHoras: args.tempoEncerramentoHoras, @@ -516,7 +540,7 @@ export const salvarSlaConfig = mutation({ return await ctx.db.insert("slaConfigs", { nome: args.nome, descricao: args.descricao, - setores: args.setores, + prioridade: args.prioridade, tempoRespostaHoras: args.tempoRespostaHoras, tempoConclusaoHoras: args.tempoConclusaoHoras, tempoEncerramentoHoras: args.tempoEncerramentoHoras, @@ -530,6 +554,102 @@ export const salvarSlaConfig = mutation({ }, }); +export const excluirSlaConfig = mutation({ + args: { + slaId: v.id("slaConfigs"), + }, + handler: async (ctx, args) => { + await assertAuth(ctx); + const sla = await ctx.db.get(args.slaId); + if (!sla) { + throw new Error("Configuração de SLA não encontrada"); + } + await ctx.db.delete(args.slaId); + return { sucesso: true }; + }, +}); + +export const prorrogarChamado = mutation({ + args: { + ticketId: v.id("tickets"), + horasAdicionais: v.number(), + prazo: v.union(v.literal("resposta"), v.literal("conclusao")), + motivo: v.string(), + }, + handler: async (ctx, args) => { + const usuario = await assertAuth(ctx); + const ticket = await ctx.db.get(args.ticketId); + if (!ticket) { + throw new Error("Chamado não encontrado"); + } + + const agora = Date.now(); + const horasMs = args.horasAdicionais * 60 * 60 * 1000; + let novoPrazoResposta = ticket.prazoResposta; + let novoPrazoConclusao = ticket.prazoConclusao; + let prazoExtendido: number; + + if (args.prazo === "resposta") { + prazoExtendido = (ticket.prazoResposta || agora) + horasMs; + novoPrazoResposta = prazoExtendido; + // Se o prazo de conclusão é antes do novo prazo de resposta, ajuste-o também + if (ticket.prazoConclusao && ticket.prazoConclusao < prazoExtendido) { + novoPrazoConclusao = prazoExtendido + (ticket.prazoConclusao - (ticket.prazoResposta || agora)); + } + } else { + prazoExtendido = (ticket.prazoConclusao || agora) + horasMs; + novoPrazoConclusao = prazoExtendido; + } + + // Atualizar timeline + const timelineAtualizada = ticket.timeline?.map((etapa) => { + if (args.prazo === "resposta" && etapa.etapa === "resposta_inicial") { + return { + ...etapa, + prazo: prazoExtendido, + status: prazoExtendido > agora ? "pendente" : etapa.status, + }; + } + if (args.prazo === "conclusao" && etapa.etapa === "conclusao") { + return { + ...etapa, + prazo: prazoExtendido, + status: prazoExtendido > agora ? "pendente" : etapa.status, + }; + } + return etapa; + }) || ticket.timeline; + + await ctx.db.patch(ticket._id, { + prazoResposta: novoPrazoResposta, + prazoConclusao: novoPrazoConclusao, + timeline: timelineAtualizada, + atualizadoEm: agora, + ultimaInteracaoEm: agora, + }); + + await registrarInteracao(ctx, { + ticketId: ticket._id, + autorId: usuario._id, + origem: "ti", + tipo: "status", + conteudo: `Prazo ${args.prazo === "resposta" ? "de resposta" : "de conclusão"} prorrogado em ${args.horasAdicionais}h. Motivo: ${args.motivo}`, + }); + + const ticketAtualizado = await ctx.db.get(ticket._id); + if (ticketAtualizado) { + await registrarNotificacoes(ctx, { + ticket: ticketAtualizado, + titulo: `Prazo prorrogado - Chamado ${ticketAtualizado.numero}`, + mensagem: `O prazo ${args.prazo === "resposta" ? "de resposta" : "de conclusão"} foi prorrogado em ${args.horasAdicionais} horas. Motivo: ${args.motivo}`, + usuarioEvento: usuario._id, + }); + } + + return { sucesso: true }; + }, +}); + export const emitirAlertaPrazo = mutation({ args: { ticketId: v.id("tickets"), diff --git a/packages/backend/convex/schema.ts b/packages/backend/convex/schema.ts index 2b5291f..e08aeac 100644 --- a/packages/backend/convex/schema.ts +++ b/packages/backend/convex/schema.ts @@ -913,7 +913,12 @@ export default defineSchema({ slaConfigs: defineTable({ nome: v.string(), descricao: v.optional(v.string()), - setores: v.optional(v.array(v.string())), + prioridade: v.union( + v.literal("baixa"), + v.literal("media"), + v.literal("alta"), + v.literal("critica") + ), tempoRespostaHoras: v.number(), tempoConclusaoHoras: v.number(), tempoEncerramentoHoras: v.optional(v.number()), @@ -925,6 +930,7 @@ export default defineSchema({ atualizadoEm: v.number(), }) .index("by_ativo", ["ativo"]) + .index("by_prioridade", ["prioridade", "ativo"]) .index("by_nome", ["nome"]), ticketAssignments: defineTable({ diff --git a/packages/backend/convex/templatesMensagens.ts b/packages/backend/convex/templatesMensagens.ts index d76bc11..c7b027a 100644 --- a/packages/backend/convex/templatesMensagens.ts +++ b/packages/backend/convex/templatesMensagens.ts @@ -287,6 +287,115 @@ export const criarTemplatesPadrao = mutation({ + "", variaveis: ["remetente", "mensagem", "conversaId", "urlSistema"], }, + { + codigo: "chamado_registrado", + nome: "Chamado Registrado", + titulo: "Chamado {{numeroTicket}} registrado", + corpo: "" + + "
" + + "

Chamado registrado com sucesso!

" + + "

Olá {{solicitante}},

" + + "

Recebemos sua solicitação e iniciaremos o atendimento em breve.

" + + "
" + + "

Ticket: {{numeroTicket}}

" + + "

Prioridade: {{prioridade}}

" + + "

Categoria: {{categoria}}

" + + "
" + + "

" + + "" + + "Acompanhar chamado" + + "" + + "

" + + "

" + + "Central de Chamados SGSE" + + "

" + + "
", + variaveis: ["solicitante", "numeroTicket", "prioridade", "categoria", "urlSistema"], + }, + { + codigo: "chamado_atualizado", + nome: "Atualização no Chamado", + titulo: "Atualização no chamado {{numeroTicket}}", + corpo: "" + + "
" + + "

Nova atualização no seu chamado

" + + "

Olá {{solicitante}},

" + + "

Há uma nova atualização no seu chamado:

" + + "
" + + "

Ticket: {{numeroTicket}}

" + + "

Mensagem:

" + + "

{{mensagem}}

" + + "
" + + "

" + + "" + + "Ver detalhes" + + "" + + "

" + + "

" + + "Central de Chamados SGSE" + + "

" + + "
", + variaveis: ["solicitante", "numeroTicket", "mensagem", "urlSistema"], + }, + { + codigo: "chamado_atribuido", + nome: "Chamado Atribuído", + titulo: "Chamado {{numeroTicket}} atribuído", + corpo: "" + + "
" + + "

Chamado atribuído

" + + "

Olá {{responsavel}},

" + + "

Um novo chamado foi atribuído para você:

" + + "
" + + "

Ticket: {{numeroTicket}}

" + + "

Solicitante: {{solicitante}}

" + + "

Prioridade: {{prioridade}}

" + + "

Descrição: {{descricao}}

" + + "
" + + "

" + + "" + + "Acessar chamado" + + "" + + "

" + + "

" + + "Central de Chamados SGSE" + + "

" + + "
", + variaveis: ["responsavel", "numeroTicket", "solicitante", "prioridade", "descricao", "urlSistema"], + }, + { + codigo: "chamado_alerta_prazo", + nome: "Alerta de Prazo do Chamado", + titulo: "⚠️ Alerta de prazo - Chamado {{numeroTicket}}", + corpo: "" + + "
" + + "

⚠️ Alerta de prazo

" + + "

Olá {{destinatario}},

" + + "

O chamado abaixo está próximo do prazo de {{tipoPrazo}}:

" + + "
" + + "

Ticket: {{numeroTicket}}

" + + "

Prazo de {{tipoPrazo}}: {{prazo}}

" + + "

Status: {{status}}

" + + "
" + + "

" + + "" + + "Ver chamado" + + "" + + "

" + + "

" + + "Central de Chamados SGSE" + + "

" + + "
", + variaveis: ["destinatario", "numeroTicket", "tipoPrazo", "prazo", "status", "urlSistema", "rotaAcesso"], + }, ]; for (const template of templatesPadrao) { From 5ef6ef85505f462e7debfff9a49eafe384b80b6d Mon Sep 17 00:00:00 2001 From: deyvisonwanderley Date: Mon, 17 Nov 2025 08:44:18 -0300 Subject: [PATCH 3/4] feat: enhance SLA management and authentication handling - Updated the useConvexWithAuth hook to improve token management and logging for better debugging. - Integrated automatic authentication handling in the central-chamados route, ensuring seamless user experience. - Added a new mutation for migrating old SLA configurations to include a priority field, enhancing data consistency. - Improved the display of SLA configurations in the UI, including detailed views and migration feedback for better user interaction. - Refactored ticket loading logic to enrich ticket data with responsible user names, improving clarity in ticket management. --- RELATORIO_TESTES.md | 186 ++++++++ apps/web/src/lib/hooks/useConvexWithAuth.ts | 18 +- .../ti/central-chamados/+page.svelte | 411 +++++++++++++++--- packages/backend/convex/chamados.ts | 56 ++- packages/backend/convex/schema.ts | 12 +- 5 files changed, 613 insertions(+), 70 deletions(-) create mode 100644 RELATORIO_TESTES.md diff --git a/RELATORIO_TESTES.md b/RELATORIO_TESTES.md new file mode 100644 index 0000000..3780090 --- /dev/null +++ b/RELATORIO_TESTES.md @@ -0,0 +1,186 @@ +# Relatório de Testes - Sistema de Central de Chamados + +**Data:** 16 de novembro de 2025 +**Testador:** Sistema Automatizado +**Página Testada:** `/ti/central-chamados` + +## Resumo Executivo + +Foram realizados testes completos na página de Central de Chamados do sistema SGSE. A maioria das funcionalidades está funcionando corretamente, mas foram identificados alguns problemas que precisam ser corrigidos. + +## Testes Realizados + +### ✅ Testes Bem-Sucedidos + +1. **Login no Sistema** + - Status: ✅ PASSOU + - Usuário logado: Deyvison (dfw@poli.br) + +2. **Visualização de SLAs Configurados** + - Status: ✅ PASSOU + - Tabela de SLAs exibe 7 SLAs ativos corretamente + - Resumo mostra: 4 Baixa, 2 Média, 1 Alta/Crítica + - Detalhes completos (tempos, prioridades) são exibidos corretamente + +3. **Cards de Prioridade** + - Status: ✅ PASSOU + - Cards mostram corretamente "Configurado" ou "Não configurado" + - Botão "Configurar" funciona corretamente + - Detalhes dos SLAs configurados são exibidos nos cards + +4. **Criação de SLA** + - Status: ✅ PASSOU + - SLA criado com sucesso para prioridade "Alta" + - Formulário preenche corretamente quando clica em "Configurar" + - Tabela atualiza automaticamente após criação + - Card de prioridade atualiza para "Configurado" + +5. **Edição de SLA** + - Status: ✅ PASSOU + - Botão "Editar" abre formulário com dados corretos + - Atualização funciona corretamente + +6. **Lista de Chamados** + - Status: ✅ PASSOU + - 4 chamados sendo exibidos corretamente + - Filtros funcionando (status, responsável, setor) + - Detalhes do chamado são exibidos ao selecionar + +7. **Atribuição de Responsável** + - Status: ✅ PASSOU + - Dropdown mostra 2 usuários TI: Deyvison e Suporte_TI + - Formulário está funcional + +8. **Prorrogação de Prazo** + - Status: ✅ PASSOU + - Dropdown de tickets carrega corretamente (4 tickets) + - Formulário permite selecionar tipo de prazo e horas + - Botão habilita quando todos os campos estão preenchidos + +### ⚠️ Problemas Identificados + +#### 1. Templates de Email - Listagem Após Criação + +- **Status:** ⚠️ PROBLEMA +- **Descrição:** Templates são criados com sucesso (mensagem "Templates padrão criados com sucesso" aparece), mas não são listados na interface após criação +- **Ação Realizada:** Botão "Criar templates padrão" foi clicado e retornou sucesso +- **Comportamento Esperado:** Templates deveriam aparecer em uma lista após criação +- **Comportamento Atual:** Seção continua mostrando "Nenhum template encontrado" +- **Severidade:** MÉDIA +- **Impacto:** Usuários não conseguem visualizar/editar templates de email após criação +- **Possível Causa:** Query de templates pode não estar sendo atualizada após criação, ou filtro pode estar excluindo templates de chamados + +#### 2. Warning no Console - Token de Autenticação + +- **Status:** ⚠️ AVISO (Não crítico) +- **Descrição:** `⚠️ [useConvexWithAuth] Token não disponível` aparece no console durante carregamento inicial +- **Severidade:** BAIXA +- **Impacto:** Não afeta funcionalidade (autenticação funciona corretamente após carregamento) +- **Observação:** Parece ser um problema de timing durante inicialização da página + +#### 3. Warning no Console - Formato de Query + +- **Status:** ⚠️ AVISO (Não crítico) +- **Descrição:** `🔍 [usuariosTI] Formato inesperado: object {data: undefined, isLoading: undefined, error: undefined, isStale: undefined}` aparece no console +- **Severidade:** BAIXA +- **Impacto:** Não afeta funcionalidade (usuários são carregados corretamente - 2 usuários TI encontrados) +- **Observação:** Indica possível inconsistência no formato de retorno da query durante carregamento inicial + +## Detalhes dos Testes + +### Teste de Criação de SLA + +- **Prioridade Testada:** Alta +- **Valores Inseridos:** + - Nome: "SLA - Alta - Teste" + - Tempo de Resposta: 2h + - Tempo de Conclusão: 8h + - Auto-encerramento: 24h + - Alerta: 2h antes +- **Resultado:** ✅ SLA criado e exibido na tabela e no card + +### Teste de Edição de SLA + +- **SLA Editado:** Prioridade Baixa +- **Alterações:** + - Nome: "SLA Baixa - Editado em Teste" + - Tempo de Resposta: 6h +- **Resultado:** ✅ Atualização bem-sucedida + +### Teste de Prorrogação + +- **Ticket Selecionado:** SGSE-202511-3750 +- **Prazo:** Conclusão +- **Horas Adicionais:** 24h +- **Motivo:** "Teste de prorrogação de prazo - necessário mais tempo para análise" +- **Resultado:** ✅ Formulário preenchido corretamente, botão habilitado + +## Lista de Erros Encontrados + +### Erros Críticos + +- **Nenhum erro crítico encontrado** + +### Erros de Funcionalidade + +1. **Templates de Email não aparecem após criação** + - Localização: Seção "Templates de Email - Chamados" + - Ação necessária: Verificar query de templates e atualização reativa após criação + +### Avisos (Warnings) + +1. **Token de autenticação não disponível durante carregamento inicial** + - Localização: Console do navegador + - Ação necessária: Melhorar timing de inicialização de autenticação + +2. **Formato inesperado de query durante carregamento** + - Localização: Console do navegador (usuariosTI) + - Ação necessária: Verificar formato de retorno de useQuery do convex-svelte + +## Recomendações + +### Prioridade ALTA + +1. **Corrigir listagem de templates de email após criação** + - Verificar se a query `templatesChamados` está sendo atualizada após criação + - Verificar se o filtro de templates está correto (deve incluir templates de chamados) + - Adicionar refresh automático após criação de templates + +### Prioridade MÉDIA + +2. **Investigar e corrigir warnings no console** + - Melhorar timing de autenticação para evitar warning inicial + - Padronizar formato de retorno de queries do convex-svelte + +### Prioridade BAIXA + +3. **Melhorar logs de debug** + - Reduzir verbosidade de logs informativos + - Manter apenas logs de erro e warnings importantes + +## Conclusão + +O sistema está **funcionalmente operacional**, com a maioria das funcionalidades testadas funcionando corretamente: + +✅ **Funcionalidades Testadas e Funcionando:** + +- Login e autenticação +- Visualização de SLAs (tabela e cards) +- Criação de SLAs +- Edição de SLAs +- Lista de chamados +- Atribuição de responsável +- Prorrogação de prazo (formulário funcional) +- Criação de templates (backend funciona, frontend não atualiza) + +⚠️ **Problemas Identificados:** + +- Templates não aparecem na lista após criação (problema de atualização reativa) +- Warnings no console (não afetam funcionalidade) + +**Status Geral:** ✅ **OPERACIONAL COM PEQUENOS AJUSTES NECESSÁRIOS** + +**Próximos Passos:** + +1. Corrigir atualização reativa de templates após criação +2. Investigar e resolver warnings do console (opcional, não crítico) diff --git a/apps/web/src/lib/hooks/useConvexWithAuth.ts b/apps/web/src/lib/hooks/useConvexWithAuth.ts index ca7514b..593440f 100644 --- a/apps/web/src/lib/hooks/useConvexWithAuth.ts +++ b/apps/web/src/lib/hooks/useConvexWithAuth.ts @@ -30,15 +30,25 @@ export function useConvexWithAuth() { const clientWithAuth = client as ConvexClientWithAuth; // Configurar token se disponível - if (clientWithAuth && typeof clientWithAuth.setAuth === "function" && token) { + if (clientWithAuth && token) { try { - clientWithAuth.setAuth(token); - if (import.meta.env.DEV) { - console.log("✅ [useConvexWithAuth] Token configurado:", token.substring(0, 20) + "..."); + // Tentar setAuth se disponível + if (typeof clientWithAuth.setAuth === "function") { + clientWithAuth.setAuth(token); + if (import.meta.env.DEV) { + console.log("✅ [useConvexWithAuth] Token configurado via setAuth:", token.substring(0, 20) + "..."); + } + } else { + // Se setAuth não estiver disponível, o token deve ser passado via createSvelteAuthClient + if (import.meta.env.DEV) { + console.log("ℹ️ [useConvexWithAuth] Token disponível, autenticação gerenciada por createSvelteAuthClient"); + } } } catch (e) { console.warn("⚠️ [useConvexWithAuth] Erro ao configurar token:", e); } + } else if (!token && import.meta.env.DEV) { + console.warn("⚠️ [useConvexWithAuth] Token não disponível"); } return client; diff --git a/apps/web/src/routes/(dashboard)/ti/central-chamados/+page.svelte b/apps/web/src/routes/(dashboard)/ti/central-chamados/+page.svelte index c811492..fcfef8b 100644 --- a/apps/web/src/routes/(dashboard)/ti/central-chamados/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/central-chamados/+page.svelte @@ -1,5 +1,4 @@
@@ -447,7 +558,7 @@ {getStatusLabel(ticket.status)} - {ticket.setorResponsavel ?? "—"} + {(ticket as any).responsavelNome ?? ticket.setorResponsavel ?? "—"} {ticket.prioridade} {ticket.prazoConclusao ? prazoRestante(ticket.prazoConclusao) : "--"} @@ -592,6 +703,167 @@ + +
+
+
+

SLAs Configurados

+

Visualize todos os SLAs ativos com seus tempos e configurações

+
+
+ + {#if slaConfigsQuery === undefined || slaConfigsQuery === null || ('data' in slaConfigsQuery && slaConfigsQuery.data === undefined)} +
+ +
+ {:else} + {@const slaConfigs = ('data' in slaConfigsQuery && slaConfigsQuery.data !== undefined) + ? (Array.isArray(slaConfigsQuery.data) ? slaConfigsQuery.data : []) + : (Array.isArray(slaConfigsQuery) ? slaConfigsQuery : [])} + {@const slaConfigsAtivos = slaConfigs.filter((s: SlaConfig) => s.ativo)} + {@const slaConfigsPorPrioridadeCount = { + baixa: slaConfigsAtivos.filter((s: SlaConfig) => s.prioridade === 'baixa').length, + media: slaConfigsAtivos.filter((s: SlaConfig) => s.prioridade === 'media').length, + alta: slaConfigsAtivos.filter((s: SlaConfig) => s.prioridade === 'alta').length, + critica: slaConfigsAtivos.filter((s: SlaConfig) => s.prioridade === 'critica').length, + }} + + {#if slaConfigsAtivos.length === 0} +
+

Nenhum SLA configurado

+

Configure SLAs para cada prioridade na seção abaixo

+
+ {:else} + +
+
+
{slaConfigsAtivos.length}
+
Total de SLAs
+
+
+
{slaConfigsPorPrioridadeCount.baixa}
+
Prioridade Baixa
+
+
+
{slaConfigsPorPrioridadeCount.media}
+
Prioridade Média
+
+
+
{slaConfigsPorPrioridadeCount.alta + slaConfigsPorPrioridadeCount.critica}
+
Prioridade Alta/Crítica
+
+
+ + +
+ + + + + + + + + + + + + + + {#each slaConfigsAtivos as sla (sla._id)} + + + + + + + + + + + {/each} + +
NomePrioridadeTempo de RespostaTempo de ConclusãoAuto-encerramentoAlerta AntecedênciaStatusAções
+
{sla.nome}
+ {#if sla.descricao} +
{sla.descricao}
+ {/if} +
+ + {sla.prioridade} + + +
+ {sla.tempoRespostaHoras}h + {#if sla.tempoRespostaHoras >= 24} + + ({Math.floor(sla.tempoRespostaHoras / 24)}d {sla.tempoRespostaHoras % 24}h) + + {/if} +
+
+
+ {sla.tempoConclusaoHoras}h + {#if sla.tempoConclusaoHoras >= 24} + + ({Math.floor(sla.tempoConclusaoHoras / 24)}d {sla.tempoConclusaoHoras % 24}h) + + {/if} +
+
+ {#if sla.tempoEncerramentoHoras} +
+ {sla.tempoEncerramentoHoras}h + {#if sla.tempoEncerramentoHoras >= 24} + + ({Math.floor(sla.tempoEncerramentoHoras / 24)}d {sla.tempoEncerramentoHoras % 24}h) + + {/if} +
+ {:else} + Não configurado + {/if} +
+
+ {sla.alertaAntecedenciaHoras}h + antes +
+
+ + + Ativo + + +
+ + +
+
+
+ {/if} + {/if} +
+ +
@@ -602,14 +874,32 @@ +
+ {#if migracaoFeedback} +
+ {migracaoFeedback} +
+ {/if}
- +
{#each ["baixa", "media", "alta", "critica"] as prioridade} {@const slaAtual = slaConfigsPorPrioridade[prioridade]} -
+

{prioridade}

{#if slaAtual} @@ -620,24 +910,28 @@
{#if slaAtual}
-
+
Resposta: - {slaAtual.tempoRespostaHoras}h + {slaAtual.tempoRespostaHoras}h
-
+
Conclusão: - {slaAtual.tempoConclusaoHoras}h + {slaAtual.tempoConclusaoHoras}h
{#if slaAtual.tempoEncerramentoHoras} -
+
Auto-encerramento: - {slaAtual.tempoEncerramentoHoras}h + {slaAtual.tempoEncerramentoHoras}h
{/if} +
+ Alerta: + {slaAtual.alertaAntecedenciaHoras}h antes +
-
-
-

Prorrogar prazo

-

Recurso exclusivo para a equipe de TI

-
-
- - -
-
- - -
-
- - -
-
- - -
- {#if prorrogacaoFeedback} -
- {prorrogacaoFeedback} + +
+

Prorrogar prazo

+

Recurso exclusivo para a equipe de TI

+
+
+ +
- {/if} - +
+ + +
+
+ + +
+
+ + +
+ {#if prorrogacaoFeedback} +
+ {prorrogacaoFeedback} +
+ {/if} + +
@@ -865,34 +942,9 @@
-
-
-

Configuração de SLA por Prioridade

-

Configure SLAs separados para cada nível de prioridade

-
-
- - -
- {#if migracaoFeedback} -
- {migracaoFeedback} -
- {/if} +
+

Configuração de SLA por Prioridade

+

Configure SLAs separados para cada nível de prioridade

@@ -1042,88 +1094,5 @@
{/if} - -
-
-

Templates de Email - Chamados

-

Templates automáticos usados nas notificações de chamados.

-
- -
- {#if carregandoTemplates} -
- -

Carregando templates...

-
- {:else if templatesChamados.length === 0} -
-

Nenhum template encontrado

-

- Os templates de chamados serão criados automaticamente quando o sistema for inicializado. - Clique no botão abaixo para criar os templates padrão agora. -

- {#if templatesFeedback} -
- {templatesFeedback} -
- {/if} - -
- {:else} - {#each templatesChamados as template (template._id)} -
-
-

{template.nome}

- Sistema -
-

{template.titulo}

- {#if template.variaveis && template.variaveis.length > 0} -
-

Variáveis:

-
- {#each template.variaveis as variavel} - {{variavel}} - {/each} -
-
- {/if} -
-

Código: {template.codigo || "N/A"}

-
-
- {/each} - {:else} -
-

Carregando templates...

-
- {/if} -
-
diff --git a/packages/backend/convex/chamados.ts b/packages/backend/convex/chamados.ts index 499182d..7bfe140 100644 --- a/packages/backend/convex/chamados.ts +++ b/packages/backend/convex/chamados.ts @@ -520,6 +520,102 @@ export const listarSlaConfigs = query({ }, }); +export const obterEstatisticasChamados = query({ + args: {}, + handler: async (ctx) => { + await assertAuth(ctx); + const todosTickets = await ctx.db.query("tickets").collect(); + + const total = todosTickets.length; + const abertos = todosTickets.filter((t) => t.status === "aberto").length; + const emAndamento = todosTickets.filter((t) => t.status === "em_andamento").length; + const vencidos = todosTickets.filter( + (t) => (t.prazoConclusao && t.prazoConclusao < Date.now()) || t.status === "cancelado" + ).length; + + return { total, abertos, emAndamento, vencidos }; + }, +}); + +export const obterDadosSlaGrafico = query({ + args: {}, + handler: async (ctx) => { + await assertAuth(ctx); + const agora = Date.now(); + const todosTickets = await ctx.db.query("tickets").collect(); + const slaConfigs = await ctx.db.query("slaConfigs").collect(); + + // Agrupar SLAs por prioridade + const slaPorPrioridade = new Map>(); + slaConfigs.filter(s => s.ativo).forEach(sla => { + if (sla.prioridade) { + slaPorPrioridade.set(sla.prioridade, sla); + } + }); + + // Calcular status de SLA para cada ticket + const statusSla = { + dentroPrazo: 0, + proximoVencimento: 0, + vencido: 0, + semPrazo: 0, + }; + + const porPrioridade = { + baixa: { dentroPrazo: 0, proximoVencimento: 0, vencido: 0, total: 0 }, + media: { dentroPrazo: 0, proximoVencimento: 0, vencido: 0, total: 0 }, + alta: { dentroPrazo: 0, proximoVencimento: 0, vencido: 0, total: 0 }, + critica: { dentroPrazo: 0, proximoVencimento: 0, vencido: 0, total: 0 }, + }; + + todosTickets.forEach(ticket => { + if (!ticket.prazoConclusao) { + statusSla.semPrazo++; + return; + } + + const prazoConclusao = ticket.prazoConclusao; + const horasRestantes = (prazoConclusao - agora) / (1000 * 60 * 60); + const sla = slaPorPrioridade.get(ticket.prioridade); + const alertaHoras = sla?.alertaAntecedenciaHoras ?? 2; + + if (prazoConclusao < agora) { + statusSla.vencido++; + if (ticket.prioridade && porPrioridade[ticket.prioridade as keyof typeof porPrioridade]) { + porPrioridade[ticket.prioridade as keyof typeof porPrioridade].vencido++; + porPrioridade[ticket.prioridade as keyof typeof porPrioridade].total++; + } + } else if (horasRestantes <= alertaHoras) { + statusSla.proximoVencimento++; + if (ticket.prioridade && porPrioridade[ticket.prioridade as keyof typeof porPrioridade]) { + porPrioridade[ticket.prioridade as keyof typeof porPrioridade].proximoVencimento++; + porPrioridade[ticket.prioridade as keyof typeof porPrioridade].total++; + } + } else { + statusSla.dentroPrazo++; + if (ticket.prioridade && porPrioridade[ticket.prioridade as keyof typeof porPrioridade]) { + porPrioridade[ticket.prioridade as keyof typeof porPrioridade].dentroPrazo++; + porPrioridade[ticket.prioridade as keyof typeof porPrioridade].total++; + } + } + }); + + // Calcular taxa de cumprimento + const totalComPrazo = statusSla.dentroPrazo + statusSla.proximoVencimento + statusSla.vencido; + const taxaCumprimento = totalComPrazo > 0 + ? Math.round((statusSla.dentroPrazo / totalComPrazo) * 100) + : 100; + + return { + statusSla, + porPrioridade, + taxaCumprimento, + totalComPrazo, + atualizadoEm: agora, + }; + }, +}); + export const salvarSlaConfig = mutation({ args: { slaId: v.optional(v.id("slaConfigs")), @@ -718,40 +814,3 @@ export const generateUploadUrl = mutation({ }, }); -/** - * Migração: Adiciona o campo 'prioridade' aos SLAs antigos que não possuem - * Esta mutation corrige documentos criados antes da migração do schema - */ -export const migrarSlaConfigs = mutation({ - args: {}, - handler: async (ctx) => { - const usuario = await assertAuth(ctx); - - // Buscar todos os SLAs - const slaConfigs = await ctx.db.query("slaConfigs").collect(); - - let migrados = 0; - for (const sla of slaConfigs) { - // Verificar se o documento não tem o campo 'prioridade' - // Usando type assertion para acessar campos não tipados - const slaDoc = sla as any; - if (!slaDoc.prioridade) { - // Adicionar prioridade padrão "media" para SLAs antigos - await ctx.db.patch(sla._id, { - prioridade: "media" as "baixa" | "media" | "alta" | "critica", - atualizadoPor: usuario._id, - atualizadoEm: Date.now(), - }); - migrados++; - console.log(`✅ SLA migrado: ${sla.nome} (ID: ${sla._id})`); - } - } - - return { - sucesso: true, - migrados, - total: slaConfigs.length, - }; - }, -}); -