import jsPDF from 'jspdf'; import type { Doc } from '@sgse-app/backend/convex/_generated/dataModel'; import logoGovPE from '$lib/assets/logo_governo_PE.png'; type Funcionario = Doc<'funcionarios'>; // Helper para adicionar logo no canto superior esquerdo async function addLogo(doc: jsPDF): Promise { try { // Criar uma promise para carregar a imagem const logoImg = await new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = 'anonymous'; // Para evitar problemas de CORS img.onload = () => resolve(img); img.onerror = (err) => reject(err); // Timeout de 3 segundos setTimeout(() => reject(new Error('Timeout loading logo')), 3000); // Importante: definir src depois de definir os handlers img.src = logoGovPE; }); // Logo proporcional: largura 25mm, altura ajustada automaticamente const logoWidth = 25; const aspectRatio = logoImg.height / logoImg.width; const logoHeight = logoWidth * aspectRatio; // Adicionar a imagem ao PDF doc.addImage(logoImg, 'PNG', 15, 10, logoWidth, logoHeight); // Retorna a posição Y onde o conteúdo pode começar (logo + margem) return 10 + logoHeight + 5; } catch (err) { console.error('Erro ao carregar logo:', err); return 20; // Posição padrão se a logo falhar } } // Helper para adicionar texto formatado function addText(doc: jsPDF, text: string, x: number, y: number, options?: { bold?: boolean; size?: number; align?: 'left' | 'center' | 'right' }) { if (options?.bold) { doc.setFont('helvetica', 'bold'); } else { doc.setFont('helvetica', 'normal'); } if (options?.size) { doc.setFontSize(options.size); } const align = options?.align || 'left'; doc.text(text, x, y, { align }); } // Helper para adicionar campo com valor function addField(doc: jsPDF, label: string, value: string, x: number, y: number, width?: number) { doc.setFontSize(10); doc.setFont('helvetica', 'bold'); doc.text(label, x, y); doc.setFont('helvetica', 'normal'); const labelWidth = doc.getTextWidth(label) + 2; if (width) { // Desenhar linha para preenchimento doc.line(x + labelWidth, y + 1, x + width, y + 1); if (value) { doc.text(value, x + labelWidth + 2, y); } } else { doc.text(value || '_____________________', x + labelWidth + 2, y); } return y + 7; } /** * 1. Declaração de Acumulação de Cargo, Emprego, Função Pública ou Proventos */ export async function gerarDeclaracaoAcumulacaoCargo(funcionario: Funcionario): Promise { const doc = new jsPDF(); // Adicionar logo e obter posição inicial do conteúdo let y = await addLogo(doc); // Cabeçalho (ao lado da logo) addText(doc, 'GOVERNO DO ESTADO DE PERNAMBUCO', 105, Math.max(y - 10, 20), { bold: true, size: 14, align: 'center' }); addText(doc, 'SECRETARIA DE ESPORTES', 105, Math.max(y - 2, 28), { bold: true, size: 12, align: 'center' }); y = Math.max(y, 40); y += 5; addText(doc, 'DECLARAÇÃO DE ACUMULAÇÃO DE CARGO, EMPREGO,', 105, y, { bold: true, size: 12, align: 'center' }); y += 6; addText(doc, 'FUNÇÃO PÚBLICA OU PROVENTOS', 105, y, { bold: true, size: 12, align: 'center' }); y += 15; // Corpo doc.setFontSize(11); doc.setFont('helvetica', 'normal'); const text1 = `Eu, ${funcionario.nome}, portador(a) do CPF nº ${funcionario.cpf}, `; const text2 = `inscrito(a) no RG nº ${funcionario.rg}, residente e domiciliado(a) à ${funcionario.endereco}, `; const text3 = `${funcionario.cidade}/${funcionario.uf}, DECLARO, para os devidos fins, que:`; doc.text(text1, 20, y, { maxWidth: 170 }); y += 7; doc.text(text2, 20, y, { maxWidth: 170 }); y += 7; doc.text(text3, 20, y, { maxWidth: 170 }); y += 15; // Opções doc.setFont('helvetica', 'bold'); doc.text('( ) NÃO EXERÇO', 25, y); y += 7; doc.setFont('helvetica', 'normal'); doc.text('Outro cargo, emprego ou função pública, bem como não percebo proventos de', 30, y, { maxWidth: 160 }); y += 5; doc.text('aposentadoria de regime próprio de previdência social ou do regime geral de', 30, y, { maxWidth: 160 }); y += 5; doc.text('previdência social.', 30, y); y += 12; doc.setFont('helvetica', 'bold'); doc.text('( ) EXERÇO', 25, y); y += 7; doc.setFont('helvetica', 'normal'); doc.text('Outro cargo, emprego ou função pública, conforme discriminado abaixo:', 30, y, { maxWidth: 160 }); y += 10; // Campos para preenchimento de outro cargo y = addField(doc, 'Órgão/Entidade:', funcionario.orgaoOrigem || '', 30, y, 160); y = addField(doc, 'Cargo/Função:', '', 30, y, 160); y = addField(doc, 'Carga Horária:', '', 30, y, 80); y = addField(doc, 'Remuneração:', '', 30, y, 80); y += 5; doc.setFont('helvetica', 'bold'); doc.text('( ) PERCEBO', 25, y); y += 7; doc.setFont('helvetica', 'normal'); doc.text('Proventos de aposentadoria:', 30, y); y += 10; y = addField(doc, 'Regime:', funcionario.aposentado === 'funape_ipsep' ? 'FUNAPE/IPSEP' : funcionario.aposentado === 'inss' ? 'INSS' : '', 30, y, 160); y = addField(doc, 'Valor:', '', 30, y, 80); y += 15; // Declaração de veracidade doc.text('Declaro, ainda, que estou ciente de que a acumulação ilegal de cargos,', 20, y, { maxWidth: 170 }); y += 5; doc.text('empregos ou funções públicas constitui infração administrativa, sujeitando-me', 20, y, { maxWidth: 170 }); y += 5; doc.text('às sanções legais cabíveis.', 20, y); y += 20; // Data e local const hoje = new Date().toLocaleDateString('pt-BR'); doc.text(`Recife, ${hoje}`, 20, y); y += 25; // Assinatura doc.line(70, y, 140, y); y += 5; addText(doc, funcionario.nome, 105, y, { align: 'center' }); y += 5; addText(doc, `CPF: ${funcionario.cpf}`, 105, y, { size: 9, align: 'center' }); // Rodapé doc.setFontSize(8); doc.setTextColor(100); doc.text('SGSE - Sistema de Gerenciamento de Secretaria', 105, 285, { align: 'center' }); return doc.output('blob'); } /** * 2. Declaração de Dependentes para Fins de Imposto de Renda */ export async function gerarDeclaracaoDependentesIR(funcionario: Funcionario): Promise { const doc = new jsPDF(); // Adicionar logo e obter posição inicial do conteúdo let y = await addLogo(doc); // Cabeçalho (ao lado da logo) addText(doc, 'GOVERNO DO ESTADO DE PERNAMBUCO', 105, Math.max(y - 10, 20), { bold: true, size: 14, align: 'center' }); addText(doc, 'SECRETARIA DE ESPORTES', 105, Math.max(y - 2, 28), { bold: true, size: 12, align: 'center' }); y = Math.max(y, 40); y += 5; addText(doc, 'DECLARAÇÃO DE DEPENDENTES', 105, y, { bold: true, size: 12, align: 'center' }); y += 6; addText(doc, 'PARA FINS DE IMPOSTO DE RENDA', 105, y, { bold: true, size: 12, align: 'center' }); y += 15; // Corpo doc.setFontSize(11); doc.setFont('helvetica', 'normal'); const text1 = `Eu, ${funcionario.nome}, portador(a) do CPF nº ${funcionario.cpf}, `; const text2 = `inscrito(a) no RG nº ${funcionario.rg}, matrícula nº ${funcionario.matricula}, `; const text3 = `DECLARO, para fins de dedução no Imposto de Renda na Fonte, que possuo os seguintes dependentes:`; doc.text(text1, 20, y, { maxWidth: 170 }); y += 7; doc.text(text2, 20, y, { maxWidth: 170 }); y += 7; doc.text(text3, 20, y, { maxWidth: 170 }); y += 15; // Tabela de dependentes doc.setFont('helvetica', 'bold'); doc.setFontSize(10); doc.text('NOME', 20, y); doc.text('CPF', 80, y); doc.text('PARENTESCO', 130, y); doc.text('NASC.', 175, y); y += 2; doc.line(20, y, 195, y); y += 8; // Linhas para preenchimento (5 linhas) doc.setFont('helvetica', 'normal'); for (let i = 0; i < 5; i++) { doc.line(20, y, 75, y); doc.line(80, y, 125, y); doc.line(130, y, 170, y); doc.line(175, y, 195, y); y += 12; } y += 10; // Declaração de veracidade doc.setFontSize(11); doc.text('Declaro estar ciente de que a inclusão de dependente sem direito constitui', 20, y, { maxWidth: 170 }); y += 5; doc.text('falsidade ideológica, sujeitando-me às penalidades previstas em lei, inclusive', 20, y, { maxWidth: 170 }); y += 5; doc.text('ao recolhimento do imposto devido acrescido de multa e juros.', 20, y, { maxWidth: 170 }); y += 20; // Data e local const hoje = new Date().toLocaleDateString('pt-BR'); doc.text(`Recife, ${hoje}`, 20, y); y += 25; // Assinatura doc.line(70, y, 140, y); y += 5; addText(doc, funcionario.nome, 105, y, { align: 'center' }); y += 5; addText(doc, `CPF: ${funcionario.cpf} | Matrícula: ${funcionario.matricula}`, 105, y, { size: 9, align: 'center' }); // Rodapé doc.setFontSize(8); doc.setTextColor(100); doc.text('SGSE - Sistema de Gerenciamento de Secretaria', 105, 285, { align: 'center' }); return doc.output('blob'); } /** * 3. Declaração de Idoneidade */ export async function gerarDeclaracaoIdoneidade(funcionario: Funcionario): Promise { const doc = new jsPDF(); // Adicionar logo e obter posição inicial do conteúdo let y = await addLogo(doc); // Cabeçalho (ao lado da logo) addText(doc, 'GOVERNO DO ESTADO DE PERNAMBUCO', 105, Math.max(y - 10, 20), { bold: true, size: 14, align: 'center' }); addText(doc, 'SECRETARIA DE ESPORTES', 105, Math.max(y - 2, 28), { bold: true, size: 12, align: 'center' }); y = Math.max(y, 40); y += 5; addText(doc, 'DECLARAÇÃO DE IDONEIDADE MORAL', 105, y, { bold: true, size: 12, align: 'center' }); y += 15; // Corpo doc.setFontSize(11); doc.setFont('helvetica', 'normal'); const text1 = `Eu, ${funcionario.nome}, portador(a) do CPF nº ${funcionario.cpf}, `; const text2 = `inscrito(a) no RG nº ${funcionario.rg}, residente e domiciliado(a) à ${funcionario.endereco}, `; const text3 = `${funcionario.cidade}/${funcionario.uf}, DECLARO, sob as penas da lei, que:`; doc.text(text1, 20, y, { maxWidth: 170 }); y += 7; doc.text(text2, 20, y, { maxWidth: 170 }); y += 7; doc.text(text3, 20, y, { maxWidth: 170 }); y += 15; // Itens da declaração const itens = [ 'Gozo de boa saúde física e mental para o exercício das atribuições do cargo/função;', 'Não fui condenado(a) por crime contra a Administração Pública;', 'Não fui condenado(a) por ato de improbidade administrativa;', 'Não sofri, no exercício de função pública, penalidade incompatível com a investidura em cargo público;', 'Não estou em situação de incompatibilidade ou impedimento para o exercício de cargo ou função pública;', 'Tenho idoneidade moral e reputação ilibada;', 'Não respondo a processo administrativo disciplinar em qualquer esfera da Administração Pública;', 'Não fui demitido(a) ou exonerado(a) de cargo ou função pública por justa causa.' ]; itens.forEach((item, index) => { doc.text(`${index + 1}. ${item}`, 20, y, { maxWidth: 170 }); y += 12; }); y += 10; // Declaração de veracidade doc.text('Declaro, ainda, que todas as informações aqui prestadas são verdadeiras,', 20, y, { maxWidth: 170 }); y += 5; doc.text('estando ciente de que a falsidade desta declaração configura crime previsto no', 20, y, { maxWidth: 170 }); y += 5; doc.text('Código Penal Brasileiro, passível de apuração na forma da lei.', 20, y); y += 20; // Data e local const hoje = new Date().toLocaleDateString('pt-BR'); doc.text(`Recife, ${hoje}`, 20, y); y += 25; // Assinatura doc.line(70, y, 140, y); y += 5; addText(doc, funcionario.nome, 105, y, { align: 'center' }); y += 5; addText(doc, `CPF: ${funcionario.cpf}`, 105, y, { size: 9, align: 'center' }); // Rodapé doc.setFontSize(8); doc.setTextColor(100); doc.text('SGSE - Sistema de Gerenciamento de Secretaria', 105, 285, { align: 'center' }); return doc.output('blob'); } /** * 4. Termo de Declaração de Nepotismo */ export async function gerarTermoNepotismo(funcionario: Funcionario): Promise { const doc = new jsPDF(); // Adicionar logo e obter posição inicial do conteúdo let y = await addLogo(doc); // Cabeçalho (ao lado da logo) addText(doc, 'GOVERNO DO ESTADO DE PERNAMBUCO', 105, Math.max(y - 10, 20), { bold: true, size: 14, align: 'center' }); addText(doc, 'SECRETARIA DE ESPORTES', 105, Math.max(y - 2, 28), { bold: true, size: 12, align: 'center' }); y = Math.max(y, 40); y += 5; addText(doc, 'TERMO DE DECLARAÇÃO DE NEPOTISMO', 105, y, { bold: true, size: 12, align: 'center' }); y += 15; // Corpo doc.setFontSize(11); doc.setFont('helvetica', 'normal'); const text1 = `Eu, ${funcionario.nome}, portador(a) do CPF nº ${funcionario.cpf}, `; const text2 = `inscrito(a) no RG nº ${funcionario.rg}, matrícula nº ${funcionario.matricula}, `; const text3 = `nomeado(a) para o cargo/função de ${funcionario.descricaoCargo || '_________________'}, `; const text4 = `DECLARO, para os fins do disposto na Súmula Vinculante nº 13 do STF e demais `; const text5 = `normas de combate ao nepotismo, que:`; doc.text(text1, 20, y, { maxWidth: 170 }); y += 7; doc.text(text2, 20, y, { maxWidth: 170 }); y += 7; doc.text(text3, 20, y, { maxWidth: 170 }); y += 7; doc.text(text4, 20, y, { maxWidth: 170 }); y += 5; doc.text(text5, 20, y, { maxWidth: 170 }); y += 15; // Opções doc.setFont('helvetica', 'bold'); doc.text('( ) NÃO POSSUO', 25, y); y += 7; doc.setFont('helvetica', 'normal'); doc.text('Cônjuge, companheiro(a) ou parente em linha reta, colateral ou por afinidade, até', 30, y, { maxWidth: 160 }); y += 5; doc.text('o terceiro grau, exercendo cargo em comissão ou função de confiança nesta', 30, y, { maxWidth: 160 }); y += 5; doc.text('Secretaria ou em órgão a ela vinculado.', 30, y); y += 12; doc.setFont('helvetica', 'bold'); doc.text('( ) POSSUO', 25, y); y += 7; doc.setFont('helvetica', 'normal'); doc.text('O(s) seguinte(s) parente(s) com vínculo nesta Secretaria:', 30, y); y += 10; // Campos para parentes for (let i = 0; i < 3; i++) { y = addField(doc, 'Nome:', '', 30, y, 160); y = addField(doc, 'CPF:', '', 30, y, 80); y = addField(doc, 'Grau de Parentesco:', '', 110, y - 7, 80); y = addField(doc, 'Cargo/Função:', '', 30, y, 160); y = addField(doc, 'Órgão:', '', 30, y, 160); y += 8; } y += 5; // Declaração de veracidade doc.text('Declaro estar ciente de que a nomeação, designação ou contratação em', 20, y, { maxWidth: 170 }); y += 5; doc.text('desconformidade com as vedações ao nepotismo importará em nulidade do ato,', 20, y, { maxWidth: 170 }); y += 5; doc.text('sem prejuízo das sanções administrativas, civis e penais cabíveis.', 20, y); y += 20; // Data e local const hoje = new Date().toLocaleDateString('pt-BR'); doc.text(`Recife, ${hoje}`, 20, y); y += 25; // Assinatura doc.line(70, y, 140, y); y += 5; addText(doc, funcionario.nome, 105, y, { align: 'center' }); y += 5; addText(doc, `CPF: ${funcionario.cpf} | Matrícula: ${funcionario.matricula}`, 105, y, { size: 9, align: 'center' }); // Rodapé doc.setFontSize(8); doc.setTextColor(100); doc.text('SGSE - Sistema de Gerenciamento de Secretaria', 105, 285, { align: 'center' }); return doc.output('blob'); } /** * 5. Termo de Opção - Remuneração */ export async function gerarTermoOpcaoRemuneracao(funcionario: Funcionario): Promise { const doc = new jsPDF(); // Adicionar logo e obter posição inicial do conteúdo let y = await addLogo(doc); // Cabeçalho (ao lado da logo) addText(doc, 'GOVERNO DO ESTADO DE PERNAMBUCO', 105, Math.max(y - 10, 20), { bold: true, size: 14, align: 'center' }); addText(doc, 'SECRETARIA DE ESPORTES', 105, Math.max(y - 2, 28), { bold: true, size: 12, align: 'center' }); y = Math.max(y, 40); y += 5; addText(doc, 'TERMO DE OPÇÃO DE REMUNERAÇÃO', 105, y, { bold: true, size: 12, align: 'center' }); y += 15; // Corpo doc.setFontSize(11); doc.setFont('helvetica', 'normal'); const text1 = `Eu, ${funcionario.nome}, portador(a) do CPF nº ${funcionario.cpf}, `; const text2 = `inscrito(a) no RG nº ${funcionario.rg}, matrícula nº ${funcionario.matricula}, `; const text3 = `nomeado(a) para o cargo/função de ${funcionario.descricaoCargo || '_________________'}, `; const text4 = `nos termos do Ato/Portaria nº ${funcionario.nomeacaoPortaria || '_____'} de ${funcionario.nomeacaoData || '___/___/___'}, `; const text5 = `DECLARO, para os devidos fins, que:`; doc.text(text1, 20, y, { maxWidth: 170 }); y += 7; doc.text(text2, 20, y, { maxWidth: 170 }); y += 7; doc.text(text3, 20, y, { maxWidth: 170 }); y += 7; doc.text(text4, 20, y, { maxWidth: 170 }); y += 7; doc.text(text5, 20, y); y += 15; // Seção 1 - Vínculo Anterior doc.setFont('helvetica', 'bold'); doc.text('1. QUANTO AO VÍNCULO ANTERIOR:', 20, y); y += 10; doc.setFont('helvetica', 'normal'); doc.text('( ) NÃO POSSUO outro vínculo com a Administração Pública', 25, y); y += 10; doc.text('( ) POSSUO vínculo efetivo com:', 25, y); y += 8; y = addField(doc, 'Órgão/Entidade:', funcionario.orgaoOrigem || '', 30, y, 160); y = addField(doc, 'Cargo:', '', 30, y, 160); y = addField(doc, 'Matrícula:', '', 30, y, 80); y += 10; // Seção 2 - Opção de Remuneração doc.setFont('helvetica', 'bold'); doc.text('2. QUANTO À REMUNERAÇÃO, OPTO POR RECEBER:', 20, y); y += 10; doc.setFont('helvetica', 'normal'); doc.text('( ) A remuneração do cargo em comissão/função gratificada ora assumido', 25, y); y += 10; doc.text('( ) A remuneração do cargo efetivo + a gratificação/símbolo', 25, y); y += 10; doc.text('( ) A remuneração do cargo efetivo (sem percepção de gratificação)', 25, y); y += 15; // Seção 3 - Dados Bancários doc.setFont('helvetica', 'bold'); doc.text('3. DADOS BANCÁRIOS PARA PAGAMENTO:', 20, y); y += 10; doc.setFont('helvetica', 'normal'); y = addField(doc, 'Banco:', 'Bradesco', 20, y, 80); y = addField(doc, 'Agência:', funcionario.contaBradescoAgencia || '', 110, y - 7, 80); y = addField(doc, 'Conta Corrente:', funcionario.contaBradescoNumero || '', 20, y, 80); y = addField(doc, 'Dígito:', funcionario.contaBradescoDV || '', 110, y - 7, 40); y += 15; // Declaração de ciência doc.text('Declaro estar ciente de que:', 20, y); y += 8; const ciencias = [ 'A remuneração será paga conforme a opção acima, respeitada a legislação vigente;', 'Qualquer alteração na opção deverá ser comunicada formalmente à Secretaria;', 'A não apresentação deste termo poderá implicar em atraso no pagamento;', 'As informações aqui prestadas são verdadeiras e atualizadas.' ]; ciencias.forEach((item, index) => { doc.text(`${index + 1}. ${item}`, 25, y, { maxWidth: 165 }); y += 10; }); y += 5; // Data e local const hoje = new Date().toLocaleDateString('pt-BR'); doc.text(`Recife, ${hoje}`, 20, y); y += 25; // Assinatura doc.line(70, y, 140, y); y += 5; addText(doc, funcionario.nome, 105, y, { align: 'center' }); y += 5; addText(doc, `CPF: ${funcionario.cpf} | Matrícula: ${funcionario.matricula}`, 105, y, { size: 9, align: 'center' }); // Rodapé doc.setFontSize(8); doc.setTextColor(100); doc.text('SGSE - Sistema de Gerenciamento de Secretaria', 105, 285, { align: 'center' }); return doc.output('blob'); } // Função helper para download export function downloadBlob(blob: Blob, filename: string) { const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }