Files
bot_palmas/setup/migrate.php
T
Lizandro GuarnizoandClaude Sonnet 4.6 860e93a20b feat: módulo de empresa con números WhatsApp y endpoints API por empresa
- DB: company_phones (3 tipos de permiso, límites por usuarios ERP)
- DB: company_endpoints (11 endpoints upload/download configurables)
- DB: erp_active_users en companies (base para calcular límites)
- Company edit: 3 pestañas — General / Números WhatsApp / Endpoints API
- Límites: tipo 1 = usuarios_erp×10, tipo 2+3 = usuarios_erp
- Endpoints: URL configurable + botón Probar + preview de respuesta JSON
- Layout::close() agregado

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 21:57:31 -05:00

263 lines
11 KiB
PHP

<?php
declare(strict_types=1);
require_once dirname(__DIR__) . '/config/env.php';
require_once dirname(__DIR__) . '/config/db.php';
$db = db();
// ─── Tablas ───────────────────────────────────────────────────────────────────
$db->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 UNIQUE,
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',
'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;
");
// ─── 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";