Merge remote-tracking branch 'origin/feat-cotrole_acesso' into feat-funcionarios-ferias

This commit is contained in:
2025-10-29 10:08:06 -03:00
33 changed files with 6449 additions and 1202 deletions

View File

@@ -192,6 +192,13 @@ export default defineSchema({
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")),
@@ -214,17 +221,22 @@ export default defineSchema({
.index("by_email", ["email"])
.index("by_role", ["roleId"])
.index("by_ativo", ["ativo"])
.index("by_status_presenca", ["statusPresenca"]),
.index("by_status_presenca", ["statusPresenca"])
.index("by_bloqueado", ["bloqueado"]),
roles: defineTable({
nome: v.string(), // "admin", "ti", "usuario_avancado", "usuario"
nome: v.string(), // "admin", "ti_master", "ti_usuario", "usuario_avancado", "usuario"
descricao: v.string(),
nivel: v.number(), // 0 = admin, 1 = ti, 2 = usuario_avancado, 3 = usuario
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_setor", ["setor"])
.index("by_customizado", ["customizado"]),
permissoes: defineTable({
nome: v.string(), // "funcionarios.criar", "simbolos.editar", etc.
@@ -300,6 +312,128 @@ export default defineSchema({
.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
perfisCustomizados: defineTable({
nome: v.string(),
descricao: v.string(),
nivel: v.number(), // >= 3
roleId: v.id("roles"), // role correspondente criada
criadoPor: v.id("usuarios"), // TI_MASTER que criou
criadoEm: v.number(),
atualizadoEm: v.number(),
})
.index("by_nome", ["nome"])
.index("by_nivel", ["nivel"])
.index("by_criado_por", ["criadoPor"])
.index("by_role", ["roleId"]),
// 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
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()),
})
.index("by_status", ["status"])
.index("by_destinatario", ["destinatarioId"])
.index("by_enviado_por", ["enviadoPor"])
.index("by_criado_em", ["criadoEm"]),
configuracaoAcesso: defineTable({
chave: v.string(), // "sessao_duracao", "max_tentativas_login", etc.
valor: v.string(),