diff --git a/api/_debug_health.php b/api/_debug_health.php new file mode 100644 index 0000000..1d18475 --- /dev/null +++ b/api/_debug_health.php @@ -0,0 +1,4 @@ + $_SERVER['REQUEST_URI'] ?? '']); +echo json_encode(['ok'=>true,'uri'=>$_SERVER['REQUEST_URI'] ?? '']); diff --git a/api/delete_template.php b/api/delete_template.php index ff15dcb..4bcee7e 100644 --- a/api/delete_template.php +++ b/api/delete_template.php @@ -1,8 +1,32 @@ $e->getMessage(), 'trace'=>$e->getTraceAsString()]); + if (php_sapi_name() !== 'cli') http_response_code(500); + echo json_encode(['success'=>false,'error'=>$e->getMessage()]); + exit; +}); +register_shutdown_function(function(){ + $err = error_get_last(); + if ($err) { + if (function_exists('writeLog')) writeLog('ERROR', 'Shutdown error in delete_template.php', ['error' => $err]); + if (php_sapi_name() !== 'cli') { + http_response_code(500); + echo json_encode(['success'=>false,'error'=>$err['message']]); + } + } +}); // Bypass authentication when debug=true is provided in query string $debugMode = isset($_GET['debug']) && $_GET['debug'] === 'true'; diff --git a/api/save_survey_response.php b/api/save_survey_response.php new file mode 100644 index 0000000..5c2182a --- /dev/null +++ b/api/save_survey_response.php @@ -0,0 +1,148 @@ + false, 'error' => 'phone/user_id and text are required']); + exit; +} + +// Normalize encoding - try to ensure UTF-8 +if (!function_exists('normalize_text')) { +function normalize_text($s) { + if (empty($s)) return $s; + // If valid UTF-8, attempt to repair common mojibake (Ã, Â) before returning + if (mb_check_encoding($s, 'UTF-8')) { + if (preg_match('/Ã|Â/', $s)) { + $step = @iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $s); + if ($step) { + $step2 = @iconv('ISO-8859-1', 'UTF-8//TRANSLIT', $step); + if ($step2 && mb_check_encoding($step2, 'UTF-8')) return $step2; + } + } + return $s; + } + // Try latin1 -> utf8 + $try = @iconv('ISO-8859-1', 'UTF-8//TRANSLIT', $s); + if ($try && mb_check_encoding($try, 'UTF-8')) return $try; + // Try CP1252 + $try2 = @iconv('CP1252', 'UTF-8//TRANSLIT', $s); + if ($try2 && mb_check_encoding($try2, 'UTF-8')) return $try2; + // Fallback: utf8_decode then re-encode + $auto = mb_convert_encoding($s, 'UTF-8', 'auto'); + // Try to repair common double-encoding mojibake (look for sequences like à or Â) + if (preg_match('/[\xC2\xC3][\x80-\xBF]/', $s) || preg_match('/Ã|Â/', $s)) { + $step = @iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $s); + if ($step) { + $step2 = @iconv('ISO-8859-1', 'UTF-8//TRANSLIT', $step); + if ($step2 && mb_check_encoding($step2, 'UTF-8')) return $step2; + } + } + return $auto; +} +} + +$rawClean = normalize_text($raw); + +// Parse key: value lines +$lines = preg_split('/\r?\n/', $rawClean); +$parsed = []; +foreach ($lines as $line) { + $line = trim($line); + if ($line === '') continue; + // Try separator ':' or '-' + if (strpos($line, ':') !== false) { + list($k,$v) = explode(':', $line, 2); + } elseif (strpos($line, '?') !== false && preg_match('/^\s*([^\?]+\?)\s*(.+)$/', $line, $m)) { + $k = $m[1]; $v = $m[2]; + } else { + // last resort split by whitespace before last token + $parts = preg_split('/\s{2,}/', $line); + if (count($parts) === 2) { $k = $parts[0]; $v = $parts[1]; } else { continue; } + } + $k = trim($k); + $v = trim($v); + // normalize key and value encoding + $k = normalize_text($k); + $v = normalize_text($v); + $parsed[$k] = $v; +} + +// Map expected questions (in Spanish) to fields +if (!function_exists('yesno_to_bool')) { +function yesno_to_bool($s) { + $s = mb_strtolower($s); + // Normalize common utf8 artifacts + $s = str_replace(['\u00a0','\xa0'], ' ', $s); + $s = trim($s); + if (in_array($s, ['si','sí','s','yes','y','true','1'])) return 1; + if (in_array($s, ['no','n','false','0'])) return 0; + return null; +} +} + +$qeasy = null; $qhuman = null; $rating = null; $comment = null; +// try keys in parsed +foreach ($parsed as $k => $v) { + $kl = mb_strtolower($k); + if (strpos($kl, 'fue') !== false && strpos($kl, 'facil') !== false) { + $qeasy = yesno_to_bool($v); + } elseif (strpos($kl, 'asesor') !== false || strpos($kl, 'humano') !== false) { + $qhuman = yesno_to_bool($v); + } elseif (strpos($kl, 'calificaci') !== false || strpos($kl, 'calificacion') !== false || strpos($kl, 'calificaci') !== false) { + $rating = intval(preg_replace('/[^0-9]/','',$v)); + } elseif (strpos($kl, 'coment') !== false) { + $comment = $v; + } +} + +// fallback: try regex in raw +if ($qeasy === null) { + if (preg_match('/fue\s+([^,:\?]+)\s+interact/i', $rawClean, $m)) { $qeasy = yesno_to_bool($m[1]); } +} +if ($qhuman === null) { + if (preg_match('/asesor.*:\s*([^,\n]+)/i', $rawClean, $m)) { $qhuman = yesno_to_bool($m[1]); } +} +if ($rating === null) { + if (preg_match('/calificaci[oó]n[:\s]*([0-9]+)/i', $rawClean, $m)) { $rating = intval($m[1]); } +} +if ($comment === null) { + if (preg_match('/comentario[:\s]*(.+)$/i', $rawClean, $m)) { $comment = trim($m[1]); } +} + +// Insert into DB +try { + $db = Database::getInstance(); + $phoneNormalized = $phone ? preg_replace('/[^0-9+]/','',$phone) : null; + $now = date('Y-m-d H:i:s'); + $data = [ + 'user_id' => $user_id, + 'phone_number' => $phoneNormalized, + 'q_easy_interact' => $qeasy, + 'q_human_resolved' => $qhuman, + 'rating' => $rating, + 'comment' => $comment, + 'raw_text' => $rawClean, + 'created_at' => $now + ]; + + $id = $db->insert('survey_responses', $data); + + if (function_exists('writeLog')) writeLog('INFO', 'Survey response saved', ['id'=>$id, 'phone'=>$phoneNormalized, 'parsed'=>$parsed]); + + echo json_encode(['success'=>true,'id'=>$id,'parsed'=>$parsed]); +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['success'=>false,'error'=>$e->getMessage()]); +} diff --git a/config/config.php b/config/config.php index ee778a9..708c319 100644 --- a/config/config.php +++ b/config/config.php @@ -19,6 +19,7 @@ define('DB_CHARSET', 'utf8mb4'); define('ADMIN_PASSWORD', '$2y$10$IXCY8Sm1xFkfhC6Y67Ahn.QLHxE.sjWfmTEOKFZdCN2a9s9YLVuGW'); // password123 // Función para cargar archivo .env +if (!function_exists('loadEnvFile')) { function loadEnvFile($path) { if (!file_exists($path)) { return false; @@ -41,22 +42,27 @@ function loadEnvFile($path) { } return true; } +} // Cargar .env si existe loadEnvFile(__DIR__ . '/../.env'); // Función helper para obtener variables de entorno +if (!function_exists('env')) { function env($key, $default = null) { return $_ENV[$key] ?? getenv($key) ?: $default; } +} // Incluir clase Database require_once __DIR__ . '/../classes/Database.php'; // Función para verificar si la instalación está completada +if (!function_exists('isInstallationCompleted')) { function isInstallationCompleted() { return file_exists(dirname(__DIR__) . '/.installation_completed'); } +} // Función para obtener configuración desde base de datos function getConfigFromDB($key, $default = null) { @@ -516,11 +522,14 @@ function getAdminUsers() { } // Función para verificar login +if (!function_exists('isUserLoggedIn')) { function isUserLoggedIn() { return isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true; } +} // Función para verificar intentos de login +if (!function_exists('checkLoginAttempts')) { function checkLoginAttempts($ip) { $attemptsFile = dirname(__DIR__) . '/.login_attempts.json'; @@ -544,8 +553,10 @@ function checkLoginAttempts($ip) { return $ipData['attempts'] < MAX_LOGIN_ATTEMPTS; } +} // Función para registrar intento de login fallido +if (!function_exists('recordFailedLogin')) { function recordFailedLogin($ip) { $attemptsFile = dirname(__DIR__) . '/.login_attempts.json'; @@ -566,8 +577,10 @@ function recordFailedLogin($ip) { file_put_contents($attemptsFile, json_encode($attempts)); } +} // Función para limpiar intentos de login exitoso +if (!function_exists('clearLoginAttempts')) { function clearLoginAttempts($ip) { $attemptsFile = dirname(__DIR__) . '/.login_attempts.json'; @@ -577,6 +590,7 @@ function clearLoginAttempts($ip) { file_put_contents($attemptsFile, json_encode($attempts)); } } +} // Autoloader spl_autoload_register(function ($class) { @@ -782,6 +796,7 @@ function validateWhatsAppConfig() { } // Función para logging centralizado +if (!function_exists('writeLog')) { function writeLog($level, $message, $context = []) { if (!ENABLE_LOGGING) return; @@ -803,6 +818,7 @@ function writeLog($level, $message, $context = []) { file_put_contents($logDir . '/system.log', $logMessage, FILE_APPEND | LOCK_EX); } +} // Crear directorios necesarios $requiredDirs = [ diff --git a/scripts/find_template.php b/scripts/find_template.php new file mode 100644 index 0000000..77cb61e --- /dev/null +++ b/scripts/find_template.php @@ -0,0 +1,6 @@ +fetch('SELECT id, name FROM message_templates LIMIT 1'); +if (!$t) { echo "No templates found\n"; exit(1); } +print_r($t);