From 71550874ce05e31157a3b530f596e268910cd1a2 Mon Sep 17 00:00:00 2001 From: deyvisonwanderley Date: Tue, 18 Nov 2025 03:10:10 -0300 Subject: [PATCH] feat: update system branding and improve user interface consistency - Changed all instances of "Sistema de Gerenciamento da Secretaria de Esportes" to "Sistema de Gerenciamento de Secretaria" for a more concise branding. - Enhanced the PrintModal component with a user-friendly interface for selecting sections to include in PDF generation. - Improved error handling and user feedback during PDF generation processes. - Updated various components and routes to reflect the new branding, ensuring consistency across the application. --- apps/web/src/lib/components/PrintModal.svelte | 1168 ++++++++--------- apps/web/src/lib/components/Sidebar.svelte | 4 +- .../lib/components/chamados/TicketForm.svelte | 188 ++- .../ferias/WizardSolicitacaoFerias.svelte | 28 +- .../components/ti/CybersecurityWizcard.svelte | 2 +- .../components/ti/ReportGeneratorModal.svelte | 2 +- .../web/src/lib/utils/declaracoesGenerator.ts | 10 +- apps/web/src/routes/(dashboard)/+page.svelte | 2 +- .../(dashboard)/abrir-chamado/+page.svelte | 70 +- .../routes/(dashboard)/perfil/+page.svelte | 4 +- .../recursos-humanos/ferias/+page.svelte | 2 +- .../src/routes/(dashboard)/ti/+page.svelte | 4 +- .../ti/central-chamados/+page.svelte | 323 +++-- .../(dashboard)/ti/cibersecurity/+page.svelte | 4 +- .../ti/configuracoes-email/+page.svelte | 2 +- .../(dashboard)/ti/monitoramento/+page.svelte | 2 +- .../ti/painel-administrativo/+page.svelte | 2 +- packages/backend/convex/actions/email.ts | 466 +++---- packages/backend/convex/chamados.ts | 4 +- packages/backend/convex/templatesMensagens.ts | 842 ++++++------ 20 files changed, 1697 insertions(+), 1432 deletions(-) diff --git a/apps/web/src/lib/components/PrintModal.svelte b/apps/web/src/lib/components/PrintModal.svelte index 26ade77..6fd95ba 100644 --- a/apps/web/src/lib/components/PrintModal.svelte +++ b/apps/web/src/lib/components/PrintModal.svelte @@ -1,584 +1,584 @@ - - - - - - - + + + + + + + diff --git a/apps/web/src/lib/components/Sidebar.svelte b/apps/web/src/lib/components/Sidebar.svelte index fbc1386..05cc6b7 100644 --- a/apps/web/src/lib/components/Sidebar.svelte +++ b/apps/web/src/lib/components/Sidebar.svelte @@ -203,7 +203,7 @@ @@ -521,7 +521,7 @@

SGSE

- Sistema de Gerenciamento da
Secretaria de Esportes + Sistema de Gerenciamento de Secretaria

diff --git a/apps/web/src/lib/components/chamados/TicketForm.svelte b/apps/web/src/lib/components/chamados/TicketForm.svelte index 132bcab..a7acf17 100644 --- a/apps/web/src/lib/components/chamados/TicketForm.svelte +++ b/apps/web/src/lib/components/chamados/TicketForm.svelte @@ -76,38 +76,52 @@ const loading = $derived(props.loading ?? false);
-
-
- - - {#if errors.titulo} - {errors.titulo} - {/if} -
+ +
+ + + {#if errors.titulo} + {errors.titulo} + {/if} +
+ +
-
- {#each ["chamado", "reclamacao", "elogio", "sugestao"] as opcao} -
+
+ + {#if errors.categoria} + {errors.categoria} + {/if} +
+ + +
+ {#if errors.descricao} @@ -155,7 +197,8 @@ const loading = $derived(props.loading ?? false); {/if}
-
+ +

Anexos (opcional)

@@ -164,6 +207,20 @@ const loading = $derived(props.loading ?? false);

@@ -196,16 +253,31 @@ const loading = $derived(props.loading ?? false); {/if}
-
+ +
@@ -215,6 +287,20 @@ const loading = $derived(props.loading ?? false); onclick={resetForm} disabled={loading} > + + + Limpar
diff --git a/apps/web/src/lib/components/ferias/WizardSolicitacaoFerias.svelte b/apps/web/src/lib/components/ferias/WizardSolicitacaoFerias.svelte index ea98b30..c748d87 100644 --- a/apps/web/src/lib/components/ferias/WizardSolicitacaoFerias.svelte +++ b/apps/web/src/lib/components/ferias/WizardSolicitacaoFerias.svelte @@ -227,12 +227,13 @@
-
+
{#each Array(totalPassos) as _, i (i)} -
+ {@const labels = ['Ano & Saldo', 'Períodos', 'Confirmação']} +
i + 1} class:text-white={passoAtual > i + 1} class:border-4={passoAtual === i + 1} @@ -261,10 +262,16 @@ {/if}
+ +

+ {labels[i]} +

+ {#if i < totalPassos - 1}
i + 1} class:bg-base-300={passoAtual <= i + 1} >
@@ -272,19 +279,6 @@
{/each}
- - -
-
-

Ano & Saldo

-
-
-

Períodos

-
-
-

Confirmação

-
-
diff --git a/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte b/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte index a60e04a..97f0d08 100644 --- a/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte +++ b/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte @@ -896,7 +896,7 @@ doc.setFontSize(8); doc.setFont('helvetica', 'normal'); doc.setTextColor(128, 128, 128); - doc.text('SGSE - Sistema de Gerenciamento da Secretaria de Esportes', 105, 285, { + doc.text('SGSE - Sistema de Gerenciamento de Secretaria', 105, 285, { align: 'center' }); doc.text(`Gerado em: ${agoraStr}`, 105, 290, { align: 'center' }); diff --git a/apps/web/src/lib/components/ti/ReportGeneratorModal.svelte b/apps/web/src/lib/components/ti/ReportGeneratorModal.svelte index bf566b2..edd26a3 100644 --- a/apps/web/src/lib/components/ti/ReportGeneratorModal.svelte +++ b/apps/web/src/lib/components/ti/ReportGeneratorModal.svelte @@ -197,7 +197,7 @@ doc.setFontSize(8); doc.setTextColor(128, 128, 128); doc.text( - `SGSE - Sistema de Gestão da Secretaria de Esportes | Página ${i} de ${pageCount}`, + `SGSE - Sistema de Gerenciamento de Secretaria | Página ${i} de ${pageCount}`, doc.internal.pageSize.getWidth() / 2, doc.internal.pageSize.getHeight() - 10, { align: 'center' } diff --git a/apps/web/src/lib/utils/declaracoesGenerator.ts b/apps/web/src/lib/utils/declaracoesGenerator.ts index d4fd33a..530df2f 100644 --- a/apps/web/src/lib/utils/declaracoesGenerator.ts +++ b/apps/web/src/lib/utils/declaracoesGenerator.ts @@ -172,7 +172,7 @@ export async function gerarDeclaracaoAcumulacaoCargo(funcionario: Funcionario): // Rodapé doc.setFontSize(8); doc.setTextColor(100); - doc.text('SGSE - Sistema de Gerenciamento da Secretaria de Esportes', 105, 285, { align: 'center' }); + doc.text('SGSE - Sistema de Gerenciamento de Secretaria', 105, 285, { align: 'center' }); return doc.output('blob'); } @@ -260,7 +260,7 @@ export async function gerarDeclaracaoDependentesIR(funcionario: Funcionario): Pr // Rodapé doc.setFontSize(8); doc.setTextColor(100); - doc.text('SGSE - Sistema de Gerenciamento da Secretaria de Esportes', 105, 285, { align: 'center' }); + doc.text('SGSE - Sistema de Gerenciamento de Secretaria', 105, 285, { align: 'center' }); return doc.output('blob'); } @@ -341,7 +341,7 @@ export async function gerarDeclaracaoIdoneidade(funcionario: Funcionario): Promi // Rodapé doc.setFontSize(8); doc.setTextColor(100); - doc.text('SGSE - Sistema de Gerenciamento da Secretaria de Esportes', 105, 285, { align: 'center' }); + doc.text('SGSE - Sistema de Gerenciamento de Secretaria', 105, 285, { align: 'center' }); return doc.output('blob'); } @@ -440,7 +440,7 @@ export async function gerarTermoNepotismo(funcionario: Funcionario): Promise

- Bem-vindo ao Sistema de Gerenciamento da Secretaria de Esportes + Bem-vindo ao SGSE - Sistema de Gerenciamento de Secretaria

{currentTime.toLocaleDateString("pt-BR", { diff --git a/apps/web/src/routes/(dashboard)/abrir-chamado/+page.svelte b/apps/web/src/routes/(dashboard)/abrir-chamado/+page.svelte index 83af3b3..7df3090 100644 --- a/apps/web/src/routes/(dashboard)/abrir-chamado/+page.svelte +++ b/apps/web/src/routes/(dashboard)/abrir-chamado/+page.svelte @@ -136,7 +136,7 @@

Registre reclamações, sugestões, elogios ou chamados técnicos. Toda interação gera - notificações automáticas via e-mail e chat com a assinatura do SGSE. + notificações automáticas via e-mail e chat com a assinatura do SGSE - Sistema de Gerenciamento de Secretaria.

Resposta ágil @@ -164,11 +164,31 @@
-
-

Formulário

-

- Informe os detalhes para que nossa equipe possa priorizar o atendimento. -

+
+
+
+ + + +
+
+

Formulário

+

+ Informe os detalhes para que nossa equipe possa priorizar o atendimento. +

+
+
{#if resetSignal % 2 === 0} @@ -180,13 +200,37 @@
diff --git a/apps/web/src/routes/(dashboard)/perfil/+page.svelte b/apps/web/src/routes/(dashboard)/perfil/+page.svelte index 0424489..95ea561 100644 --- a/apps/web/src/routes/(dashboard)/perfil/+page.svelte +++ b/apps/web/src/routes/(dashboard)/perfil/+page.svelte @@ -1233,11 +1233,11 @@ {#if ehGestor}

- SGSE - Sistema de Gerenciamento da Secretaria de Esportes. + SGSE - Sistema de Gerenciamento de Secretaria.

-
-
-

Detalhes do chamado

- {#if !detalheSelecionado} -

Selecione um chamado na tabela.

- {:else} -
-
-
-

Solicitante

-

{detalheSelecionado.solicitanteNome}

+ + {#if !detalheSelecionado} +
+
+ + + +

Nenhum chamado selecionado

+

+ Selecione um chamado na tabela acima para visualizar detalhes e realizar ações. +

+
+
+ {:else} + +
+ +
+
+
+
+
+ {detalheSelecionado.numero} +
+ + {getStatusLabel(detalheSelecionado.status)} + +
+

{detalheSelecionado.titulo}

+
+
+ + + + Solicitante: + {detalheSelecionado.solicitanteNome} +
+ {#if detalheSelecionado.prazoConclusao} +
+ + + + Prazo: + {prazoRestante(detalheSelecionado.prazoConclusao) ?? "--"} +
+ {/if}
- - {getStatusLabel(detalheSelecionado.status)} -
-

{detalheSelecionado.descricao}

-
-
-

Prazo resposta

-

{prazoRestante(detalheSelecionado.prazoResposta) ?? "--"}

+
+
+ + +
+ +
+
+ + + +

Detalhes do chamado

+
+
+
+

Descrição

+

+ {detalheSelecionado.descricao} +

+
+
+
+

Prazo de resposta

+

+ {prazoRestante(detalheSelecionado.prazoResposta) ?? "--"} +

-
-

Prazo conclusão

-

{prazoRestante(detalheSelecionado.prazoConclusao) ?? "--"}

+
+

Prazo de conclusão

+

+ {prazoRestante(detalheSelecionado.prazoConclusao) ?? "--"} +

+

Histórico e Timeline

- {/if} -
+
- -
-

Responder chamado

- {#if !detalheSelecionado} -

Selecione um chamado na tabela para responder.

- {:else} -
+ +
+
+ + + +

Responder chamado

+
+
- {/if} -
- -
-

Atribuir responsável

-
- - - {#if assignFeedback} -
- {assignFeedback} -
- {/if} -
- -
-

Prorrogar prazo

-

Recurso exclusivo para a equipe de TI

+ +
+
+ + + +

Atribuir responsável

+
-
- - + + + {#if assignFeedback} +
+ {assignFeedback} +
+ {/if} + +
+
+ + +
+
+ + + +
+

Prorrogar prazo

+

Recurso exclusivo para a equipe de TI

+
+
-
+
+ {/if}
@@ -1057,7 +1194,7 @@ {:else} -
+
{slaConfigsAtivos.length}
Total de SLAs
@@ -1071,8 +1208,12 @@
Prioridade Média
-
{slaConfigsPorPrioridadeCount.alta + slaConfigsPorPrioridadeCount.critica}
-
Prioridade Alta/Crítica
+
{slaConfigsPorPrioridadeCount.alta}
+
Prioridade Alta
+
+
+
{slaConfigsPorPrioridadeCount.critica}
+
Prioridade Crítica
diff --git a/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte b/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte index b31d71b..6541008 100644 --- a/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte @@ -11,14 +11,14 @@ - Cibersecurity SGSE • Wizcard TI + Cibersecurity SGSE - Sistema de Gerenciamento de Secretaria • Wizcard TI

- Cibersecurity • SGSE + Cibersecurity • SGSE - Sistema de Gerenciamento de Secretaria

Segurança Avançada

diff --git a/apps/web/src/routes/(dashboard)/ti/configuracoes-email/+page.svelte b/apps/web/src/routes/(dashboard)/ti/configuracoes-email/+page.svelte index 6e05735..15a7f1a 100644 --- a/apps/web/src/routes/(dashboard)/ti/configuracoes-email/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/configuracoes-email/+page.svelte @@ -404,7 +404,7 @@ id="smtp-nome-remetente" type="text" bind:value={nomeRemetente} - placeholder="SGSE - Sistema de Gestão" + placeholder="SGSE - Sistema de Gerenciamento de Secretaria" class="input input-bordered" />

diff --git a/apps/web/src/routes/(dashboard)/ti/monitoramento/+page.svelte b/apps/web/src/routes/(dashboard)/ti/monitoramento/+page.svelte index 32b2e99..8eb9a14 100644 --- a/apps/web/src/routes/(dashboard)/ti/monitoramento/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/monitoramento/+page.svelte @@ -24,7 +24,7 @@
-

Monitoramento SGSE

+

Monitoramento SGSE - Sistema de Gerenciamento de Secretaria

Sistema de monitoramento técnico em tempo real

diff --git a/apps/web/src/routes/(dashboard)/ti/painel-administrativo/+page.svelte b/apps/web/src/routes/(dashboard)/ti/painel-administrativo/+page.svelte index c8a798b..54e5976 100644 --- a/apps/web/src/routes/(dashboard)/ti/painel-administrativo/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/painel-administrativo/+page.svelte @@ -122,6 +122,6 @@
- Sistema de Gestão da Secretaria de Esportes - Versão 2.0 com controle avançado de acesso + SGSE - Sistema de Gerenciamento de Secretaria - Versão 2.0 com controle avançado de acesso
diff --git a/packages/backend/convex/actions/email.ts b/packages/backend/convex/actions/email.ts index adc368d..c22ea26 100644 --- a/packages/backend/convex/actions/email.ts +++ b/packages/backend/convex/actions/email.ts @@ -1,233 +1,233 @@ -"use node"; - -import { action } from "../_generated/server"; -import { v } from "convex/values"; -import { internal } from "../_generated/api"; -import { decryptSMTPPasswordNode } from "./utils/nodeCrypto"; -import nodemailer from "nodemailer"; - -export const enviar = action({ - args: { - emailId: v.id("notificacoesEmail"), - }, - returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }), - handler: async (ctx, args) => { - "use node"; - - let email; - try { - // Buscar email da fila - email = await ctx.runQuery(internal.email.getEmailById, { - emailId: args.emailId, - }); - - if (!email) { - return { sucesso: false, erro: "Email não encontrado" }; - } - - // Buscar configuração SMTP ativa - const configRaw = await ctx.runQuery( - internal.email.getActiveEmailConfig, - {} - ); - - if (!configRaw) { - console.error( - "❌ Configuração SMTP não encontrada ou inativa para email:", - email.destinatario - ); - return { - sucesso: false, - erro: "Configuração de email não encontrada ou inativa. Verifique as configurações SMTP no painel de TI.", - }; - } - - console.log("📧 Tentando enviar email:", { - para: email.destinatario, - assunto: email.assunto, - servidor: configRaw.servidor, - porta: configRaw.porta, - }); - - // Descriptografar senha usando função compatível com Node.js - let senhaDescriptografada: string; - try { - senhaDescriptografada = await decryptSMTPPasswordNode( - configRaw.senhaHash - ); - } catch (decryptError) { - const decryptErrorMessage = - decryptError instanceof Error - ? decryptError.message - : String(decryptError); - console.error( - "Erro ao descriptografar senha SMTP:", - decryptErrorMessage - ); - return { - sucesso: false, - erro: `Erro ao descriptografar senha SMTP: ${decryptErrorMessage}`, - }; - } - - const config = { - ...configRaw, - senha: senhaDescriptografada, - }; - - // Config já foi validado acima - - // Avisar mas não bloquear se não foi testado - if (!config.testadoEm) { - console.warn( - "⚠️ Configuração SMTP não foi testada. Tentando enviar mesmo assim..." - ); - } - - // Marcar como enviando - await ctx.runMutation(internal.email.markEmailEnviando, { - emailId: args.emailId, - }); - - // Criar transporter do nodemailer com configuração melhorada - const transporterOptions: { - host: string; - port: number; - secure: boolean; - requireTLS?: boolean; - auth: { - user: string; - pass: string; - }; - tls?: { - rejectUnauthorized: boolean; - ciphers?: string; - }; - connectionTimeout: number; - greetingTimeout: number; - socketTimeout: number; - pool?: boolean; - maxConnections?: number; - maxMessages?: number; - } = { - host: config.servidor, - port: config.porta, - secure: config.usarSSL, - auth: { - user: config.usuario, - pass: config.senha, // Senha já descriptografada - }, - connectionTimeout: 15000, // 15 segundos - greetingTimeout: 15000, - socketTimeout: 15000, - pool: true, // Usar pool de conexões - maxConnections: 5, - maxMessages: 100, - }; - - // Adicionar TLS apenas se necessário - if (config.usarTLS) { - transporterOptions.requireTLS = true; - transporterOptions.tls = { - rejectUnauthorized: false, // Permitir certificados autoassinados - }; - } else if (config.usarSSL) { - transporterOptions.tls = { - rejectUnauthorized: false, - }; - } - - const transporter = nodemailer.createTransport(transporterOptions); - - // Verificar conexão antes de enviar - try { - await transporter.verify(); - console.log("✅ Conexão SMTP verificada com sucesso"); - } catch (verifyError) { - const verifyErrorMessage = - verifyError instanceof Error - ? verifyError.message - : String(verifyError); - console.warn( - "⚠️ Falha na verificação SMTP, mas tentando enviar mesmo assim:", - verifyErrorMessage - ); - // Não bloquear envio por falha na verificação, apenas avisar - } - - // Validar email destinatário antes de enviar - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(email.destinatario)) { - throw new Error(`Email destinatário inválido: ${email.destinatario}`); - } - - // Criar versão texto do HTML (remover tags e decodificar entidades básicas) - const textoPlano = email.corpo - .replace(/<[^>]*>/g, "") // Remover tags HTML - .replace(/ /g, " ") - .replace(/&/g, "&") - .replace(/</g, "<") - .replace(/>/g, ">") - .replace(/"/g, '"') - .replace(/'/g, "'") - .trim(); - - // Enviar email - const info = await transporter.sendMail({ - from: `"${config.nomeRemetente}" <${config.emailRemetente}>`, - to: email.destinatario, - subject: email.assunto, - html: email.corpo, - text: textoPlano || email.assunto, // Versão texto para clientes que não suportam HTML - headers: { - "X-Mailer": "SGSE-Sistema", - "X-Priority": "3", - }, - }); - - interface MessageInfo { - messageId?: string; - response?: string; - } - - const messageInfo = info as MessageInfo; - - console.log("✅ Email enviado com sucesso!", { - para: email.destinatario, - assunto: email.assunto, - messageId: messageInfo.messageId, - response: messageInfo.response, - }); - - // Marcar como enviado - await ctx.runMutation(internal.email.markEmailEnviado, { - emailId: args.emailId, - }); - - return { sucesso: true }; - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : String(error); - const errorStack = error instanceof Error ? error.stack : undefined; - - console.error("❌ Erro ao enviar email:", { - emailId: args.emailId, - destinatario: email?.destinatario, - erro: errorMessage, - stack: errorStack, - }); - - // Marcar como falha com detalhes completos - const erroCompleto = errorStack - ? `${errorMessage}\n\nStack: ${errorStack}` - : errorMessage; - - await ctx.runMutation(internal.email.markEmailFalha, { - emailId: args.emailId, - erro: erroCompleto.substring(0, 2000), // Limitar tamanho do erro - }); - - return { sucesso: false, erro: errorMessage }; - } - }, -}); +"use node"; + +import { action } from "../_generated/server"; +import { v } from "convex/values"; +import { internal } from "../_generated/api"; +import { decryptSMTPPasswordNode } from "./utils/nodeCrypto"; +import nodemailer from "nodemailer"; + +export const enviar = action({ + args: { + emailId: v.id("notificacoesEmail"), + }, + returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }), + handler: async (ctx, args) => { + "use node"; + + let email; + try { + // Buscar email da fila + email = await ctx.runQuery(internal.email.getEmailById, { + emailId: args.emailId, + }); + + if (!email) { + return { sucesso: false, erro: "Email não encontrado" }; + } + + // Buscar configuração SMTP ativa + const configRaw = await ctx.runQuery( + internal.email.getActiveEmailConfig, + {} + ); + + if (!configRaw) { + console.error( + "❌ Configuração SMTP não encontrada ou inativa para email:", + email.destinatario + ); + return { + sucesso: false, + erro: "Configuração de email não encontrada ou inativa. Verifique as configurações SMTP no painel de TI.", + }; + } + + console.log("📧 Tentando enviar email:", { + para: email.destinatario, + assunto: email.assunto, + servidor: configRaw.servidor, + porta: configRaw.porta, + }); + + // Descriptografar senha usando função compatível com Node.js + let senhaDescriptografada: string; + try { + senhaDescriptografada = await decryptSMTPPasswordNode( + configRaw.senhaHash + ); + } catch (decryptError) { + const decryptErrorMessage = + decryptError instanceof Error + ? decryptError.message + : String(decryptError); + console.error( + "Erro ao descriptografar senha SMTP:", + decryptErrorMessage + ); + return { + sucesso: false, + erro: `Erro ao descriptografar senha SMTP: ${decryptErrorMessage}`, + }; + } + + const config = { + ...configRaw, + senha: senhaDescriptografada, + }; + + // Config já foi validado acima + + // Avisar mas não bloquear se não foi testado + if (!config.testadoEm) { + console.warn( + "⚠️ Configuração SMTP não foi testada. Tentando enviar mesmo assim..." + ); + } + + // Marcar como enviando + await ctx.runMutation(internal.email.markEmailEnviando, { + emailId: args.emailId, + }); + + // Criar transporter do nodemailer com configuração melhorada + const transporterOptions: { + host: string; + port: number; + secure: boolean; + requireTLS?: boolean; + auth: { + user: string; + pass: string; + }; + tls?: { + rejectUnauthorized: boolean; + ciphers?: string; + }; + connectionTimeout: number; + greetingTimeout: number; + socketTimeout: number; + pool?: boolean; + maxConnections?: number; + maxMessages?: number; + } = { + host: config.servidor, + port: config.porta, + secure: config.usarSSL, + auth: { + user: config.usuario, + pass: config.senha, // Senha já descriptografada + }, + connectionTimeout: 15000, // 15 segundos + greetingTimeout: 15000, + socketTimeout: 15000, + pool: true, // Usar pool de conexões + maxConnections: 5, + maxMessages: 100, + }; + + // Adicionar TLS apenas se necessário + if (config.usarTLS) { + transporterOptions.requireTLS = true; + transporterOptions.tls = { + rejectUnauthorized: false, // Permitir certificados autoassinados + }; + } else if (config.usarSSL) { + transporterOptions.tls = { + rejectUnauthorized: false, + }; + } + + const transporter = nodemailer.createTransport(transporterOptions); + + // Verificar conexão antes de enviar + try { + await transporter.verify(); + console.log("✅ Conexão SMTP verificada com sucesso"); + } catch (verifyError) { + const verifyErrorMessage = + verifyError instanceof Error + ? verifyError.message + : String(verifyError); + console.warn( + "⚠️ Falha na verificação SMTP, mas tentando enviar mesmo assim:", + verifyErrorMessage + ); + // Não bloquear envio por falha na verificação, apenas avisar + } + + // Validar email destinatário antes de enviar + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email.destinatario)) { + throw new Error(`Email destinatário inválido: ${email.destinatario}`); + } + + // Criar versão texto do HTML (remover tags e decodificar entidades básicas) + const textoPlano = email.corpo + .replace(/<[^>]*>/g, "") // Remover tags HTML + .replace(/ /g, " ") + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'") + .trim(); + + // Enviar email + const info = await transporter.sendMail({ + from: `"${config.nomeRemetente}" <${config.emailRemetente}>`, + to: email.destinatario, + subject: email.assunto, + html: email.corpo, + text: textoPlano || email.assunto, // Versão texto para clientes que não suportam HTML + headers: { + "X-Mailer": "SGSE-Sistema-de-Gerenciamento-de-Secretaria", + "X-Priority": "3", + }, + }); + + interface MessageInfo { + messageId?: string; + response?: string; + } + + const messageInfo = info as MessageInfo; + + console.log("✅ Email enviado com sucesso!", { + para: email.destinatario, + assunto: email.assunto, + messageId: messageInfo.messageId, + response: messageInfo.response, + }); + + // Marcar como enviado + await ctx.runMutation(internal.email.markEmailEnviado, { + emailId: args.emailId, + }); + + return { sucesso: true }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + const errorStack = error instanceof Error ? error.stack : undefined; + + console.error("❌ Erro ao enviar email:", { + emailId: args.emailId, + destinatario: email?.destinatario, + erro: errorMessage, + stack: errorStack, + }); + + // Marcar como falha com detalhes completos + const erroCompleto = errorStack + ? `${errorMessage}\n\nStack: ${errorStack}` + : errorMessage; + + await ctx.runMutation(internal.email.markEmailFalha, { + emailId: args.emailId, + erro: erroCompleto.substring(0, 2000), // Limitar tamanho do erro + }); + + return { sucesso: false, erro: errorMessage }; + } + }, +}); diff --git a/packages/backend/convex/chamados.ts b/packages/backend/convex/chamados.ts index 7bfe140..f25df21 100644 --- a/packages/backend/convex/chamados.ts +++ b/packages/backend/convex/chamados.ts @@ -127,7 +127,7 @@ async function registrarNotificacoes( destinatario: ticket.solicitanteEmail, destinatarioId: ticket.solicitanteId, assunto: `${titulo} - Chamado ${ticket.numero}`, - corpo: `${mensagem}\n\n---\nCentral de Chamados SGSE`, + corpo: `${mensagem}\n\n---\nCentral de Chamados SGSE - Sistema de Gerenciamento de Secretaria`, enviadoPor: usuarioEvento, }); } @@ -151,7 +151,7 @@ async function registrarNotificacoes( destinatario: responsavel.email, destinatarioId: ticket.responsavelId, assunto: `${titulo} - Chamado ${ticket.numero}`, - corpo: `${mensagem}\n\n---\nCentral de Chamados SGSE`, + corpo: `${mensagem}\n\n---\nCentral de Chamados SGSE - Sistema de Gerenciamento de Secretaria`, enviadoPor: usuarioEvento, }); } diff --git a/packages/backend/convex/templatesMensagens.ts b/packages/backend/convex/templatesMensagens.ts index c7b027a..1b92ecf 100644 --- a/packages/backend/convex/templatesMensagens.ts +++ b/packages/backend/convex/templatesMensagens.ts @@ -1,421 +1,421 @@ -import { v } from "convex/values"; -import { mutation, query } from "./_generated/server"; -import { registrarAtividade } from "./logsAtividades"; -import { Doc } from "./_generated/dataModel"; - -/** - * Listar todos os templates - */ -export const listarTemplates = query({ - args: {}, - handler: async (ctx) => { - const templates = await ctx.db.query("templatesMensagens").collect(); - return templates; - }, -}); - -/** - * Obter template por código - */ -export const obterTemplatePorCodigo = query({ - args: { - codigo: v.string(), - }, - handler: async (ctx, args) => { - const template = await ctx.db - .query("templatesMensagens") - .withIndex("by_codigo", (q) => q.eq("codigo", args.codigo)) - .first(); - - return template; - }, -}); - -/** - * Criar template customizado (apenas TI_MASTER) - */ -export const criarTemplate = mutation({ - args: { - codigo: v.string(), - nome: v.string(), - titulo: v.string(), - corpo: v.string(), - variaveis: v.optional(v.array(v.string())), - criadoPorId: v.id("usuarios"), - }, - returns: v.union( - v.object({ sucesso: v.literal(true), templateId: v.id("templatesMensagens") }), - v.object({ sucesso: v.literal(false), erro: v.string() }) - ), - handler: async (ctx, args) => { - // Verificar se código já existe - const existente = await ctx.db - .query("templatesMensagens") - .withIndex("by_codigo", (q) => q.eq("codigo", args.codigo)) - .first(); - - if (existente) { - return { sucesso: false as const, erro: "Código de template já existe" }; - } - - // Criar template - const templateId = await ctx.db.insert("templatesMensagens", { - codigo: args.codigo, - nome: args.nome, - tipo: "customizado", - titulo: args.titulo, - corpo: args.corpo, - variaveis: args.variaveis, - criadoPor: args.criadoPorId, - criadoEm: Date.now(), - }); - - // Log de atividade - await registrarAtividade( - ctx, - args.criadoPorId, - "criar", - "templates", - JSON.stringify({ templateId, codigo: args.codigo, nome: args.nome }), - templateId - ); - - return { sucesso: true as const, templateId }; - }, -}); - -/** - * Editar template customizado (apenas TI_MASTER, não edita templates do sistema) - */ -export const editarTemplate = mutation({ - args: { - templateId: v.id("templatesMensagens"), - nome: v.optional(v.string()), - titulo: v.optional(v.string()), - corpo: v.optional(v.string()), - variaveis: v.optional(v.array(v.string())), - editadoPorId: v.id("usuarios"), - }, - returns: v.union( - v.object({ sucesso: v.literal(true) }), - v.object({ sucesso: v.literal(false), erro: v.string() }) - ), - handler: async (ctx, args) => { - const template = await ctx.db.get(args.templateId); - if (!template) { - return { sucesso: false as const, erro: "Template não encontrado" }; - } - - // Não permite editar templates do sistema - if (template.tipo === "sistema") { - return { sucesso: false as const, erro: "Templates do sistema não podem ser editados" }; - } - - // Atualizar template - const updates: Partial> = {}; - if (args.nome !== undefined) updates.nome = args.nome; - if (args.titulo !== undefined) updates.titulo = args.titulo; - if (args.corpo !== undefined) updates.corpo = args.corpo; - if (args.variaveis !== undefined) updates.variaveis = args.variaveis; - - await ctx.db.patch(args.templateId, updates); - - // Log de atividade - await registrarAtividade( - ctx, - args.editadoPorId, - "editar", - "templates", - JSON.stringify(updates), - args.templateId - ); - - return { sucesso: true as const }; - }, -}); - -/** - * Excluir template customizado (apenas TI_MASTER, não exclui templates do sistema) - */ -export const excluirTemplate = mutation({ - args: { - templateId: v.id("templatesMensagens"), - excluidoPorId: v.id("usuarios"), - }, - returns: v.union( - v.object({ sucesso: v.literal(true) }), - v.object({ sucesso: v.literal(false), erro: v.string() }) - ), - handler: async (ctx, args) => { - const template = await ctx.db.get(args.templateId); - if (!template) { - return { sucesso: false as const, erro: "Template não encontrado" }; - } - - // Não permite excluir templates do sistema - if (template.tipo === "sistema") { - return { sucesso: false as const, erro: "Templates do sistema não podem ser excluídos" }; - } - - // Excluir template - await ctx.db.delete(args.templateId); - - // Log de atividade - await registrarAtividade( - ctx, - args.excluidoPorId, - "excluir", - "templates", - JSON.stringify({ templateId: args.templateId, codigo: template.codigo }), - args.templateId - ); - - return { sucesso: true as const }; - }, -}); - -/** - * Renderizar template com variáveis - */ -export function renderizarTemplate(template: string, variaveis: Record): string { - let resultado = template; - - for (const [chave, valor] of Object.entries(variaveis)) { - const placeholder = `{{${chave}}}`; - resultado = resultado.replace(new RegExp(placeholder, "g"), valor); - } - - return resultado; -} - -/** - * Criar templates padrão do sistema (chamado no seed) - */ -export const criarTemplatesPadrao = mutation({ - args: {}, - handler: async (ctx) => { - const templatesPadrao = [ - { - codigo: "USUARIO_BLOQUEADO", - nome: "Usuário Bloqueado", - titulo: "Sua conta foi bloqueada", - corpo: "Sua conta no SGSE foi bloqueada.\n\nMotivo: {{motivo}}\n\nPara mais informações, entre em contato com a TI.", - variaveis: ["motivo"], - }, - { - codigo: "USUARIO_DESBLOQUEADO", - nome: "Usuário Desbloqueado", - titulo: "Sua conta foi desbloqueada", - corpo: "Sua conta no SGSE foi desbloqueada e você já pode acessar o sistema normalmente.", - variaveis: [], - }, - { - codigo: "SENHA_RESETADA", - nome: "Senha Resetada", - titulo: "Sua senha foi resetada", - corpo: "Sua senha foi resetada pela equipe de TI.\n\nNova senha temporária: {{senha}}\n\nPor favor, altere sua senha no próximo login.", - variaveis: ["senha"], - }, - { - codigo: "PERMISSAO_ALTERADA", - nome: "Permissão Alterada", - titulo: "Suas permissões foram atualizadas", - corpo: "Suas permissões de acesso ao sistema foram atualizadas.\n\nPara verificar suas novas permissões, acesse o menu de perfil.", - variaveis: [], - }, - { - codigo: "AVISO_GERAL", - nome: "Aviso Geral", - titulo: "{{titulo}}", - corpo: "{{mensagem}}", - variaveis: ["titulo", "mensagem"], - }, - { - codigo: "BEM_VINDO", - nome: "Boas-vindas", - titulo: "Bem-vindo ao SGSE", - corpo: "Olá {{nome}},\n\nSeja bem-vindo ao Sistema de Gestão da Secretaria de Esportes!\n\nSuas credenciais de acesso:\nMatrícula: {{matricula}}\nSenha temporária: {{senha}}\n\nPor favor, altere sua senha no primeiro acesso.\n\nEquipe de TI", - variaveis: ["nome", "matricula", "senha"], - }, - { - codigo: "chat_mensagem", - nome: "Nova Mensagem no Chat", - titulo: "Nova mensagem de {{remetente}}", - corpo: "" - + "
" - + "

Nova mensagem no chat

" - + "

{{remetente}} enviou uma nova mensagem:

" - + "
" - + "

{{mensagem}}

" - + "
" - + "

" - + "" - + "Ver conversa" - + "" - + "

" - + "

" - + "Você está recebendo este email porque não estava online quando a mensagem foi enviada. " - + "Você pode desativar essas notificações nas configurações da conversa." - + "

" - + "
", - variaveis: ["remetente", "mensagem", "conversaId", "urlSistema"], - }, - { - codigo: "chat_mencao", - nome: "Menção no Chat", - titulo: "{{remetente}} mencionou você", - corpo: "" - + "
" - + "

Você foi mencionado!

" - + "

{{remetente}} mencionou você em uma mensagem:

" - + "
" - + "

{{mensagem}}

" - + "
" - + "

" - + "" - + "Ver mensagem" - + "" - + "

" - + "

" - + "Você está recebendo este email porque foi mencionado em uma conversa. " - + "Você pode desativar essas notificações nas configurações da conversa." - + "

" - + "
", - 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) { - // Verificar se já existe - const existente = await ctx.db - .query("templatesMensagens") - .withIndex("by_codigo", (q) => q.eq("codigo", template.codigo)) - .first(); - - if (!existente) { - await ctx.db.insert("templatesMensagens", { - ...template, - tipo: "sistema", - criadoEm: Date.now(), - }); - } - } - - return { sucesso: true }; - }, -}); - - +import { v } from "convex/values"; +import { mutation, query } from "./_generated/server"; +import { registrarAtividade } from "./logsAtividades"; +import { Doc } from "./_generated/dataModel"; + +/** + * Listar todos os templates + */ +export const listarTemplates = query({ + args: {}, + handler: async (ctx) => { + const templates = await ctx.db.query("templatesMensagens").collect(); + return templates; + }, +}); + +/** + * Obter template por código + */ +export const obterTemplatePorCodigo = query({ + args: { + codigo: v.string(), + }, + handler: async (ctx, args) => { + const template = await ctx.db + .query("templatesMensagens") + .withIndex("by_codigo", (q) => q.eq("codigo", args.codigo)) + .first(); + + return template; + }, +}); + +/** + * Criar template customizado (apenas TI_MASTER) + */ +export const criarTemplate = mutation({ + args: { + codigo: v.string(), + nome: v.string(), + titulo: v.string(), + corpo: v.string(), + variaveis: v.optional(v.array(v.string())), + criadoPorId: v.id("usuarios"), + }, + returns: v.union( + v.object({ sucesso: v.literal(true), templateId: v.id("templatesMensagens") }), + v.object({ sucesso: v.literal(false), erro: v.string() }) + ), + handler: async (ctx, args) => { + // Verificar se código já existe + const existente = await ctx.db + .query("templatesMensagens") + .withIndex("by_codigo", (q) => q.eq("codigo", args.codigo)) + .first(); + + if (existente) { + return { sucesso: false as const, erro: "Código de template já existe" }; + } + + // Criar template + const templateId = await ctx.db.insert("templatesMensagens", { + codigo: args.codigo, + nome: args.nome, + tipo: "customizado", + titulo: args.titulo, + corpo: args.corpo, + variaveis: args.variaveis, + criadoPor: args.criadoPorId, + criadoEm: Date.now(), + }); + + // Log de atividade + await registrarAtividade( + ctx, + args.criadoPorId, + "criar", + "templates", + JSON.stringify({ templateId, codigo: args.codigo, nome: args.nome }), + templateId + ); + + return { sucesso: true as const, templateId }; + }, +}); + +/** + * Editar template customizado (apenas TI_MASTER, não edita templates do sistema) + */ +export const editarTemplate = mutation({ + args: { + templateId: v.id("templatesMensagens"), + nome: v.optional(v.string()), + titulo: v.optional(v.string()), + corpo: v.optional(v.string()), + variaveis: v.optional(v.array(v.string())), + editadoPorId: v.id("usuarios"), + }, + returns: v.union( + v.object({ sucesso: v.literal(true) }), + v.object({ sucesso: v.literal(false), erro: v.string() }) + ), + handler: async (ctx, args) => { + const template = await ctx.db.get(args.templateId); + if (!template) { + return { sucesso: false as const, erro: "Template não encontrado" }; + } + + // Não permite editar templates do sistema + if (template.tipo === "sistema") { + return { sucesso: false as const, erro: "Templates do sistema não podem ser editados" }; + } + + // Atualizar template + const updates: Partial> = {}; + if (args.nome !== undefined) updates.nome = args.nome; + if (args.titulo !== undefined) updates.titulo = args.titulo; + if (args.corpo !== undefined) updates.corpo = args.corpo; + if (args.variaveis !== undefined) updates.variaveis = args.variaveis; + + await ctx.db.patch(args.templateId, updates); + + // Log de atividade + await registrarAtividade( + ctx, + args.editadoPorId, + "editar", + "templates", + JSON.stringify(updates), + args.templateId + ); + + return { sucesso: true as const }; + }, +}); + +/** + * Excluir template customizado (apenas TI_MASTER, não exclui templates do sistema) + */ +export const excluirTemplate = mutation({ + args: { + templateId: v.id("templatesMensagens"), + excluidoPorId: v.id("usuarios"), + }, + returns: v.union( + v.object({ sucesso: v.literal(true) }), + v.object({ sucesso: v.literal(false), erro: v.string() }) + ), + handler: async (ctx, args) => { + const template = await ctx.db.get(args.templateId); + if (!template) { + return { sucesso: false as const, erro: "Template não encontrado" }; + } + + // Não permite excluir templates do sistema + if (template.tipo === "sistema") { + return { sucesso: false as const, erro: "Templates do sistema não podem ser excluídos" }; + } + + // Excluir template + await ctx.db.delete(args.templateId); + + // Log de atividade + await registrarAtividade( + ctx, + args.excluidoPorId, + "excluir", + "templates", + JSON.stringify({ templateId: args.templateId, codigo: template.codigo }), + args.templateId + ); + + return { sucesso: true as const }; + }, +}); + +/** + * Renderizar template com variáveis + */ +export function renderizarTemplate(template: string, variaveis: Record): string { + let resultado = template; + + for (const [chave, valor] of Object.entries(variaveis)) { + const placeholder = `{{${chave}}}`; + resultado = resultado.replace(new RegExp(placeholder, "g"), valor); + } + + return resultado; +} + +/** + * Criar templates padrão do sistema (chamado no seed) + */ +export const criarTemplatesPadrao = mutation({ + args: {}, + handler: async (ctx) => { + const templatesPadrao = [ + { + codigo: "USUARIO_BLOQUEADO", + nome: "Usuário Bloqueado", + titulo: "Sua conta foi bloqueada", + corpo: "Sua conta no SGSE foi bloqueada.\n\nMotivo: {{motivo}}\n\nPara mais informações, entre em contato com a TI.", + variaveis: ["motivo"], + }, + { + codigo: "USUARIO_DESBLOQUEADO", + nome: "Usuário Desbloqueado", + titulo: "Sua conta foi desbloqueada", + corpo: "Sua conta no SGSE foi desbloqueada e você já pode acessar o sistema normalmente.", + variaveis: [], + }, + { + codigo: "SENHA_RESETADA", + nome: "Senha Resetada", + titulo: "Sua senha foi resetada", + corpo: "Sua senha foi resetada pela equipe de TI.\n\nNova senha temporária: {{senha}}\n\nPor favor, altere sua senha no próximo login.", + variaveis: ["senha"], + }, + { + codigo: "PERMISSAO_ALTERADA", + nome: "Permissão Alterada", + titulo: "Suas permissões foram atualizadas", + corpo: "Suas permissões de acesso ao sistema foram atualizadas.\n\nPara verificar suas novas permissões, acesse o menu de perfil.", + variaveis: [], + }, + { + codigo: "AVISO_GERAL", + nome: "Aviso Geral", + titulo: "{{titulo}}", + corpo: "{{mensagem}}", + variaveis: ["titulo", "mensagem"], + }, + { + codigo: "BEM_VINDO", + nome: "Boas-vindas", + titulo: "Bem-vindo ao SGSE", + corpo: "Olá {{nome}},\n\nSeja bem-vindo ao SGSE - Sistema de Gerenciamento de Secretaria!\n\nSuas credenciais de acesso:\nMatrícula: {{matricula}}\nSenha temporária: {{senha}}\n\nPor favor, altere sua senha no primeiro acesso.\n\nEquipe de TI", + variaveis: ["nome", "matricula", "senha"], + }, + { + codigo: "chat_mensagem", + nome: "Nova Mensagem no Chat", + titulo: "Nova mensagem de {{remetente}}", + corpo: "" + + "
" + + "

Nova mensagem no chat

" + + "

{{remetente}} enviou uma nova mensagem:

" + + "
" + + "

{{mensagem}}

" + + "
" + + "

" + + "" + + "Ver conversa" + + "" + + "

" + + "

" + + "Você está recebendo este email porque não estava online quando a mensagem foi enviada. " + + "Você pode desativar essas notificações nas configurações da conversa." + + "

" + + "
", + variaveis: ["remetente", "mensagem", "conversaId", "urlSistema"], + }, + { + codigo: "chat_mencao", + nome: "Menção no Chat", + titulo: "{{remetente}} mencionou você", + corpo: "" + + "
" + + "

Você foi mencionado!

" + + "

{{remetente}} mencionou você em uma mensagem:

" + + "
" + + "

{{mensagem}}

" + + "
" + + "

" + + "" + + "Ver mensagem" + + "" + + "

" + + "

" + + "Você está recebendo este email porque foi mencionado em uma conversa. " + + "Você pode desativar essas notificações nas configurações da conversa." + + "

" + + "
", + 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 - Sistema de Gerenciamento de Secretaria" + + "

" + + "
", + 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 - Sistema de Gerenciamento de Secretaria" + + "

" + + "
", + 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 - Sistema de Gerenciamento de Secretaria" + + "

" + + "
", + 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 - Sistema de Gerenciamento de Secretaria" + + "

" + + "
", + variaveis: ["destinatario", "numeroTicket", "tipoPrazo", "prazo", "status", "urlSistema", "rotaAcesso"], + }, + ]; + + for (const template of templatesPadrao) { + // Verificar se já existe + const existente = await ctx.db + .query("templatesMensagens") + .withIndex("by_codigo", (q) => q.eq("codigo", template.codigo)) + .first(); + + if (!existente) { + await ctx.db.insert("templatesMensagens", { + ...template, + tipo: "sistema", + criadoEm: Date.now(), + }); + } + } + + return { sucesso: true }; + }, +}); + +