One Hat Cyber Team
Your IP :
216.73.216.36
Server IP :
162.240.179.46
Server :
Linux vps-14493116.nutrivittasaude.com.br 5.14.0-611.49.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Apr 21 16:39:08 EDT 2026 x86_64
Server Software :
Apache
PHP Version :
8.2.31
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
home
/
lifeprimeti
/
meta.lifeprimeti.com.br
/
admin
/
View File Name :
atualizar.php
<?php require_once __DIR__ . '/../config/database.php'; $directAccess = !isset($GLOBALS['_SETUP_INCLUDE']); if ($directAccess) { requireSuperAdmin(); $titulo = 'Atualizacao do Sistema'; require_once __DIR__ . '/../includes/header.php'; } define('BASE_DIR', realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR); $tempDir = BASE_DIR . 'backup' . DIRECTORY_SEPARATOR . '_update_temp' . DIRECTORY_SEPARATOR; $log = []; $errors = []; $updatedFiles = []; $newFiles = []; $skippedFiles = []; $migrationOutput = []; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['run_migrations'])) { try { $migrationOutput = runMigrations($pdo); $log = $migrationOutput; } catch (Exception $e) { $errors[] = 'Erro na migracao: ' . $e->getMessage(); } } if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['update_zip'])) { if ($_FILES['update_zip']['error'] !== UPLOAD_ERR_OK) { $errors[] = 'Erro no upload: ' . $_FILES['update_zip']['error']; } else { $zipPath = $_FILES['update_zip']['tmp_name']; $zip = new ZipArchive(); if ($zip->open($zipPath) !== true) { $errors[] = 'Nao foi possivel abrir o arquivo ZIP.'; } else { if (is_dir($tempDir)) { removeDir($tempDir); } mkdir($tempDir, 0755, true); $zip->extractTo($tempDir); $zip->close(); if (!file_exists($tempDir . 'config' . DIRECTORY_SEPARATOR . 'database.php')) { $errors[] = 'ZIP invalido: estrutura do sistema nao encontrada (config/database.php ausente).'; removeDir($tempDir); } else { // Scan extracted files $extractedFiles = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($tempDir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($extractedFiles as $file) { if ($file->isDir()) continue; $relativePath = substr($file->getPathname(), strlen($tempDir)); $targetPath = BASE_DIR . $relativePath; $relativePathClean = str_replace(DIRECTORY_SEPARATOR, '/', $relativePath); // Skip files that should not be overwritten $skipDirs = ['backup/', 'config/database.php', 'assets/uploads/']; $shouldSkip = false; foreach ($skipDirs as $skip) { if (strpos($relativePathClean, $skip) === 0) { $skippedFiles[] = $relativePathClean; $shouldSkip = true; break; } } if ($shouldSkip) continue; if (file_exists($targetPath)) { $existingHash = md5_file($targetPath); $newHash = md5_file($file->getPathname()); if ($existingHash !== $newHash) { $updatedFiles[] = $relativePathClean; copy($file->getPathname(), $targetPath); } } else { $newFiles[] = $relativePathClean; $dir = dirname($targetPath); if (!is_dir($dir)) mkdir($dir, 0755, true); copy($file->getPathname(), $targetPath); } } // Run database migrations try { $migrationOutput = runMigrations($pdo); if (!empty($migrationOutput)) { foreach ($migrationOutput as $m) { $log[] = $m; } } } catch (Exception $e) { $errors[] = 'Erro na migracao do banco: ' . $e->getMessage(); } // Cleanup removeDir($tempDir); $log[] = 'Arquivos atualizados: ' . count($updatedFiles); $log[] = 'Arquivos novos: ' . count($newFiles); $log[] = 'Arquivos ignorados: ' . count($skippedFiles); $_SESSION['success_msg'] = 'Sistema atualizado com sucesso!'; } } } } function removeDir($dir) { if (!is_dir($dir)) return; $it = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS); $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); foreach ($files as $file) { if ($file->isDir()) rmdir($file->getRealPath()); else unlink($file->getRealPath()); } rmdir($dir); } function runMigrations($pdo) { $output = []; // Ensure base tables exist before trying to migrate them $baseTables = [ "CREATE TABLE IF NOT EXISTS planos ( id INT AUTO_INCREMENT PRIMARY KEY, nome VARCHAR(100) NOT NULL, valor DECIMAL(10,2) NOT NULL DEFAULT 0, recorencia ENUM('mensal','trimestral','semestral','anual') NOT NULL DEFAULT 'mensal', limite_profissionais INT DEFAULT 0, limite_clientes INT DEFAULT 0, recursos JSON, whatsapp_available TINYINT(1) DEFAULT 0, gateway_hubpay TINYINT(1) DEFAULT 1, gateway_mercadopago TINYINT(1) DEFAULT 0, gateway_asaas TINYINT(1) DEFAULT 0, gateway_manual TINYINT(1) DEFAULT 1, ativo TINYINT(1) NOT NULL DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )", "CREATE TABLE IF NOT EXISTS empresas ( id INT AUTO_INCREMENT PRIMARY KEY, plano_id INT NULL, nome VARCHAR(200) NOT NULL, slug VARCHAR(100) NOT NULL UNIQUE, documento VARCHAR(20), telefone VARCHAR(20), email VARCHAR(100), endereco TEXT, logo VARCHAR(255), cor_primaria VARCHAR(7) DEFAULT '#667eea', cor_secundaria VARCHAR(7) DEFAULT '#764ba2', ativo TINYINT(1) NOT NULL DEFAULT 1, aprovado TINYINT(1) NOT NULL DEFAULT 0, bloqueado TINYINT(1) NOT NULL DEFAULT 0, bloqueado_motivo VARCHAR(255) DEFAULT NULL, bloqueado_em DATETIME DEFAULT NULL, payment_gateway ENUM('hubpay','mercadopago','asaas','manual') DEFAULT NULL, payment_due_date DATE NULL, payment_grace_start DATETIME DEFAULT NULL, ultima_fatura_gerada DATE NULL, plano_valor DECIMAL(10,2) DEFAULT 0, data_expiracao DATE NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (plano_id) REFERENCES planos(id) ON DELETE SET NULL )", "CREATE TABLE IF NOT EXISTS grupos ( id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, nome VARCHAR(100) NOT NULL, descricao TEXT, permissoes JSON NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE )", "CREATE TABLE IF NOT EXISTS usuarios ( id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, grupo_id INT NULL, nome VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL, telefone VARCHAR(20), senha VARCHAR(255) NOT NULL, tipo ENUM('super_admin','admin','profissional','cliente') NOT NULL DEFAULT 'cliente', foto VARCHAR(255), ativo TINYINT(1) NOT NULL DEFAULT 1, aprovado TINYINT(1) NOT NULL DEFAULT 0, hash_link VARCHAR(100) UNIQUE, whatsapp VARCHAR(20), data_nascimento DATE NULL, cep VARCHAR(9), endereco VARCHAR(255), numero VARCHAR(20), complemento VARCHAR(100), bairro VARCHAR(100), cidade VARCHAR(100), estado CHAR(2), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE, FOREIGN KEY (grupo_id) REFERENCES grupos(id) ON DELETE SET NULL )", "CREATE TABLE IF NOT EXISTS configuracoes ( id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL UNIQUE, opcao_pagar ENUM('sim','nao') NOT NULL DEFAULT 'nao', gateway_padrao ENUM('mercadopago','asaas','nenhum') NOT NULL DEFAULT 'nenhum', mp_public_key VARCHAR(255), mp_access_token VARCHAR(255), asaas_api_key VARCHAR(255), asaas_token VARCHAR(255), whatsapp_api VARCHAR(255), whatsapp_token VARCHAR(255), whatsapp_instance VARCHAR(100), horario_inicio TIME DEFAULT '08:00', horario_fim TIME DEFAULT '18:00', intervalo_padrao INT DEFAULT 0, confirma_cadastro TINYINT(1) NOT NULL DEFAULT 1, confirma_agendamento TINYINT(1) NOT NULL DEFAULT 1, smtp_host VARCHAR(255) DEFAULT '', smtp_porta INT DEFAULT 587, smtp_usuario VARCHAR(255) DEFAULT '', smtp_senha VARCHAR(255) DEFAULT '', smtp_criptografia ENUM('tls','ssl','nenhuma') NOT NULL DEFAULT 'tls', smtp_email_from VARCHAR(255) DEFAULT '', smtp_nome_from VARCHAR(255) DEFAULT '', smtp_ativo TINYINT(1) NOT NULL DEFAULT 0, openai_api_key VARCHAR(255), gemini_api_key VARCHAR(255), claude_api_key VARCHAR(255), waba_phone_id VARCHAR(100) DEFAULT '', waba_token VARCHAR(512) DEFAULT '', waba_business_id VARCHAR(100) DEFAULT '', waba_webhook_token VARCHAR(100) DEFAULT '', lembrete_ativo TINYINT(1) NOT NULL DEFAULT 0, lembrete_timezone VARCHAR(50) DEFAULT 'America/Sao_Paulo', lembrete_metodo ENUM('email','whatsapp','digigo') NOT NULL DEFAULT 'email', hubpay_api_key VARCHAR(255) DEFAULT '', hubpay_webhook_secret VARCHAR(255) DEFAULT '', hubpay_ambiente ENUM('test','live') NOT NULL DEFAULT 'test', digigo_base_url VARCHAR(255) DEFAULT '', digigo_admin_token VARCHAR(512) DEFAULT '', digigo_user_token VARCHAR(512) DEFAULT '', digigo_user_name VARCHAR(100) DEFAULT '', digigo_connected TINYINT(1) DEFAULT 0, digigo_envio_ativo TINYINT(1) DEFAULT 0, digigo_modo ENUM('proprio','global') NOT NULL DEFAULT 'proprio', app_nome VARCHAR(100) DEFAULT NULL, login_bg_tipo ENUM('imagem','video') DEFAULT NULL, login_bg_arquivo VARCHAR(255) DEFAULT NULL, confirmacao_agendamento_ativo TINYINT(1) NOT NULL DEFAULT 0, confirmacao_agendamento_metodo ENUM('email','whatsapp','digigo') NOT NULL DEFAULT 'email', timezone VARCHAR(50) DEFAULT 'America/Sao_Paulo', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE )", "CREATE TABLE IF NOT EXISTS profissionais ( id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, usuario_id INT NOT NULL UNIQUE, foto VARCHAR(255), especialidade VARCHAR(255), descricao TEXT, cor_calendario VARCHAR(7) DEFAULT '#667eea', comissao_tipo ENUM('percentual','fixo') NOT NULL DEFAULT 'percentual', comissao_valor DECIMAL(10,2) DEFAULT 0, cargo_id INT NULL, mostrar TINYINT(1) DEFAULT 1, data_nascimento DATE NULL, cpf VARCHAR(14) NULL, chave_pix_tipo ENUM('telefone','cpf','aleatoria','cnpj','email') NULL, chave_pix VARCHAR(255) NULL, cep VARCHAR(10) NULL, endereco VARCHAR(255) NULL, numero VARCHAR(20) NULL, complemento VARCHAR(100) NULL, bairro VARCHAR(100) NULL, cidade VARCHAR(100) NULL, estado CHAR(2) NULL, ativo TINYINT(1) NOT NULL DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE, FOREIGN KEY (usuario_id) REFERENCES usuarios(id) ON DELETE CASCADE )", "CREATE TABLE IF NOT EXISTS servicos ( id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, nome VARCHAR(150) NOT NULL, descricao TEXT, preco DECIMAL(10,2) NOT NULL, duracao INT NOT NULL, foto VARCHAR(255), comissao_tipo ENUM('percentual','fixo') NOT NULL DEFAULT 'percentual', comissao_valor DECIMAL(10,2) DEFAULT 0, ativo TINYINT(1) NOT NULL DEFAULT 1, mostrar_site TINYINT(1) NOT NULL DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE )", "CREATE TABLE IF NOT EXISTS agendamentos ( id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, cliente_id INT NOT NULL, profissional_id INT NOT NULL, servico_id INT NOT NULL, data DATE NOT NULL, hora TIME NOT NULL, hora_fim TIME NOT NULL, status ENUM('pendente','confirmado','em_andamento','concluido','cancelado') NOT NULL DEFAULT 'pendente', confirmado_cliente TINYINT(1) NOT NULL DEFAULT 0, confirmado_admin TINYINT(1) NOT NULL DEFAULT 0, pagamento_tipo ENUM('gratis','manual','mercadopago','asaas') DEFAULT 'gratis', pagamento_status ENUM('pendente','aprovado','recusado','cancelado') DEFAULT 'pendente', observacao TEXT, lembrete_enviado TINYINT(1) NOT NULL DEFAULT 0, hubpay_charge_id VARCHAR(100) DEFAULT NULL, hubpay_pix_copy_paste TEXT DEFAULT NULL, hubpay_pix_qrcode TEXT DEFAULT NULL, hash_link VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE, FOREIGN KEY (cliente_id) REFERENCES usuarios(id) ON DELETE CASCADE, FOREIGN KEY (profissional_id) REFERENCES profissionais(id) ON DELETE CASCADE, FOREIGN KEY (servico_id) REFERENCES servicos(id) ON DELETE CASCADE, INDEX idx_data (data), INDEX idx_status (status) )", ]; foreach ($baseTables as $sql) { try { $pdo->exec($sql); } catch (PDOException $e) {} } // Now safely query columns (tables exist) $existingConfig = []; try { $existingConfig = $pdo->query("SHOW COLUMNS FROM configuracoes")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) {} // 1. ENUM usuarios tipo try { $stmt = $pdo->query("SHOW COLUMNS FROM usuarios LIKE 'tipo'"); $col = $stmt->fetch(); if ($col && strpos($col['Type'], 'super_admin') === false) { $pdo->exec("ALTER TABLE usuarios MODIFY COLUMN tipo ENUM('super_admin','admin','profissional','cliente') NOT NULL DEFAULT 'cliente'"); $output[] = 'Coluna tipo atualizada.'; } } catch (Exception $e) {} // 2. Tabelas novas $tabelas = [ "CREATE TABLE IF NOT EXISTS cargos (id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, nome VARCHAR(100) NOT NULL, ativo TINYINT(1) DEFAULT 1, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS formas_pagamento (id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, nome VARCHAR(100) NOT NULL, taxa DECIMAL(5,2) DEFAULT 0, ativo TINYINT(1) DEFAULT 1, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS frequencias (id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, nome VARCHAR(100) NOT NULL, dias INT NOT NULL, ativo TINYINT(1) DEFAULT 1, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS fornecedores (id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, cpf_cnpj VARCHAR(20) UNIQUE, razao_social VARCHAR(200), nome_fantasia VARCHAR(200), email VARCHAR(100), telefones JSON, chave_pix_tipo ENUM('telefone','cpf','aleatoria','cnpj','email') NULL, chave_pix VARCHAR(255), cep VARCHAR(10), endereco VARCHAR(255), numero VARCHAR(20), complemento VARCHAR(100), bairro VARCHAR(100), cidade VARCHAR(100), estado CHAR(2), ativo TINYINT(1) DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS anexos (id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, modelo VARCHAR(50) NOT NULL, modelo_id INT NOT NULL, nome_original VARCHAR(255), arquivo VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS caixa (id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, usuario_id INT NOT NULL, data_abertura DATETIME NOT NULL, data_fechamento DATETIME NULL, valor_abertura DECIMAL(10,2) NOT NULL DEFAULT 0, valor_fechamento DECIMAL(10,2) DEFAULT 0, status ENUM('aberto','fechado') NOT NULL DEFAULT 'aberto', observacoes TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE, FOREIGN KEY (usuario_id) REFERENCES usuarios(id) ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS caixa_movimentos (id INT AUTO_INCREMENT PRIMARY KEY, caixa_id INT NOT NULL, tipo ENUM('entrada','saida','sangria','suprimento') NOT NULL, descricao VARCHAR(255), valor DECIMAL(10,2) NOT NULL, forma_pagamento_id INT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (caixa_id) REFERENCES caixa(id) ON DELETE CASCADE, FOREIGN KEY (forma_pagamento_id) REFERENCES formas_pagamento(id) ON DELETE SET NULL)", "CREATE TABLE IF NOT EXISTS suporte_tickets (id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, usuario_id INT NOT NULL, titulo VARCHAR(200) NOT NULL, mensagem TEXT NOT NULL, status ENUM('aberto','fechado') NOT NULL DEFAULT 'aberto', respondido TINYINT(1) NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE, FOREIGN KEY (usuario_id) REFERENCES usuarios(id) ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS config_smtp (id INT AUTO_INCREMENT PRIMARY KEY, host VARCHAR(255) NOT NULL DEFAULT '', porta INT NOT NULL DEFAULT 587, usuario VARCHAR(255) NOT NULL DEFAULT '', senha VARCHAR(255) NOT NULL DEFAULT '', criptografia ENUM('tls','ssl','nenhuma') NOT NULL DEFAULT 'tls', email_from VARCHAR(255) NOT NULL DEFAULT '', nome_from VARCHAR(255) NOT NULL DEFAULT '', ativo TINYINT(1) NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP)", "CREATE TABLE IF NOT EXISTS password_reset_tokens (id INT AUTO_INCREMENT PRIMARY KEY, usuario_id INT NOT NULL, token VARCHAR(100) NOT NULL UNIQUE, usado TINYINT(1) NOT NULL DEFAULT 0, expires_at DATETIME NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (usuario_id) REFERENCES usuarios(id) ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS suporte_respostas (id INT AUTO_INCREMENT PRIMARY KEY, ticket_id INT NOT NULL, usuario_id INT NOT NULL, mensagem TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (ticket_id) REFERENCES suporte_tickets(id) ON DELETE CASCADE, FOREIGN KEY (usuario_id) REFERENCES usuarios(id) ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS ia_prompts (id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, titulo VARCHAR(200) NOT NULL, prompt_texto TEXT NOT NULL, ia_provedor ENUM('openai','gemini','claude') NOT NULL DEFAULT 'openai', modelo VARCHAR(100) NOT NULL DEFAULT 'gpt-4o-mini', temperatura DECIMAL(3,2) DEFAULT 0.7, max_tokens INT DEFAULT 2048, ativo TINYINT(1) DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS campanhas (id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, titulo VARCHAR(200) NOT NULL, assunto VARCHAR(255) NOT NULL, corpo_html TEXT NOT NULL, status ENUM('rascunho','agendada','enviada') NOT NULL DEFAULT 'rascunho', data_agendamento DATETIME NULL, enviadas INT NOT NULL DEFAULT 0, total_destinatarios INT NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS lembretes_config (id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, tipo ENUM('minutos','horas','dias','meses') NOT NULL DEFAULT 'horas', valor INT NOT NULL DEFAULT 1, metodo ENUM('email','whatsapp','digigo') NOT NULL DEFAULT 'email', ativo TINYINT(1) NOT NULL DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE)", "CREATE TABLE IF NOT EXISTS comissoes (id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, profissional_id INT NOT NULL, periodo_inicio DATE NOT NULL, periodo_fim DATE NOT NULL, valor DECIMAL(10,2) NOT NULL, status ENUM('pendente','pago','cancelado') NOT NULL DEFAULT 'pendente', forma_pagamento_id INT NULL, data_pagamento DATE NULL, observacao TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE, FOREIGN KEY (profissional_id) REFERENCES profissionais(id) ON DELETE CASCADE, UNIQUE KEY uk_comissao_periodo (empresa_id, profissional_id, periodo_inicio, periodo_fim))", ]; foreach ($tabelas as $sql) { try { $pdo->exec($sql); } catch (PDOException $e) {} } // 3. Colunas em profissionais $colsProf = ['cargo_id'=>'INT NULL','mostrar'=>'TINYINT(1) DEFAULT 1','data_nascimento'=>'DATE NULL','cpf'=>'VARCHAR(14) NULL','chave_pix_tipo'=>"ENUM('telefone','cpf','aleatoria','cnpj','email') NULL",'chave_pix'=>'VARCHAR(255) NULL','cep'=>'VARCHAR(10) NULL','endereco'=>'VARCHAR(255) NULL','numero'=>'VARCHAR(20) NULL','complemento'=>'VARCHAR(100) NULL','bairro'=>'VARCHAR(100) NULL','cidade'=>'VARCHAR(100) NULL','estado'=>'CHAR(2) NULL']; try { $existingProf = $pdo->query("SHOW COLUMNS FROM profissionais")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) { $existingProf = []; } foreach (array_diff(array_keys($colsProf), $existingProf) as $col) { try { $pdo->exec("ALTER TABLE profissionais ADD COLUMN $col {$colsProf[$col]}"); $output[] = "Coluna $col adicionada em profissionais."; } catch (PDOException $e) {} } // 4. Colunas em usuarios $colsUser = ['whatsapp'=>'VARCHAR(20)','data_nascimento'=>'DATE NULL','cep'=>'VARCHAR(9)','endereco'=>'VARCHAR(255)','numero'=>'VARCHAR(20)','complemento'=>'VARCHAR(100)','bairro'=>'VARCHAR(100)','cidade'=>'VARCHAR(100)','estado'=>'CHAR(2)']; try { $existingUser = $pdo->query("SHOW COLUMNS FROM usuarios")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) { $existingUser = []; } foreach (array_diff(array_keys($colsUser), $existingUser) as $col) { try { $pdo->exec("ALTER TABLE usuarios ADD COLUMN $col {$colsUser[$col]}"); } catch (PDOException $e) {} } // 5. Colunas novas em configuracoes $allConfigCols = [ 'openai_api_key'=>'VARCHAR(255)','gemini_api_key'=>'VARCHAR(255)','claude_api_key'=>'VARCHAR(255)', 'waba_phone_id'=>'VARCHAR(100) DEFAULT ""','waba_token'=>'VARCHAR(512) DEFAULT ""','waba_business_id'=>'VARCHAR(100) DEFAULT ""','waba_webhook_token'=>'VARCHAR(100) DEFAULT ""', 'lembrete_ativo'=>'TINYINT(1) NOT NULL DEFAULT 0','lembrete_timezone'=>'VARCHAR(50) DEFAULT "America/Sao_Paulo"','lembrete_metodo'=>"ENUM('email','whatsapp','digigo') NOT NULL DEFAULT 'email'", 'hubpay_api_key'=>'VARCHAR(255) DEFAULT ""','hubpay_webhook_secret'=>'VARCHAR(255) DEFAULT ""','hubpay_ambiente'=>"ENUM('test','live') NOT NULL DEFAULT 'test'", 'digigo_base_url'=>'VARCHAR(255) DEFAULT ""','digigo_admin_token'=>'VARCHAR(512) DEFAULT ""','digigo_user_token'=>'VARCHAR(512) DEFAULT ""','digigo_user_name'=>'VARCHAR(100) DEFAULT ""','digigo_connected'=>'TINYINT(1) DEFAULT 0','digigo_envio_ativo'=>'TINYINT(1) DEFAULT 0', 'app_nome'=>'VARCHAR(100) DEFAULT NULL','login_bg_tipo'=>"ENUM('imagem','video') DEFAULT NULL",'login_bg_arquivo'=>'VARCHAR(255) DEFAULT NULL', 'confirmacao_agendamento_ativo'=>'TINYINT(1) NOT NULL DEFAULT 0','confirmacao_agendamento_metodo'=>"ENUM('email','whatsapp','digigo') NOT NULL DEFAULT 'email'", 'timezone'=>'VARCHAR(50) DEFAULT "America/Sao_Paulo"', ]; try { $existingCfg = $pdo->query("SHOW COLUMNS FROM configuracoes")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) { $existingCfg = []; } foreach (array_diff(array_keys($allConfigCols), $existingCfg) as $col) { try { $pdo->exec("ALTER TABLE configuracoes ADD COLUMN $col {$allConfigCols[$col]}"); $output[] = "Coluna $col adicionada em configuracoes."; } catch (PDOException $e) {} } // 6. Colunas agendamentos try { $existingAg = $pdo->query("SHOW COLUMNS FROM agendamentos")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) { $existingAg = []; } $agCols = ['lembrete_enviado'=>'TINYINT(1) NOT NULL DEFAULT 0','hubpay_charge_id'=>'VARCHAR(100) DEFAULT NULL','hubpay_pix_copy_paste'=>'TEXT DEFAULT NULL','hubpay_pix_qrcode'=>'TEXT DEFAULT NULL']; foreach (array_diff(array_keys($agCols), $existingAg) as $col) { try { $pdo->exec("ALTER TABLE agendamentos ADD COLUMN $col {$agCols[$col]}"); $output[] = "Coluna $col adicionada em agendamentos."; } catch (PDOException $e) {} } // 7. ENUM lembretes_config metodo try { $pdo->exec("ALTER TABLE lembretes_config MODIFY COLUMN metodo ENUM('email','whatsapp','digigo') NOT NULL DEFAULT 'email'"); } catch (Exception $e) {} try { $pdo->exec("ALTER TABLE configuracoes MODIFY COLUMN lembrete_metodo ENUM('email','whatsapp','digigo') NOT NULL DEFAULT 'email'"); } catch (Exception $e) {} // 8. whatsapp_available em planos try { $existingPlanos = $pdo->query("SHOW COLUMNS FROM planos")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) { $existingPlanos = []; } if (!in_array('whatsapp_available', $existingPlanos)) { $pdo->exec("ALTER TABLE planos ADD COLUMN whatsapp_available TINYINT(1) DEFAULT 0"); $output[] = 'Coluna whatsapp_available adicionada em planos.'; } // 9. contas_pagar_id em comissoes try { $existingCom = $pdo->query("SHOW COLUMNS FROM comissoes")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) { $existingCom = []; } if (!in_array('contas_pagar_id', $existingCom)) { try { $pdo->exec("ALTER TABLE comissoes ADD COLUMN contas_pagar_id INT NULL"); $output[] = 'Coluna contas_pagar_id adicionada em comissoes.'; } catch (PDOException $e) {} } // 10. Colunas contas_pagar e contas_receber $colsPagar = ['fornecedor_id'=>'INT NULL','profissional_id'=>'INT NULL','forma_pagamento_id'=>'INT NULL','frequencia_id'=>'INT NULL','recorrente'=>'TINYINT(1) DEFAULT 0','total_vezes'=>'INT DEFAULT 0','parcela_atual'=>'INT DEFAULT 0','observacoes'=>'TEXT']; try { $existingPagar = $pdo->query("SHOW COLUMNS FROM contas_pagar")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) { $existingPagar = []; } foreach (array_diff(array_keys($colsPagar), $existingPagar) as $col) { try { $pdo->exec("ALTER TABLE contas_pagar ADD COLUMN $col {$colsPagar[$col]}"); } catch (PDOException $e) {} } $colsReceber = ['descricao'=>'VARCHAR(255)','forma_pagamento_id'=>'INT NULL','frequencia_id'=>'INT NULL','recorrente'=>'TINYINT(1) DEFAULT 0','total_vezes'=>'INT DEFAULT 0','parcela_atual'=>'INT DEFAULT 0','juros'=>'DECIMAL(10,2) DEFAULT 0','mora'=>'DECIMAL(10,2) DEFAULT 0','observacoes'=>'TEXT']; try { $existingReceber = $pdo->query("SHOW COLUMNS FROM contas_receber")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) { $existingReceber = []; } foreach (array_diff(array_keys($colsReceber), $existingReceber) as $col) { try { $pdo->exec("ALTER TABLE contas_receber ADD COLUMN $col {$colsReceber[$col]}"); } catch (PDOException $e) {} } // 11. Campanhas migration try { $existingCamp = $pdo->query("SHOW COLUMNS FROM campanhas")->fetchAll(PDO::FETCH_COLUMN); if (!in_array('titulo', $existingCamp) && in_array('nome', $existingCamp)) { $pdo->exec("ALTER TABLE campanhas CHANGE COLUMN nome titulo VARCHAR(200) NOT NULL"); $output[] = 'Coluna nome -> titulo renomeada em campanhas.'; } $existingCamp = $pdo->query("SHOW COLUMNS FROM campanhas")->fetchAll(PDO::FETCH_COLUMN); if (!in_array('corpo_html', $existingCamp) && in_array('mensagem', $existingCamp)) { $pdo->exec("ALTER TABLE campanhas CHANGE COLUMN mensagem corpo_html TEXT NOT NULL"); $output[] = 'Coluna mensagem -> corpo_html renomeada em campanhas.'; } $campExtraCols = ['assunto'=>'VARCHAR(255) NOT NULL DEFAULT ""','corpo_html'=>'TEXT','enviadas'=>'INT NOT NULL DEFAULT 0','total_destinatarios'=>'INT NOT NULL DEFAULT 0','updated_at'=>'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP']; $existingCamp2 = $pdo->query("SHOW COLUMNS FROM campanhas")->fetchAll(PDO::FETCH_COLUMN); foreach (array_diff(array_keys($campExtraCols), $existingCamp2) as $col) { $pdo->exec("ALTER TABLE campanhas ADD COLUMN $col {$campExtraCols[$col]}"); } } catch (PDOException $e) {} // 12. metodo_envio e destinos em campanhas try { $existingCampCols = $pdo->query("SHOW COLUMNS FROM campanhas")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) { $existingCampCols = []; } $newCampCols = ['metodo_envio'=>"ENUM('email','whatsapp','digigo') NOT NULL DEFAULT 'email'",'destinos'=>'TEXT DEFAULT NULL']; foreach (array_diff(array_keys($newCampCols), $existingCampCols) as $col) { try { $pdo->exec("ALTER TABLE campanhas ADD COLUMN $col {$newCampCols[$col]}"); $output[] = "Coluna $col adicionada em campanhas."; } catch (PDOException $e) {} } // Campanhas - delay/horario/dia_util $colsCampDel = ['delay_min'=>'INT NOT NULL DEFAULT 1','delay_max'=>'INT NOT NULL DEFAULT 5','horario_inicio'=>"TIME DEFAULT '08:00'",'horario_fim'=>"TIME DEFAULT '18:00'",'dia_util_only'=>'TINYINT(1) NOT NULL DEFAULT 1']; foreach (array_diff(array_keys($colsCampDel), $existingCampCols) as $col) { try { $pdo->exec("ALTER TABLE campanhas ADD COLUMN $col {$colsCampDel[$col]}"); $output[] = "Coluna $col adicionada em campanhas."; } catch (PDOException $e) {} } // Modelos mensagem table $pdo->exec("CREATE TABLE IF NOT EXISTS modelos_mensagem ( id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, tipo ENUM('confirmacao','cancelamento','lembrete','confirmado','personalizado') NOT NULL DEFAULT 'personalizado', titulo VARCHAR(200) NOT NULL, mensagem_texto TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE, UNIQUE KEY uk_empresa_tipo (empresa_id, tipo) )"); $output[] = 'Tabela modelos_mensagem criada.'; // Horarios profissionais - intervalo try { $existingHp = $pdo->query("SHOW COLUMNS FROM horarios_profissionais")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) { $existingHp = []; } if (!in_array('intervalo_inicio', $existingHp)) { try { $pdo->exec("ALTER TABLE horarios_profissionais ADD COLUMN intervalo_inicio TIME DEFAULT NULL"); $output[] = 'Coluna intervalo_inicio adicionada em horarios_profissionais.'; } catch (PDOException $e) {} } if (!in_array('intervalo_fim', $existingHp)) { try { $pdo->exec("ALTER TABLE horarios_profissionais ADD COLUMN intervalo_fim TIME DEFAULT NULL"); $output[] = 'Coluna intervalo_fim adicionada em horarios_profissionais.'; } catch (PDOException $e) {} } // digigo_modo if (!in_array('digigo_modo', $existingCfg)) { $pdo->exec("ALTER TABLE configuracoes ADD COLUMN digigo_modo ENUM('proprio','global') NOT NULL DEFAULT 'proprio'"); $output[] = 'Coluna digigo_modo adicionada em configuracoes.'; } // intervalo_padrao default 0 try { $stmtInt = $pdo->query("SHOW COLUMNS FROM configuracoes LIKE 'intervalo_padrao'"); $colInt = $stmtInt->fetch(); if ($colInt && strpos($colInt['Default'] ?? '', '0') === false) { $pdo->exec("ALTER TABLE configuracoes ALTER COLUMN intervalo_padrao SET DEFAULT 0"); $output[] = 'intervalo_padrao alterado para default 0.'; } } catch (Exception $e) {} // Super admin padrao $check = $pdo->query("SELECT id FROM usuarios WHERE email = 'super@agendamento.com'"); if (!$check->fetch()) { $senhaHash = password_hash('admin123', PASSWORD_DEFAULT); $pdo->prepare("INSERT INTO usuarios (empresa_id, grupo_id, nome, email, telefone, senha, tipo, aprovado) VALUES (1,1,'Super Admin','super@agendamento.com','(11) 99999-9999',?,'super_admin',1)")->execute([$senhaHash]); $output[] = 'Super admin criado.'; } // Config SMTP padrao $checkSmtp = $pdo->query("SELECT id FROM config_smtp LIMIT 1"); if (!$checkSmtp->fetch()) { $pdo->exec("INSERT INTO config_smtp (id) VALUES (1)"); $output[] = 'Config SMTP criada.'; } // Planos gateways columns try { $existingPlanosCols = $pdo->query("SHOW COLUMNS FROM planos")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) { $existingPlanosCols = []; } $planosGatewayCols = [ 'gateway_hubpay'=>'TINYINT(1) DEFAULT 1', 'gateway_mercadopago'=>'TINYINT(1) DEFAULT 0', 'gateway_asaas'=>'TINYINT(1) DEFAULT 0', 'gateway_manual'=>'TINYINT(1) DEFAULT 1', ]; foreach (array_diff(array_keys($planosGatewayCols), $existingPlanosCols) as $col) { try { $pdo->exec("ALTER TABLE planos ADD COLUMN $col {$planosGatewayCols[$col]}"); $output[] = "Coluna $col adicionada em planos."; } catch (PDOException $e) {} } // Empresas bloqueio/payment columns try { $existingEmpCols = $pdo->query("SHOW COLUMNS FROM empresas")->fetchAll(PDO::FETCH_COLUMN); } catch (Exception $e) { $existingEmpCols = []; } $empresaNovasCols = [ 'bloqueado'=>'TINYINT(1) NOT NULL DEFAULT 0', 'bloqueado_motivo'=>'VARCHAR(255) DEFAULT NULL', 'bloqueado_em'=>'DATETIME DEFAULT NULL', 'payment_gateway'=>"ENUM('hubpay','mercadopago','asaas','manual') DEFAULT NULL", 'payment_due_date'=>'DATE NULL', 'payment_grace_start'=>'DATETIME DEFAULT NULL', 'ultima_fatura_gerada'=>'DATE NULL', ]; foreach (array_diff(array_keys($empresaNovasCols), $existingEmpCols) as $col) { try { $pdo->exec("ALTER TABLE empresas ADD COLUMN $col {$empresaNovasCols[$col]}"); $output[] = "Coluna $col adicionada em empresas."; } catch (PDOException $e) {} } // Faturas table try { $pdo->exec("CREATE TABLE IF NOT EXISTS faturas ( id INT AUTO_INCREMENT PRIMARY KEY, empresa_id INT NOT NULL, plano_id INT NULL, valor DECIMAL(10,2) NOT NULL, status ENUM('pendente','paga','vencida','cancelada') NOT NULL DEFAULT 'pendente', metodo ENUM('hubpay','mercadopago','asaas','manual') NOT NULL DEFAULT 'manual', transacao_id VARCHAR(255) DEFAULT NULL, data_vencimento DATE NOT NULL, data_pagamento DATE DEFAULT NULL, enviado_em DATETIME DEFAULT NULL, enviado_por VARCHAR(50) DEFAULT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE, FOREIGN KEY (plano_id) REFERENCES planos(id) ON DELETE SET NULL )"); $output[] = 'Tabela faturas criada.'; } catch (PDOException $e) {} return $output; } if ($directAccess): ?> <div class="row"> <div class="col-lg-8 animate-fadeIn"> <div class="card"> <div class="card-header"><h3 class="card-title"><i class="bi bi-cloud-download me-2 text-info"></i>Atualizar Sistema</h3></div> <div class="card-body"> <p class="text-secondary mb-3">Envie um arquivo ZIP contendo a nova versao do sistema. O atualizador vai:</p> <ol class="small text-secondary mb-4"> <li>Extrair o ZIP em uma pasta temporaria</li> <li>Comparar cada arquivo com a versao atual (por hash MD5)</li> <li>Substituir apenas arquivos modificados e criar os novos</li> <li>Ignorar <code>config/database.php</code>, <code>assets/uploads/</code> e <code>backup/</code></li> <li>Executar migracoes de banco de dados automaticamente</li> </ol> <form method="POST" enctype="multipart/form-data"> <div class="mb-3"> <label class="form-label">Arquivo ZIP da atualizacao</label> <input type="file" name="update_zip" class="form-control" accept=".zip" required> </div> <button type="submit" class="btn btn-info" onclick="this.disabled=true;this.innerHTML='<span class=\'spinner-border spinner-border-sm me-1\'></span>Atualizando...';this.form.submit()"> <i class="bi bi-upload me-1"></i>Enviar e Atualizar </button> </form> <hr> <form method="POST" onsubmit="this.querySelector('button').disabled=true;this.querySelector('button').innerHTML='<span class=\'spinner-border spinner-border-sm me-1\'></span>Executando...'"> <button type="submit" name="run_migrations" value="1" class="btn btn-warning"> <i class="bi bi-database-gear me-1"></i>Executar Migracoes de BD </button> </form> <?php if (!empty($migrationOutput)): ?> <div class="alert alert-success mt-3"> <h6 class="fw-semibold"><i class="bi bi-check-circle me-1"></i>Migracoes executadas</h6> <ul class="mb-0 small"><?php foreach ($migrationOutput as $m): ?><li><?= sanitize($m) ?></li><?php endforeach; ?></ul> </div> <?php endif; ?> <?php if (!empty($updatedFiles) || !empty($newFiles) || !empty($errors)): ?> <hr> <?php if (!empty($errors)): ?> <div class="alert alert-danger"> <h6 class="fw-semibold"><i class="bi bi-x-circle me-1"></i>Erros</h6> <ul class="mb-0 small"><?php foreach ($errors as $e): ?><li><?= sanitize($e) ?></li><?php endforeach; ?></ul> </div> <?php endif; ?> <?php if (!empty($log)): ?> <div class="alert alert-success"> <h6 class="fw-semibold"><i class="bi bi-check-circle me-1"></i>Resultado</h6> <ul class="mb-0 small"><?php foreach ($log as $l): ?><li><?= sanitize($l) ?></li><?php endforeach; ?></ul> </div> <?php endif; ?> <?php if (!empty($updatedFiles)): ?> <h6 class="fw-semibold mt-3">Arquivos atualizados (<?= count($updatedFiles) ?>)</h6> <ul class="small text-secondary" style="max-height:200px;overflow-y:auto"> <?php foreach ($updatedFiles as $f): ?><li><?= sanitize($f) ?></li><?php endforeach; ?> </ul> <?php endif; ?> <?php if (!empty($newFiles)): ?> <h6 class="fw-semibold mt-2">Arquivos novos (<?= count($newFiles) ?>)</h6> <ul class="small text-secondary" style="max-height:200px;overflow-y:auto"> <?php foreach ($newFiles as $f): ?><li><?= sanitize($f) ?></li><?php endforeach; ?> </ul> <?php endif; ?> <?php endif; ?> </div> </div> </div> </div> <?php require_once __DIR__ . '/../includes/footer.php'; ?> <?php endif; ?>