1514 lines
48 KiB
TypeScript
1514 lines
48 KiB
TypeScript
import { defineSchema, defineTable } from "convex/server";
|
|
import { Infer, v } from "convex/values";
|
|
|
|
export const simboloTipo = v.union(
|
|
v.literal("cargo_comissionado"),
|
|
v.literal("funcao_gratificada")
|
|
);
|
|
export type SimboloTipo = Infer<typeof simboloTipo>;
|
|
|
|
export const ataqueCiberneticoTipo = v.union(
|
|
v.literal("phishing"),
|
|
v.literal("malware"),
|
|
v.literal("ransomware"),
|
|
v.literal("brute_force"),
|
|
v.literal("credential_stuffing"),
|
|
v.literal("sql_injection"),
|
|
v.literal("xss"),
|
|
v.literal("path_traversal"),
|
|
v.literal("command_injection"),
|
|
v.literal("nosql_injection"),
|
|
v.literal("xxe"),
|
|
v.literal("man_in_the_middle"),
|
|
v.literal("ddos"),
|
|
v.literal("engenharia_social"),
|
|
v.literal("cve_exploit"),
|
|
v.literal("apt"),
|
|
v.literal("zero_day"),
|
|
v.literal("supply_chain"),
|
|
v.literal("fileless_malware"),
|
|
v.literal("polymorphic_malware"),
|
|
v.literal("ransomware_lateral"),
|
|
v.literal("deepfake_phishing"),
|
|
v.literal("adversarial_ai"),
|
|
v.literal("side_channel"),
|
|
v.literal("firmware_bootloader"),
|
|
v.literal("bec"),
|
|
v.literal("botnet"),
|
|
v.literal("ot_ics"),
|
|
v.literal("quantum_attack")
|
|
);
|
|
export type AtaqueCiberneticoTipo = Infer<typeof ataqueCiberneticoTipo>;
|
|
|
|
export const severidadeSeguranca = v.union(
|
|
v.literal("informativo"),
|
|
v.literal("baixo"),
|
|
v.literal("moderado"),
|
|
v.literal("alto"),
|
|
v.literal("critico")
|
|
);
|
|
export type SeveridadeSeguranca = Infer<typeof severidadeSeguranca>;
|
|
|
|
export const statusEventoSeguranca = v.union(
|
|
v.literal("detectado"),
|
|
v.literal("investigando"),
|
|
v.literal("contido"),
|
|
v.literal("falso_positivo"),
|
|
v.literal("escalado"),
|
|
v.literal("resolvido")
|
|
);
|
|
export type StatusEventoSeguranca = Infer<typeof statusEventoSeguranca>;
|
|
|
|
export const sensorSegurancaTipo = v.union(
|
|
v.literal("network"),
|
|
v.literal("endpoint"),
|
|
v.literal("application"),
|
|
v.literal("gateway"),
|
|
v.literal("ot"),
|
|
v.literal("honeypot")
|
|
);
|
|
export type SensorSegurancaTipo = Infer<typeof sensorSegurancaTipo>;
|
|
|
|
export const sensorSegurancaStatus = v.union(
|
|
v.literal("ativo"),
|
|
v.literal("inativo"),
|
|
v.literal("degradado"),
|
|
v.literal("manutencao")
|
|
);
|
|
export type SensorSegurancaStatus = Infer<typeof sensorSegurancaStatus>;
|
|
|
|
export const threatIntelTipo = v.union(
|
|
v.literal("open_source"),
|
|
v.literal("commercial"),
|
|
v.literal("internal"),
|
|
v.literal("gov"),
|
|
v.literal("research")
|
|
);
|
|
|
|
export const threatIntelFormato = v.union(
|
|
v.literal("json"),
|
|
v.literal("stix"),
|
|
v.literal("csv"),
|
|
v.literal("text"),
|
|
v.literal("custom")
|
|
);
|
|
|
|
export const acaoIncidenteTipo = v.union(
|
|
v.literal("block_ip"),
|
|
v.literal("unblock_ip"),
|
|
v.literal("block_port"),
|
|
v.literal("liberar_porta"),
|
|
v.literal("notificar"),
|
|
v.literal("isolar_host"),
|
|
v.literal("gerar_relatorio"),
|
|
v.literal("criar_ticket"),
|
|
v.literal("ajuste_regra"),
|
|
v.literal("custom")
|
|
);
|
|
|
|
export const acaoIncidenteStatus = v.union(
|
|
v.literal("pendente"),
|
|
v.literal("executando"),
|
|
v.literal("concluido"),
|
|
v.literal("falhou")
|
|
);
|
|
|
|
export const reportStatus = v.union(
|
|
v.literal("pendente"),
|
|
v.literal("processando"),
|
|
v.literal("concluido"),
|
|
v.literal("falhou")
|
|
);
|
|
|
|
export const situacaoContrato = v.union(
|
|
v.literal("em_execucao"),
|
|
v.literal("rescendido"),
|
|
v.literal("aguardando_assinatura"),
|
|
v.literal("finalizado")
|
|
);
|
|
|
|
export default defineSchema({
|
|
contratos: defineTable({
|
|
contratadaId: v.id("empresas"),
|
|
objeto: v.string(),
|
|
numeroNotaEmpenho: v.string(),
|
|
responsavelId: v.id("funcionarios"),
|
|
departamento: v.string(),
|
|
situacao: situacaoContrato,
|
|
numeroProcessoLicitatorio: v.string(),
|
|
modalidade: v.string(),
|
|
numeroContrato: v.string(),
|
|
anoContrato: v.number(),
|
|
dataInicioVigencia: v.string(),
|
|
dataFimVigencia: v.string(),
|
|
nomeFiscal: v.string(),
|
|
valorTotal: v.string(),
|
|
dataAditivoPrazo: v.optional(v.string()),
|
|
diasAvisoVencimento: v.number(),
|
|
criadoPor: v.id("usuarios"),
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.optional(v.number()),
|
|
})
|
|
.index("by_responsavel", ["responsavelId"])
|
|
.index("by_situacao", ["situacao"])
|
|
.index("by_vigencia_inicio", ["dataInicioVigencia"])
|
|
.index("by_vigencia_fim", ["dataFimVigencia"]),
|
|
|
|
todos: defineTable({
|
|
text: v.string(),
|
|
completed: v.boolean(),
|
|
}),
|
|
enderecos: defineTable({
|
|
cep: v.string(),
|
|
logradouro: v.string(),
|
|
numero: v.string(),
|
|
complemento: v.optional(v.string()),
|
|
bairro: v.string(),
|
|
cidade: v.string(),
|
|
uf: v.string(),
|
|
criadoPor: v.optional(v.id("usuarios")),
|
|
atualizadoPor: v.optional(v.id("usuarios")),
|
|
}).index("by_cep", ["cep"]),
|
|
empresas: defineTable({
|
|
razao_social: v.string(),
|
|
nome_fantasia: v.optional(v.string()),
|
|
cnpj: v.string(),
|
|
telefone: v.string(),
|
|
email: v.string(),
|
|
descricao: v.optional(v.string()),
|
|
enderecoId: v.optional(v.id("enderecos")),
|
|
criadoPor: v.optional(v.id("usuarios")),
|
|
})
|
|
.index("by_razao_social", ["razao_social"])
|
|
.index("by_cnpj", ["cnpj"]),
|
|
contatosEmpresa: defineTable({
|
|
empresaId: v.id("empresas"),
|
|
nome: v.string(),
|
|
funcao: v.string(),
|
|
email: v.string(),
|
|
telefone: v.string(),
|
|
adicionadoPor: v.optional(v.id("usuarios")),
|
|
descricao: v.optional(v.string()),
|
|
})
|
|
.index("by_empresa", ["empresaId"])
|
|
.index("by_email", ["email"]),
|
|
funcionarios: defineTable({
|
|
// Campos obrigatórios existentes
|
|
nome: v.string(),
|
|
nascimento: v.string(),
|
|
rg: v.string(),
|
|
cpf: v.string(),
|
|
endereco: v.string(),
|
|
cep: v.string(),
|
|
cidade: v.string(),
|
|
uf: v.string(),
|
|
telefone: v.string(),
|
|
email: v.string(),
|
|
matricula: v.optional(v.string()),
|
|
admissaoData: v.optional(v.string()),
|
|
desligamentoData: v.optional(v.string()),
|
|
simboloId: v.id("simbolos"),
|
|
simboloTipo: simboloTipo,
|
|
gestorId: v.optional(v.id("usuarios")),
|
|
statusFerias: v.optional(
|
|
v.union(v.literal("ativo"), v.literal("em_ferias"))
|
|
),
|
|
|
|
// Regime de trabalho (para cálculo correto de férias)
|
|
regimeTrabalho: v.optional(
|
|
v.union(
|
|
v.literal("clt"), // CLT - Consolidação das Leis do Trabalho
|
|
v.literal("estatutario_pe"), // Servidor Público Estadual de Pernambuco
|
|
v.literal("estatutario_federal"), // Servidor Público Federal
|
|
v.literal("estatutario_municipal") // Servidor Público Municipal
|
|
)
|
|
),
|
|
|
|
// Dados Pessoais Adicionais (opcionais)
|
|
nomePai: v.optional(v.string()),
|
|
nomeMae: v.optional(v.string()),
|
|
naturalidade: v.optional(v.string()),
|
|
naturalidadeUF: v.optional(v.string()),
|
|
sexo: v.optional(
|
|
v.union(v.literal("masculino"), v.literal("feminino"), v.literal("outro"))
|
|
),
|
|
estadoCivil: v.optional(
|
|
v.union(
|
|
v.literal("solteiro"),
|
|
v.literal("casado"),
|
|
v.literal("divorciado"),
|
|
v.literal("viuvo"),
|
|
v.literal("uniao_estavel")
|
|
)
|
|
),
|
|
nacionalidade: v.optional(v.string()),
|
|
|
|
// Documentos Pessoais
|
|
rgOrgaoExpedidor: v.optional(v.string()),
|
|
rgDataEmissao: v.optional(v.string()),
|
|
carteiraProfissionalNumero: v.optional(v.string()),
|
|
carteiraProfissionalSerie: v.optional(v.string()),
|
|
carteiraProfissionalDataEmissao: v.optional(v.string()),
|
|
reservistaNumero: v.optional(v.string()),
|
|
reservistaSerie: v.optional(v.string()),
|
|
tituloEleitorNumero: v.optional(v.string()),
|
|
tituloEleitorZona: v.optional(v.string()),
|
|
tituloEleitorSecao: v.optional(v.string()),
|
|
pisNumero: v.optional(v.string()),
|
|
|
|
// Formação e Saúde
|
|
grauInstrucao: v.optional(
|
|
v.union(
|
|
v.literal("fundamental"),
|
|
v.literal("medio"),
|
|
v.literal("superior"),
|
|
v.literal("pos_graduacao"),
|
|
v.literal("mestrado"),
|
|
v.literal("doutorado")
|
|
)
|
|
),
|
|
formacao: v.optional(v.string()),
|
|
formacaoRegistro: v.optional(v.string()),
|
|
grupoSanguineo: v.optional(
|
|
v.union(v.literal("A"), v.literal("B"), v.literal("AB"), v.literal("O"))
|
|
),
|
|
fatorRH: v.optional(v.union(v.literal("positivo"), v.literal("negativo"))),
|
|
|
|
// Cargo e Vínculo
|
|
descricaoCargo: v.optional(v.string()),
|
|
nomeacaoPortaria: v.optional(v.string()),
|
|
nomeacaoData: v.optional(v.string()),
|
|
nomeacaoDOE: v.optional(v.string()),
|
|
pertenceOrgaoPublico: v.optional(v.boolean()),
|
|
orgaoOrigem: v.optional(v.string()),
|
|
aposentado: v.optional(
|
|
v.union(v.literal("nao"), v.literal("funape_ipsep"), v.literal("inss"))
|
|
),
|
|
|
|
// Dados Bancários
|
|
contaBradescoNumero: v.optional(v.string()),
|
|
contaBradescoDV: v.optional(v.string()),
|
|
contaBradescoAgencia: v.optional(v.string()),
|
|
|
|
// Documentos Anexos (Storage IDs)
|
|
certidaoAntecedentesPF: v.optional(v.id("_storage")),
|
|
certidaoAntecedentesJFPE: v.optional(v.id("_storage")),
|
|
certidaoAntecedentesSDS: v.optional(v.id("_storage")),
|
|
certidaoAntecedentesTJPE: v.optional(v.id("_storage")),
|
|
certidaoImprobidade: v.optional(v.id("_storage")),
|
|
rgFrente: v.optional(v.id("_storage")),
|
|
rgVerso: v.optional(v.id("_storage")),
|
|
cpfFrente: v.optional(v.id("_storage")),
|
|
cpfVerso: v.optional(v.id("_storage")),
|
|
situacaoCadastralCPF: v.optional(v.id("_storage")),
|
|
tituloEleitorFrente: v.optional(v.id("_storage")),
|
|
tituloEleitorVerso: v.optional(v.id("_storage")),
|
|
comprovanteVotacao: v.optional(v.id("_storage")),
|
|
carteiraProfissionalFrente: v.optional(v.id("_storage")),
|
|
carteiraProfissionalVerso: v.optional(v.id("_storage")),
|
|
comprovantePIS: v.optional(v.id("_storage")),
|
|
certidaoRegistroCivil: v.optional(v.id("_storage")),
|
|
certidaoNascimentoDependentes: v.optional(v.id("_storage")),
|
|
cpfDependentes: v.optional(v.id("_storage")),
|
|
reservistaDoc: v.optional(v.id("_storage")),
|
|
comprovanteEscolaridade: v.optional(v.id("_storage")),
|
|
comprovanteResidencia: v.optional(v.id("_storage")),
|
|
comprovanteContaBradesco: v.optional(v.id("_storage")),
|
|
|
|
// Dependentes do funcionário (uploads opcionais)
|
|
dependentes: v.optional(
|
|
v.array(
|
|
v.object({
|
|
parentesco: v.union(
|
|
v.literal("filho"),
|
|
v.literal("filha"),
|
|
v.literal("conjuge"),
|
|
v.literal("outro")
|
|
),
|
|
nome: v.string(),
|
|
cpf: v.string(),
|
|
nascimento: v.string(),
|
|
documentoId: v.optional(v.id("_storage")),
|
|
// Benefícios/declarações por dependente
|
|
salarioFamilia: v.optional(v.boolean()),
|
|
impostoRenda: v.optional(v.boolean()),
|
|
})
|
|
)
|
|
),
|
|
|
|
// Declarações (Storage IDs)
|
|
declaracaoAcumulacaoCargo: v.optional(v.id("_storage")),
|
|
declaracaoDependentesIR: v.optional(v.id("_storage")),
|
|
declaracaoIdoneidade: v.optional(v.id("_storage")),
|
|
termoNepotismo: v.optional(v.id("_storage")),
|
|
termoOpcaoRemuneracao: v.optional(v.id("_storage")),
|
|
})
|
|
.index("by_matricula", ["matricula"])
|
|
.index("by_nome", ["nome"])
|
|
.index("by_simboloId", ["simboloId"])
|
|
.index("by_simboloTipo", ["simboloTipo"])
|
|
.index("by_cpf", ["cpf"])
|
|
.index("by_rg", ["rg"])
|
|
.index("by_gestor", ["gestorId"]),
|
|
|
|
atestados: defineTable({
|
|
funcionarioId: v.id("funcionarios"),
|
|
tipo: v.union(
|
|
v.literal("atestado_medico"),
|
|
v.literal("declaracao_comparecimento")
|
|
),
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
cid: v.optional(v.string()), // Apenas para atestado médico
|
|
observacoes: v.optional(v.string()),
|
|
documentoId: v.optional(v.id("_storage")),
|
|
criadoPor: v.id("usuarios"),
|
|
criadoEm: v.number(),
|
|
})
|
|
.index("by_funcionario", ["funcionarioId"])
|
|
.index("by_tipo", ["tipo"])
|
|
.index("by_data_inicio", ["dataInicio"])
|
|
.index("by_funcionario_and_tipo", ["funcionarioId", "tipo"]),
|
|
|
|
licencas: defineTable({
|
|
funcionarioId: v.id("funcionarios"),
|
|
tipo: v.union(v.literal("maternidade"), v.literal("paternidade")),
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
documentoId: v.optional(v.id("_storage")),
|
|
observacoes: v.optional(v.string()),
|
|
licencaOriginalId: v.optional(v.id("licencas")), // Para prorrogações
|
|
ehProrrogacao: v.boolean(),
|
|
criadoPor: v.id("usuarios"),
|
|
criadoEm: v.number(),
|
|
})
|
|
.index("by_funcionario", ["funcionarioId"])
|
|
.index("by_tipo", ["tipo"])
|
|
.index("by_data_inicio", ["dataInicio"])
|
|
.index("by_licenca_original", ["licencaOriginalId"])
|
|
.index("by_funcionario_and_tipo", ["funcionarioId", "tipo"]),
|
|
|
|
ferias: defineTable({
|
|
funcionarioId: v.id("funcionarios"),
|
|
anoReferencia: v.number(),
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
diasFerias: v.number(),
|
|
status: v.union(
|
|
v.literal("aguardando_aprovacao"),
|
|
v.literal("aprovado"),
|
|
v.literal("reprovado"),
|
|
v.literal("data_ajustada_aprovada"),
|
|
v.literal("EmFérias"),
|
|
v.literal("Cancelado_RH")
|
|
),
|
|
gestorId: v.optional(v.id("usuarios")),
|
|
observacao: v.optional(v.string()),
|
|
motivoReprovacao: v.optional(v.string()),
|
|
dataAprovacao: v.optional(v.number()),
|
|
dataReprovacao: v.optional(v.number()),
|
|
diasAbono: 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_funcionario_and_ano", ["funcionarioId", "anoReferencia"])
|
|
.index("by_funcionario_and_status", ["funcionarioId", "status"])
|
|
.index("by_status", ["status"])
|
|
.index("by_ano", ["anoReferencia"]),
|
|
|
|
notificacoesFerias: defineTable({
|
|
destinatarioId: v.id("usuarios"),
|
|
feriasId: v.id("ferias"),
|
|
tipo: v.union(
|
|
v.literal("nova_solicitacao"),
|
|
v.literal("aprovado"),
|
|
v.literal("reprovado"),
|
|
v.literal("data_ajustada")
|
|
),
|
|
lida: v.boolean(),
|
|
mensagem: v.string(),
|
|
})
|
|
.index("by_destinatario", ["destinatarioId"])
|
|
.index("by_destinatario_and_lida", ["destinatarioId", "lida"]),
|
|
|
|
// Solicitações de Ausências
|
|
solicitacoesAusencias: defineTable({
|
|
funcionarioId: v.id("funcionarios"),
|
|
dataInicio: v.string(),
|
|
dataFim: v.string(),
|
|
motivo: v.string(),
|
|
status: v.union(
|
|
v.literal("aguardando_aprovacao"),
|
|
v.literal("aprovado"),
|
|
v.literal("reprovado")
|
|
),
|
|
gestorId: v.optional(v.id("usuarios")),
|
|
dataAprovacao: v.optional(v.number()),
|
|
dataReprovacao: v.optional(v.number()),
|
|
motivoReprovacao: v.optional(v.string()),
|
|
observacao: v.optional(v.string()),
|
|
criadoEm: v.number(),
|
|
})
|
|
.index("by_funcionario", ["funcionarioId"])
|
|
.index("by_status", ["status"])
|
|
.index("by_funcionario_and_status", ["funcionarioId", "status"]),
|
|
|
|
notificacoesAusencias: defineTable({
|
|
destinatarioId: v.id("usuarios"),
|
|
solicitacaoAusenciaId: v.id("solicitacoesAusencias"),
|
|
tipo: v.union(
|
|
v.literal("nova_solicitacao"),
|
|
v.literal("aprovado"),
|
|
v.literal("reprovado")
|
|
),
|
|
lida: v.boolean(),
|
|
mensagem: v.string(),
|
|
})
|
|
.index("by_destinatario", ["destinatarioId"])
|
|
.index("by_destinatario_and_lida", ["destinatarioId", "lida"]),
|
|
|
|
|
|
times: defineTable({
|
|
nome: v.string(),
|
|
descricao: v.optional(v.string()),
|
|
gestorId: v.id("usuarios"),
|
|
gestorSuperiorId: v.optional(v.id("usuarios")),
|
|
ativo: v.boolean(),
|
|
cor: v.optional(v.string()), // Cor para identificação visual
|
|
})
|
|
.index("by_gestor", ["gestorId"])
|
|
.index("by_gestor_superior", ["gestorSuperiorId"]),
|
|
|
|
timesMembros: defineTable({
|
|
timeId: v.id("times"),
|
|
funcionarioId: v.id("funcionarios"),
|
|
dataEntrada: v.number(),
|
|
dataSaida: v.optional(v.number()),
|
|
ativo: v.boolean(),
|
|
})
|
|
.index("by_time", ["timeId"])
|
|
.index("by_funcionario", ["funcionarioId"])
|
|
.index("by_time_and_ativo", ["timeId", "ativo"]),
|
|
|
|
cursos: defineTable({
|
|
funcionarioId: v.id("funcionarios"),
|
|
descricao: v.string(),
|
|
data: v.string(),
|
|
certificadoId: v.optional(v.id("_storage")),
|
|
}).index("by_funcionario", ["funcionarioId"]),
|
|
|
|
simbolos: defineTable({
|
|
nome: v.string(),
|
|
tipo: simboloTipo,
|
|
descricao: v.string(),
|
|
vencValor: v.string(),
|
|
repValor: v.string(),
|
|
valor: v.string(),
|
|
}),
|
|
|
|
solicitacoesAcesso: defineTable({
|
|
nome: v.string(),
|
|
matricula: v.string(),
|
|
email: v.string(),
|
|
telefone: v.string(),
|
|
status: v.union(
|
|
v.literal("pendente"),
|
|
v.literal("aprovado"),
|
|
v.literal("rejeitado")
|
|
),
|
|
dataSolicitacao: v.number(),
|
|
dataResposta: v.optional(v.number()),
|
|
observacoes: v.optional(v.string()),
|
|
})
|
|
.index("by_status", ["status"])
|
|
.index("by_matricula", ["matricula"])
|
|
.index("by_email", ["email"]),
|
|
|
|
// Sistema de Autenticação e Controle de Acesso
|
|
usuarios: defineTable({
|
|
authId: v.string(),
|
|
nome: v.string(),
|
|
email: v.string(),
|
|
funcionarioId: v.optional(v.id("funcionarios")),
|
|
roleId: v.id("roles"),
|
|
ativo: v.boolean(),
|
|
primeiroAcesso: v.boolean(),
|
|
ultimoAcesso: v.optional(v.number()),
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.number(),
|
|
|
|
// Controle de Bloqueio e Segurança
|
|
bloqueado: v.optional(v.boolean()),
|
|
motivoBloqueio: v.optional(v.string()),
|
|
dataBloqueio: v.optional(v.number()),
|
|
tentativasLogin: v.optional(v.number()), // contador de tentativas falhas
|
|
ultimaTentativaLogin: v.optional(v.number()), // timestamp da última tentativa
|
|
|
|
// Campos de Chat e Perfil
|
|
avatar: v.optional(v.string()), // "avatar-1" até "avatar-15" ou storageId
|
|
fotoPerfil: v.optional(v.id("_storage")),
|
|
setor: v.optional(v.string()),
|
|
statusMensagem: v.optional(v.string()), // max 100 chars
|
|
statusPresenca: v.optional(
|
|
v.union(
|
|
v.literal("online"),
|
|
v.literal("offline"),
|
|
v.literal("ausente"),
|
|
v.literal("externo"),
|
|
v.literal("em_reuniao")
|
|
)
|
|
),
|
|
ultimaAtividade: v.optional(v.number()), // timestamp
|
|
notificacoesAtivadas: v.optional(v.boolean()),
|
|
somNotificacao: v.optional(v.boolean()),
|
|
})
|
|
.index("by_email", ["email"])
|
|
.index("by_role", ["roleId"])
|
|
.index("by_ativo", ["ativo"])
|
|
.index("by_status_presenca", ["statusPresenca"])
|
|
.index("by_bloqueado", ["bloqueado"])
|
|
.index("by_funcionarioId", ["funcionarioId"])
|
|
.index("authId", ["authId"]),
|
|
|
|
roles: defineTable({
|
|
nome: v.string(), // "admin", "ti_master", "ti_usuario", "usuario_avancado", "usuario"
|
|
descricao: v.string(),
|
|
nivel: v.number(), // 0 = admin, 1 = ti_master, 2 = ti_usuario, 3+ = customizado
|
|
setor: v.optional(v.string()), // "ti", "rh", "financeiro", etc.
|
|
customizado: v.optional(v.boolean()), // se é um perfil customizado criado por TI_MASTER
|
|
criadoPor: v.optional(v.id("usuarios")), // usuário TI_MASTER que criou este perfil
|
|
editavel: v.optional(v.boolean()), // se pode ser editado (false para roles fixas)
|
|
})
|
|
.index("by_nome", ["nome"])
|
|
.index("by_nivel", ["nivel"])
|
|
.index("by_setor", ["setor"])
|
|
.index("by_customizado", ["customizado"]),
|
|
|
|
permissoes: defineTable({
|
|
nome: v.string(), // "funcionarios.criar", "simbolos.editar", etc.
|
|
descricao: v.string(),
|
|
recurso: v.string(), // "funcionarios", "simbolos", "usuarios", etc.
|
|
acao: v.string(), // "criar", "ler", "editar", "excluir"
|
|
})
|
|
.index("by_recurso", ["recurso"])
|
|
.index("by_recurso_e_acao", ["recurso", "acao"])
|
|
.index("by_nome", ["nome"]),
|
|
|
|
rolePermissoes: defineTable({
|
|
roleId: v.id("roles"),
|
|
permissaoId: v.id("permissoes"),
|
|
})
|
|
.index("by_role", ["roleId"])
|
|
.index("by_permissao", ["permissaoId"]),
|
|
|
|
sessoes: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
token: v.string(),
|
|
ipAddress: v.optional(v.string()),
|
|
userAgent: v.optional(v.string()),
|
|
criadoEm: v.number(),
|
|
expiraEm: v.number(),
|
|
ativo: v.boolean(),
|
|
})
|
|
.index("by_usuario", ["usuarioId"])
|
|
.index("by_token", ["token"])
|
|
.index("by_ativo", ["ativo"])
|
|
.index("by_expiracao", ["expiraEm"]),
|
|
|
|
logsAcesso: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
tipo: v.union(
|
|
v.literal("login"),
|
|
v.literal("logout"),
|
|
v.literal("acesso_negado"),
|
|
v.literal("senha_alterada"),
|
|
v.literal("sessao_expirada")
|
|
),
|
|
ipAddress: v.optional(v.string()),
|
|
userAgent: v.optional(v.string()),
|
|
detalhes: v.optional(v.string()),
|
|
timestamp: v.number(),
|
|
})
|
|
.index("by_usuario", ["usuarioId"])
|
|
.index("by_tipo", ["tipo"])
|
|
.index("by_timestamp", ["timestamp"]),
|
|
|
|
// Logs de Login Detalhados
|
|
logsLogin: defineTable({
|
|
usuarioId: v.optional(v.id("usuarios")), // pode ser null se falha antes de identificar usuário
|
|
matriculaOuEmail: v.string(), // tentativa de login
|
|
sucesso: v.boolean(),
|
|
motivoFalha: v.optional(v.string()), // "senha_incorreta", "usuario_bloqueado", "usuario_inexistente"
|
|
ipAddress: v.optional(v.string()),
|
|
userAgent: v.optional(v.string()),
|
|
device: v.optional(v.string()),
|
|
browser: v.optional(v.string()),
|
|
sistema: v.optional(v.string()),
|
|
timestamp: v.number(),
|
|
})
|
|
.index("by_usuario", ["usuarioId"])
|
|
.index("by_sucesso", ["sucesso"])
|
|
.index("by_timestamp", ["timestamp"])
|
|
.index("by_ip", ["ipAddress"]),
|
|
|
|
// Logs de Atividades
|
|
logsAtividades: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
acao: v.string(), // "criar", "editar", "excluir", "bloquear", "desbloquear", etc.
|
|
recurso: v.string(), // "funcionarios", "simbolos", "usuarios", "perfis", etc.
|
|
recursoId: v.optional(v.string()), // ID do recurso afetado
|
|
detalhes: v.optional(v.string()), // JSON com detalhes da ação
|
|
timestamp: v.number(),
|
|
})
|
|
.index("by_usuario", ["usuarioId"])
|
|
.index("by_acao", ["acao"])
|
|
.index("by_recurso", ["recurso"])
|
|
.index("by_timestamp", ["timestamp"])
|
|
.index("by_recurso_id", ["recurso", "recursoId"]),
|
|
|
|
// Histórico de Bloqueios
|
|
bloqueiosUsuarios: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
motivo: v.string(),
|
|
bloqueadoPor: v.id("usuarios"), // ID do TI_MASTER que bloqueou
|
|
dataInicio: v.number(),
|
|
dataFim: v.optional(v.number()), // quando foi desbloqueado
|
|
desbloqueadoPor: v.optional(v.id("usuarios")),
|
|
ativo: v.boolean(), // se é o bloqueio atual ativo
|
|
})
|
|
.index("by_usuario", ["usuarioId"])
|
|
.index("by_bloqueado_por", ["bloqueadoPor"])
|
|
.index("by_ativo", ["ativo"])
|
|
.index("by_data_inicio", ["dataInicio"]),
|
|
|
|
// Perfis Customizados
|
|
|
|
// Templates de Mensagens
|
|
templatesMensagens: defineTable({
|
|
codigo: v.string(), // "USUARIO_BLOQUEADO", "SENHA_RESETADA", etc.
|
|
nome: v.string(),
|
|
tipo: v.union(
|
|
v.literal("sistema"), // predefinido, não editável
|
|
v.literal("customizado") // criado por TI_MASTER
|
|
),
|
|
titulo: v.string(),
|
|
corpo: v.string(), // pode ter variáveis {{variavel}}
|
|
variaveis: v.optional(v.array(v.string())), // ["motivo", "senha", etc.]
|
|
criadoPor: v.optional(v.id("usuarios")),
|
|
criadoEm: v.number(),
|
|
})
|
|
.index("by_codigo", ["codigo"])
|
|
.index("by_tipo", ["tipo"])
|
|
.index("by_criado_por", ["criadoPor"]),
|
|
|
|
// Configuração de Email/SMTP
|
|
configuracaoEmail: defineTable({
|
|
servidor: v.string(), // smtp.gmail.com
|
|
porta: v.number(), // 587, 465, etc.
|
|
usuario: v.string(),
|
|
senhaHash: v.string(), // senha criptografada reversível (AES-GCM) - necessário para descriptografar e usar no SMTP
|
|
emailRemetente: v.string(),
|
|
nomeRemetente: v.string(),
|
|
usarSSL: v.boolean(),
|
|
usarTLS: v.boolean(),
|
|
ativo: v.boolean(),
|
|
testadoEm: v.optional(v.number()),
|
|
configuradoPor: v.id("usuarios"),
|
|
atualizadoEm: v.number(),
|
|
}).index("by_ativo", ["ativo"]),
|
|
|
|
// Fila de Emails
|
|
notificacoesEmail: defineTable({
|
|
destinatario: v.string(), // email
|
|
destinatarioId: v.optional(v.id("usuarios")),
|
|
assunto: v.string(),
|
|
corpo: v.string(), // HTML ou texto
|
|
templateId: v.optional(v.id("templatesMensagens")),
|
|
status: v.union(
|
|
v.literal("pendente"),
|
|
v.literal("enviando"),
|
|
v.literal("enviado"),
|
|
v.literal("falha")
|
|
),
|
|
tentativas: v.number(),
|
|
ultimaTentativa: v.optional(v.number()),
|
|
erroDetalhes: v.optional(v.string()),
|
|
enviadoPor: v.id("usuarios"),
|
|
criadoEm: v.number(),
|
|
enviadoEm: v.optional(v.number()),
|
|
agendadaPara: v.optional(v.number()), // timestamp para agendamento
|
|
})
|
|
.index("by_status", ["status"])
|
|
.index("by_destinatario", ["destinatarioId"])
|
|
.index("by_enviado_por", ["enviadoPor"])
|
|
.index("by_criado_em", ["criadoEm"])
|
|
.index("by_agendamento", ["agendadaPara"]),
|
|
|
|
configuracaoAcesso: defineTable({
|
|
chave: v.string(), // "sessao_duracao", "max_tentativas_login", etc.
|
|
valor: v.string(),
|
|
descricao: v.string(),
|
|
}).index("by_chave", ["chave"]),
|
|
|
|
// Rate Limiting de Emails
|
|
rateLimitEmails: defineTable({
|
|
remetenteId: v.id("usuarios"),
|
|
timestamp: v.number(),
|
|
contador: v.number(), // quantidade de emails enviados neste período
|
|
periodo: v.union(
|
|
v.literal("minuto"), // último minuto
|
|
v.literal("hora") // última hora
|
|
),
|
|
})
|
|
.index("by_remetente_periodo", ["remetenteId", "periodo", "timestamp"])
|
|
.index("by_timestamp", ["timestamp"]),
|
|
|
|
// Sistema de Chat
|
|
conversas: defineTable({
|
|
tipo: v.union(
|
|
v.literal("individual"),
|
|
v.literal("grupo"),
|
|
v.literal("sala_reuniao")
|
|
),
|
|
nome: v.optional(v.string()), // nome do grupo/sala
|
|
avatar: v.optional(v.string()), // avatar do grupo/sala
|
|
participantes: v.array(v.id("usuarios")), // IDs dos participantes
|
|
administradores: v.optional(v.array(v.id("usuarios"))), // IDs dos administradores (apenas para sala_reuniao)
|
|
ultimaMensagem: v.optional(v.string()),
|
|
ultimaMensagemTimestamp: v.optional(v.number()),
|
|
ultimaMensagemRemetenteId: v.optional(v.id("usuarios")), // ID do remetente da última mensagem
|
|
criadoPor: v.id("usuarios"),
|
|
criadoEm: v.number(),
|
|
})
|
|
.index("by_criado_por", ["criadoPor"])
|
|
.index("by_tipo", ["tipo"])
|
|
.index("by_ultima_mensagem", ["ultimaMensagemTimestamp"]),
|
|
|
|
mensagens: defineTable({
|
|
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
|
|
arquivoId: v.optional(v.id("_storage")),
|
|
arquivoNome: v.optional(v.string()),
|
|
arquivoTamanho: v.optional(v.number()),
|
|
arquivoTipo: v.optional(v.string()),
|
|
linkPreview: v.optional(
|
|
v.object({
|
|
url: v.string(),
|
|
titulo: v.optional(v.string()),
|
|
descricao: v.optional(v.string()),
|
|
imagem: v.optional(v.string()),
|
|
site: v.optional(v.string()),
|
|
})
|
|
),
|
|
reagiuPor: v.optional(
|
|
v.array(
|
|
v.object({
|
|
usuarioId: v.id("usuarios"),
|
|
emoji: v.string(),
|
|
})
|
|
)
|
|
),
|
|
mencoes: v.optional(v.array(v.id("usuarios"))),
|
|
respostaPara: v.optional(v.id("mensagens")), // ID da mensagem que está respondendo
|
|
agendadaPara: v.optional(v.number()), // timestamp
|
|
enviadaEm: v.number(),
|
|
editadaEm: v.optional(v.number()),
|
|
deletada: v.optional(v.boolean()),
|
|
lidaPor: v.optional(v.array(v.id("usuarios"))), // IDs dos usuários que leram a mensagem
|
|
})
|
|
.index("by_conversa", ["conversaId", "enviadaEm"])
|
|
.index("by_remetente", ["remetenteId"])
|
|
.index("by_agendamento", ["agendadaPara"])
|
|
.index("by_resposta", ["respostaPara"]),
|
|
|
|
leituras: defineTable({
|
|
conversaId: v.id("conversas"),
|
|
usuarioId: v.id("usuarios"),
|
|
ultimaMensagemLida: v.id("mensagens"),
|
|
lidaEm: v.number(),
|
|
})
|
|
.index("by_conversa_usuario", ["conversaId", "usuarioId"])
|
|
.index("by_usuario", ["usuarioId"]),
|
|
|
|
notificacoes: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
tipo: v.union(
|
|
v.literal("nova_mensagem"),
|
|
v.literal("mencao"),
|
|
v.literal("grupo_criado"),
|
|
v.literal("adicionado_grupo"),
|
|
v.literal("alerta_seguranca")
|
|
),
|
|
conversaId: v.optional(v.id("conversas")),
|
|
mensagemId: v.optional(v.id("mensagens")),
|
|
remetenteId: v.optional(v.id("usuarios")),
|
|
titulo: v.string(),
|
|
descricao: v.string(),
|
|
lida: v.boolean(),
|
|
criadaEm: v.number(),
|
|
})
|
|
.index("by_usuario", ["usuarioId", "lida", "criadaEm"])
|
|
.index("by_usuario_lida", ["usuarioId", "lida"]),
|
|
|
|
digitando: defineTable({
|
|
conversaId: v.id("conversas"),
|
|
usuarioId: v.id("usuarios"),
|
|
iniciouEm: v.number(),
|
|
})
|
|
.index("by_conversa", ["conversaId", "iniciouEm"])
|
|
.index("by_usuario", ["usuarioId"]),
|
|
|
|
// Push Notifications
|
|
pushSubscriptions: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
endpoint: v.string(), // URL do serviço de push
|
|
keys: v.object({
|
|
p256dh: v.string(), // Chave pública
|
|
auth: v.string(), // Chave de autenticação
|
|
}),
|
|
userAgent: v.optional(v.string()),
|
|
criadoEm: v.number(),
|
|
ultimaAtividade: v.number(),
|
|
ativo: v.boolean(),
|
|
})
|
|
.index("by_usuario", ["usuarioId", "ativo"])
|
|
.index("by_endpoint", ["endpoint"]),
|
|
|
|
// Preferências de Notificação por Conversa
|
|
preferenciasNotificacaoConversa: defineTable({
|
|
usuarioId: v.id("usuarios"),
|
|
conversaId: v.id("conversas"),
|
|
pushAtivado: v.boolean(), // Receber push notifications
|
|
emailAtivado: v.boolean(), // Receber emails quando offline
|
|
somAtivado: v.boolean(), // Tocar som
|
|
silenciado: v.boolean(), // Silenciar completamente
|
|
apenasMencoes: v.boolean(), // Notificar apenas quando mencionado
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.number(),
|
|
})
|
|
.index("by_usuario_conversa", ["usuarioId", "conversaId"])
|
|
.index("by_conversa", ["conversaId"]),
|
|
|
|
// Tabelas de Monitoramento do Sistema
|
|
systemMetrics: defineTable({
|
|
timestamp: v.number(),
|
|
// Métricas de Sistema
|
|
cpuUsage: v.optional(v.number()),
|
|
memoryUsage: v.optional(v.number()),
|
|
networkLatency: v.optional(v.number()),
|
|
storageUsed: v.optional(v.number()),
|
|
// Métricas de Aplicação
|
|
usuariosOnline: v.optional(v.number()),
|
|
mensagensPorMinuto: v.optional(v.number()),
|
|
tempoRespostaMedio: v.optional(v.number()),
|
|
errosCount: v.optional(v.number()),
|
|
}).index("by_timestamp", ["timestamp"]),
|
|
|
|
alertConfigurations: defineTable({
|
|
metricName: v.string(),
|
|
threshold: v.number(),
|
|
operator: v.union(
|
|
v.literal(">"),
|
|
v.literal("<"),
|
|
v.literal(">="),
|
|
v.literal("<="),
|
|
v.literal("==")
|
|
),
|
|
enabled: v.boolean(),
|
|
notifyByEmail: v.boolean(),
|
|
notifyByChat: v.boolean(),
|
|
createdBy: v.id("usuarios"),
|
|
lastModified: v.number(),
|
|
}).index("by_enabled", ["enabled"]),
|
|
|
|
alertHistory: defineTable({
|
|
configId: v.id("alertConfigurations"),
|
|
metricName: v.string(),
|
|
metricValue: v.number(),
|
|
threshold: v.number(),
|
|
timestamp: v.number(),
|
|
status: v.union(v.literal("triggered"), v.literal("resolved")),
|
|
notificationsSent: v.object({
|
|
email: v.boolean(),
|
|
chat: v.boolean(),
|
|
}),
|
|
})
|
|
.index("by_timestamp", ["timestamp"])
|
|
.index("by_status", ["status"])
|
|
.index("by_config", ["configId", "timestamp"]),
|
|
|
|
tickets: defineTable({
|
|
numero: v.string(),
|
|
titulo: v.string(),
|
|
descricao: v.string(),
|
|
tipo: v.union(
|
|
v.literal("reclamacao"),
|
|
v.literal("elogio"),
|
|
v.literal("sugestao"),
|
|
v.literal("chamado")
|
|
),
|
|
categoria: v.optional(v.string()),
|
|
status: v.union(
|
|
v.literal("aberto"),
|
|
v.literal("em_andamento"),
|
|
v.literal("aguardando_usuario"),
|
|
v.literal("resolvido"),
|
|
v.literal("encerrado"),
|
|
v.literal("cancelado")
|
|
),
|
|
prioridade: v.union(
|
|
v.literal("baixa"),
|
|
v.literal("media"),
|
|
v.literal("alta"),
|
|
v.literal("critica")
|
|
),
|
|
solicitanteId: v.id("usuarios"),
|
|
solicitanteNome: v.string(),
|
|
solicitanteEmail: v.string(),
|
|
responsavelId: v.optional(v.id("usuarios")),
|
|
setorResponsavel: v.optional(v.string()),
|
|
slaConfigId: v.optional(v.id("slaConfigs")),
|
|
conversaId: v.optional(v.id("conversas")),
|
|
prazoResposta: v.optional(v.number()),
|
|
prazoConclusao: v.optional(v.number()),
|
|
prazoEncerramento: v.optional(v.number()),
|
|
timeline: v.optional(
|
|
v.array(
|
|
v.object({
|
|
etapa: v.string(),
|
|
status: v.union(
|
|
v.literal("pendente"),
|
|
v.literal("em_andamento"),
|
|
v.literal("concluido"),
|
|
v.literal("vencido")
|
|
),
|
|
prazo: v.optional(v.number()),
|
|
concluidoEm: v.optional(v.number()),
|
|
observacao: v.optional(v.string()),
|
|
})
|
|
)
|
|
),
|
|
alertasEmitidos: v.optional(
|
|
v.array(
|
|
v.object({
|
|
tipo: v.union(
|
|
v.literal("resposta"),
|
|
v.literal("conclusao"),
|
|
v.literal("encerramento")
|
|
),
|
|
emitidoEm: v.number(),
|
|
})
|
|
)
|
|
),
|
|
anexos: v.optional(
|
|
v.array(
|
|
v.object({
|
|
arquivoId: v.id("_storage"),
|
|
nome: v.optional(v.string()),
|
|
tipo: v.optional(v.string()),
|
|
tamanho: v.optional(v.number()),
|
|
})
|
|
)
|
|
),
|
|
tags: v.optional(v.array(v.string())),
|
|
canalOrigem: v.optional(v.string()),
|
|
ultimaInteracaoEm: v.number(),
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.number(),
|
|
})
|
|
.index("by_numero", ["numero"])
|
|
.index("by_status", ["status"])
|
|
.index("by_solicitante", ["solicitanteId", "status"])
|
|
.index("by_responsavel", ["responsavelId", "status"])
|
|
.index("by_setor", ["setorResponsavel", "status"]),
|
|
|
|
ticketInteractions: defineTable({
|
|
ticketId: v.id("tickets"),
|
|
autorId: v.optional(v.id("usuarios")),
|
|
origem: v.union(
|
|
v.literal("usuario"),
|
|
v.literal("ti"),
|
|
v.literal("sistema")
|
|
),
|
|
tipo: v.union(
|
|
v.literal("mensagem"),
|
|
v.literal("status"),
|
|
v.literal("anexo"),
|
|
v.literal("alerta")
|
|
),
|
|
conteudo: v.string(),
|
|
anexos: v.optional(
|
|
v.array(
|
|
v.object({
|
|
arquivoId: v.id("_storage"),
|
|
nome: v.optional(v.string()),
|
|
tipo: v.optional(v.string()),
|
|
tamanho: v.optional(v.number()),
|
|
})
|
|
)
|
|
),
|
|
statusAnterior: v.optional(
|
|
v.union(
|
|
v.literal("aberto"),
|
|
v.literal("em_andamento"),
|
|
v.literal("aguardando_usuario"),
|
|
v.literal("resolvido"),
|
|
v.literal("encerrado"),
|
|
v.literal("cancelado")
|
|
)
|
|
),
|
|
statusNovo: v.optional(
|
|
v.union(
|
|
v.literal("aberto"),
|
|
v.literal("em_andamento"),
|
|
v.literal("aguardando_usuario"),
|
|
v.literal("resolvido"),
|
|
v.literal("encerrado"),
|
|
v.literal("cancelado")
|
|
)
|
|
),
|
|
visibilidade: v.union(
|
|
v.literal("publico"),
|
|
v.literal("interno")
|
|
),
|
|
criadoEm: v.number(),
|
|
})
|
|
.index("by_ticket", ["ticketId"])
|
|
.index("by_ticket_type", ["ticketId", "tipo"])
|
|
.index("by_autor", ["autorId"]),
|
|
|
|
slaConfigs: defineTable({
|
|
nome: v.string(),
|
|
descricao: v.optional(v.string()),
|
|
prioridade: v.optional(
|
|
v.union(
|
|
v.literal("baixa"),
|
|
v.literal("media"),
|
|
v.literal("alta"),
|
|
v.literal("critica")
|
|
)
|
|
),
|
|
tempoRespostaHoras: v.number(),
|
|
tempoConclusaoHoras: v.number(),
|
|
tempoEncerramentoHoras: v.optional(v.number()),
|
|
alertaAntecedenciaHoras: v.number(),
|
|
ativo: v.boolean(),
|
|
criadoPor: v.id("usuarios"),
|
|
atualizadoPor: v.optional(v.id("usuarios")),
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.number(),
|
|
})
|
|
.index("by_ativo", ["ativo"])
|
|
.index("by_prioridade", ["prioridade", "ativo"])
|
|
.index("by_nome", ["nome"]),
|
|
|
|
ticketAssignments: defineTable({
|
|
ticketId: v.id("tickets"),
|
|
responsavelId: v.id("usuarios"),
|
|
atribuidoPor: v.id("usuarios"),
|
|
motivo: v.optional(v.string()),
|
|
ativo: v.boolean(),
|
|
criadoEm: v.number(),
|
|
encerradoEm: v.optional(v.number()),
|
|
})
|
|
.index("by_ticket", ["ticketId", "ativo"])
|
|
.index("by_responsavel", ["responsavelId", "ativo"]),
|
|
|
|
// Sistema de Segurança Cibernética
|
|
networkSensors: defineTable({
|
|
nome: v.string(),
|
|
tipo: sensorSegurancaTipo,
|
|
status: sensorSegurancaStatus,
|
|
escopo: v.optional(v.string()),
|
|
ipMonitorado: v.optional(v.string()),
|
|
hostname: v.optional(v.string()),
|
|
regioes: v.optional(v.array(v.string())),
|
|
portasMonitoradas: v.optional(v.array(v.number())),
|
|
protocolos: v.optional(v.array(v.string())),
|
|
capacidades: v.optional(v.array(v.string())),
|
|
ultimaSincronizacao: v.number(),
|
|
ultimoHeartbeat: v.optional(v.number()),
|
|
latenciaMs: v.optional(v.number()),
|
|
errosConsecutivos: v.optional(v.number()),
|
|
agenteVersao: v.optional(v.string()),
|
|
notas: v.optional(v.string()),
|
|
})
|
|
.index("by_tipo", ["tipo"])
|
|
.index("by_status", ["status"])
|
|
.index("by_hostname", ["hostname"]),
|
|
|
|
ipReputation: defineTable({
|
|
indicador: v.string(),
|
|
categoria: v.union(
|
|
v.literal("ip"),
|
|
v.literal("dominio"),
|
|
v.literal("hash"),
|
|
v.literal("email")
|
|
),
|
|
reputacao: v.number(), // -100 (malicioso) até 100 (confiável)
|
|
severidadeMax: severidadeSeguranca,
|
|
whitelist: v.boolean(),
|
|
blacklist: v.boolean(),
|
|
ocorrencias: v.number(),
|
|
primeiroRegistro: v.number(),
|
|
ultimoRegistro: v.number(),
|
|
bloqueadoAte: v.optional(v.number()),
|
|
origem: v.optional(v.string()),
|
|
comentarios: v.optional(v.string()),
|
|
classificacoes: v.optional(v.array(v.string())),
|
|
ultimaAcaoId: v.optional(v.id("incidentActions")),
|
|
})
|
|
.index("by_indicador", ["indicador"])
|
|
.index("by_reputacao", ["reputacao"])
|
|
.index("by_blacklist", ["blacklist"])
|
|
.index("by_whitelist", ["whitelist"]),
|
|
|
|
portRules: defineTable({
|
|
porta: v.number(),
|
|
protocolo: v.union(
|
|
v.literal("tcp"),
|
|
v.literal("udp"),
|
|
v.literal("icmp"),
|
|
v.literal("quic"),
|
|
v.literal("any")
|
|
),
|
|
acao: v.union(
|
|
v.literal("permitir"),
|
|
v.literal("bloquear"),
|
|
v.literal("monitorar"),
|
|
v.literal("rate_limit")
|
|
),
|
|
temporario: v.boolean(),
|
|
severidadeMin: severidadeSeguranca,
|
|
duracaoSegundos: v.optional(v.number()),
|
|
expiraEm: v.optional(v.number()),
|
|
criadoPor: v.id("usuarios"),
|
|
atualizadoPor: v.optional(v.id("usuarios")),
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.number(),
|
|
notas: v.optional(v.string()),
|
|
tags: v.optional(v.array(v.string())),
|
|
listaReferencia: v.optional(v.id("ipReputation")),
|
|
})
|
|
.index("by_porta_protocolo", ["porta", "protocolo"])
|
|
.index("by_acao", ["acao"])
|
|
.index("by_expiracao", ["expiraEm"]),
|
|
|
|
threatIntelFeeds: defineTable({
|
|
nomeFonte: v.string(),
|
|
tipo: threatIntelTipo,
|
|
formato: threatIntelFormato,
|
|
url: v.optional(v.string()),
|
|
ativo: v.boolean(),
|
|
prioridade: v.union(
|
|
v.literal("baixa"),
|
|
v.literal("media"),
|
|
v.literal("alta"),
|
|
v.literal("critica")
|
|
),
|
|
ultimaSincronizacao: v.optional(v.number()),
|
|
entradasProcessadas: v.optional(v.number()),
|
|
errosConsecutivos: v.optional(v.number()),
|
|
autenticacaoNecessaria: v.optional(v.boolean()),
|
|
configuracao: v.optional(
|
|
v.object({
|
|
tokenId: v.optional(v.id("_storage")),
|
|
escopo: v.optional(v.string()),
|
|
})
|
|
),
|
|
criadoPor: v.id("usuarios"),
|
|
atualizadoPor: v.optional(v.id("usuarios")),
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.number(),
|
|
})
|
|
.index("by_tipo", ["tipo"])
|
|
.index("by_ativo", ["ativo"])
|
|
.index("by_prioridade", ["prioridade"]),
|
|
|
|
securityEvents: defineTable({
|
|
referencia: v.string(),
|
|
timestamp: v.number(),
|
|
tipoAtaque: ataqueCiberneticoTipo,
|
|
severidade: severidadeSeguranca,
|
|
status: statusEventoSeguranca,
|
|
descricao: v.string(),
|
|
origemIp: v.optional(v.string()),
|
|
origemRegiao: v.optional(v.string()),
|
|
origemAsn: v.optional(v.string()),
|
|
destinoIp: v.optional(v.string()),
|
|
destinoPorta: v.optional(v.number()),
|
|
protocolo: v.optional(v.string()),
|
|
transporte: v.optional(v.string()),
|
|
sensorId: v.optional(v.id("networkSensors")),
|
|
detectadoPor: v.optional(v.string()),
|
|
mitreTechnique: v.optional(v.string()),
|
|
geolocalizacao: v.optional(
|
|
v.object({
|
|
pais: v.optional(v.string()),
|
|
regiao: v.optional(v.string()),
|
|
cidade: v.optional(v.string()),
|
|
latitude: v.optional(v.number()),
|
|
longitude: v.optional(v.number()),
|
|
})
|
|
),
|
|
fingerprint: v.optional(
|
|
v.object({
|
|
userAgent: v.optional(v.string()),
|
|
deviceId: v.optional(v.string()),
|
|
ja3: v.optional(v.string()),
|
|
tlsVersion: v.optional(v.string()),
|
|
})
|
|
),
|
|
indicadores: v.optional(
|
|
v.array(
|
|
v.object({
|
|
tipo: v.string(),
|
|
valor: v.string(),
|
|
confianca: v.optional(v.number()),
|
|
})
|
|
)
|
|
),
|
|
metricas: v.optional(
|
|
v.object({
|
|
pps: v.optional(v.number()),
|
|
bps: v.optional(v.number()),
|
|
rpm: v.optional(v.number()),
|
|
errosPorSegundo: v.optional(v.number()),
|
|
hostsAfetados: v.optional(v.number()),
|
|
})
|
|
),
|
|
correlacoes: v.optional(v.array(v.id("securityEvents"))),
|
|
referenciasExternas: v.optional(v.array(v.string())),
|
|
tags: v.optional(v.array(v.string())),
|
|
criadoPor: v.optional(v.id("usuarios")),
|
|
atualizadoEm: v.number(),
|
|
})
|
|
.index("by_referencia", ["referencia"])
|
|
.index("by_timestamp", ["timestamp"])
|
|
.index("by_tipo", ["tipoAtaque", "timestamp"])
|
|
.index("by_severidade", ["severidade", "timestamp"])
|
|
.index("by_status", ["status", "timestamp"]),
|
|
|
|
incidentActions: defineTable({
|
|
eventoId: v.id("securityEvents"),
|
|
tipo: acaoIncidenteTipo,
|
|
origem: v.union(v.literal("automatico"), v.literal("manual")),
|
|
status: acaoIncidenteStatus,
|
|
executadoPor: v.optional(v.id("usuarios")),
|
|
detalhes: v.optional(v.string()),
|
|
resultado: v.optional(v.string()),
|
|
relacionadoA: v.optional(v.id("ipReputation")),
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.number(),
|
|
})
|
|
.index("by_evento", ["eventoId", "status"])
|
|
.index("by_tipo", ["tipo", "status"]),
|
|
|
|
reportRequests: defineTable({
|
|
solicitanteId: v.id("usuarios"),
|
|
filtros: v.object({
|
|
dataInicio: v.number(),
|
|
dataFim: v.number(),
|
|
severidades: v.optional(v.array(severidadeSeguranca)),
|
|
tiposAtaque: v.optional(v.array(ataqueCiberneticoTipo)),
|
|
incluirIndicadores: v.optional(v.boolean()),
|
|
incluirMetricas: v.optional(v.boolean()),
|
|
incluirAcoes: v.optional(v.boolean()),
|
|
}),
|
|
status: reportStatus,
|
|
resultadoId: v.optional(v.id("_storage")),
|
|
observacoes: v.optional(v.string()),
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.number(),
|
|
concluidoEm: v.optional(v.number()),
|
|
erro: v.optional(v.string()),
|
|
})
|
|
.index("by_status", ["status"])
|
|
.index("by_solicitante", ["solicitanteId", "status"])
|
|
.index("by_criado_em", ["criadoEm"]),
|
|
|
|
rateLimitConfig: defineTable({
|
|
nome: v.string(),
|
|
tipo: v.union(
|
|
v.literal("ip"),
|
|
v.literal("usuario"),
|
|
v.literal("endpoint"),
|
|
v.literal("global")
|
|
),
|
|
identificador: v.optional(v.string()),
|
|
limite: v.number(),
|
|
janelaSegundos: v.number(),
|
|
estrategia: v.union(
|
|
v.literal("fixed_window"),
|
|
v.literal("sliding_window"),
|
|
v.literal("token_bucket")
|
|
),
|
|
acaoExcedido: v.union(
|
|
v.literal("bloquear"),
|
|
v.literal("throttle"),
|
|
v.literal("alertar")
|
|
),
|
|
bloqueioTemporarioSegundos: v.optional(v.number()),
|
|
ativo: v.boolean(),
|
|
prioridade: v.number(),
|
|
criadoPor: v.id("usuarios"),
|
|
atualizadoPor: v.optional(v.id("usuarios")),
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.number(),
|
|
notas: v.optional(v.string()),
|
|
tags: v.optional(v.array(v.string()))
|
|
})
|
|
.index("by_tipo_identificador", ["tipo", "identificador"])
|
|
.index("by_ativo", ["ativo"])
|
|
.index("by_prioridade", ["prioridade"])
|
|
,
|
|
alertConfigs: defineTable({
|
|
nome: v.string(),
|
|
canais: v.object({
|
|
email: v.boolean(),
|
|
chat: v.boolean(),
|
|
}),
|
|
emails: v.array(v.string()),
|
|
chatUsers: v.array(v.string()),
|
|
severidadeMin: severidadeSeguranca,
|
|
tiposAtaque: v.optional(v.array(ataqueCiberneticoTipo)),
|
|
reenvioMin: v.number(),
|
|
criadoPor: v.id("usuarios"),
|
|
criadoEm: v.number(),
|
|
atualizadoEm: v.number(),
|
|
})
|
|
.index("by_criadoEm", ["criadoEm"]),
|
|
|
|
// Sistema de Controle de Ponto
|
|
registrosPonto: defineTable({
|
|
funcionarioId: v.id("funcionarios"),
|
|
tipo: v.union(
|
|
v.literal("entrada"),
|
|
v.literal("saida_almoco"),
|
|
v.literal("retorno_almoco"),
|
|
v.literal("saida")
|
|
),
|
|
data: v.string(), // YYYY-MM-DD
|
|
hora: v.number(),
|
|
minuto: v.number(),
|
|
segundo: v.number(),
|
|
timestamp: v.number(), // Timestamp completo para ordenação
|
|
imagemId: v.optional(v.id("_storage")),
|
|
sincronizadoComServidor: v.boolean(),
|
|
toleranciaMinutos: v.number(),
|
|
dentroDoPrazo: v.boolean(),
|
|
|
|
// Informações de Rede
|
|
ipAddress: v.optional(v.string()),
|
|
ipPublico: v.optional(v.string()),
|
|
ipLocal: v.optional(v.string()),
|
|
|
|
// Informações do Navegador
|
|
userAgent: v.optional(v.string()),
|
|
browser: v.optional(v.string()),
|
|
browserVersion: v.optional(v.string()),
|
|
engine: v.optional(v.string()),
|
|
|
|
// Informações do Sistema
|
|
sistemaOperacional: v.optional(v.string()),
|
|
osVersion: v.optional(v.string()),
|
|
arquitetura: v.optional(v.string()),
|
|
plataforma: v.optional(v.string()),
|
|
|
|
// Informações de Localização
|
|
latitude: v.optional(v.number()),
|
|
longitude: v.optional(v.number()),
|
|
precisao: v.optional(v.number()),
|
|
endereco: v.optional(v.string()),
|
|
cidade: v.optional(v.string()),
|
|
estado: v.optional(v.string()),
|
|
pais: v.optional(v.string()),
|
|
timezone: v.optional(v.string()),
|
|
|
|
// Informações do Dispositivo
|
|
deviceType: v.optional(v.string()),
|
|
deviceModel: v.optional(v.string()),
|
|
screenResolution: v.optional(v.string()),
|
|
coresTela: v.optional(v.number()),
|
|
idioma: v.optional(v.string()),
|
|
|
|
// Informações Adicionais
|
|
isMobile: v.optional(v.boolean()),
|
|
isTablet: v.optional(v.boolean()),
|
|
isDesktop: v.optional(v.boolean()),
|
|
connectionType: v.optional(v.string()),
|
|
memoryInfo: v.optional(v.string()),
|
|
|
|
// Justificativa opcional para o registro
|
|
justificativa: v.optional(v.string()),
|
|
|
|
criadoEm: v.number(),
|
|
})
|
|
.index("by_funcionario_data", ["funcionarioId", "data"])
|
|
.index("by_data", ["data"])
|
|
.index("by_dentro_prazo", ["dentroDoPrazo", "data"])
|
|
.index("by_funcionario_timestamp", ["funcionarioId", "timestamp"]),
|
|
|
|
configuracaoPonto: defineTable({
|
|
horarioEntrada: v.string(), // HH:mm
|
|
horarioSaidaAlmoco: v.string(), // HH:mm
|
|
horarioRetornoAlmoco: v.string(), // HH:mm
|
|
horarioSaida: v.string(), // HH:mm
|
|
toleranciaMinutos: v.number(),
|
|
// Nomes personalizados dos tipos de registro
|
|
nomeEntrada: v.optional(v.string()), // Padrão: "Entrada 1"
|
|
nomeSaidaAlmoco: v.optional(v.string()), // Padrão: "Saída 1"
|
|
nomeRetornoAlmoco: v.optional(v.string()), // Padrão: "Entrada 2"
|
|
nomeSaida: v.optional(v.string()), // Padrão: "Saída 2"
|
|
// Ajuste de fuso horário (GMT offset em horas)
|
|
gmtOffset: v.optional(v.number()), // Padrão: 0 (UTC)
|
|
ativo: v.boolean(),
|
|
atualizadoPor: v.id("usuarios"),
|
|
atualizadoEm: v.number(),
|
|
})
|
|
.index("by_ativo", ["ativo"]),
|
|
|
|
configuracaoRelogio: defineTable({
|
|
servidorNTP: v.optional(v.string()),
|
|
portaNTP: v.optional(v.number()),
|
|
usarServidorExterno: v.boolean(),
|
|
fallbackParaPC: v.boolean(),
|
|
ultimaSincronizacao: v.optional(v.number()),
|
|
offsetSegundos: v.optional(v.number()),
|
|
// Ajuste de fuso horário (GMT offset em horas)
|
|
gmtOffset: v.optional(v.number()), // Padrão: 0 (UTC)
|
|
atualizadoPor: v.id("usuarios"),
|
|
atualizadoEm: v.number(),
|
|
})
|
|
.index("by_ativo", ["usarServidorExterno"]),
|
|
|
|
// Banco de Horas - Saldo diário de horas trabalhadas
|
|
bancoHoras: defineTable({
|
|
funcionarioId: v.id("funcionarios"),
|
|
data: v.string(), // YYYY-MM-DD
|
|
cargaHorariaDiaria: v.number(), // Horas esperadas do dia (em minutos)
|
|
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
|
|
calculadoEm: v.number(),
|
|
})
|
|
.index("by_funcionario_data", ["funcionarioId", "data"])
|
|
.index("by_funcionario", ["funcionarioId"])
|
|
.index("by_data", ["data"]),
|
|
});
|