Merge remote-tracking branch 'origin' into feat-pedidos
This commit is contained in:
@@ -18,7 +18,16 @@ export const ausenciasTables = {
|
||||
dataReprovacao: v.optional(v.number()),
|
||||
motivoReprovacao: v.optional(v.string()),
|
||||
observacao: v.optional(v.string()),
|
||||
criadoEm: v.number()
|
||||
criadoEm: v.number(),
|
||||
historicoAlteracoes: v.optional(
|
||||
v.array(
|
||||
v.object({
|
||||
data: v.number(),
|
||||
usuarioId: v.id('usuarios'),
|
||||
acao: v.string()
|
||||
})
|
||||
)
|
||||
)
|
||||
})
|
||||
.index('by_funcionario', ['funcionarioId'])
|
||||
.index('by_status', ['status'])
|
||||
|
||||
@@ -23,12 +23,16 @@ export const chatTables = {
|
||||
conversaId: v.id('conversas'),
|
||||
remetenteId: v.id('usuarios'),
|
||||
tipo: v.union(v.literal('texto'), v.literal('arquivo'), v.literal('imagem')),
|
||||
conteudo: v.string(), // texto ou nome do arquivo
|
||||
conteudoBusca: v.optional(v.string()), // versão normalizada para busca
|
||||
conteudo: v.string(), // texto ou nome do arquivo (pode ser criptografado)
|
||||
conteudoBusca: v.optional(v.string()), // versão normalizada para busca (não criptografada se mensagem for E2E)
|
||||
arquivoId: v.optional(v.id('_storage')),
|
||||
arquivoNome: v.optional(v.string()),
|
||||
arquivoTamanho: v.optional(v.number()),
|
||||
arquivoTipo: v.optional(v.string()),
|
||||
// Campos para criptografia E2E
|
||||
criptografado: v.optional(v.boolean()), // Indica se a mensagem está criptografada
|
||||
iv: v.optional(v.string()), // Initialization Vector (base64) para descriptografia
|
||||
keyId: v.optional(v.string()), // Identificador da chave usada para criptografar
|
||||
linkPreview: v.optional(
|
||||
v.object({
|
||||
url: v.string(),
|
||||
@@ -169,5 +173,17 @@ export const chatTables = {
|
||||
atualizadoEm: v.number()
|
||||
})
|
||||
.index('by_usuario_conversa', ['usuarioId', 'conversaId'])
|
||||
.index('by_conversa', ['conversaId'])
|
||||
.index('by_conversa', ['conversaId']),
|
||||
|
||||
// Chaves de Criptografia E2E por Conversa
|
||||
chavesCriptografia: defineTable({
|
||||
conversaId: v.id('conversas'),
|
||||
chaveCompartilhada: v.string(), // Chave criptografada compartilhada (base64)
|
||||
keyId: v.string(), // Identificador único da chave
|
||||
criadoPor: v.id('usuarios'), // Usuário que criou/compartilhou a chave
|
||||
criadoEm: v.number(),
|
||||
ativo: v.boolean() // Se a chave está ativa (permite rotação de chaves)
|
||||
})
|
||||
.index('by_conversa', ['conversaId', 'ativo'])
|
||||
.index('by_key_id', ['keyId'])
|
||||
};
|
||||
|
||||
@@ -45,7 +45,8 @@ export const feriasTables = {
|
||||
v.literal('nova_solicitacao'),
|
||||
v.literal('aprovado'),
|
||||
v.literal('reprovado'),
|
||||
v.literal('data_ajustada')
|
||||
v.literal('data_ajustada'),
|
||||
v.literal('cancelado')
|
||||
),
|
||||
lida: v.boolean(),
|
||||
mensagem: v.string()
|
||||
|
||||
@@ -35,7 +35,9 @@ export const funcionariosTables = {
|
||||
simboloId: v.id('simbolos'),
|
||||
simboloTipo: simboloTipo,
|
||||
gestorId: v.optional(v.id('usuarios')),
|
||||
statusFerias: v.optional(v.union(v.literal('ativo'), v.literal('em_ferias'))),
|
||||
statusFerias: v.optional(
|
||||
v.union(v.literal('ativo'), v.literal('em_ferias'), v.literal('em_licenca'))
|
||||
),
|
||||
|
||||
// Regime de trabalho (para cálculo correto de férias)
|
||||
regimeTrabalho: v.optional(
|
||||
|
||||
98
packages/backend/convex/tables/lgpdTables.ts
Normal file
98
packages/backend/convex/tables/lgpdTables.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { defineTable } from 'convex/server';
|
||||
import { v } from 'convex/values';
|
||||
|
||||
export const lgpdTables = {
|
||||
// ========== LGPD - Lei Geral de Proteção de Dados ==========
|
||||
|
||||
// Solicitações de direitos LGPD
|
||||
solicitacoesLGPD: defineTable({
|
||||
tipo: v.union(
|
||||
v.literal('acesso'),
|
||||
v.literal('correcao'),
|
||||
v.literal('exclusao'),
|
||||
v.literal('portabilidade'),
|
||||
v.literal('revogacao_consentimento'),
|
||||
v.literal('informacao_compartilhamento')
|
||||
),
|
||||
usuarioId: v.id('usuarios'),
|
||||
funcionarioId: v.optional(v.id('funcionarios')),
|
||||
status: v.union(
|
||||
v.literal('pendente'),
|
||||
v.literal('em_analise'),
|
||||
v.literal('concluida'),
|
||||
v.literal('rejeitada'),
|
||||
v.literal('cancelada')
|
||||
),
|
||||
dadosSolicitados: v.optional(v.string()), // JSON com detalhes da solicitação
|
||||
resposta: v.optional(v.string()), // Resposta da solicitação
|
||||
arquivoResposta: v.optional(v.id('_storage')), // Arquivo gerado (ex: exportação de dados)
|
||||
respondidoPor: v.optional(v.id('usuarios')),
|
||||
respondidoEm: v.optional(v.number()),
|
||||
criadoEm: v.number(),
|
||||
prazoResposta: v.number(), // Prazo legal (15 dias) - timestamp
|
||||
observacoes: v.optional(v.string())
|
||||
})
|
||||
.index('by_usuario', ['usuarioId'])
|
||||
.index('by_status', ['status'])
|
||||
.index('by_tipo', ['tipo'])
|
||||
.index('by_prazo', ['prazoResposta'])
|
||||
.index('by_funcionario', ['funcionarioId']),
|
||||
|
||||
// Consentimentos dos usuários
|
||||
consentimentos: defineTable({
|
||||
usuarioId: v.id('usuarios'),
|
||||
tipo: v.union(
|
||||
v.literal('termo_uso'),
|
||||
v.literal('politica_privacidade'),
|
||||
v.literal('comunicacoes'),
|
||||
v.literal('compartilhamento_dados')
|
||||
),
|
||||
aceito: v.boolean(),
|
||||
versao: v.string(), // Versão do documento aceito (ex: "1.0")
|
||||
ipAddress: v.optional(v.string()),
|
||||
userAgent: v.optional(v.string()),
|
||||
aceitoEm: v.number(),
|
||||
revogadoEm: v.optional(v.number()),
|
||||
revogadoPor: v.optional(v.id('usuarios')) // Se revogado pelo próprio usuário ou por TI
|
||||
})
|
||||
.index('by_usuario', ['usuarioId'])
|
||||
.index('by_tipo', ['tipo'])
|
||||
.index('by_usuario_tipo', ['usuarioId', 'tipo'])
|
||||
.index('by_versao', ['versao']),
|
||||
|
||||
// Registro de Operações de Tratamento (ROT)
|
||||
registrosTratamento: defineTable({
|
||||
finalidade: v.string(), // Finalidade do tratamento
|
||||
baseLegal: v.string(), // Base legal (ex: "Art. 7º, II - Execução de políticas públicas")
|
||||
categoriasDados: v.array(v.string()), // ["dados_identificacao", "dados_contato", "dados_profissionais"]
|
||||
categoriasTitulares: v.array(v.string()), // ["funcionarios", "servidores", "colaboradores"]
|
||||
medidasSeguranca: v.array(v.string()), // ["criptografia", "controle_acesso", "logs_auditoria"]
|
||||
prazoRetencao: v.number(), // em dias
|
||||
compartilhamentoTerceiros: v.boolean(),
|
||||
terceiros: v.optional(v.array(v.string())), // Lista de terceiros com quem compartilha
|
||||
responsavel: v.id('usuarios'), // Responsável pelo tratamento
|
||||
criadoEm: v.number(),
|
||||
atualizadoEm: v.number(),
|
||||
ativo: v.boolean(),
|
||||
descricao: v.optional(v.string()) // Descrição detalhada
|
||||
})
|
||||
.index('by_finalidade', ['finalidade'])
|
||||
.index('by_ativo', ['ativo'])
|
||||
.index('by_responsavel', ['responsavel']),
|
||||
|
||||
// Configurações LGPD
|
||||
configuracaoLGPD: defineTable({
|
||||
encarregadoNome: v.optional(v.string()),
|
||||
encarregadoEmail: v.optional(v.string()),
|
||||
encarregadoTelefone: v.optional(v.string()),
|
||||
encarregadoHorarioAtendimento: v.optional(v.string()), // Ex: "Segunda a Sexta, das 8h às 17h"
|
||||
prazoRespostaPadrao: v.number(), // em dias (padrão: 15)
|
||||
diasAlertaVencimento: v.number(), // dias antes do prazo para alertar (padrão: 3)
|
||||
termoObrigatorio: v.boolean(), // Se o termo de consentimento é obrigatório
|
||||
versaoTermoAtual: v.string(), // Versão atual do termo (ex: "1.0")
|
||||
politicaRetencao: v.optional(v.string()), // JSON com política de retenção por tipo de dado
|
||||
ativo: v.boolean(),
|
||||
atualizadoPor: v.id('usuarios'),
|
||||
atualizadoEm: v.number()
|
||||
}).index('by_ativo', ['ativo'])
|
||||
};
|
||||
@@ -204,11 +204,48 @@ export const pontoTables = {
|
||||
horasTrabalhadas: v.number(), // Horas realmente trabalhadas (em minutos)
|
||||
saldoMinutos: v.number(), // Saldo do dia (positivo = horas extras, negativo = déficit)
|
||||
registrosPontoIds: v.array(v.id('registrosPonto')), // IDs dos registros do dia
|
||||
// Novos campos para sistema avançado
|
||||
ajustesIds: v.optional(v.array(v.id('ajustesBancoHoras'))), // IDs dos ajustes aplicados no dia
|
||||
motivoAbono: v.optional(v.string()), // Motivo do abono (atestado, licença, ausência, etc.)
|
||||
tipoDia: v.optional(
|
||||
v.union(
|
||||
v.literal('normal'),
|
||||
v.literal('atestado'),
|
||||
v.literal('licenca'),
|
||||
v.literal('ausencia'),
|
||||
v.literal('abonado'),
|
||||
v.literal('descontado')
|
||||
)
|
||||
), // Tipo do dia
|
||||
inconsistenciasIds: v.optional(v.array(v.id('inconsistenciasBancoHoras'))), // IDs de inconsistências detectadas
|
||||
calculadoEm: v.number()
|
||||
})
|
||||
.index('by_funcionario_data', ['funcionarioId', 'data'])
|
||||
.index('by_funcionario', ['funcionarioId'])
|
||||
.index('by_data', ['data']),
|
||||
.index('by_data', ['data'])
|
||||
.index('by_tipo_dia', ['tipoDia']),
|
||||
|
||||
// Banco de Horas Mensal - Agregação mensal do banco de horas
|
||||
bancoHorasMensal: defineTable({
|
||||
funcionarioId: v.id('funcionarios'),
|
||||
mes: v.string(), // YYYY-MM (ex: "2024-01")
|
||||
saldoInicialMinutos: v.number(), // Saldo acumulado do mês anterior (pode ser negativo)
|
||||
saldoFinalMinutos: v.number(), // Saldo acumulado ao final do mês
|
||||
saldoMesMinutos: v.number(), // Saldo apenas do mês atual (sem acumular)
|
||||
diasTrabalhados: v.number(), // Quantidade de dias com registros no mês
|
||||
horasExtras: v.number(), // Total de minutos positivos do mês
|
||||
horasDeficit: v.number(), // Total de minutos negativos do mês (valor absoluto)
|
||||
// Novos campos para sistema avançado
|
||||
totalAjustes: v.optional(v.number()), // Total de ajustes aplicados no mês (em minutos)
|
||||
totalAbonos: v.optional(v.number()), // Total de abonos no mês (em minutos)
|
||||
totalDescontos: v.optional(v.number()), // Total de descontos no mês (em minutos)
|
||||
inconsistenciasResolvidas: v.optional(v.number()), // Quantidade de inconsistências resolvidas
|
||||
calculadoEm: v.number(),
|
||||
atualizadoEm: v.number()
|
||||
})
|
||||
.index('by_funcionario_mes', ['funcionarioId', 'mes'])
|
||||
.index('by_funcionario', ['funcionarioId'])
|
||||
.index('by_mes', ['mes']),
|
||||
|
||||
// Homologações de Ponto - Edições e ajustes realizados pelo gestor
|
||||
homologacoesPonto: defineTable({
|
||||
@@ -262,5 +299,119 @@ export const pontoTables = {
|
||||
.index('by_gestor', ['gestorId'])
|
||||
.index('by_ativo', ['ativo'])
|
||||
.index('by_data_inicio', ['dataInicio'])
|
||||
.index('by_data_fim', ['dataFim'])
|
||||
.index('by_data_fim', ['dataFim']),
|
||||
|
||||
// Configuração de Banco de Horas - Configurações gerais do sistema
|
||||
configuracaoBancoHoras: defineTable({
|
||||
// Limites de saldo
|
||||
limiteSaldoPositivoMinutos: v.optional(v.number()), // Limite máximo de saldo positivo (em minutos)
|
||||
limiteSaldoNegativoMinutos: v.optional(v.number()), // Limite máximo de saldo negativo (em minutos)
|
||||
// Regras de cálculo
|
||||
considerarAjustesAutomaticos: v.optional(v.boolean()), // Se deve considerar ajustes automáticos (atestados, licenças, ausências)
|
||||
// Periodicidade de verificação
|
||||
periodicidadeVerificacao: v.optional(
|
||||
v.union(v.literal('diario'), v.literal('semanal'), v.literal('mensal'))
|
||||
),
|
||||
// Metadados
|
||||
atualizadoPor: v.id('usuarios'),
|
||||
atualizadoEm: v.number()
|
||||
}).index('by_ativo', ['atualizadoEm']),
|
||||
|
||||
// Alertas de Banco de Horas - Configuração de alertas por tipo
|
||||
alertasBancoHoras: defineTable({
|
||||
tipoAlerta: v.union(
|
||||
v.literal('saldo_negativo'),
|
||||
v.literal('saldo_negativo_critico'),
|
||||
v.literal('inconsistencia_detectada'),
|
||||
v.literal('dias_sem_registro'),
|
||||
v.literal('limite_saldo_excedido')
|
||||
),
|
||||
// Periodicidade
|
||||
periodicidade: v.union(v.literal('diario'), v.literal('semanal'), v.literal('mensal')),
|
||||
// Canais de envio
|
||||
enviarEmail: v.boolean(),
|
||||
enviarChat: v.boolean(),
|
||||
// Destinatários específicos (opcional - se vazio, envia para gestor padrão)
|
||||
destinatariosEmail: v.optional(v.array(v.id('usuarios'))), // IDs de usuários que receberão email
|
||||
destinatariosChat: v.optional(v.array(v.id('usuarios'))), // IDs de usuários que receberão chat
|
||||
// Thresholds e limites
|
||||
threshold: v.optional(v.number()), // Valor limite para disparar alerta
|
||||
limiteMinutos: v.optional(v.number()), // Limite em minutos (para saldo negativo)
|
||||
// Status
|
||||
ativo: v.boolean(),
|
||||
// Metadados
|
||||
criadoPor: v.id('usuarios'),
|
||||
criadoEm: v.number(),
|
||||
atualizadoPor: v.optional(v.id('usuarios')),
|
||||
atualizadoEm: v.optional(v.number())
|
||||
})
|
||||
.index('by_tipo', ['tipoAlerta'])
|
||||
.index('by_ativo', ['ativo'])
|
||||
.index('by_tipo_ativo', ['tipoAlerta', 'ativo']),
|
||||
|
||||
// Ajustes de Banco de Horas - Registro de ajustes manuais e automáticos
|
||||
ajustesBancoHoras: defineTable({
|
||||
funcionarioId: v.id('funcionarios'),
|
||||
tipo: v.union(v.literal('abonar'), v.literal('descontar'), v.literal('compensar')),
|
||||
// Motivo vinculado
|
||||
motivoTipo: v.optional(
|
||||
v.union(
|
||||
v.literal('atestado'),
|
||||
v.literal('licenca'),
|
||||
v.literal('ausencia'),
|
||||
v.literal('manual')
|
||||
)
|
||||
),
|
||||
motivoId: v.optional(v.string()), // ID do atestado, licença, ausência ou null para manual
|
||||
motivoDescricao: v.optional(v.string()), // Descrição do motivo
|
||||
// Valor do ajuste
|
||||
valorMinutos: v.number(), // Valor em minutos (positivo para abonar, negativo para descontar)
|
||||
// Data de aplicação
|
||||
dataAplicacao: v.string(), // YYYY-MM-DD
|
||||
// Gestor responsável (null se automático)
|
||||
gestorId: v.optional(v.id('usuarios')),
|
||||
// Observações
|
||||
observacoes: v.optional(v.string()),
|
||||
// Status
|
||||
aplicado: v.boolean(), // Se já foi aplicado ao banco de horas
|
||||
// Metadados
|
||||
criadoEm: v.number(),
|
||||
aplicadoEm: v.optional(v.number())
|
||||
})
|
||||
.index('by_funcionario', ['funcionarioId'])
|
||||
.index('by_data_aplicacao', ['dataAplicacao'])
|
||||
.index('by_funcionario_data', ['funcionarioId', 'dataAplicacao'])
|
||||
.index('by_tipo', ['tipo'])
|
||||
.index('by_aplicado', ['aplicado'])
|
||||
.index('by_gestor', ['gestorId']),
|
||||
|
||||
// Inconsistências de Banco de Horas - Registro de inconsistências detectadas
|
||||
inconsistenciasBancoHoras: defineTable({
|
||||
funcionarioId: v.id('funcionarios'),
|
||||
tipo: v.union(
|
||||
v.literal('ponto_com_atestado'),
|
||||
v.literal('ponto_com_licenca'),
|
||||
v.literal('ponto_com_ausencia'),
|
||||
v.literal('registro_duplicado'),
|
||||
v.literal('sequencia_invalida'),
|
||||
v.literal('saldo_inconsistente')
|
||||
),
|
||||
descricao: v.string(), // Descrição detalhada da inconsistência
|
||||
dataDetectada: v.string(), // YYYY-MM-DD
|
||||
dataInconsistencia: v.string(), // YYYY-MM-DD (data do dia com inconsistência)
|
||||
// Status
|
||||
status: v.union(v.literal('pendente'), v.literal('resolvida'), v.literal('ignorada')),
|
||||
// Resolução
|
||||
resolucao: v.optional(v.string()), // Descrição da resolução
|
||||
resolvidoPor: v.optional(v.id('usuarios')), // Usuário que resolveu
|
||||
resolvidoEm: v.optional(v.number()), // Timestamp da resolução
|
||||
// Metadados
|
||||
criadoEm: v.number()
|
||||
})
|
||||
.index('by_funcionario', ['funcionarioId'])
|
||||
.index('by_status', ['status'])
|
||||
.index('by_funcionario_status', ['funcionarioId', 'status'])
|
||||
.index('by_data_detectada', ['dataDetectada'])
|
||||
.index('by_tipo', ['tipo'])
|
||||
.index('by_data_inconsistencia', ['dataInconsistencia'])
|
||||
};
|
||||
|
||||
@@ -164,6 +164,7 @@ export const systemTables = {
|
||||
severidadeMin: severidadeSeguranca,
|
||||
tiposAtaque: v.optional(v.array(ataqueCiberneticoTipo)),
|
||||
reenvioMin: v.number(),
|
||||
templateCodigo: v.optional(v.string()), // Template a ser usado para email/chat
|
||||
criadoPor: v.id('usuarios'),
|
||||
criadoEm: v.number(),
|
||||
atualizadoEm: v.number()
|
||||
@@ -222,5 +223,24 @@ export const systemTables = {
|
||||
sshUsername: v.optional(v.string()), // Usuário SSH para acesso ao servidor
|
||||
sshPasswordHash: v.optional(v.string()), // Hash da senha SSH (criptografada)
|
||||
sshPort: v.optional(v.number()) // Porta SSH (padrão: 22)
|
||||
}).index('by_ativo', ['ativo'])
|
||||
}).index('by_ativo', ['ativo']),
|
||||
|
||||
// Logs de Erros do Servidor (500, etc)
|
||||
errosServidor: defineTable({
|
||||
statusCode: v.number(), // Código HTTP do erro (500, 502, etc)
|
||||
mensagem: v.string(), // Mensagem do erro
|
||||
stack: v.optional(v.string()), // Stack trace do erro
|
||||
url: v.optional(v.string()), // URL onde ocorreu o erro
|
||||
method: v.optional(v.string()), // Método HTTP (GET, POST, etc)
|
||||
ipAddress: v.optional(v.string()), // IP do cliente
|
||||
userAgent: v.optional(v.string()), // User agent do navegador
|
||||
usuarioId: v.optional(v.id('usuarios')), // Usuário autenticado (se houver)
|
||||
notificado: v.boolean(), // Se a equipe técnica já foi notificada
|
||||
notificadoEm: v.optional(v.number()), // Timestamp da notificação
|
||||
criadoEm: v.number() // Timestamp do erro
|
||||
})
|
||||
.index('by_status_code', ['statusCode'])
|
||||
.index('by_notificado', ['notificado'])
|
||||
.index('by_criado_em', ['criadoEm'])
|
||||
.index('by_usuario', ['usuarioId'])
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user