exec(" CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(150) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); $db->exec(" CREATE TABLE IF NOT EXISTS webhook_logs ( id BIGINT AUTO_INCREMENT PRIMARY KEY, event_field VARCHAR(50) NOT NULL DEFAULT 'messages', from_number VARCHAR(30), contact_name VARCHAR(150), message_type VARCHAR(30), message_preview TEXT, raw_payload LONGTEXT, received_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_received_at (received_at), INDEX idx_from_number (from_number), INDEX idx_event_field (event_field) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); $db->exec(" CREATE TABLE IF NOT EXISTS conversations ( id BIGINT AUTO_INCREMENT PRIMARY KEY, message_id VARCHAR(100) NOT NULL UNIQUE, phone_number VARCHAR(30) NOT NULL, contact_name VARCHAR(150), direction ENUM('inbound','outbound') DEFAULT 'inbound', message_type VARCHAR(30), content TEXT, media_id VARCHAR(150), timestamp INT UNSIGNED, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_conv_phone (phone_number), INDEX idx_conv_created (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); $db->exec(" CREATE TABLE IF NOT EXISTS notifications ( id BIGINT AUTO_INCREMENT PRIMARY KEY, type VARCHAR(50) NOT NULL DEFAULT 'new_message', reference_id BIGINT, phone_number VARCHAR(30), message TEXT, is_read TINYINT(1) DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_notif_read (is_read), INDEX idx_notif_created (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); $db->exec(" CREATE TABLE IF NOT EXISTS media_queue ( id BIGINT AUTO_INCREMENT PRIMARY KEY, message_id VARCHAR(100) NOT NULL, media_id VARCHAR(150) NOT NULL, media_type VARCHAR(30), attempts TINYINT DEFAULT 0, processed TINYINT(1) DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_mq_processed (processed), INDEX idx_mq_message_id (message_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); // ─── Tabla de empresas (multi-tenant) ───────────────────────────────────────── $db->exec(" CREATE TABLE IF NOT EXISTS companies ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(150) NOT NULL, display_name VARCHAR(150), phone_number_id VARCHAR(50) NOT NULL DEFAULT '', display_phone VARCHAR(30), api_base_url VARCHAR(500) NOT NULL, api_key VARCHAR(255), bot_type ENUM('ai','normal','hybrid') DEFAULT 'normal', requires_approval TINYINT(1) DEFAULT 0, is_active TINYINT(1) DEFAULT 1, config_json TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_phone_number_id (phone_number_id), INDEX idx_is_active (is_active) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); // ─── Cola de mensajes salientes ────────────────────────────────────────────── $db->exec(" CREATE TABLE IF NOT EXISTS outbound_queue ( id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id INT NOT NULL, to_number VARCHAR(30) NOT NULL, message_type VARCHAR(30) NOT NULL DEFAULT 'text', payload TEXT NOT NULL, status ENUM('queued','sent','delivered','failed') DEFAULT 'queued', attempts TINYINT DEFAULT 0, max_attempts TINYINT DEFAULT 5, last_error TEXT, wam_id VARCHAR(100), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_company_status (company_id, status), INDEX idx_status (status), FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); // ─── Contexto de conversación del bot ───────────────────────────────────────── $db->exec(" CREATE TABLE IF NOT EXISTS bot_context ( id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id INT NOT NULL, phone_number VARCHAR(30) NOT NULL, bot_type ENUM('normal','ai') DEFAULT 'normal', current_node VARCHAR(100), ai_history LONGTEXT, metadata TEXT, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY idx_bot_context (company_id, phone_number), FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); // ─── Cola de aprobación — mensajes que requieren revisión humana ────────────── $db->exec(" CREATE TABLE IF NOT EXISTS pending_approval ( id BIGINT AUTO_INCREMENT PRIMARY KEY, company_id INT NOT NULL, phone_number VARCHAR(30) NOT NULL, contact_name VARCHAR(150), incoming_msg TEXT NOT NULL, bot_response TEXT, bot_type VARCHAR(30), context_json TEXT, status ENUM('pending','approved','rejected') DEFAULT 'pending', reviewed_by VARCHAR(100), review_note TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, reviewed_at DATETIME, INDEX idx_company_status (company_id, status), INDEX idx_status (status), FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); // ─── Agregar company_id a tablas existentes ──────────────────────────────────── try { $db->exec("ALTER TABLE conversations ADD COLUMN company_id INT DEFAULT NULL AFTER id, ADD INDEX idx_conv_company (company_id)"); } catch (\PDOException $e) { // Columna ya existe } try { $db->exec("ALTER TABLE webhook_logs ADD COLUMN company_id INT DEFAULT NULL AFTER id, ADD INDEX idx_wh_company (company_id)"); } catch (\PDOException $e) {} try { $db->exec("ALTER TABLE notifications ADD COLUMN company_id INT DEFAULT NULL AFTER id, ADD INDEX idx_notif_company (company_id)"); } catch (\PDOException $e) {} // ─── Tabla de configuración general ──────────────────────────────────────────── $db->exec(" CREATE TABLE IF NOT EXISTS settings ( `key` VARCHAR(100) NOT NULL PRIMARY KEY, `value` TEXT DEFAULT NULL, `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); // Seed defaults si están vacíos $seedSettings = [ 'whatsapp_access_token' => '', 'whatsapp_app_secret' => '', 'whatsapp_verify_token' => '', 'whatsapp_business_account_id' => '', 'whatsapp_default_phone_number_id' => '', 'ai_provider' => 'mock', 'openai_api_key' => '', 'openai_model' => 'gpt-4o-mini', 'gemini_api_key' => '', 'gemini_model' => 'gemini-2.5-flash', 'claude_api_key' => '', 'claude_model' => 'claude-haiku-4-5', 'ai_max_tokens' => '500', 'ai_default_prompt' => 'Eres un asistente virtual de Palmas360. Responde de forma amable y profesional.', ]; $insertStmt = $db->prepare("INSERT IGNORE INTO settings (`key`, `value`) VALUES (?, ?)"); foreach ($seedSettings as $k => $v) { $insertStmt->execute([$k, $v]); } // ─── Números WhatsApp por empresa ───────────────────────────────────────────── $db->exec(" CREATE TABLE IF NOT EXISTS company_phones ( id INT AUTO_INCREMENT PRIMARY KEY, company_id INT NOT NULL, wa_number VARCHAR(25) NOT NULL, label VARCHAR(100) DEFAULT '', permission_type TINYINT NOT NULL DEFAULT 1 COMMENT '1=solo reporta, 2=solo recibe, 3=ambos', is_active TINYINT NOT NULL DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uq_cp (company_id, wa_number), FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); // ─── Endpoints API configurables por empresa ─────────────────────────────────── $db->exec(" CREATE TABLE IF NOT EXISTS company_endpoints ( id INT AUTO_INCREMENT PRIMARY KEY, company_id INT NOT NULL, endpoint_key VARCHAR(50) NOT NULL, direction ENUM('upload','download') NOT NULL, url VARCHAR(500) DEFAULT '', method VARCHAR(10) DEFAULT 'GET', last_response TEXT, last_called_at DATETIME, is_active TINYINT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uq_ce (company_id, endpoint_key), FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); $db->exec(" CREATE TABLE IF NOT EXISTS multi_company_sessions ( wa_number VARCHAR(20) NOT NULL PRIMARY KEY, companies_json TEXT NOT NULL, selected_id INT DEFAULT NULL, expires_at DATETIME NOT NULL, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_expires (expires_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); // ─── AI usage logs ─────────────────────────────────────────────────────────── $db->exec(" CREATE TABLE IF NOT EXISTS ai_logs ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, company_id INT DEFAULT NULL, phone_number VARCHAR(20) DEFAULT NULL, provider VARCHAR(20) NOT NULL, model VARCHAR(60) NOT NULL DEFAULT '', call_type VARCHAR(30) NOT NULL DEFAULT 'chat', input_preview VARCHAR(500) DEFAULT NULL, output_preview VARCHAR(500) DEFAULT NULL, tokens_in INT DEFAULT NULL, tokens_out INT DEFAULT NULL, duration_ms INT DEFAULT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_company (company_id), INDEX idx_provider (provider), INDEX idx_created (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; "); // ─── Campo erp_active_users en companies ────────────────────────────────────── try { $db->exec("ALTER TABLE companies ADD COLUMN erp_active_users INT DEFAULT 0 AFTER is_active"); } catch (\PDOException $e) {} // ─── Usuario admin por defecto ──────────────────────────────────────────────── $email = 'admin@palmas360.com'; $password = 'Palmas360@Admin26'; $hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]); $stmt = $db->prepare("INSERT IGNORE INTO users (name, email, password) VALUES (?, ?, ?)"); $stmt->execute(['Lizandro', $email, $hash]); echo "\n✅ Tablas creadas correctamente.\n"; echo "👤 Usuario: {$email}\n"; echo "🔑 Contraseña: {$password}\n"; echo "⚠️ Cambia la contraseña después del primer login.\n\n";