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; export default defineSchema({ todos: defineTable({ text: v.string(), completed: v.boolean(), }), 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"]), solicitacoesFerias: defineTable({ funcionarioId: v.id("funcionarios"), anoReferencia: v.number(), status: v.union( v.literal("aguardando_aprovacao"), v.literal("aprovado"), v.literal("reprovado"), v.literal("data_ajustada_aprovada") ), periodos: v.array( v.object({ dataInicio: v.string(), dataFim: v.string(), diasCorridos: v.number(), }) ), observacao: v.optional(v.string()), motivoReprovacao: v.optional(v.string()), gestorId: v.optional(v.id("usuarios")), dataAprovacao: v.optional(v.number()), dataReprovacao: v.optional(v.number()), historicoAlteracoes: v.optional( v.array( v.object({ data: v.number(), usuarioId: v.id("usuarios"), acao: v.string(), periodosAnteriores: v.optional( v.array( v.object({ dataInicio: v.string(), dataFim: v.string(), diasCorridos: v.number(), }) ) ), }) ) ), }) .index("by_funcionario", ["funcionarioId"]) .index("by_status", ["status"]) .index("by_funcionario_and_status", ["funcionarioId", "status"]) .index("by_ano", ["anoReferencia"]), notificacoesFerias: defineTable({ destinatarioId: v.id("usuarios"), solicitacaoFeriasId: v.id("solicitacoesFerias"), 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"]), // Períodos aquisitivos e saldos de férias periodosAquisitivos: defineTable({ funcionarioId: v.id("funcionarios"), anoReferencia: v.number(), // Ano do período aquisitivo (ex: 2024) dataInicio: v.string(), // Data de início do período aquisitivo dataFim: v.string(), // Data de fim do período aquisitivo diasDireito: v.number(), // Dias de férias que tem direito (30 ou proporcional) diasUsados: v.number(), // Dias já usados diasPendentes: v.number(), // Dias em solicitações aguardando aprovação diasDisponiveis: v.number(), // Dias disponíveis = direito - usados - pendentes abonoPermitido: v.boolean(), // Se pode vender 1/3 das férias diasAbono: v.number(), // Dias vendidos como abono pecuniário status: v.union( v.literal("ativo"), // Período vigente v.literal("vencido"), // Período vencido (não tirou férias) v.literal("concluido") // Período totalmente utilizado ), }) .index("by_funcionario", ["funcionarioId"]) .index("by_funcionario_and_ano", ["funcionarioId", "anoReferencia"]) .index("by_funcionario_and_status", ["funcionarioId", "status"]), times: defineTable({ nome: v.string(), descricao: v.optional(v.string()), gestorId: v.id("usuarios"), ativo: v.boolean(), cor: v.optional(v.string()), // Cor para identificação visual }).index("by_gestor", ["gestorId"]), 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.optional(v.string()), senhaHash: v.string(), // Senha criptografada com bcrypt 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") ), 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"]), });