797 lines
20 KiB
TypeScript
797 lines
20 KiB
TypeScript
import { v } from 'convex/values';
|
|
import { mutation, query } from './_generated/server';
|
|
import { getCurrentUserFunction } from './auth';
|
|
import { Id, Doc } from './_generated/dataModel';
|
|
import type { QueryCtx, MutationCtx } from './_generated/server';
|
|
import { registrarAtividade } from './logsAtividades';
|
|
|
|
/**
|
|
* Verificar se usuário aceitou o termo de consentimento
|
|
*/
|
|
export const verificarConsentimento = query({
|
|
args: {
|
|
tipo: v.optional(
|
|
v.union(
|
|
v.literal('termo_uso'),
|
|
v.literal('politica_privacidade'),
|
|
v.literal('comunicacoes'),
|
|
v.literal('compartilhamento_dados')
|
|
)
|
|
)
|
|
},
|
|
returns: v.union(
|
|
v.object({
|
|
aceito: v.boolean(),
|
|
versao: v.string(),
|
|
aceitoEm: v.number()
|
|
}),
|
|
v.null()
|
|
),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getCurrentUserFunction(ctx);
|
|
if (!usuario) {
|
|
return null;
|
|
}
|
|
|
|
const tipo = args.tipo || 'termo_uso';
|
|
|
|
const consentimento = await ctx.db
|
|
.query('consentimentos')
|
|
.withIndex('by_usuario_tipo', (q) => q.eq('usuarioId', usuario._id).eq('tipo', tipo))
|
|
.order('desc')
|
|
.first();
|
|
|
|
if (!consentimento || !consentimento.aceito || consentimento.revogadoEm) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
aceito: consentimento.aceito,
|
|
versao: consentimento.versao,
|
|
aceitoEm: consentimento.aceitoEm
|
|
};
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Registrar consentimento do usuário
|
|
*/
|
|
export const registrarConsentimento = mutation({
|
|
args: {
|
|
tipo: v.union(
|
|
v.literal('termo_uso'),
|
|
v.literal('politica_privacidade'),
|
|
v.literal('comunicacoes'),
|
|
v.literal('compartilhamento_dados')
|
|
),
|
|
aceito: v.boolean(),
|
|
versao: v.string(),
|
|
ipAddress: v.optional(v.string()),
|
|
userAgent: v.optional(v.string())
|
|
},
|
|
returns: v.object({ sucesso: v.boolean(), consentimentoId: v.id('consentimentos') }),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getCurrentUserFunction(ctx);
|
|
if (!usuario) {
|
|
throw new Error('Usuário não autenticado');
|
|
}
|
|
|
|
// Verificar se já existe consentimento ativo
|
|
const existente = await ctx.db
|
|
.query('consentimentos')
|
|
.withIndex('by_usuario_tipo', (q) => q.eq('usuarioId', usuario._id).eq('tipo', args.tipo))
|
|
.order('desc')
|
|
.first();
|
|
|
|
if (existente && existente.aceito && !existente.revogadoEm) {
|
|
// Atualizar consentimento existente
|
|
await ctx.db.patch(existente._id, {
|
|
aceito: args.aceito,
|
|
versao: args.versao,
|
|
aceitoEm: Date.now(),
|
|
ipAddress: args.ipAddress,
|
|
userAgent: args.userAgent,
|
|
revogadoEm: undefined,
|
|
revogadoPor: undefined
|
|
});
|
|
|
|
return { sucesso: true, consentimentoId: existente._id };
|
|
}
|
|
|
|
// Criar novo consentimento
|
|
const consentimentoId = await ctx.db.insert('consentimentos', {
|
|
usuarioId: usuario._id,
|
|
tipo: args.tipo,
|
|
aceito: args.aceito,
|
|
versao: args.versao,
|
|
ipAddress: args.ipAddress,
|
|
userAgent: args.userAgent,
|
|
aceitoEm: Date.now()
|
|
});
|
|
|
|
// Log de atividade
|
|
await registrarAtividade(
|
|
ctx,
|
|
usuario._id,
|
|
'aceitar_consentimento',
|
|
'consentimentos',
|
|
JSON.stringify({ tipo: args.tipo, versao: args.versao }),
|
|
consentimentoId.toString()
|
|
);
|
|
|
|
return { sucesso: true, consentimentoId };
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Revogar consentimento
|
|
*/
|
|
export const revogarConsentimento = mutation({
|
|
args: {
|
|
tipo: v.union(
|
|
v.literal('termo_uso'),
|
|
v.literal('politica_privacidade'),
|
|
v.literal('comunicacoes'),
|
|
v.literal('compartilhamento_dados')
|
|
)
|
|
},
|
|
returns: v.object({ sucesso: v.boolean() }),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getCurrentUserFunction(ctx);
|
|
if (!usuario) {
|
|
throw new Error('Usuário não autenticado');
|
|
}
|
|
|
|
const consentimento = await ctx.db
|
|
.query('consentimentos')
|
|
.withIndex('by_usuario_tipo', (q) => q.eq('usuarioId', usuario._id).eq('tipo', args.tipo))
|
|
.order('desc')
|
|
.first();
|
|
|
|
if (!consentimento) {
|
|
throw new Error('Consentimento não encontrado');
|
|
}
|
|
|
|
await ctx.db.patch(consentimento._id, {
|
|
revogadoEm: Date.now(),
|
|
revogadoPor: usuario._id
|
|
});
|
|
|
|
// Log de atividade
|
|
await registrarAtividade(
|
|
ctx,
|
|
usuario._id,
|
|
'revogar_consentimento',
|
|
'consentimentos',
|
|
JSON.stringify({ tipo: args.tipo }),
|
|
consentimento._id.toString()
|
|
);
|
|
|
|
return { sucesso: true };
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Listar consentimentos do usuário
|
|
*/
|
|
export const listarConsentimentos = query({
|
|
args: {},
|
|
returns: v.array(
|
|
v.object({
|
|
_id: v.id('consentimentos'),
|
|
tipo: v.string(),
|
|
aceito: v.boolean(),
|
|
versao: v.string(),
|
|
aceitoEm: v.number(),
|
|
revogadoEm: v.union(v.number(), v.null())
|
|
})
|
|
),
|
|
handler: async (ctx) => {
|
|
const usuario = await getCurrentUserFunction(ctx);
|
|
if (!usuario) {
|
|
return [];
|
|
}
|
|
|
|
const consentimentos = await ctx.db
|
|
.query('consentimentos')
|
|
.withIndex('by_usuario', (q) => q.eq('usuarioId', usuario._id))
|
|
.order('desc')
|
|
.collect();
|
|
|
|
return consentimentos.map((c) => ({
|
|
_id: c._id,
|
|
tipo: c.tipo,
|
|
aceito: c.aceito,
|
|
versao: c.versao,
|
|
aceitoEm: c.aceitoEm,
|
|
revogadoEm: c.revogadoEm ?? null
|
|
}));
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Criar solicitação de direito LGPD
|
|
*/
|
|
export const criarSolicitacao = mutation({
|
|
args: {
|
|
tipo: v.union(
|
|
v.literal('acesso'),
|
|
v.literal('correcao'),
|
|
v.literal('exclusao'),
|
|
v.literal('portabilidade'),
|
|
v.literal('revogacao_consentimento'),
|
|
v.literal('informacao_compartilhamento')
|
|
),
|
|
dadosSolicitados: v.optional(v.string()),
|
|
observacoes: v.optional(v.string())
|
|
},
|
|
returns: v.object({ sucesso: v.boolean(), solicitacaoId: v.id('solicitacoesLGPD') }),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getCurrentUserFunction(ctx);
|
|
if (!usuario) {
|
|
throw new Error('Usuário não autenticado');
|
|
}
|
|
|
|
// Prazo de resposta: 15 dias (conforme LGPD)
|
|
const prazoResposta = Date.now() + 15 * 24 * 60 * 60 * 1000;
|
|
|
|
const solicitacaoId = await ctx.db.insert('solicitacoesLGPD', {
|
|
tipo: args.tipo,
|
|
usuarioId: usuario._id,
|
|
funcionarioId: usuario.funcionarioId,
|
|
status: 'pendente',
|
|
dadosSolicitados: args.dadosSolicitados,
|
|
observacoes: args.observacoes,
|
|
criadoEm: Date.now(),
|
|
prazoResposta
|
|
});
|
|
|
|
// Log de atividade
|
|
await registrarAtividade(
|
|
ctx,
|
|
usuario._id,
|
|
'criar_solicitacao_lgpd',
|
|
'solicitacoesLGPD',
|
|
JSON.stringify({ tipo: args.tipo }),
|
|
solicitacaoId.toString()
|
|
);
|
|
|
|
return { sucesso: true, solicitacaoId };
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Listar solicitações do usuário
|
|
*/
|
|
export const listarMinhasSolicitacoes = query({
|
|
args: {
|
|
status: v.optional(
|
|
v.union(
|
|
v.literal('pendente'),
|
|
v.literal('em_analise'),
|
|
v.literal('concluida'),
|
|
v.literal('rejeitada')
|
|
)
|
|
)
|
|
},
|
|
returns: v.array(
|
|
v.object({
|
|
_id: v.id('solicitacoesLGPD'),
|
|
tipo: v.string(),
|
|
status: v.string(),
|
|
criadoEm: v.number(),
|
|
prazoResposta: v.number(),
|
|
respondidoEm: v.union(v.number(), v.null()),
|
|
resposta: v.union(v.string(), v.null()),
|
|
arquivoResposta: v.union(v.string(), v.null())
|
|
})
|
|
),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getCurrentUserFunction(ctx);
|
|
if (!usuario) {
|
|
return [];
|
|
}
|
|
|
|
let solicitacoes = await ctx.db
|
|
.query('solicitacoesLGPD')
|
|
.withIndex('by_usuario', (q) => q.eq('usuarioId', usuario._id))
|
|
.order('desc')
|
|
.collect();
|
|
|
|
if (args.status) {
|
|
solicitacoes = solicitacoes.filter((s) => s.status === args.status);
|
|
}
|
|
|
|
return solicitacoes.map((s) => ({
|
|
_id: s._id,
|
|
tipo: s.tipo,
|
|
status: s.status,
|
|
criadoEm: s.criadoEm,
|
|
prazoResposta: s.prazoResposta,
|
|
respondidoEm: s.respondidoEm ?? null,
|
|
resposta: s.resposta ?? null,
|
|
arquivoResposta: s.arquivoResposta ? s.arquivoResposta.toString() : null
|
|
}));
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Listar todas as solicitações (apenas TI)
|
|
*/
|
|
export const listarSolicitacoes = query({
|
|
args: {
|
|
status: v.optional(
|
|
v.union(
|
|
v.literal('pendente'),
|
|
v.literal('em_analise'),
|
|
v.literal('concluida'),
|
|
v.literal('rejeitada')
|
|
)
|
|
),
|
|
tipo: v.optional(
|
|
v.union(
|
|
v.literal('acesso'),
|
|
v.literal('correcao'),
|
|
v.literal('exclusao'),
|
|
v.literal('portabilidade'),
|
|
v.literal('revogacao_consentimento'),
|
|
v.literal('informacao_compartilhamento')
|
|
)
|
|
),
|
|
limite: v.optional(v.number())
|
|
},
|
|
returns: v.array(
|
|
v.object({
|
|
_id: v.id('solicitacoesLGPD'),
|
|
tipo: v.string(),
|
|
status: v.string(),
|
|
usuarioNome: v.string(),
|
|
usuarioEmail: v.string(),
|
|
usuarioMatricula: v.union(v.string(), v.null()),
|
|
criadoEm: v.number(),
|
|
prazoResposta: v.number(),
|
|
respondidoEm: v.union(v.number(), v.null()),
|
|
respondidoPorNome: v.union(v.string(), v.null())
|
|
})
|
|
),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getCurrentUserFunction(ctx);
|
|
if (!usuario) {
|
|
return [];
|
|
}
|
|
|
|
// Verificar se é TI (simplificado - pode melhorar com verificação de role)
|
|
// Por enquanto, qualquer usuário autenticado pode ver (será melhorado)
|
|
|
|
let solicitacoes = await ctx.db.query('solicitacoesLGPD').order('desc').collect();
|
|
|
|
if (args.status) {
|
|
solicitacoes = solicitacoes.filter((s) => s.status === args.status);
|
|
}
|
|
|
|
if (args.tipo) {
|
|
solicitacoes = solicitacoes.filter((s) => s.tipo === args.tipo);
|
|
}
|
|
|
|
if (args.limite) {
|
|
solicitacoes = solicitacoes.slice(0, args.limite);
|
|
}
|
|
|
|
// Enriquecer com dados do usuário
|
|
const resultado = await Promise.all(
|
|
solicitacoes.map(async (s) => {
|
|
const usuarioSolicitante = await ctx.db.get(s.usuarioId);
|
|
let matricula: string | null = null;
|
|
|
|
if (usuarioSolicitante?.funcionarioId) {
|
|
const funcionario = await ctx.db.get(usuarioSolicitante.funcionarioId);
|
|
matricula = funcionario?.matricula ?? null;
|
|
}
|
|
|
|
let respondidoPorNome: string | null = null;
|
|
if (s.respondidoPor) {
|
|
const respondente = await ctx.db.get(s.respondidoPor);
|
|
respondidoPorNome = respondente?.nome ?? null;
|
|
}
|
|
|
|
return {
|
|
_id: s._id,
|
|
tipo: s.tipo,
|
|
status: s.status,
|
|
usuarioNome: usuarioSolicitante?.nome ?? 'Usuário Desconhecido',
|
|
usuarioEmail: usuarioSolicitante?.email ?? '',
|
|
usuarioMatricula: matricula,
|
|
criadoEm: s.criadoEm,
|
|
prazoResposta: s.prazoResposta,
|
|
respondidoEm: s.respondidoEm ?? null,
|
|
respondidoPorNome
|
|
};
|
|
})
|
|
);
|
|
|
|
return resultado;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Responder solicitação (apenas TI)
|
|
*/
|
|
export const responderSolicitacao = mutation({
|
|
args: {
|
|
solicitacaoId: v.id('solicitacoesLGPD'),
|
|
resposta: v.string(),
|
|
status: v.union(
|
|
v.literal('concluida'),
|
|
v.literal('rejeitada'),
|
|
v.literal('em_analise')
|
|
),
|
|
arquivoResposta: v.optional(v.id('_storage'))
|
|
},
|
|
returns: v.object({ sucesso: v.boolean() }),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getCurrentUserFunction(ctx);
|
|
if (!usuario) {
|
|
throw new Error('Usuário não autenticado');
|
|
}
|
|
|
|
const solicitacao = await ctx.db.get(args.solicitacaoId);
|
|
if (!solicitacao) {
|
|
throw new Error('Solicitação não encontrada');
|
|
}
|
|
|
|
await ctx.db.patch(args.solicitacaoId, {
|
|
status: args.status,
|
|
resposta: args.resposta,
|
|
arquivoResposta: args.arquivoResposta,
|
|
respondidoPor: usuario._id,
|
|
respondidoEm: Date.now()
|
|
});
|
|
|
|
// Log de atividade
|
|
await registrarAtividade(
|
|
ctx,
|
|
usuario._id,
|
|
'responder_solicitacao_lgpd',
|
|
'solicitacoesLGPD',
|
|
JSON.stringify({ solicitacaoId: args.solicitacaoId, status: args.status }),
|
|
args.solicitacaoId.toString()
|
|
);
|
|
|
|
return { sucesso: true };
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Exportar dados do usuário (portabilidade)
|
|
*/
|
|
export const exportarDadosUsuario = query({
|
|
args: {},
|
|
returns: v.object({
|
|
dados: v.string() // JSON string com todos os dados do usuário
|
|
}),
|
|
handler: async (ctx) => {
|
|
const usuario = await getCurrentUserFunction(ctx);
|
|
if (!usuario) {
|
|
throw new Error('Usuário não autenticado');
|
|
}
|
|
|
|
// Buscar todos os dados do usuário
|
|
const dadosUsuario: any = {
|
|
usuario: {
|
|
nome: usuario.nome,
|
|
email: usuario.email,
|
|
setor: usuario.setor
|
|
},
|
|
consentimentos: [],
|
|
solicitacoes: [],
|
|
atividades: []
|
|
};
|
|
|
|
// Consentimentos
|
|
const consentimentos = await ctx.db
|
|
.query('consentimentos')
|
|
.withIndex('by_usuario', (q) => q.eq('usuarioId', usuario._id))
|
|
.collect();
|
|
dadosUsuario.consentimentos = consentimentos.map((c) => ({
|
|
tipo: c.tipo,
|
|
aceito: c.aceito,
|
|
versao: c.versao,
|
|
aceitoEm: c.aceitoEm,
|
|
revogadoEm: c.revogadoEm
|
|
}));
|
|
|
|
// Solicitações LGPD
|
|
const solicitacoes = await ctx.db
|
|
.query('solicitacoesLGPD')
|
|
.withIndex('by_usuario', (q) => q.eq('usuarioId', usuario._id))
|
|
.collect();
|
|
dadosUsuario.solicitacoes = solicitacoes.map((s) => ({
|
|
tipo: s.tipo,
|
|
status: s.status,
|
|
criadoEm: s.criadoEm,
|
|
respondidoEm: s.respondidoEm
|
|
}));
|
|
|
|
// Dados do funcionário (se houver)
|
|
if (usuario.funcionarioId) {
|
|
const funcionario = await ctx.db.get(usuario.funcionarioId);
|
|
if (funcionario) {
|
|
dadosUsuario.funcionario = {
|
|
nome: funcionario.nome,
|
|
matricula: funcionario.matricula,
|
|
cpf: funcionario.cpf,
|
|
email: funcionario.email,
|
|
telefone: funcionario.telefone,
|
|
cargo: funcionario.cargo,
|
|
setor: funcionario.setor
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
dados: JSON.stringify(dadosUsuario, null, 2)
|
|
};
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Criar Registro de Operação de Tratamento (ROT)
|
|
*/
|
|
export const criarRegistroTratamento = mutation({
|
|
args: {
|
|
finalidade: v.string(),
|
|
baseLegal: v.string(),
|
|
categoriasDados: v.array(v.string()),
|
|
categoriasTitulares: v.array(v.string()),
|
|
medidasSeguranca: v.array(v.string()),
|
|
prazoRetencao: v.number(),
|
|
compartilhamentoTerceiros: v.boolean(),
|
|
terceiros: v.optional(v.array(v.string())),
|
|
descricao: v.optional(v.string())
|
|
},
|
|
returns: v.object({ sucesso: v.boolean(), registroId: v.id('registrosTratamento') }),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getCurrentUserFunction(ctx);
|
|
if (!usuario) {
|
|
throw new Error('Usuário não autenticado');
|
|
}
|
|
|
|
const agora = Date.now();
|
|
|
|
const registroId = await ctx.db.insert('registrosTratamento', {
|
|
finalidade: args.finalidade,
|
|
baseLegal: args.baseLegal,
|
|
categoriasDados: args.categoriasDados,
|
|
categoriasTitulares: args.categoriasTitulares,
|
|
medidasSeguranca: args.medidasSeguranca,
|
|
prazoRetencao: args.prazoRetencao,
|
|
compartilhamentoTerceiros: args.compartilhamentoTerceiros,
|
|
terceiros: args.terceiros,
|
|
responsavel: usuario._id,
|
|
descricao: args.descricao,
|
|
criadoEm: agora,
|
|
atualizadoEm: agora,
|
|
ativo: true
|
|
});
|
|
|
|
// Log de atividade
|
|
await registrarAtividade(
|
|
ctx,
|
|
usuario._id,
|
|
'criar_rot',
|
|
'registrosTratamento',
|
|
JSON.stringify({ finalidade: args.finalidade }),
|
|
registroId.toString()
|
|
);
|
|
|
|
return { sucesso: true, registroId };
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Listar Registros de Tratamento
|
|
*/
|
|
export const listarRegistrosTratamento = query({
|
|
args: {
|
|
ativo: v.optional(v.boolean())
|
|
},
|
|
returns: v.array(
|
|
v.object({
|
|
_id: v.id('registrosTratamento'),
|
|
finalidade: v.string(),
|
|
baseLegal: v.string(),
|
|
categoriasDados: v.array(v.string()),
|
|
categoriasTitulares: v.array(v.string()),
|
|
medidasSeguranca: v.array(v.string()),
|
|
prazoRetencao: v.number(),
|
|
compartilhamentoTerceiros: v.boolean(),
|
|
terceiros: v.union(v.array(v.string()), v.null()),
|
|
responsavelNome: v.string(),
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.number(),
|
|
ativo: v.boolean()
|
|
})
|
|
),
|
|
handler: async (ctx, args) => {
|
|
let registros = await ctx.db.query('registrosTratamento').collect();
|
|
|
|
if (args.ativo !== undefined) {
|
|
registros = registros.filter((r) => r.ativo === args.ativo);
|
|
}
|
|
|
|
// Enriquecer com nome do responsável
|
|
const resultado = await Promise.all(
|
|
registros.map(async (r) => {
|
|
const responsavel = await ctx.db.get(r.responsavel);
|
|
return {
|
|
_id: r._id,
|
|
finalidade: r.finalidade,
|
|
baseLegal: r.baseLegal,
|
|
categoriasDados: r.categoriasDados,
|
|
categoriasTitulares: r.categoriasTitulares,
|
|
medidasSeguranca: r.medidasSeguranca,
|
|
prazoRetencao: r.prazoRetencao,
|
|
compartilhamentoTerceiros: r.compartilhamentoTerceiros,
|
|
terceiros: r.terceiros ?? null,
|
|
responsavelNome: responsavel?.nome ?? 'Desconhecido',
|
|
criadoEm: r.criadoEm,
|
|
atualizadoEm: r.atualizadoEm,
|
|
ativo: r.ativo
|
|
};
|
|
})
|
|
);
|
|
|
|
return resultado;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Obter configurações LGPD
|
|
*/
|
|
export const obterConfiguracaoLGPD = query({
|
|
args: {},
|
|
returns: v.union(
|
|
v.object({
|
|
encarregadoNome: v.union(v.string(), v.null()),
|
|
encarregadoEmail: v.union(v.string(), v.null()),
|
|
encarregadoTelefone: v.union(v.string(), v.null()),
|
|
prazoRespostaPadrao: v.number(),
|
|
diasAlertaVencimento: v.number()
|
|
}),
|
|
v.null()
|
|
),
|
|
handler: async (ctx) => {
|
|
const config = await ctx.db
|
|
.query('configuracaoLGPD')
|
|
.withIndex('by_ativo', (q) => q.eq('ativo', true))
|
|
.first();
|
|
|
|
if (!config) {
|
|
// Retornar valores padrão
|
|
return {
|
|
encarregadoNome: null,
|
|
encarregadoEmail: null,
|
|
encarregadoTelefone: null,
|
|
prazoRespostaPadrao: 15,
|
|
diasAlertaVencimento: 3
|
|
};
|
|
}
|
|
|
|
return {
|
|
encarregadoNome: config.encarregadoNome ?? null,
|
|
encarregadoEmail: config.encarregadoEmail ?? null,
|
|
encarregadoTelefone: config.encarregadoTelefone ?? null,
|
|
prazoRespostaPadrao: config.prazoRespostaPadrao,
|
|
diasAlertaVencimento: config.diasAlertaVencimento
|
|
};
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Atualizar configurações LGPD (apenas TI)
|
|
*/
|
|
export const atualizarConfiguracaoLGPD = mutation({
|
|
args: {
|
|
encarregadoNome: v.optional(v.string()),
|
|
encarregadoEmail: v.optional(v.string()),
|
|
encarregadoTelefone: v.optional(v.string()),
|
|
prazoRespostaPadrao: v.optional(v.number()),
|
|
diasAlertaVencimento: v.optional(v.number())
|
|
},
|
|
returns: v.object({ sucesso: v.boolean() }),
|
|
handler: async (ctx, args) => {
|
|
const usuario = await getCurrentUserFunction(ctx);
|
|
if (!usuario) {
|
|
throw new Error('Usuário não autenticado');
|
|
}
|
|
|
|
// Buscar configuração ativa ou criar nova
|
|
let config = await ctx.db
|
|
.query('configuracaoLGPD')
|
|
.withIndex('by_ativo', (q) => q.eq('ativo', true))
|
|
.first();
|
|
|
|
if (config) {
|
|
// Desativar configuração antiga
|
|
await ctx.db.patch(config._id, { ativo: false });
|
|
}
|
|
|
|
// Criar nova configuração
|
|
await ctx.db.insert('configuracaoLGPD', {
|
|
encarregadoNome: args.encarregadoNome,
|
|
encarregadoEmail: args.encarregadoEmail,
|
|
encarregadoTelefone: args.encarregadoTelefone,
|
|
prazoRespostaPadrao: args.prazoRespostaPadrao ?? 15,
|
|
diasAlertaVencimento: args.diasAlertaVencimento ?? 3,
|
|
ativo: true,
|
|
atualizadoPor: usuario._id,
|
|
atualizadoEm: Date.now()
|
|
});
|
|
|
|
// Log de atividade
|
|
await registrarAtividade(
|
|
ctx,
|
|
usuario._id,
|
|
'atualizar_config_lgpd',
|
|
'configuracaoLGPD',
|
|
JSON.stringify(args),
|
|
''
|
|
);
|
|
|
|
return { sucesso: true };
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Obter estatísticas LGPD (apenas TI)
|
|
*/
|
|
export const obterEstatisticasLGPD = query({
|
|
args: {},
|
|
returns: v.object({
|
|
totalSolicitacoes: v.number(),
|
|
solicitacoesPendentes: v.number(),
|
|
solicitacoesVencendo: v.number(),
|
|
solicitacoesPorTipo: v.record(v.string(), v.number()),
|
|
totalConsentimentos: v.number(),
|
|
consentimentosAtivos: v.number(),
|
|
totalROTs: v.number(),
|
|
rotsAtivos: v.number()
|
|
}),
|
|
handler: async (ctx) => {
|
|
const solicitacoes = await ctx.db.query('solicitacoesLGPD').collect();
|
|
const consentimentos = await ctx.db.query('consentimentos').collect();
|
|
const rots = await ctx.db.query('registrosTratamento').collect();
|
|
|
|
const agora = Date.now();
|
|
const tresDias = 3 * 24 * 60 * 60 * 1000;
|
|
|
|
const solicitacoesVencendo = solicitacoes.filter(
|
|
(s) =>
|
|
s.status === 'pendente' || s.status === 'em_analise'
|
|
? s.prazoResposta - agora <= tresDias && s.prazoResposta > agora
|
|
: false
|
|
).length;
|
|
|
|
const solicitacoesPorTipo: Record<string, number> = {};
|
|
solicitacoes.forEach((s) => {
|
|
solicitacoesPorTipo[s.tipo] = (solicitacoesPorTipo[s.tipo] || 0) + 1;
|
|
});
|
|
|
|
const consentimentosAtivos = consentimentos.filter(
|
|
(c) => c.aceito && !c.revogadoEm
|
|
).length;
|
|
|
|
return {
|
|
totalSolicitacoes: solicitacoes.length,
|
|
solicitacoesPendentes: solicitacoes.filter((s) => s.status === 'pendente').length,
|
|
solicitacoesVencendo,
|
|
solicitacoesPorTipo,
|
|
totalConsentimentos: consentimentos.length,
|
|
consentimentosAtivos,
|
|
totalROTs: rots.length,
|
|
rotsAtivos: rots.filter((r) => r.ativo).length
|
|
};
|
|
}
|
|
});
|
|
|