<?php

require_once 'config.php';

/*
|--------------------------------------------------------------------------
| WEBHOOK WHATSAPP - INTERLINK CRM
|--------------------------------------------------------------------------
| Incluye:
| - Validación webhook Meta
| - Registro de mensajes entrantes y salientes
| - Multi-cliente por mismo WhatsApp
| - Cliente seleccionado en sesión
| - Consulta de deuda: SUM(invoices.total) - SUM(payments.amount)
| - Últimas facturas por issue_date DESC, id DESC
| - Último pago por paid_at DESC, id DESC
| - Formas de pago
| - Flujo soporte Fibra / Antena WISP
| - Derivación a humano
| - Recepción de comprobantes de transferencia por imagen/PDF
| - Validación básica de comprobantes antes de conciliación
| - Registro de pagos pendientes de conciliación
|--------------------------------------------------------------------------
*/

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $verify_token = $_GET['hub_verify_token'] ?? '';
    $challenge = $_GET['hub_challenge'] ?? '';

    if ($verify_token === VERIFY_TOKEN) {
        echo $challenge;
        exit;
    }

    http_response_code(403);
    exit;
}

$input = file_get_contents("php://input");
log_file('webhook_log.txt', $input);

$data = json_decode($input, true);

if (isset($data['entry'][0]['changes'][0]['value']['messages'][0])) {

    $message = $data['entry'][0]['changes'][0]['value']['messages'][0];

    $from = $message['from'] ?? '';
    $message_type = $message['type'] ?? 'text';
    $text = obtenerTextoMensaje($message);

    guardarMensaje($conn, $from, 'in', $text);

    $to_send = normalizarNumeroSalida($from);
    $session = obtenerSesion($conn, $from);

    // Seguridad CRM: si el teléfono fue reasignado a otro cliente, no conservar customer_id viejo.
    limpiarSesionSiClienteNoCoincide($conn, $from, $session);
    $session = obtenerSesion($conn, $from);

    if (esComprobanteMedia($message)) {
        $respuesta = procesarComprobanteRecibido($conn, $from, $message, $session);
    } else {
        $respuesta = procesarMensaje($conn, $from, $text, $session);
    }

    $envio = responder($to_send, $respuesta);
    if (!empty($envio['ok'])) {
        guardarMensaje(
            $conn,
            $from,
            'out',
            $respuesta,
            json_encode($envio, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
            $envio['provider_message_id'] ?? null
        );
    } else {
        log_file(
            'response_log.txt',
            'MENSAJE NO GUARDADO COMO ENVIADO POR FALLA META' . PHP_EOL .
            json_encode($envio, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
        );
    }
}

http_response_code(200);
exit;



/* ================= MEDIA / COMPROBANTES ================= */

function obtenerTextoMensaje($message)
{
    $type = $message['type'] ?? 'text';

    if ($type === 'text') {
        return trim($message['text']['body'] ?? '');
    }

    if ($type === 'image') {
        $caption = trim($message['image']['caption'] ?? '');
        return $caption !== '' ? $caption : '[imagen recibida]';
    }

    if ($type === 'document') {
        $caption = trim($message['document']['caption'] ?? '');
        $filename = trim($message['document']['filename'] ?? 'documento');
        return $caption !== '' ? $caption : '[documento recibido: ' . $filename . ']';
    }

    return '[' . $type . ' recibido]';
}

function esComprobanteMedia($message)
{
    $type = $message['type'] ?? 'text';
    return in_array($type, ['image', 'document'], true);
}

function procesarComprobanteRecibido($conn, $phone, $message, $session)
{
    $type = $message['type'] ?? '';
    $media = $message[$type] ?? [];

    $media_id = $media['id'] ?? '';
    $mime = $media['mime_type'] ?? '';
    $caption = trim($media['caption'] ?? '');
    $filename = trim($media['filename'] ?? '');

    if ($media_id === '') {
        log_file('error_log_custom.txt', 'Archivo sin media_id. Phone: ' . $phone);
        return "Recibimos tu archivo, pero no pude identificarlo correctamente.\n\n" .
               "Por favor reenviá la imagen o PDF del comprobante.";
    }

    $cliente = resolverClienteActual($conn, $phone, $session);
    $customer_id = $cliente ? (int)$cliente['id'] : null;

    // Si el usuario avisó que paga desde un número no registrado o de un tercero,
    // el comprobante se puede asociar a esa cuenta, pero NO se vincula este WhatsApp
    // como teléfono del cliente ni se habilitan consultas sensibles.
    $es_pago_tercero = false;
    $clientePagoTercero = obtenerClientePagoTerceroDesdeSesion($conn, $session);
    if (!$customer_id && $clientePagoTercero) {
        $cliente = $clientePagoTercero;
        $customer_id = (int)$clientePagoTercero['id'];
        $es_pago_tercero = true;
    }

    $descarga = descargarMediaWhatsapp($media_id, $mime, $filename, $phone);

    if (!$descarga['ok']) {
        return "Recibimos tu archivo, pero no pude descargarlo correctamente.\n\n" .
               "Por favor intentá reenviarlo o comunicate con administración.";
    }

    /*
      Validación segura:
      - Primero intenta OCR local sobre imagen/PDF si tesseract/poppler están instalados.
      - También usa caption y nombre de archivo.
      - Si no detecta señales mínimas, queda como not_receipt.
      - Nunca impacta automáticamente en payments.
    */
    $ocr_text = extraerTextoOCR($descarga['path'], $descarga['mime']);
    $raw_text = trim($caption . "
" . $filename . "
" . $ocr_text);

    log_file(
        'ocr_log.txt',
        "PHONE: " . $phone . PHP_EOL .
        "MEDIA_ID: " . $media_id . PHP_EOL .
        "MIME: " . $descarga['mime'] . PHP_EOL .
        "FILE: " . $descarga['path'] . PHP_EOL .
        "TEXT: " . ($raw_text !== '' ? $raw_text : '[sin texto detectado]')
    );

    $validacion = validarComprobanteBasico($raw_text);
    $datos = extraerDatosComprobanteDesdeTexto($raw_text);

    $receipt_id = guardarComprobantePendiente(
        $conn,
        $phone,
        $customer_id,
        $media_id,
        $descarga['path'],
        $descarga['mime'],
        $filename,
        $caption,
        $datos['amount'],
        $datos['date'],
        $datos['reference'],
        $datos['sender_tax_id'],
        $datos['receipt_date'],
        $raw_text,
        $validacion['status'],
        $validacion['score'],
        $validacion['reason']
    );

    if (!$receipt_id) {
        return "Recibimos el archivo, pero no pude registrarlo en el sistema.\n\n" .
               "Por favor comunicate con administración de INTERLINK.";
    }

    if ($validacion['status'] === 'not_receipt') {
        $session_customer_id = ($cliente && telefonoClienteCoincideWhatsapp($cliente, $phone)) ? $customer_id : null;
        actualizarSesion($conn, $phone, $session_customer_id, $session_customer_id, 'archivo_no_comprobante', null, 'archivo #' . $receipt_id, 0, null);

        return "Recibimos la imagen/documento, pero no pudimos identificarlo como comprobante de transferencia.\n\n" .
               "Para cargar un comprobante, enviá una captura o PDF donde se vea claramente:\n" .
               "- importe\n" .
               "- fecha\n" .
               "- número de operación o comprobante\n" .
               "- destinatario, alias, CBU/CVU o banco/billetera.\n\n" .
               "Importante: aunque el sistema lo detecte, el pago queda sujeto a conciliación administrativa.";
    }

    if ($customer_id) {
        return respuestaComprobanteRegistrado(
            $conn,
            $phone,
            $receipt_id,
            $cliente,
            $validacion['reason'],
            $es_pago_tercero
        );
    }

    $clientes = buscarClientesPorTelefono($conn, $phone);

    if (count($clientes) === 1) {
        $cliente = $clientes[0];
        return respuestaComprobanteRegistrado(
            $conn,
            $phone,
            $receipt_id,
            $cliente,
            $validacion['reason'],
            false
        );
    }

    if (count($clientes) > 1) {
        $context = [
            'receipt_id' => $receipt_id,
            'clientes' => $clientes,
        ];

        actualizarSesion(
            $conn,
            $phone,
            null,
            null,
            'seleccion_cliente_comprobante',
            'esperando_cliente_comprobante',
            'comprobante #' . $receipt_id,
            0,
            json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
        );

        return mensajeSeleccionClienteComprobante($clientes);
    }

    $context = [
        'receipt_id' => $receipt_id,
        'phone' => $phone,
    ];

    actualizarSesion(
        $conn,
        $phone,
        null,
        null,
        'identificar_cliente_comprobante',
        'esperando_dato_cliente',
        'comprobante #' . $receipt_id,
        0,
        json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
    );

    return "Comprobante recibido ✅

" .
           "No pude asociarlo automáticamente porque este WhatsApp no figura en clientes.

" .
           "Para aplicarlo correctamente, respondé con uno de estos datos del titular del servicio:
" .
           "• código de cliente
" .
           "• DNI/CUIT
" .
           "• nombre y apellido
" .
           "• domicilio del servicio

" .
           "Ejemplo: DNI 12345678 o Juan Pérez San Martín 123.

" .
           "Mientras tanto queda pendiente de revisión administrativa.";
}

function obtenerClientePagoTerceroDesdeSesion($conn, $session)
{
    if (!$session) {
        return null;
    }

    $flow = (string)($session['current_flow'] ?? '');
    if (!in_array($flow, ['pago_tercero', 'seleccion_cliente_pago_tercero'], true)) {
        return null;
    }

    $context = json_decode($session['context_json'] ?? '{}', true);
    $customer_id = isset($context['target_customer_id']) ? (int)$context['target_customer_id'] : 0;

    if ($customer_id <= 0) {
        return null;
    }

    return buscarClientePorId($conn, $customer_id);
}

function respuestaComprobanteRegistrado($conn, $phone, $receipt_id, $cliente, $validation_reason = '', $es_pago_tercero = false)
{
    $resultado = actualizarSesionComprobanteAsociadoSeguro($conn, $phone, $cliente, $receipt_id, 'comprobante #' . $receipt_id);
    $nombre = $cliente['full_name'] ?? 'Cliente';

    if (!empty($resultado['registered_phone'])) {
        return "Comprobante recibido ✅\n\n" .
               "Cuenta: " . $nombre . "\n" .
               "Estado: pendiente de conciliación administrativa.\n" .
               "Validación automática: " . ($validation_reason ?: 'archivo recibido') . "\n\n" .
               "Cuando el pago sea confirmado, se impactará en tu cuenta.";
    }

    return "Comprobante recibido para revisión ✅\n\n" .
           "Cuenta indicada: " . $nombre . "\n" .
           "Estado: pendiente de conciliación administrativa.\n" .
           "Validación automática: " . ($validation_reason ?: 'archivo recibido') . "\n\n" .
           "Importante: este WhatsApp no figura como número registrado de la cuenta. Por seguridad no se mostrarán saldo, facturas ni datos del servicio desde este número hasta validación administrativa.";
}

function procesarIdentificacionClienteComprobante($conn, $phone, $text, $session)
{
    $context = json_decode($session['context_json'] ?? '{}', true);
    $receipt_id = isset($context['receipt_id']) ? (int)$context['receipt_id'] : 0;

    if (!$receipt_id) {
        actualizarSesion($conn, $phone, null, null, null, null, $text, 0, null);
        return "La identificación del comprobante venció o no está disponible.

" . menuPrincipal();
    }

    $t = quitarAcentos(mb_strtolower(trim((string)$text), 'UTF-8'));

    if (esAgradecimientoOCierre($text) || textoSinDatosClienteParaPago($t) || esNegacionClienteNoRegistrado($t)) {
        actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'post_comprobante_sin_cuenta', $text, 1, json_encode(['receipt_id' => $receipt_id], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        return "Perfecto. El comprobante queda pendiente para revisión administrativa ✅

" .
               "No voy a seguir pidiendo datos de cliente desde este WhatsApp.
" .
               "Si administración necesita confirmar algo, te contactará por este mismo medio.

" .
               "Para iniciar otra consulta escribí: menú.";
    }

    if ($t === 'operador' || $t === 'humano' || $t === 'persona') {
        actualizarSesion($conn, $phone, null, null, 'comprobante_pago', null, $text, 1, null);
        return "Perfecto. El comprobante quedó pendiente y será revisado por administración.";
    }

    $clientes = buscarClientesParaComprobante($conn, $text);

    if (count($clientes) === 0) {
        actualizarSesion(
            $conn,
            $phone,
            null,
            null,
            'identificar_cliente_comprobante',
            'esperando_dato_cliente',
            $text,
            1,
            json_encode(['receipt_id' => $receipt_id, 'last_search' => $text], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
        );

        return "No encontré una cuenta con ese dato.

" .
               "El comprobante queda pendiente para revisión administrativa.
" .
               "Podés responder con otro dato del titular: código de cliente, DNI/CUIT, nombre completo o domicilio.";
    }

    if (count($clientes) === 1) {
        $cliente = $clientes[0];
        $customer_id = isset($cliente['id']) ? (int)$cliente['id'] : 0;
        if ($customer_id > 0) {
            return respuestaComprobanteAsociadoManual($conn, $phone, $receipt_id, $cliente);
        }
    }

    $context = [
        'receipt_id' => $receipt_id,
        'clientes' => $clientes,
    ];

    actualizarSesion(
        $conn,
        $phone,
        null,
        null,
        'seleccion_cliente_comprobante',
        'esperando_cliente_comprobante',
        $text,
        0,
        json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
    );

    return mensajeSeleccionClienteComprobante($clientes);
}

function procesarSeleccionClienteComprobante($conn, $phone, $text, $session)

{
    $opcion = (int)trim($text);
    $context = json_decode($session['context_json'] ?? '{}', true);
    $clientes = $context['clientes'] ?? [];
    $receipt_id = isset($context['receipt_id']) ? (int)$context['receipt_id'] : 0;

    if (!$receipt_id || !is_array($clientes) || count($clientes) === 0) {
        actualizarSesion($conn, $phone, null, null, null, null, $text, 0, null);
        return "La selección del comprobante venció o no está disponible.\n\n" . menuPrincipal();
    }

    if ($opcion < 1 || $opcion > count($clientes)) {
        return mensajeSeleccionClienteComprobante($clientes);
    }

    $cliente = $clientes[$opcion - 1];
    $customer_id = isset($cliente['id']) ? (int)$cliente['id'] : null;

    if (!$customer_id) {
        return "No pude asociar el comprobante a esa cuenta. Por favor intentá nuevamente.";
    }

    return respuestaComprobanteAsociadoManual($conn, $phone, $receipt_id, $cliente);
}

function mensajeSeleccionClienteComprobante($clientes)
{
    $respuesta = "Comprobante recibido ✅

" .
                 "Encontré más de una cuenta posible.
" .
                 "Respondé con el número de la cuenta a la que corresponde el comprobante:

";

    $n = 1;
    foreach ($clientes as $cliente) {
        $nombre = $cliente['full_name'] ?? 'Cliente';
        $codigo = trim((string)($cliente['customer_code'] ?? ''));
        $direccion = trim((string)($cliente['address'] ?? ''));
        $respuesta .= $n . ") " . $nombre;
        if ($codigo !== '') {
            $respuesta .= " | Cod: " . $codigo;
        }
        if ($direccion !== '') {
            $respuesta .= " | " . $direccion;
        }
        $respuesta .= "
";
        $n++;
    }

    return trim($respuesta);
}

function descargarMediaWhatsapp($media_id, $mime = '', $filename = '', $phone = '')
{
    global $conn;
    $cfg = obtenerWhatsappRuntimeConfig($conn ?? null);
    $version = $cfg['api_version'] ?: 'v22.0';
    $token = $cfg['token'];
    if ($token === '') {
        log_file('error_log_custom.txt', 'No se pudo descargar media: falta token WhatsApp.');
        return ['ok' => false];
    }
    $info_url = "https://graph.facebook.com/" . $version . "/" . urlencode($media_id);
    $headers = ["Authorization: Bearer " . $token];

    $ch = curl_init($info_url);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    $info_response = curl_exec($ch);
    $info_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $info_error = curl_error($ch);
    curl_close($ch);

    if ($info_code !== 200 || !$info_response) {
        log_file('error_log_custom.txt', "Error obteniendo media_url. HTTP: {$info_code}. ERROR: {$info_error}. RESPONSE: {$info_response}");
        return ['ok' => false];
    }

    $info = json_decode($info_response, true);
    $media_url = $info['url'] ?? '';
    $mime_real = $info['mime_type'] ?? $mime;

    if ($media_url === '') {
        log_file('error_log_custom.txt', 'Meta no devolvió media_url para media_id: ' . $media_id);
        return ['ok' => false];
    }

    $ch = curl_init($media_url);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 60);
    $content = curl_exec($ch);
    $download_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $download_error = curl_error($ch);
    curl_close($ch);

    if ($download_code !== 200 || $content === false || $content === '') {
        log_file('error_log_custom.txt', "Error descargando media. HTTP: {$download_code}. ERROR: {$download_error}");
        return ['ok' => false];
    }

    $dir = __DIR__ . '/receipts/' . date('Y') . '/' . date('m');

    if (!is_dir($dir)) {
        mkdir($dir, 0775, true);
    }

    $ext = extensionDesdeMime($mime_real);
    $safe_phone = preg_replace('/\D+/', '', $phone);
    $safe_media = preg_replace('/[^a-zA-Z0-9_\-]/', '', $media_id);
    $base = date('Ymd_His') . '_' . $safe_phone . '_' . substr($safe_media, 0, 32);

    if ($filename !== '') {
        $name_ext = pathinfo($filename, PATHINFO_EXTENSION);
        if ($name_ext !== '') {
            $ext = strtolower($name_ext);
        }
    }

    $file = $dir . '/' . $base . '.' . $ext;
    file_put_contents($file, $content);

    return [
        'ok' => true,
        'path' => $file,
        'mime' => $mime_real,
    ];
}

function extensionDesdeMime($mime)
{
    $mime = strtolower((string)$mime);

    if (strpos($mime, 'pdf') !== false) {
        return 'pdf';
    }

    if (strpos($mime, 'png') !== false) {
        return 'png';
    }

    if (strpos($mime, 'webp') !== false) {
        return 'webp';
    }

    if (strpos($mime, 'jpeg') !== false || strpos($mime, 'jpg') !== false) {
        return 'jpg';
    }

    return 'bin';
}

function extraerTextoOCR($file_path, $mime)
{
    if (!$file_path || !file_exists($file_path)) {
        return '';
    }

    $mime = strtolower((string)$mime);
    $texto = '';

    // PDF: intenta pdftotext. Sirve para PDFs digitales. Para PDF escaneado no alcanza.
    if (strpos($mime, 'pdf') !== false || strtolower(pathinfo($file_path, PATHINFO_EXTENSION)) === 'pdf') {
        $cmd = 'pdftotext -layout ' . escapeshellarg($file_path) . ' - 2>/dev/null';
        $texto = trim((string)shell_exec($cmd));

        if ($texto !== '') {
            return $texto;
        }

        // Intento básico de OCR para la primera página si pdftoppm y tesseract están disponibles.
        $tmp_base = sys_get_temp_dir() . '/wa_pdf_' . md5($file_path . microtime(true));
        $cmd_img = 'pdftoppm -f 1 -singlefile -png ' . escapeshellarg($file_path) . ' ' . escapeshellarg($tmp_base) . ' 2>/dev/null';
        shell_exec($cmd_img);
        $tmp_png = $tmp_base . '.png';

        if (file_exists($tmp_png)) {
            $cmd_ocr = 'tesseract ' . escapeshellarg($tmp_png) . ' stdout -l spa+eng --psm 6 2>/dev/null';
            $texto = trim((string)shell_exec($cmd_ocr));
            @unlink($tmp_png);
            return $texto;
        }

        return '';
    }

    // Imagen: OCR local con tesseract.
    if (
        strpos($mime, 'image/') !== false ||
        in_array(strtolower(pathinfo($file_path, PATHINFO_EXTENSION)), ['jpg', 'jpeg', 'png', 'webp'], true)
    ) {
        $cmd = 'tesseract ' . escapeshellarg($file_path) . ' stdout -l spa+eng --psm 6 2>/dev/null';
        $texto = trim((string)shell_exec($cmd));
        return $texto;
    }

    return '';
}

function validarComprobanteBasico($texto_completo)
{
    $texto = mb_strtolower((string)$texto_completo, 'UTF-8');
    $texto_normalizado = quitarAcentos($texto);
    $solo_numeros = preg_replace('/\D+/', '', $texto);

    $score = 0;
    $motivos = [];

    // Señales generales de comprobante o transferencia
    $palabras_clave = [
        'transferencia',
        'comprobante',
        'operacion',
        'operación',
        'pago',
        'pagaste',
        'enviado',
        'enviada',
        'realizada',
        'exitosa',
        'aprobada',
        'importe',
        'monto',
        'fecha',
        'alias',
        'cbu',
        'cvu',
        'cuenta',
        'titular',
        'destinatario',
        'mercado pago',
        'banco',
        'uala',
        'naranja x',
        'modo'
    ];

    foreach ($palabras_clave as $palabra) {
        if (strpos($texto_normalizado, quitarAcentos($palabra)) !== false) {
            $score += 10;
            $motivos[] = 'Contiene palabra clave: ' . $palabra;
        }
    }

    // Datos propios de INTERLINK / cuenta bancaria
    $identificadores_texto = [
        'soluciones.dc' => 40,
        'soluciones dc' => 35,
        'timoteo david chavez' => 45,
        'timoteo david' => 35,
        'david chavez' => 35,
        'chavez timoteo' => 35,
        'c ahorros pago remuneraciones' => 25,
        'ahorros pago remuneraciones' => 25
    ];

    foreach ($identificadores_texto as $dato => $puntos) {
        $dato_normalizado = quitarAcentos(mb_strtolower($dato, 'UTF-8'));
        if (strpos($texto_normalizado, $dato_normalizado) !== false) {
            $score += $puntos;
            $motivos[] = 'Coincide dato de cuenta: ' . $dato;
        }
    }

    // Búsqueda numérica flexible: detecta aunque OCR quite guiones o espacios
    if (strpos($solo_numeros, '2850106840094091240018') !== false) {
        $score += 50;
        $motivos[] = 'Coincide CBU destino';
    }

    if (strpos($solo_numeros, '20277496748') !== false) {
        $score += 45;
        $motivos[] = 'Coincide CUIT titular';
    }

    if (strpos($solo_numeros, '410609409124001') !== false) {
        $score += 40;
        $motivos[] = 'Coincide número de cuenta';
    }

    // Variante con guiones del CUIT en texto
    if (strpos($texto_normalizado, '20-27749674-8') !== false) {
        $score += 45;
        $motivos[] = 'Coincide CUIT titular';
    }

    // Detectar posible importe: $ 12.345,67 / 12345.67 / 12345
    if (preg_match('/(?:\$|ars|importe|monto)?\s*[0-9]{1,3}(?:[\.\s][0-9]{3})+(?:,[0-9]{2})?|(?:\$|ars|importe|monto)\s*[0-9]+(?:[\.,][0-9]{2})?/iu', $texto)) {
        $score += 15;
        $motivos[] = 'Detecta posible importe';
    }

    // Detectar fecha dd/mm/yyyy, dd-mm-yyyy o yyyy-mm-dd
    if (preg_match('/\b\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}\b/', $texto) || preg_match('/\b\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}\b/', $texto)) {
        $score += 10;
        $motivos[] = 'Detecta posible fecha';
    }

    // Debe haber señal bancaria fuerte o combinación suficiente de señales.
    // No se aprueba como pago: solo needs_review.
    if ($score >= 40) {
        return [
            'status' => 'needs_review',
            'score' => $score,
            'reason' => implode('; ', array_unique($motivos))
        ];
    }

    return [
        'status' => 'not_receipt',
        'score' => $score,
        'reason' => implode('; ', array_unique($motivos)) ?: 'No se detectaron datos suficientes de comprobante'
    ];
}

function quitarAcentos($texto)
{
    return str_replace(
        ['á','é','í','ó','ú','ñ','Á','É','Í','Ó','Ú','Ñ'],
        ['a','e','i','o','u','n','a','e','i','o','u','n'],
        $texto
    );
}

function guardarComprobantePendiente($conn, $phone, $customer_id, $media_id, $file_path, $mime, $filename, $caption, $amount, $date, $reference, $sender_tax_id, $receipt_date, $raw_text, $status = 'needs_review', $validation_score = null, $validation_reason = null)
{
    if (!in_array($status, ['pending', 'needs_review', 'not_receipt', 'approved', 'rejected'], true)) {
        $status = 'needs_review';
    }

    $stmt = $conn->prepare("
        INSERT INTO whatsapp_payment_receipts
        (
            phone,
            customer_id,
            whatsapp_media_id,
            file_path,
            file_mime,
            original_filename,
            caption,
            extracted_amount,
            extracted_date,
            extracted_reference,
            sender_tax_id,
            receipt_date,
            raw_text,
            status,
            validation_score,
            validation_reason,
            created_at
        )
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
    ");

    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error guardarComprobantePendiente prepare: ' . $conn->error);
        return null;
    }

    $stmt->bind_param(
        "sisssssdssssssis",
        $phone,
        $customer_id,
        $media_id,
        $file_path,
        $mime,
        $filename,
        $caption,
        $amount,
        $date,
        $reference,
        $sender_tax_id,
        $receipt_date,
        $raw_text,
        $status,
        $validation_score,
        $validation_reason
    );

    if (!$stmt->execute()) {
        log_file('error_log_custom.txt', 'Error guardarComprobantePendiente execute: ' . $stmt->error);
        return null;
    }

    return $conn->insert_id;
}

function actualizarCustomerComprobante($conn, $receipt_id, $customer_id)
{
    $stmt = $conn->prepare("UPDATE whatsapp_payment_receipts SET customer_id = ? WHERE id = ? LIMIT 1");

    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error actualizarCustomerComprobante prepare: ' . $conn->error);
        return false;
    }

    $stmt->bind_param("ii", $customer_id, $receipt_id);
    return $stmt->execute();
}

function extraerDatosComprobanteDesdeTexto($text)
{
    $text = trim((string)$text);
    $amount = null;
    $date = null;
    $reference = null;
    $sender_tax_id = null;
    $receipt_date = null;

    if ($text !== '') {
        if (preg_match('/(?:\$|ars|pesos|importe|monto)?\s*([0-9]{1,3}(?:[\.\s][0-9]{3})*(?:,[0-9]{2})|[0-9]+(?:\.[0-9]{2})?)/iu', $text, $m)) {
            $number = str_replace([' ', '.'], '', $m[1]);
            $number = str_replace(',', '.', $number);
            $amount = is_numeric($number) ? (float)$number : null;
        }

        // Fecha del comprobante: dd/mm/yyyy, dd-mm-yyyy o yyyy-mm-dd.
        if (preg_match('/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})/', $text, $m)) {
            $d = str_pad($m[1], 2, '0', STR_PAD_LEFT);
            $mo = str_pad($m[2], 2, '0', STR_PAD_LEFT);
            $y = $m[3];
            if (strlen($y) === 2) {
                $y = '20' . $y;
            }
            $date = $y . '-' . $mo . '-' . $d;
            $receipt_date = $date;
        } elseif (preg_match('/(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})/', $text, $m)) {
            $y = $m[1];
            $mo = str_pad($m[2], 2, '0', STR_PAD_LEFT);
            $d = str_pad($m[3], 2, '0', STR_PAD_LEFT);
            $date = $y . '-' . $mo . '-' . $d;
            $receipt_date = $date;
        }

        if (preg_match('/(?:referencia|ref|operaci[oó]n|comprobante|transacci[oó]n)\s*[:#\-]?\s*([a-z0-9\-\.]+)/iu', $text, $m)) {
            $reference = $m[1];
        }

        $sender_tax_id = extraerIdentificadorFiscalEmisor($text);
    }

    return [
        'amount' => $amount,
        'date' => $date,
        'reference' => $reference,
        'sender_tax_id' => $sender_tax_id,
        'receipt_date' => $receipt_date,
    ];
}

function extraerIdentificadorFiscalEmisor($text)
{
    $text = (string)$text;

    // Busca CUIT/CUIL con guiones: 20-12345678-9 / 27-12345678-4 / 30-12345678-9.
    if (preg_match('/\b(20|23|24|27|30|33|34)[\-\s]?([0-9]{8})[\-\s]?([0-9])\b/', $text, $m)) {
        $cuit = $m[1] . $m[2] . $m[3];

        // Evitar guardar el CUIT propio como emisor si el comprobante muestra el destinatario INTERLINK.
        if ($cuit !== '20277496748') {
            return $cuit;
        }
    }

    // Busca DNI explícito.
    if (preg_match('/(?:dni|documento|doc\.?|cuil|cuit)\s*[:#\-]?\s*([0-9\.]{7,13})/iu', $text, $m)) {
        $n = preg_replace('/\D+/', '', $m[1]);
        if ($n !== '' && $n !== '20277496748') {
            return $n;
        }
    }

    return null;
}

/* ================= FUNCIONES PRINCIPALES ================= */

function procesarMensaje($conn, $phone, $text, $session)
{
    $intent = detectarIntent($text);
    $flow = $session['current_flow'] ?? null;
    $telefonoRegistrado = telefonoTieneClienteRegistrado($conn, $phone);

    // Seguridad conversacional para números no registrados:
    // si una sesión vieja quedó esperando datos de un cliente, no debe condicionar
    // indefinidamente una nueva consulta comercial o de instalación.
    if (!$telefonoRegistrado && $session && esFlujoNoRegistrado($flow) && sesionNoRegistradaExpirada($session, 45)) {
        actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'esperando_opcion', '[sesion no registrada expirada]', 0, null);
        $session = obtenerSesion($conn, $phone);
        $flow = $session['current_flow'] ?? null;
    }

    /*
    |----------------------------------------------------------
    | Comandos globales reales.
    | "menu" y "cambiar cuenta" cortan cualquier flujo.
    | Los números NO se interpretan como menú principal hasta revisar
    | si el bot está esperando una selección o una respuesta técnica.
    |----------------------------------------------------------
    */
    if ($intent === 'menu') {
        if (!$telefonoRegistrado) {
            actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'esperando_opcion', $text, 0, null);
            return menuContactoNoRegistrado();
        }

        actualizarSesion($conn, $phone, null, null, null, null, $text, 0, null);
        return menuPrincipal($conn, $phone, null);
    }

    if ($intent === 'cambiar_cuenta') {
        actualizarSesion($conn, $phone, null, null, null, null, $text, 0, null);
        return "Perfecto. Reinicié la selección de cuenta.\n\n" . menuPrincipal($conn, $phone, null);
    }

    /*
    |----------------------------------------------------------
    | Flujos bloqueantes por estado.
    | Si el bot pidió elegir cuenta y el cliente responde 1, ese 1
    | significa cuenta #1, no Facturación.
    |----------------------------------------------------------
    */
    if ($session && $flow === 'seleccion_cliente_comprobante') {
        return procesarSeleccionClienteComprobante($conn, $phone, $text, $session);
    }

    if ($session && $flow === 'identificar_cliente_comprobante') {
        return procesarIdentificacionClienteComprobante($conn, $phone, $text, $session);
    }

    // Un cliente registrado también puede pagar una cuenta de otra persona.
    // En ese caso no se usa su cuenta por defecto: se pide identificar al titular del pago.
    if (esSolicitudPagoDeOtraCuenta(normalizarTextoBot($text))) {
        return iniciarPagoTercero($conn, $phone, $text, $intent);
    }

    /*
    |----------------------------------------------------------
    | Números no registrados / terceros.
    | Regla de seguridad: no mostrar saldo, facturas, servicios ni datos
    | de cuenta si el WhatsApp no coincide con customers.phone.
    | Puede crear una solicitud, identificar una cuenta para pago o pedir
    | cambio de número, pero siempre queda pendiente de validación humana.
    |----------------------------------------------------------
    */
    if (!$telefonoRegistrado) {
        if ($session && esFlujoNoRegistrado($flow)) {
            return procesarFlujoNoRegistrado($conn, $phone, $text, $session, $intent);
        }

        return procesarEntradaNoRegistrada($conn, $phone, $text, $session, $intent);
    }

    if ($session && $flow === 'seleccion_cliente') {
        return procesarSeleccionCliente($conn, $phone, $text, $session);
    }

    if ($session && $flow === 'soporte_admin_pendiente') {
        return procesarSoporteAdminPendiente($conn, $phone, $text, $session);
    }

    /*
    |----------------------------------------------------------
    | Flujo de soporte técnico.
    | Dentro de soporte, 1 = Fibra y 2 = Antena. No deben caer en
    | Facturación/Nueva instalación aunque coincidan con el menú principal.
    | Solo se sale con texto explícito: facturación, saldo, operador, etc.
    |----------------------------------------------------------
    */
    if ($session && $flow === 'soporte') {
        if (esSalidaTextualDeSoporte($text, $intent)) {
            return ejecutarIntentPorSalidaDeFlujo($conn, $phone, $text, $session, $intent);
        }

        return procesarSoporte($conn, $phone, $text, $session);
    }

    /*
    |----------------------------------------------------------
    | Menú Facturación de segundo nivel.
    | Dentro de facturación, 1/2/3 significan última factura, último pago
    | y forma de pago. Para salir a soporte debe escribir soporte/no tengo internet.
    |----------------------------------------------------------
    */
    if ($session && $flow === 'facturacion') {
        if (esSalidaTextualDeFacturacion($text, $intent)) {
            return ejecutarIntentPorSalidaDeFlujo($conn, $phone, $text, $session, $intent);
        }

        return procesarMenuFacturacion($conn, $phone, $text, $session, $intent);
    }

    /*
    |----------------------------------------------------------
    | Menú principal y accesos directos para teléfonos registrados.
    |----------------------------------------------------------
    */
    if ($intent === 'facturacion') {
        actualizarSesion($conn, $phone, obtenerCustomerIdSesion($session), obtenerSelectedCustomerIdSesion($session), 'facturacion', null, $text, 0, null);
        return menuFacturacion();
    }

    if ($intent === 'formas_pago') {
        actualizarSesion($conn, $phone, obtenerCustomerIdSesion($session), obtenerSelectedCustomerIdSesion($session), 'formas_pago', null, $text, 0, null);
        return respuestaFormasPago();
    }

    if ($intent === 'instalacion') {
        actualizarSesion($conn, $phone, obtenerCustomerIdSesion($session), obtenerSelectedCustomerIdSesion($session), 'instalacion', null, $text, 1, null);
        return "Gracias por tu interés en INTERLINK 👋\n\n" .
               "Voy a derivar tu consulta al área comercial para verificar cobertura y coordinar la instalación.";
    }

    if ($intent === 'humano') {
        return ejecutarIntentConCliente($conn, $phone, $text, $session, 'humano');
    }

    if ($intent === 'deuda') {
        return ejecutarIntentConCliente($conn, $phone, $text, $session, 'deuda');
    }

    if ($intent === 'ultima_factura') {
        return ejecutarIntentConCliente($conn, $phone, $text, $session, 'ultima_factura');
    }

    if ($intent === 'ultimo_pago') {
        return ejecutarIntentConCliente($conn, $phone, $text, $session, 'ultimo_pago');
    }

    if ($intent === 'soporte') {
        return ejecutarIntentConCliente($conn, $phone, $text, $session, 'soporte');
    }

    return menuPrincipal($conn, $phone, $session);
}

function esTextoNumericoSimple($text)
{
    return preg_match('/^\s*[0-9]+\s*$/', (string)$text) === 1;
}

function esSalidaTextualDeSoporte($text, $intent)
{
    if (esTextoNumericoSimple($text)) {
        return false;
    }

    return in_array($intent, ['facturacion', 'deuda', 'ultima_factura', 'ultimo_pago', 'formas_pago', 'humano', 'instalacion'], true);
}

function esSalidaTextualDeFacturacion($text, $intent)
{
    if (esTextoNumericoSimple($text)) {
        return false;
    }

    return in_array($intent, ['soporte', 'humano', 'instalacion'], true);
}

function ejecutarIntentPorSalidaDeFlujo($conn, $phone, $text, $session, $intent)
{
    if ($intent === 'facturacion') {
        actualizarSesion($conn, $phone, obtenerCustomerIdSesion($session), obtenerSelectedCustomerIdSesion($session), 'facturacion', null, $text, 0, null);
        return menuFacturacion();
    }

    if ($intent === 'formas_pago') {
        actualizarSesion($conn, $phone, obtenerCustomerIdSesion($session), obtenerSelectedCustomerIdSesion($session), 'formas_pago', null, $text, 0, null);
        return respuestaFormasPago();
    }

    if ($intent === 'instalacion') {
        actualizarSesion($conn, $phone, obtenerCustomerIdSesion($session), obtenerSelectedCustomerIdSesion($session), 'instalacion', null, $text, 1, null);
        return "Gracias por tu interés en INTERLINK 👋\n\n" .
               "Voy a derivar tu consulta al área comercial para verificar cobertura y coordinar la instalación.";
    }

    if (in_array($intent, ['deuda', 'ultima_factura', 'ultimo_pago', 'humano', 'soporte'], true)) {
        return ejecutarIntentConCliente($conn, $phone, $text, $session, $intent);
    }

    return menuPrincipal($conn, $phone, $session);
}

function procesarSoporteAdminPendiente($conn, $phone, $text, $session)
{
    $t = quitarAcentos(mb_strtolower(trim((string)$text), 'UTF-8'));

    if ($t === '1' || strpos($t, 'saldo') !== false || strpos($t, 'deuda') !== false || strpos($t, 'debo') !== false) {
        return ejecutarIntentConCliente($conn, $phone, $text, $session, 'deuda');
    }

    if ($t === '2' || strpos($t, 'forma') !== false || strpos($t, 'alias') !== false || strpos($t, 'pagar') !== false || strpos($t, 'pago') !== false) {
        actualizarSesion($conn, $phone, obtenerCustomerIdSesion($session), obtenerSelectedCustomerIdSesion($session), 'formas_pago', null, $text, 0, null);
        return respuestaFormasPago();
    }

    if ($t === '3' || strpos($t, 'comprobante') !== false || strpos($t, 'transferencia') !== false || strpos($t, 'envie') !== false || strpos($t, 'enviar') !== false) {
        actualizarSesion($conn, $phone, obtenerCustomerIdSesion($session), obtenerSelectedCustomerIdSesion($session), 'comprobante_pago', null, $text, 0, null);
        return "Perfecto. Enviá por este chat una imagen o PDF del comprobante.\n\n" .
               "El sistema lo registrará como pendiente de conciliación administrativa.\n" .
               "Importante: el servicio se normaliza cuando el pago quede confirmado.";
    }

    if ($t === '4' || strpos($t, 'operador') !== false || strpos($t, 'persona') !== false || strpos($t, 'humano') !== false) {
        return ejecutarIntentConCliente($conn, $phone, $text, $session, 'humano');
    }

    return menuSoporteAdminPendiente();
}

function menuSoporteAdminPendiente()
{
    return "Para continuar respondé con una opción:\n\n" .
           "1️⃣ Consultar saldo\n" .
           "2️⃣ Ver formas de pago\n" .
           "3️⃣ Enviar comprobante\n" .
           "4️⃣ Hablar con operador\n\n" .
           "Para volver al inicio, escribí: menú";
}

function ejecutarIntentConCliente($conn, $phone, $text, $session, $intent)
{
    $cliente = resolverClienteActual($conn, $phone, $session);

    if ($cliente) {
        return responderIntentCliente($conn, $phone, $text, $cliente, $intent);
    }

    $clientes = buscarClientesPorTelefono($conn, $phone);
    $cantidad = count($clientes);

    if ($cantidad === 0) {
        // Teléfono no registrado: no mostrar datos de cuenta por inferencia.
        // Se deriva al flujo seguro para no clientes / terceros.
        return procesarEntradaNoRegistrada($conn, $phone, $text, $session, $intent);
    }

    if ($cantidad === 1) {
        $cliente = $clientes[0];
        return responderIntentCliente($conn, $phone, $text, $cliente, $intent);
    }

    $context_json = json_encode($clientes, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    actualizarSesion($conn, $phone, null, null, 'seleccion_cliente', $intent, $text, 0, $context_json);

    return mensajeSeleccionCliente($clientes, $intent);
}

function procesarSeleccionCliente($conn, $phone, $text, $session)
{
    $opcion = (int)trim($text);
    $clientes = json_decode($session['context_json'] ?? '[]', true);
    $intentPendiente = $session['current_step'] ?? null;

    if (!$intentPendiente || !is_array($clientes) || count($clientes) === 0) {
        actualizarSesion($conn, $phone, null, null, null, null, $text, 0, null);
        return "La selección anterior venció o no está disponible.\n\n" . menuPrincipal();
    }

    if ($opcion < 1 || $opcion > count($clientes)) {
        return mensajeSeleccionCliente($clientes, $intentPendiente);
    }

    $cliente = $clientes[$opcion - 1];
    $customer_id = isset($cliente['id']) ? (int)$cliente['id'] : null;

    actualizarSesion($conn, $phone, $customer_id, $customer_id, null, null, $text, 0, null);

    return responderIntentCliente($conn, $phone, $text, $cliente, $intentPendiente);
}

function responderIntentCliente($conn, $phone, $text, $cliente, $intent)
{
    $customer_id = (int)$cliente['id'];
    $nombre = $cliente['full_name'] ?? 'Cliente';

    if ($intent === 'humano') {
        actualizarSesion($conn, $phone, $customer_id, $customer_id, null, null, $text, 1, null);

        return "Entiendo. Voy a derivar tu consulta a una persona de INTERLINK.\n\n" .
               "Cuenta seleccionada: " . $nombre . "\n" .
               "Un operador revisará tu caso.";
    }

    if ($intent === 'deuda') {
        actualizarSesion($conn, $phone, $customer_id, $customer_id, 'deuda', null, $text, 0, null);

        $saldo = consultarSaldoClientePorId($conn, $customer_id);

        if ($saldo) {
            $importe = number_format((float)$saldo['saldo'], 2, ',', '.');

            return "Hola " . $nombre . " 👋\n\n" .
                   "Tu saldo actual es de: $" . $importe . "\n\n" .
                   "Este importe surge de tus facturas emitidas menos los pagos registrados.";
        }

        return "No pude calcular el saldo de la cuenta seleccionada.";
    }

    if ($intent === 'ultima_factura') {
        actualizarSesion($conn, $phone, $customer_id, $customer_id, 'ultima_factura', null, $text, 0, null);

        $facturas = consultarUltimasFacturasPorCliente($conn, $customer_id, 3);

        if (count($facturas) === 0) {
            return "No encontré facturas registradas para la cuenta: " . $nombre . ".";
        }

        if (count($facturas) === 1) {
            $f = $facturas[0];
            $total = number_format((float)$f['total'], 2, ',', '.');

            return "Última factura de " . $nombre . ":\n\n" .
                   "Fecha de emisión: " . formatearFecha($f['issue_date']) . "\n" .
                   "Vencimiento: " . formatearFecha($f['due_date']) . "\n" .
                   "Posible suspensión: " . formatearFecha($f['suspend_date']) . "\n" .
                   "Importe: $" . $total;
        }

        $respuesta = "Últimas facturas de " . $nombre . ":\n\n";
        $n = 1;
        foreach ($facturas as $f) {
            $total = number_format((float)$f['total'], 2, ',', '.');
            $respuesta .= $n . ") Emisión: " . formatearFecha($f['issue_date']) .
                          " | Vence: " . formatearFecha($f['due_date']) .
                          " | Suspensión: " . formatearFecha($f['suspend_date']) .
                          " | $" . $total . "\n";
            $n++;
        }

        return trim($respuesta);
    }

    if ($intent === 'ultimo_pago') {
        actualizarSesion($conn, $phone, $customer_id, $customer_id, 'ultimo_pago', null, $text, 0, null);

        $pago = consultarUltimoPagoPorCliente($conn, $customer_id);

        if ($pago) {
            $amount = number_format((float)$pago['amount'], 2, ',', '.');

            return "Último pago registrado para " . $nombre . ":\n\n" .
                   "Fecha: " . formatearFecha($pago['paid_at']) . "\n" .
                   "Importe: $" . $amount . "\n" .
                   "Método: " . ($pago['method'] ?: 'No especificado') . "\n" .
                   "Referencia: " . ($pago['reference'] ?: 'Sin referencia');
        }

        return "No encontré pagos registrados para la cuenta: " . $nombre . ".";
    }

    if ($intent === 'soporte') {
        $estadoAdmin = estadoAdministrativoSoporte($conn, $customer_id);
        $estadoCuenta = $estadoAdmin['texto'];

        if (!empty($estadoAdmin['tiene_deuda'])) {
            actualizarSesion($conn, $phone, $customer_id, $customer_id, 'soporte_admin_pendiente', 'esperando_opcion_admin', $text, 0, null);

            return "Entiendo. Te ayudo a revisar la situación de tu servicio.

" .
                   "Cuenta seleccionada: " . $nombre . "
" .
                   $estadoCuenta . "

" .
                   "Antes de iniciar el diagnóstico técnico, necesitamos verificar la situación administrativa de la cuenta.

" .
                   menuSoporteAdminPendiente();
        }

        actualizarSesion($conn, $phone, $customer_id, $customer_id, 'soporte', 'esperando_tipo_servicio', $text, 0, null);

        $diagnostico = diagnosticoServiciosSoporte($conn, $customer_id);

        return "Entiendo. Te voy a guiar paso a paso para revisar tu conexión.

" .
               "Cuenta seleccionada: " . $nombre . "
" .
               $estadoCuenta . "
" .
               $diagnostico . "

" .
               "Para ayudarte mejor, ¿tu servicio es por fibra o por antena?

" .
               "Respondé con el número o escribí la opción:
" .
               "1️⃣ Fibra
" .
               "2️⃣ Antena";
    }

    return menuPrincipal($conn, $phone, null);
}

function mensajeSeleccionCliente($clientes, $intentPendiente)
{
    $accion = nombreIntentParaUsuario($intentPendiente);
    $respuesta = "Encontré más de una cuenta asociada a este WhatsApp.\n\n";
    $respuesta .= "Para continuar con: " . $accion . "\n";
    $respuesta .= "Respondé con el número de la cuenta:\n\n";

    $n = 1;
    foreach ($clientes as $cliente) {
        $nombre = $cliente['full_name'] ?? 'Cliente sin nombre';
        $telefono = $cliente['phone'] ?? '';
        $respuesta .= $n . "️⃣ " . $nombre;
        if ($telefono !== '') {
            $respuesta .= " - Tel: " . $telefono;
        }
        $respuesta .= "\n";
        $n++;
    }

    return trim($respuesta);
}

function nombreIntentParaUsuario($intent)
{
    switch ($intent) {
        case 'deuda': return 'consultar deuda / saldo';
        case 'ultima_factura': return 'consultar última factura';
        case 'ultimo_pago': return 'consultar último pago';
        case 'soporte': return 'soporte técnico';
        case 'humano': return 'hablar con un operador';
        default: return 'continuar';
    }
}


/* ================= SOPORTE ================= */

function procesarSoporte($conn, $phone, $text, $session)
{
    $t = mb_strtolower(trim($text), 'UTF-8');
    $customer_id = obtenerCustomerIdSesion($session);
    $selected_customer_id = obtenerSelectedCustomerIdSesion($session);

    if ($session['current_step'] === 'esperando_tipo_servicio') {

        if ($t === '1' || strpos($t, 'fibra') !== false || strpos($t, 'onu') !== false || strpos($t, 'pon') !== false) {
            actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, 'soporte', 'esperando_estado_pon', $text, 0, null);

            return "Perfecto. Vamos a revisar tu conexión de fibra.\n\n" .
                   "Por favor mirá la ONU/router de fibra y respondeme:\n\n" .
                   "¿La luz PON está fija, apagada o parpadeando?";
        }

        if ($t === '2' || strpos($t, 'antena') !== false || strpos($t, 'wisp') !== false || strpos($t, 'litebeam') !== false || strpos($t, 'nanoloco') !== false) {
            actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, 'soporte', 'esperando_estado_antena', $text, 0, null);

            return "Perfecto. Vamos a revisar tu conexión por antena.\n\n" .
                   "Por favor verificá el inyector PoE/fuente de la antena.\n\n" .
                   "¿Tiene luz o energía?\n\n" .
                   "Respondé: sí o no";
        }

        return "No pude identificar el tipo de servicio.\n\n" .
               "Respondé con:\n" .
               "1️⃣ Fibra\n" .
               "2️⃣ Antena";
    }

    if ($session['current_step'] === 'esperando_estado_pon') {

        if (strpos($t, 'fija') !== false || strpos($t, 'verde') !== false || strpos($t, 'encendida') !== false) {
            actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, 'soporte', 'esperando_reinicio_fibra', $text, 0, null);

            return "Perfecto. Si la luz PON está fija, la fibra parece estar enlazada.\n\n" .
                   "Ahora reiniciá el router/ONU durante 30 segundos y volvé a encenderlo.\n\n" .
                   "Cuando termine, respondeme: ya reinicié";
        }

        if (strpos($t, 'parpade') !== false || strpos($t, 'titila') !== false) {
            actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, 'soporte', 'derivado_tecnico', $text, 1, null);

            return "Gracias. Si la luz PON parpadea, puede haber pérdida de señal óptica.\n\n" .
                   "Voy a derivar tu caso al área técnica de INTERLINK para revisión.";
        }

        if (strpos($t, 'apag') !== false || strpos($t, 'roja') !== false || strpos($t, 'los') !== false) {
            actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, 'soporte', 'derivado_tecnico', $text, 1, null);

            return "Gracias. Si la luz PON está apagada o en rojo, puede haber corte de fibra, baja señal o equipo sin enlace.\n\n" .
                   "Voy a derivar tu caso al área técnica.";
        }

        return "No pude identificar el estado de la luz PON.\n\n" .
               "Respondeme con una de estas opciones:\n" .
               "- fija\n" .
               "- apagada\n" .
               "- parpadeando";
    }

    if ($session['current_step'] === 'esperando_estado_antena') {

        if ($t === 'si' || $t === 'sí' || strpos($t, 'tiene luz') !== false || strpos($t, 'encendida') !== false) {
            actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, 'soporte', 'esperando_luces_router_wisp', $text, 0, null);

            return "Bien. La antena parece tener alimentación.\n\n" .
                   "Ahora revisá el router de WiFi de tu casa.\n\n" .
                   "¿El router está encendido y emite la red WiFi?\n\n" .
                   "Respondé: sí o no";
        }

        if ($t === 'no' || strpos($t, 'apag') !== false || strpos($t, 'sin luz') !== false) {
            actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, 'soporte', 'derivado_tecnico', $text, 1, null);

            return "Gracias. Si la fuente PoE o inyector de la antena no tiene energía, la antena no puede enlazar.\n\n" .
                   "Verificá que esté enchufado y que haya corriente en el tomacorriente.\n\n" .
                   "De todos modos voy a derivar tu caso al área técnica.";
        }

        return "No pude identificar si la antena tiene energía.\n\n" .
               "Respondé solamente:\n" .
               "sí\n" .
               "o\n" .
               "no";
    }

    if ($session['current_step'] === 'esperando_luces_router_wisp') {

        if ($t === 'si' || $t === 'sí') {
            actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, 'soporte', 'esperando_reinicio_antena', $text, 0, null);

            return "Perfecto. Ahora reiniciá la antena y el router.\n\n" .
                   "Para hacerlo, desenchufá de corriente el inyector PoE y el router durante 30 segundos.\n\n" .
                   "Luego volvé a enchufarlos y esperá 3 minutos.\n\n" .
                   "Cuando termines respondeme: ya reinicié";
        }

        if ($t === 'no') {
            actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, 'soporte', 'derivado_tecnico', $text, 1, null);

            return "Gracias. Si el router no enciende o no emite WiFi, puede haber problema de energía, fuente o equipo.\n\n" .
                   "Voy a derivar tu caso al área técnica.";
        }

        return "Respondeme solamente:\n\nsí\n\no\n\nno";
    }

    if ($session['current_step'] === 'esperando_reinicio_fibra' || $session['current_step'] === 'esperando_reinicio_antena') {

        if (strpos($t, 'ya') !== false || strpos($t, 'reinicie') !== false || strpos($t, 'reinicié') !== false) {
            actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, 'soporte', 'esperando_resultado', $text, 0, null);

            return "Bien. Ahora confirmame:\n\n" .
                   "¿Ya volvió internet?\n\n" .
                   "Respondé: sí o no";
        }

        return "Cuando hayas reiniciado el equipo, respondeme: ya reinicié";
    }

    if ($session['current_step'] === 'esperando_resultado') {

        if ($t === 'si' || $t === 'sí' || strpos($t, 'volvio') !== false || strpos($t, 'volvió') !== false) {
            actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, null, null, $text, 0, null);

            return "Excelente 👌 Me alegra que el servicio haya vuelto.\n\n" .
                   "Doy por finalizada esta asistencia automática.";
        }

        if ($t === 'no' || strpos($t, 'sigue') !== false) {
            actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, 'soporte', 'derivado_tecnico', $text, 1, null);

            return "Gracias por confirmar.\n\n" .
                   "Voy a derivar tu caso al área técnica para una revisión más detallada.";
        }

        return "Respondeme solamente:\n\nsí\n\no\n\nno";
    }

    actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, null, null, $text, 0, null);

    return "Finalicé el flujo de soporte.\n\n" .
           "Si necesitás ayuda nuevamente, escribí: soporte o no tengo internet.";
}


/* ================= SESIONES ================= */

function obtenerSesion($conn, $phone)
{
    $stmt = $conn->prepare("SELECT * FROM whatsapp_bot_sessions WHERE phone = ? LIMIT 1");

    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error obtenerSesion: ' . $conn->error);
        return null;
    }

    $stmt->bind_param("s", $phone);
    $stmt->execute();

    return $stmt->get_result()->fetch_assoc();
}

function actualizarSesion($conn, $phone, $customer_id, $selected_customer_id, $flow, $step, $last_message, $human_required = 0, $context_json = null)
{
    $stmt = $conn->prepare("
        INSERT INTO whatsapp_bot_sessions
        (phone, customer_id, selected_customer_id, current_flow, current_step, last_message, human_required, context_json, updated_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())
        ON DUPLICATE KEY UPDATE
            customer_id = VALUES(customer_id),
            selected_customer_id = VALUES(selected_customer_id),
            current_flow = VALUES(current_flow),
            current_step = VALUES(current_step),
            last_message = VALUES(last_message),
            human_required = VALUES(human_required),
            context_json = VALUES(context_json),
            updated_at = NOW()
    ");

    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error actualizarSesion: ' . $conn->error);
        return false;
    }

    $stmt->bind_param("siisssis", $phone, $customer_id, $selected_customer_id, $flow, $step, $last_message, $human_required, $context_json);
    return $stmt->execute();
}

function obtenerCustomerIdSesion($session)
{
    if (!$session) {
        return null;
    }

    if (!empty($session['customer_id'])) {
        return (int)$session['customer_id'];
    }

    if (!empty($session['selected_customer_id'])) {
        return (int)$session['selected_customer_id'];
    }

    return null;
}

function obtenerSelectedCustomerIdSesion($session)
{
    if (!$session || empty($session['selected_customer_id'])) {
        return null;
    }

    return (int)$session['selected_customer_id'];
}


/* ================= CONTACTOS NO REGISTRADOS / TERCEROS ================= */

function normalizarTextoBot($text)
{
    return quitarAcentos(mb_strtolower(trim((string)$text), 'UTF-8'));
}

function telefonoTieneClienteRegistrado($conn, $phone)
{
    return count(buscarClientesPorTelefono($conn, $phone)) > 0;
}

function esFlujoNoRegistrado($flow)
{
    return in_array((string)$flow, [
        'contacto_no_registrado',
        'instalacion_nueva',
        'consulta_servicio',
        'pago_tercero',
        'seleccion_cliente_pago_tercero',
        'cambio_numero',
        'seleccion_cliente_cambio_numero',
    ], true);
}


function sesionNoRegistradaExpirada($session, $minutos = 45)
{
    if (!is_array($session)) {
        return false;
    }
    $updated = trim((string)($session['updated_at'] ?? ''));
    if ($updated === '') {
        return false;
    }
    $ts = strtotime($updated);
    if (!$ts) {
        return false;
    }
    return (time() - $ts) > ((int)$minutos * 60);
}

function esCancelacionFlujoNoRegistrado($t)
{
    $t = normalizarTextoBot($t);
    return $t === 'menu' || $t === 'menú' || $t === 'inicio' || $t === '0' ||
           strpos($t, 'cancelar') !== false ||
           strpos($t, 'salir') !== false ||
           strpos($t, 'volver') !== false ||
           strpos($t, 'otro tema') !== false ||
           strpos($t, 'nueva consulta') !== false ||
           strpos($t, 'empezar de nuevo') !== false;
}

function esNegacionClienteNoRegistrado($t)
{
    $t = normalizarTextoBot($t);

    // Evitar falso positivo: "soy cliente y cambie de numero" sí es cambio de número.
    if (strpos($t, 'soy cliente') !== false &&
        (strpos($t, 'cambie') !== false || strpos($t, 'cambio') !== false || strpos($t, 'otro numero') !== false || strpos($t, 'otro celular') !== false)) {
        return false;
    }

    return strpos($t, 'no soy cliente') !== false ||
           strpos($t, 'no soy abonado') !== false ||
           strpos($t, 'todavia no soy cliente') !== false ||
           strpos($t, 'todavía no soy cliente') !== false ||
           strpos($t, 'aun no soy cliente') !== false ||
           strpos($t, 'aún no soy cliente') !== false ||
           strpos($t, 'no tengo cuenta') !== false ||
           strpos($t, 'no tengo codigo') !== false ||
           strpos($t, 'no tengo código') !== false ||
           strpos($t, 'no tengo dni del titular') !== false ||
           strpos($t, 'no tengo datos del titular') !== false ||
           strpos($t, 'quiero ser cliente') !== false ||
           strpos($t, 'cliente nuevo') !== false;
}

function textoSinDatosClienteParaPago($t)
{
    $t = normalizarTextoBot($t);
    return strpos($t, 'no tengo dato') !== false ||
           strpos($t, 'no tengo datos') !== false ||
           strpos($t, 'no se el codigo') !== false ||
           strpos($t, 'no sé el codigo') !== false ||
           strpos($t, 'no se el código') !== false ||
           strpos($t, 'no sé el código') !== false ||
           strpos($t, 'no se dni') !== false ||
           strpos($t, 'no sé dni') !== false ||
           strpos($t, 'no lo encuentro') !== false ||
           strpos($t, 'despues lo paso') !== false ||
           strpos($t, 'después lo paso') !== false;
}


function esAgradecimientoOCierre($text)
{
    $t = normalizarTextoBot($text);
    $t = preg_replace('/[^a-z0-9\s]/u', ' ', $t);
    $t = trim(preg_replace('/\s+/', ' ', $t));

    if ($t === '') {
        return false;
    }

    $frases = [
        'gracias', 'muchas gracias', 'muchisimas gracias', 'mil gracias',
        'ok', 'oka', 'okey', 'okay', 'dale', 'listo', 'perfecto', 'bien',
        'entendido', 'de acuerdo', 'barbaro', 'buenisimo', 'excelente',
        'ya esta', 'ya está', 'correcto', 'joya', 'genial'
    ];

    if (in_array($t, $frases, true)) {
        return true;
    }

    // Detecta errores frecuentes de tipeo: "grcaias", "grasias", etc.
    $tokens = preg_split('/\s+/', $t);
    $tokensUtiles = 0;
    $tokensCortesia = 0;

    foreach ($tokens as $token) {
        $token = trim($token);
        if ($token === '') {
            continue;
        }
        $tokensUtiles++;
        if (strpos($token, 'graci') !== false || strpos($token, 'grasi') !== false || levenshtein($token, 'gracias') <= 2) {
            $tokensCortesia++;
            continue;
        }
        if (in_array($token, ['ok','oka','okey','okay','dale','listo','perfecto','bien','entendido','correcto','joya','genial'], true)) {
            $tokensCortesia++;
        }
    }

    return $tokensUtiles > 0 && $tokensUtiles <= 4 && $tokensCortesia === $tokensUtiles;
}

function procesarPostComprobantePagoTercero($conn, $phone, $text, $session)
{
    $context = json_decode($session['context_json'] ?? '{}', true);
    if (!is_array($context)) {
        $context = [];
    }

    $receipt_id = isset($context['receipt_id']) ? (int)$context['receipt_id'] : 0;
    $customer_id = isset($context['target_customer_id']) ? (int)$context['target_customer_id'] : null;
    $nombre = trim((string)($context['target_customer_name'] ?? 'la cuenta indicada'));
    if ($nombre === '') {
        $nombre = 'la cuenta indicada';
    }

    if (esAgradecimientoOCierre($text)) {
        actualizarSesion(
            $conn,
            $phone,
            null,
            null,
            'pago_tercero',
            'comprobante_asociado_ack',
            $text,
            1,
            json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
        );

        return "De nada. El comprobante ya quedó pendiente de conciliación administrativa ✅\n\n" .
               "Cuenta indicada: " . $nombre . "\n" .
               "Estado: en revisión.\n\n" .
               "No hace falta enviar más datos por ahora. Si necesitás consultar otra cosa, escribí: menú.";
    }

    registrarIntakeWhatsapp($conn, $phone, $customer_id, 'third_party_payment', 'needs_review', $text, [
        'receipt_id' => $receipt_id,
        'customer_id' => $customer_id,
        'customer_name' => $nombre,
        'nota_posterior_al_comprobante' => true,
    ], 1);

    actualizarSesion(
        $conn,
        $phone,
        null,
        null,
        'pago_tercero',
        'comprobante_asociado',
        $text,
        1,
        json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
    );

    return "Agregué ese mensaje al comprobante pendiente ✅\n\n" .
           "Cuenta indicada: " . $nombre . "\n" .
           "Estado: pendiente de conciliación administrativa.\n\n" .
           
           "Para iniciar otro trámite escribí: menú, planes, instalación, pago u operador.";
}

function respuestaMenuNoClientePorNegacion()
{
    return "Entendido.\n\n" .
           "Podés elegir cómo continuar:\n\n" .
           "1️⃣ Nueva instalación / verificar cobertura\n" .
           "2️⃣ Consultar planes o servicio\n" .
           "3️⃣ Pagar o enviar comprobante de una cuenta\n" .
           "4️⃣ Soy cliente y cambié/uso otro número\n" .
           "5️⃣ Operador\n\n" .
           "También podés escribir directamente: planes, instalación, pago u operador.";
}

function procesarEntradaNoRegistrada($conn, $phone, $text, $session, $intent)
{
    $t = normalizarTextoBot($text);

    // Si el primer mensaje ya es un número, usar el menú seguro de no registrados,
    // no el menú principal de clientes registrados.
    if ($t === '1') {
        return iniciarInstalacionNueva($conn, $phone, $text);
    }
    if ($t === '2') {
        return iniciarConsultaServicio($conn, $phone, $text);
    }
    if ($t === '3') {
        return iniciarPagoTercero($conn, $phone, $text, $intent);
    }
    if ($t === '4') {
        return iniciarCambioNumero($conn, $phone, $text);
    }
    if ($t === '5') {
        return derivarNoRegistradoOperador($conn, $phone, $text, 'menu_no_registrado');
    }

    if (esNegacionClienteNoRegistrado($t)) {
        if ($intent === 'instalacion' || esSolicitudInstalacionNueva($t)) {
            return iniciarInstalacionNueva($conn, $phone, $text);
        }
        if (esSolicitudConsultaServicio($t)) {
            return iniciarConsultaServicio($conn, $phone, $text);
        }
        actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'esperando_opcion', $text, 0, null);
        registrarIntakeWhatsapp($conn, $phone, null, 'unknown_contact', 'pending_info', $text, [
            'motivo_detectado' => 'aclaro_no_cliente',
        ], 1);
        return respuestaMenuNoClientePorNegacion();
    }

    // Orden intencional: pago/soporte/instalación/consulta antes que cambio de número.
    // Frases como "soy cliente y quiero pagar" no deben quedar atrapadas en cambio de teléfono.
    if (esSolicitudPagoTercero($t) || esSolicitudPagoDeOtraCuenta($t) || in_array($intent, ['facturacion', 'deuda', 'ultima_factura', 'ultimo_pago', 'formas_pago'], true)) {
        return iniciarPagoTercero($conn, $phone, $text, $intent);
    }

    if ($intent === 'soporte' || esSolicitudSoporteNoRegistrado($t)) {
        return iniciarSoporteNoRegistrado($conn, $phone, $text);
    }

    if ($intent === 'instalacion' || esSolicitudInstalacionNueva($t)) {
        return iniciarInstalacionNueva($conn, $phone, $text);
    }

    if (esSolicitudConsultaServicio($t)) {
        return iniciarConsultaServicio($conn, $phone, $text);
    }

    if (esSolicitudCambioNumero($t)) {
        return iniciarCambioNumero($conn, $phone, $text);
    }

    if ($intent === 'humano') {
        return derivarNoRegistradoOperador($conn, $phone, $text, 'operador_solicitado');
    }

    actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'esperando_opcion', $text, 0, null);
    registrarIntakeWhatsapp($conn, $phone, null, 'unknown_contact', 'pending_info', $text, [], 0);

    return menuContactoNoRegistrado();
}

function procesarFlujoNoRegistrado($conn, $phone, $text, $session, $intent)
{
    $flow = (string)($session['current_flow'] ?? '');

    $cambioTema = detectarCambioTemaNoRegistrado($text, $intent, $flow, $session);
    if ($cambioTema !== null) {
        return ejecutarCambioTemaNoRegistrado($conn, $phone, $text, $intent, $cambioTema);
    }

    if ($flow === 'contacto_no_registrado') {
        return procesarMenuContactoNoRegistrado($conn, $phone, $text, $intent, $session);
    }

    if ($flow === 'instalacion_nueva') {
        return procesarDatosInstalacionNueva($conn, $phone, $text, $session);
    }

    if ($flow === 'consulta_servicio') {
        return procesarConsultaServicio($conn, $phone, $text, $session);
    }

    if ($flow === 'pago_tercero') {
        return procesarPagoTercero($conn, $phone, $text, $session);
    }

    if ($flow === 'seleccion_cliente_pago_tercero') {
        return procesarSeleccionClientePagoTercero($conn, $phone, $text, $session);
    }

    if ($flow === 'cambio_numero') {
        return procesarCambioNumero($conn, $phone, $text, $session);
    }

    if ($flow === 'seleccion_cliente_cambio_numero') {
        return procesarSeleccionClienteCambioNumero($conn, $phone, $text, $session);
    }

    actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'esperando_opcion', $text, 0, null);
    return menuContactoNoRegistrado();
}

function detectarCambioTemaNoRegistrado($text, $intent, $flow = '', $session = null)
{
    $t = normalizarTextoBot($text);

    if (esCancelacionFlujoNoRegistrado($t)) {
        return 'menu';
    }

    $step = is_array($session) ? (string)($session['current_step'] ?? '') : '';

    // Si el usuario aclara que no es cliente o que no tiene datos del titular,
    // salir del flujo de identificación en vez de seguir pidiendo datos.
    if (esNegacionClienteNoRegistrado($t) || textoSinDatosClienteParaPago($t)) {
        if (esSolicitudPagoDeOtraCuenta($t) || esSolicitudPagoTercero($t)) {
            return 'pago_tercero';
        }
        if ($intent === 'instalacion' || esSolicitudInstalacionNueva($t)) {
            return 'instalacion_nueva';
        }
        if (esSolicitudConsultaServicio($t)) {
            return 'consulta_servicio';
        }
        return 'menu_no_cliente';
    }

    // Si el trámite anterior ya terminó, los números vuelven a funcionar como menú seguro
    // de contactos no registrados. Si todavía hay una selección pendiente, se respetan
    // como respuesta del paso activo.
    if (esTextoNumericoSimple($text)) {
        if (in_array($step, ['solicitud_registrada', 'consulta_registrada', 'derivado_operador', 'post_intake', 'post_consulta', 'esperando_opcion'], true)) {
            if ($t === '1') { return 'instalacion_nueva'; }
            if ($t === '2') { return 'consulta_servicio'; }
            if ($t === '3') { return 'pago_tercero'; }
            if ($t === '4') { return 'cambio_numero'; }
            if ($t === '5') { return 'humano'; }
        }
        return null;
    }

    if (strpos($t, 'operador') !== false || strpos($t, 'asesor') !== false || strpos($t, 'persona') !== false || strpos($t, 'humano') !== false) {
        return 'humano';
    }

    // Pago/facturación tiene prioridad sobre frases amplias como "soy cliente".
    if (esSolicitudPagoDeOtraCuenta($t) || esSolicitudPagoTercero($t) || in_array($intent, ['facturacion', 'deuda', 'ultima_factura', 'ultimo_pago', 'formas_pago'], true)) {
        return 'pago_tercero';
    }

    if ($intent === 'soporte' || esSolicitudSoporteNoRegistrado($t)) {
        return 'soporte_no_registrado';
    }

    if ($intent === 'instalacion' || esSolicitudInstalacionNueva($t)) {
        return 'instalacion_nueva';
    }

    if (esSolicitudConsultaServicio($t)) {
        return 'consulta_servicio';
    }

    if (esSolicitudCambioNumero($t)) {
        return 'cambio_numero';
    }

    return null;
}

function ejecutarCambioTemaNoRegistrado($conn, $phone, $text, $intent, $tema)
{
    if ($tema === 'menu') {
        actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'esperando_opcion', $text, 0, null);
        return menuContactoNoRegistrado();
    }

    if ($tema === 'menu_no_cliente') {
        actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'esperando_opcion', $text, 0, null);
        registrarIntakeWhatsapp($conn, $phone, null, 'unknown_contact', 'pending_info', $text, [
            'motivo_detectado' => 'aclaro_no_cliente_o_sin_datos',
        ], 1);
        return respuestaMenuNoClientePorNegacion();
    }

    if ($tema === 'instalacion_nueva') {
        return iniciarInstalacionNueva($conn, $phone, $text);
    }

    if ($tema === 'consulta_servicio') {
        return iniciarConsultaServicio($conn, $phone, $text);
    }

    if ($tema === 'pago_tercero') {
        return iniciarPagoTercero($conn, $phone, $text, $intent);
    }

    if ($tema === 'cambio_numero') {
        return iniciarCambioNumero($conn, $phone, $text);
    }

    if ($tema === 'soporte_no_registrado') {
        return iniciarSoporteNoRegistrado($conn, $phone, $text);
    }

    if ($tema === 'humano') {
        return derivarNoRegistradoOperador($conn, $phone, $text, 'cambio_tema_operador');
    }

    actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'esperando_opcion', $text, 0, null);
    return menuContactoNoRegistrado();
}

function procesarMenuContactoNoRegistrado($conn, $phone, $text, $intent, $session = null)
{
    $t = normalizarTextoBot($text);
    $step = is_array($session) ? (string)($session['current_step'] ?? '') : '';

    if (in_array($step, ['post_comprobante', 'post_comprobante_ack', 'post_comprobante_sin_cuenta'], true) && esAgradecimientoOCierre($text)) {
        actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'post_comprobante_ack', $text, 1, is_array($session) ? ($session['context_json'] ?? null) : null);
        return "De nada. El comprobante queda pendiente de revisión administrativa ✅\n\nPara iniciar otra consulta escribí: menú.";
    }

    if ($t === '1' || esSolicitudInstalacionNueva($t)) {
        return iniciarInstalacionNueva($conn, $phone, $text);
    }

    if ($t === '2' || esSolicitudConsultaServicio($t)) {
        return iniciarConsultaServicio($conn, $phone, $text);
    }

    if ($t === '3' || esSolicitudPagoTercero($t) || in_array($intent, ['facturacion', 'deuda', 'formas_pago'], true)) {
        return iniciarPagoTercero($conn, $phone, $text, $intent);
    }

    if ($t === '4' || esSolicitudCambioNumero($t)) {
        return iniciarCambioNumero($conn, $phone, $text);
    }

    if ($t === '5' || $intent === 'humano' || strpos($t, 'operador') !== false || strpos($t, 'persona') !== false) {
        return derivarNoRegistradoOperador($conn, $phone, $text, 'menu_no_registrado');
    }

    return menuContactoNoRegistrado();
}

function menuContactoNoRegistrado()
{
    return "Hola 👋 Soy *LINK* un asistente virtual\n\n" .
           
           "Elegí una opción:\n\n" .
           "1️⃣ Nueva instalación\n" .
           "2️⃣ Consultar planes\n" .
           "3️⃣ Pagar o enviar comprobante\n" .
           "4️⃣ Soy cliente y cambié/uso otro número\n" .
           "5️⃣ Operador\n\n" .
           "Podés responder con el número o escribir el motivo.";
}

function esSolicitudInstalacionNueva($t)
{
    return strpos($t, 'instalacion') !== false ||
           strpos($t, 'instalación') !== false ||
           strpos($t, 'instalcion') !== false ||
           strpos($t, 'instalar') !== false ||
           strpos($t, 'quiero internet') !== false ||
           strpos($t, 'nuevo servicio') !== false ||
           strpos($t, 'alta') !== false ||
           strpos($t, 'contratar') !== false ||
           strpos($t, 'poner internet') !== false;
}

function esSolicitudConsultaServicio($t)
{
    // Mantener esto lo más explícito posible. Palabras como "servicio" o "zona"
    // aparecen en domicilios o en soporte y no deben cambiar de tema por error.
    return strpos($t, 'planes') !== false ||
           strpos($t, 'precio') !== false ||
           strpos($t, 'precios') !== false ||
           strpos($t, 'tarifa') !== false ||
           strpos($t, 'tarifas') !== false ||
           strpos($t, 'cuanto sale') !== false ||
           strpos($t, 'cuánto sale') !== false ||
           strpos($t, 'cuanto cuesta') !== false ||
           strpos($t, 'cuánto cuesta') !== false ||
           strpos($t, 'velocidad') !== false ||
           strpos($t, 'megas') !== false ||
           strpos($t, 'cobertura') !== false ||
           strpos($t, 'disponible') !== false ||
           strpos($t, 'disponibilidad') !== false ||
           strpos($t, 'informacion comercial') !== false ||
           strpos($t, 'información comercial') !== false ||
           strpos($t, 'info comercial') !== false;
}

function esSolicitudSoporteNoRegistrado($t)
{
    return strpos($t, 'soporte') !== false ||
           strpos($t, 'tecnico') !== false ||
           strpos($t, 'técnico') !== false ||
           strpos($t, 'no tengo internet') !== false ||
           strpos($t, 'sin internet') !== false ||
           strpos($t, 'no funciona') !== false ||
           strpos($t, 'no anda') !== false ||
           strpos($t, 'anda lento') !== false ||
           strpos($t, 'internet lento') !== false;
}

function iniciarSoporteNoRegistrado($conn, $phone, $text)
{
    actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'esperando_opcion', $text, 0, null);
    registrarIntakeWhatsapp($conn, $phone, null, 'unknown_contact', 'pending_info', $text, [
        'motivo_detectado' => 'soporte_sin_telefono_registrado',
    ], 1);

    return 
           "Elegí cómo querés continuar:\n\n" .
           "1️⃣ Nueva instalación\n" .
           "2️⃣ Consultar planes \n" .
           "3️⃣ Pagar o enviar\n" .
           "4️⃣ Soy cliente y cambié/uso otro número\n" .
           "5️⃣ Operador\n\n" .
           "También podés responder con el dato del titular: código, DNI/CUIT, nombre completo o domicilio del servicio.";
}

function esSolicitudPagoTercero($t)
{
    return strpos($t, 'pago') !== false ||
           strpos($t, 'pagar') !== false ||
           strpos($t, 'comprobante') !== false ||
           strpos($t, 'transferencia') !== false ||
           strpos($t, 'deposito') !== false ||
           strpos($t, 'depósito') !== false ||
           strpos($t, 'saldo') !== false ||
           strpos($t, 'deuda') !== false ||
           strpos($t, 'factura') !== false;
}

function esSolicitudPagoDeOtraCuenta($t)
{
    return strpos($t, 'otra cuenta') !== false ||
           strpos($t, 'otro cliente') !== false ||
           strpos($t, 'tercero') !== false ||
           strpos($t, 'otra persona') !== false ||
           strpos($t, 'no es mi cuenta') !== false ||
           strpos($t, 'cuenta de ') !== false ||
           strpos($t, 'pago de mi ') !== false ||
           strpos($t, 'pago para ') !== false ||
           strpos($t, 'comprobante de ') !== false;
}

function esSolicitudCambioNumero($t)
{
    if (esNegacionClienteNoRegistrado($t)) {
        return false;
    }

    return strpos($t, 'cambie de numero') !== false ||
           strpos($t, 'cambié de numero') !== false ||
           strpos($t, 'cambie numero') !== false ||
           strpos($t, 'cambiar numero') !== false ||
           strpos($t, 'cambio de numero') !== false ||
           strpos($t, 'otro numero') !== false ||
           strpos($t, 'otro celular') !== false ||
           strpos($t, 'numero nuevo') !== false ||
           strpos($t, 'soy cliente') !== false ||
           strpos($t, 'no es el numero registrado') !== false ||
           strpos($t, 'numero registrado') !== false ||
           strpos($t, 'número registrado') !== false ||
           strpos($t, 'escribo desde otro') !== false ||
           strpos($t, 'actualizar telefono') !== false ||
           strpos($t, 'actualizar numero') !== false;
}

function iniciarInstalacionNueva($conn, $phone, $text)
{
    actualizarSesion($conn, $phone, null, null, 'instalacion_nueva', 'esperando_datos_instalacion', $text, 0, null);
    registrarIntakeWhatsapp($conn, $phone, null, 'new_installation', 'pending_info', $text, [], 0);

    return "Perfecto, te puedo tomar los datos para una instalación nueva.\n\n" .
           respuestaInfoInstalacionPrecliente($conn) . "\n\n" .
           "Decime por favor tu:\n" .

           "• nombre y apellido\n" .
           
           "Para cambiar de tema escribí: menú.";
}

function procesarDatosInstalacionNueva($conn, $phone, $text, $session)
{
    $limpio = trim((string)$text);
    $step = (string)($session['current_step'] ?? '');

    if ($step === 'solicitud_registrada') {
        registrarIntakeWhatsapp($conn, $phone, null, 'new_installation', 'needs_review', $text, [
            'seguimiento' => true,
            'telefono_whatsapp' => $phone,
        ], 1);

        return "Agregué ese dato a tu consulta ✅\n\n" .
               "Un operador comercial te contactará.\n\n" .
               "Si querés iniciar otro trámite, escribí: menú.";
    }

    if (mb_strlen($limpio, 'UTF-8') < 8) {
        return "Necesito un poco más de información para verificar cobertura.\n\n" .
               "podrias enviarme tu ubicacion googlemaps?.";
    }

    $intake_id = registrarIntakeWhatsapp($conn, $phone, null, 'new_installation', 'needs_review', $text, [
        'datos_contacto' => $limpio,
        'telefono_whatsapp' => $phone,
    ], 1);

    actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'post_intake', $text, 1, json_encode([
        'intake_id' => $intake_id,
        'tipo' => 'new_installation',
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));

    return "Solicitud recibida ✅\n\n" .
           "Registré tus datos para verificar cobertura e instalación nueva.\n" .
           "Un operador de INTERLINK revisará la zona y te responderá por este mismo WhatsApp.\n\n" .
           "Importante: esta solicitud no crea un cliente automáticamente; queda pendiente de revisión comercial.\n\n" .
           "Si querés consultar otra cosa, escribí menú, planes, pago u operador.";
}

function iniciarConsultaServicio($conn, $phone, $text)
{
    actualizarSesion($conn, $phone, null, null, 'consulta_servicio', 'esperando_zona_consulta', $text, 0, null);
    registrarIntakeWhatsapp($conn, $phone, null, 'service_query', 'pending_info', $text, [], 0);

    return respuestaInfoServicioPrecliente($conn) . "\n\n" .
           "Para informarte disponibilidad real, planes aplicables y posibilidad de instalación, respondé con tu barrio/localidad y domicilio o referencia de ubicación.\n\n" .
           "Con eso el área comercial verifica cobertura y opciones disponibles.\n\n" .
           "Para cambiar de tema escribí: menú.";
}

function procesarConsultaServicio($conn, $phone, $text, $session)
{
    $limpio = trim((string)$text);
    $step = (string)($session['current_step'] ?? '');

    if ($step === 'consulta_registrada') {
        registrarIntakeWhatsapp($conn, $phone, null, 'service_query', 'needs_review', $text, [
            'seguimiento' => true,
            'telefono_whatsapp' => $phone,
        ], 1);

        return "Agregué ese dato a tu consulta comercial ✅\n\n" .
               "Un operador lo verá junto con la consulta anterior.\n\n" .
               "Si querés consultar otro tema, escribí: menú.";
    }

    if (mb_strlen($limpio, 'UTF-8') < 5) {
        return "Para verificar cobertura necesito barrio/localidad y, si podés, domicilio o referencia.";
    }

    $intake_id = registrarIntakeWhatsapp($conn, $phone, null, 'service_query', 'needs_review', $text, [
        'zona_consultada' => $limpio,
        'telefono_whatsapp' => $phone,
    ], 1);

    actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'post_consulta', $text, 1, json_encode([
        'intake_id' => $intake_id,
        'tipo' => 'service_query',
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));

    return "Consulta recibida ✅\n\n" .
           "El área comercial verificará cobertura y disponibilidad para la zona indicada.\n" .
           "Te responderemos por este mismo WhatsApp.\n\n" .
           "Si querés consultar otra cosa, escribí menú, instalación, pago u operador.";
}

function iniciarPagoTercero($conn, $phone, $text, $intent = null)
{
    actualizarSesion($conn, $phone, null, null, 'pago_tercero', 'esperando_dato_cliente_pago', $text, 0, null);
    registrarIntakeWhatsapp($conn, $phone, null, 'third_party_payment', 'pending_info', $text, [
        'intent' => $intent,
    ], 0);

    $extra = '';
    if ($intent === 'formas_pago') {
        $extra = respuestaFormasPago() . "\n\n";
    }

    return $extra .
           "Puedo recibir un comprobante para una cuenta aunque este WhatsApp no sea el registrado.\n\n" .
           "Para aplicarlo correctamente, primero indicá uno de estos datos del titular del servicio:\n" .
           "• código de cliente\n" .
           "• DNI/CUIT\n" .
           "• nombre y apellido\n" .
           "• domicilio del servicio\n\n" .
           "Después enviá la captura o PDF del comprobante.\n\n" .
           "Importante: esto no cambia el teléfono registrado de la cuenta y el pago queda sujeto a conciliación administrativa.";
}

function procesarPagoTercero($conn, $phone, $text, $session)
{
    $t = normalizarTextoBot($text);
    $step = (string)($session['current_step'] ?? '');

    if (in_array($step, ['comprobante_asociado', 'comprobante_asociado_ack', 'post_comprobante'], true)) {
        return procesarPostComprobantePagoTercero($conn, $phone, $text, $session);
    }

    // Respuestas de cortesía no son datos del titular y no deben disparar búsqueda de cliente.
    // Esto también corrige sesiones que quedaron viejas en esperando_dato_cliente_pago
    // después de haber recibido un comprobante.
    if (esAgradecimientoOCierre($text)) {
        actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'post_comprobante_ack', $text, 1, null);
        return "De nada ✅

" .
               "Si ya enviaste un comprobante, queda pendiente de revisión administrativa.
" .
               "Si administración necesita confirmar algo, te contactará por este mismo medio.

" .
               "Para iniciar otra consulta escribí: menú.";
    }

    if (esNegacionClienteNoRegistrado($t) || textoSinDatosClienteParaPago($t)) {
        if (esSolicitudInstalacionNueva($t)) {
            return iniciarInstalacionNueva($conn, $phone, $text);
        }
        if (esSolicitudConsultaServicio($t)) {
            return iniciarConsultaServicio($conn, $phone, $text);
        }
        actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'esperando_opcion', $text, 0, null);
        registrarIntakeWhatsapp($conn, $phone, null, 'third_party_payment', 'pending_info', $text, [
            'resultado_busqueda' => 'usuario_indica_no_cliente_o_sin_datos',
        ], 1);
        return "Entendido. No voy a seguir solicitando datos de una cuenta.\n\n" .
               "Si luego necesitás aplicar un pago de un cliente, podés elegir la opción 3 y enviar el comprobante.\n\n" .
               menuContactoNoRegistrado();
    }

    if (strpos($t, 'forma') !== false || strpos($t, 'alias') !== false || strpos($t, 'cbu') !== false) {
        return respuestaFormasPago() . "\n\n" .
               "Luego enviá el comprobante por este chat. Si todavía no identificamos la cuenta, respondé con código, DNI/CUIT, nombre o domicilio del titular.";
    }

    if (strpos($t, 'operador') !== false || strpos($t, 'humano') !== false || strpos($t, 'persona') !== false) {
        return derivarNoRegistradoOperador($conn, $phone, $text, 'pago_tercero_operador');
    }

    $clientes = buscarClientesParaComprobante($conn, $text);

    if (count($clientes) === 0) {
        registrarIntakeWhatsapp($conn, $phone, null, 'third_party_payment', 'pending_info', $text, [
            'resultado_busqueda' => 'sin_coincidencias',
        ], 1);
        actualizarSesion($conn, $phone, null, null, 'pago_tercero', 'esperando_dato_cliente_pago', $text, 1, null);

        return "No encontré una cuenta con ese dato.\n\n" .
               "El pago puede quedar pendiente para revisión administrativa, pero para aplicarlo correctamente necesito algún dato del titular:\n" .
               "• código de cliente\n" .
               "• DNI/CUIT\n" .
               "• nombre completo\n" .
               "• domicilio del servicio\n\n" .
               "También podés enviar el comprobante y administración lo revisará manualmente.\n\n" .
               "Si no sos cliente o querés consultar otro tema, escribí: menú, planes, instalación u operador.";
    }

    if (count($clientes) === 1) {
        return confirmarClientePagoTercero($conn, $phone, $text, $clientes[0]);
    }

    actualizarSesion(
        $conn,
        $phone,
        null,
        null,
        'seleccion_cliente_pago_tercero',
        'esperando_cliente_pago',
        $text,
        0,
        json_encode(['clientes' => $clientes], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
    );

    return mensajeSeleccionClientePagoTercero($clientes);
}

function procesarSeleccionClientePagoTercero($conn, $phone, $text, $session)
{
    $opcion = (int)trim((string)$text);
    $context = json_decode($session['context_json'] ?? '{}', true);
    $clientes = $context['clientes'] ?? [];

    if (!is_array($clientes) || count($clientes) === 0) {
        actualizarSesion($conn, $phone, null, null, 'pago_tercero', 'esperando_dato_cliente_pago', $text, 0, null);
        return "La selección anterior venció.\n\n" . iniciarPagoTercero($conn, $phone, $text);
    }

    if ($opcion < 1 || $opcion > count($clientes)) {
        return mensajeSeleccionClientePagoTercero($clientes);
    }

    return confirmarClientePagoTercero($conn, $phone, $text, $clientes[$opcion - 1]);
}

function mensajeSeleccionClientePagoTercero($clientes)
{
    $respuesta = "Encontré más de una cuenta posible para aplicar el pago.\n\n" .
                 "Respondé con el número correcto:\n\n";

    $n = 1;
    foreach ($clientes as $cliente) {
        $respuesta .= $n . ") " . ($cliente['full_name'] ?? 'Cliente');
        if (!empty($cliente['customer_code'])) {
            $respuesta .= " | Cod: " . $cliente['customer_code'];
        }
        if (!empty($cliente['address'])) {
            $respuesta .= " | " . $cliente['address'];
        }
        $respuesta .= "\n";
        $n++;
    }

    return trim($respuesta) . "\n\nNo se mostrará saldo ni datos de facturación desde este número hasta validación administrativa.";
}

function confirmarClientePagoTercero($conn, $phone, $text, $cliente)
{
    $customer_id = isset($cliente['id']) ? (int)$cliente['id'] : 0;
    $nombre = $cliente['full_name'] ?? 'Cliente';

    registrarIntakeWhatsapp($conn, $phone, $customer_id, 'third_party_payment', 'needs_review', $text, [
        'customer_id' => $customer_id,
        'customer_name' => $nombre,
        'telefono_whatsapp_no_registrado' => $phone,
    ], 1);

    actualizarSesion(
        $conn,
        $phone,
        null,
        null,
        'pago_tercero',
        'esperando_comprobante_pago',
        $text,
        1,
        json_encode(['target_customer_id' => $customer_id, 'target_customer_name' => $nombre], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
    );

    return "Cuenta identificada para aplicar comprobante ✅\n\n" .
           "Cuenta: " . $nombre . "\n\n" .
           "Ahora enviá la imagen o PDF del comprobante por este chat.\n" .
           "El pago quedará pendiente de conciliación administrativa.\n\n" .
           "Importante: no cambié el teléfono registrado de la cuenta y no voy a mostrar saldo/facturas desde este WhatsApp.";
}

function iniciarCambioNumero($conn, $phone, $text)
{
    actualizarSesion($conn, $phone, null, null, 'cambio_numero', 'esperando_dato_cliente_cambio', $text, 0, null);
    registrarIntakeWhatsapp($conn, $phone, null, 'phone_change', 'pending_info', $text, [
        'telefono_nuevo_solicitado' => $phone,
    ], 0);

    return "Entiendo. Podemos registrar una solicitud de cambio de número, pero no se modifica automáticamente.\n\n" .
           "Por seguridad, respondé con un dato del titular:\n" .
           "• código de cliente\n" .
           "• DNI/CUIT\n" .
           "• nombre y apellido\n" .
           "• domicilio del servicio\n\n" .
           "Un operador verificará la identidad antes de actualizar el teléfono de la cuenta.";
}

function procesarCambioNumero($conn, $phone, $text, $session)
{
    $clientes = buscarClientesParaComprobante($conn, $text);

    if (count($clientes) === 0) {
        registrarIntakeWhatsapp($conn, $phone, null, 'phone_change', 'pending_info', $text, [
            'resultado_busqueda' => 'sin_coincidencias',
            'telefono_nuevo_solicitado' => $phone,
        ], 1);

        actualizarSesion($conn, $phone, null, null, 'cambio_numero', 'esperando_dato_cliente_cambio', $text, 1, null);
        return "No encontré una cuenta con ese dato.\n\n" .
               "La solicitud queda pendiente para revisión, pero podés responder con otro dato del titular: código, DNI/CUIT, nombre completo o domicilio.";
    }

    if (count($clientes) === 1) {
        return registrarSolicitudCambioNumero($conn, $phone, $text, $clientes[0]);
    }

    actualizarSesion(
        $conn,
        $phone,
        null,
        null,
        'seleccion_cliente_cambio_numero',
        'esperando_cliente_cambio',
        $text,
        0,
        json_encode(['clientes' => $clientes], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
    );

    return mensajeSeleccionClienteCambioNumero($clientes);
}

function procesarSeleccionClienteCambioNumero($conn, $phone, $text, $session)
{
    $opcion = (int)trim((string)$text);
    $context = json_decode($session['context_json'] ?? '{}', true);
    $clientes = $context['clientes'] ?? [];

    if (!is_array($clientes) || count($clientes) === 0) {
        actualizarSesion($conn, $phone, null, null, 'cambio_numero', 'esperando_dato_cliente_cambio', $text, 0, null);
        return "La selección anterior venció.\n\n" . iniciarCambioNumero($conn, $phone, $text);
    }

    if ($opcion < 1 || $opcion > count($clientes)) {
        return mensajeSeleccionClienteCambioNumero($clientes);
    }

    return registrarSolicitudCambioNumero($conn, $phone, $text, $clientes[$opcion - 1]);
}

function mensajeSeleccionClienteCambioNumero($clientes)
{
    $respuesta = "Encontré más de una cuenta posible.\n\n" .
                 "Respondé con el número de la cuenta para registrar la solicitud de cambio de teléfono:\n\n";

    $n = 1;
    foreach ($clientes as $cliente) {
        $respuesta .= $n . ") " . ($cliente['full_name'] ?? 'Cliente');
        if (!empty($cliente['customer_code'])) {
            $respuesta .= " | Cod: " . $cliente['customer_code'];
        }
        if (!empty($cliente['address'])) {
            $respuesta .= " | " . $cliente['address'];
        }
        $respuesta .= "\n";
        $n++;
    }

    return trim($respuesta) . "\n\nPor seguridad, esto solo registra una solicitud. No actualiza el CRM automáticamente.";
}

function registrarSolicitudCambioNumero($conn, $phone, $text, $cliente)
{
    $customer_id = isset($cliente['id']) ? (int)$cliente['id'] : 0;
    $nombre = $cliente['full_name'] ?? 'Cliente';

    $intake_id = registrarIntakeWhatsapp($conn, $phone, $customer_id, 'phone_change', 'needs_review', $text, [
        'customer_id' => $customer_id,
        'customer_name' => $nombre,
        'telefono_nuevo_solicitado' => $phone,
    ], 1);

    actualizarSesion(
        $conn,
        $phone,
        null,
        null,
        'cambio_numero',
        'solicitud_registrada',
        $text,
        1,
        json_encode(['intake_id' => $intake_id, 'target_customer_id' => $customer_id], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
    );

    return "Solicitud registrada ✅\n\n" .
           "Cuenta indicada: " . $nombre . "\n" .
           "WhatsApp nuevo informado: " . $phone . "\n\n" .
           "Por seguridad, un operador verificará la identidad antes de actualizar el teléfono en el CRM.\n" .
           "Hasta que se verifique, no se mostrarán saldo, facturas ni datos de servicio desde este número.";
}

function derivarNoRegistradoOperador($conn, $phone, $text, $motivo)
{
    actualizarSesion($conn, $phone, null, null, 'contacto_no_registrado', 'derivado_operador', $text, 1, json_encode([
        'motivo' => $motivo,
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));

    registrarIntakeWhatsapp($conn, $phone, null, 'human_request', 'needs_review', $text, [
        'motivo' => $motivo,
    ], 1);

    return "Perfecto. Voy a derivar tu consulta a una persona de INTERLINK.\n\n" .
           "Como este WhatsApp no figura registrado en clientes, el operador puede pedirte datos para validar la cuenta antes de informar saldo, facturas o estado del servicio.";
}

function asegurarTablaWhatsappServiceKnowledge($conn)
{
    static $checked = false;
    if ($checked) {
        return;
    }
    $checked = true;

    $sql = "CREATE TABLE IF NOT EXISTS whatsapp_service_knowledge (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        code VARCHAR(60) COLLATE utf8mb4_unicode_ci NOT NULL,
        title VARCHAR(160) COLLATE utf8mb4_unicode_ci NOT NULL,
        body TEXT COLLATE utf8mb4_unicode_ci NOT NULL,
        enabled TINYINT(1) NOT NULL DEFAULT 1,
        sort_order INT NOT NULL DEFAULT 100,
        created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT NULL,
        PRIMARY KEY (id),
        UNIQUE KEY uniq_wsk_code (code),
        KEY idx_wsk_enabled_sort (enabled, sort_order)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";

    if (!$conn->query($sql)) {
        log_file('error_log_custom.txt', 'Error creando whatsapp_service_knowledge: ' . $conn->error);
        return;
    }

    $defaults = [
        ['intro', 'Información general', 'INTERLINK brinda servicio de internet según factibilidad técnica de cada zona. La disponibilidad puede ser por fibra o por antena, según cobertura.', 10],
        ['plans', 'Planes', 'Los planes, velocidades y precio final se confirman según localidad, barrio, tecnología disponible y factibilidad técnica. Un operador comercial valida la opción correcta para tu domicilio.', 20],
        ['coverage', 'Cobertura', 'La cobertura se verifica por domicilio, barrio/localidad y referencia. En algunas zonas puede haber fibra y en otras servicio por antena.', 25],
        ['installation', 'Instalación nueva', 'Para verificar instalación se solicita nombre completo, domicilio, barrio/localidad y una referencia de ubicación. La solicitud queda pendiente de revisión comercial.', 30],
    ];

    $stmt = $conn->prepare("INSERT IGNORE INTO whatsapp_service_knowledge (code, title, body, enabled, sort_order, created_at, updated_at) VALUES (?, ?, ?, 1, ?, NOW(), NOW())");
    if (!$stmt) {
        return;
    }
    foreach ($defaults as $row) {
        $code = $row[0];
        $title = $row[1];
        $body = $row[2];
        $sort = (int)$row[3];
        $stmt->bind_param('sssi', $code, $title, $body, $sort);
        $stmt->execute();
    }
    $stmt->close();
}

function obtenerWhatsappServiceKnowledge($conn, $codes = [])
{
    asegurarTablaWhatsappServiceKnowledge($conn);

    $where = "enabled = 1";
    $params = [];
    $types = '';

    if (is_array($codes) && count($codes) > 0) {
        $marks = implode(',', array_fill(0, count($codes), '?'));
        $where .= " AND code IN ($marks)";
        foreach ($codes as $code) {
            $params[] = (string)$code;
            $types .= 's';
        }
    }

    $sql = "SELECT code, title, body FROM whatsapp_service_knowledge WHERE $where ORDER BY sort_order ASC, id ASC";
    $stmt = $conn->prepare($sql);
    if (!$stmt) {
        return [];
    }

    if ($params) {
        $stmt->bind_param($types, ...$params);
    }

    if (!$stmt->execute()) {
        $stmt->close();
        return [];
    }

    $res = $stmt->get_result();
    $items = [];
    while ($row = $res->fetch_assoc()) {
        $items[] = $row;
    }
    $stmt->close();
    return $items;
}

function respuestaInfoServicioPrecliente($conn)
{
    $items = obtenerWhatsappServiceKnowledge($conn, ['intro', 'plans', 'coverage', 'installation']);
    if (!$items) {
        return "Te puedo orientar sobre servicio, planes e instalación.\n\n" .
               "La disponibilidad depende de la zona y de la factibilidad técnica. Para confirmar cobertura necesito localidad, barrio y domicilio o referencia.";
    }

    $out = "Información sobre el servicio de INTERLINK:";
    foreach ($items as $item) {
        $title = trim((string)($item['title'] ?? ''));
        $body = trim((string)($item['body'] ?? ''));
        if ($body === '') {
            continue;
        }
        $out .= "\n\n• " . ($title !== '' ? $title . ': ' : '') . $body;
    }
    return trim($out);
}

function respuestaInfoInstalacionPrecliente($conn)
{
    $items = obtenerWhatsappServiceKnowledge($conn, ['installation']);
    if (!$items) {
        return "La instalación queda sujeta a cobertura y factibilidad técnica de la zona.";
    }

    $parts = [];
    foreach ($items as $item) {
        $body = trim((string)($item['body'] ?? ''));
        if ($body !== '') {
            $parts[] = $body;
        }
    }
    return $parts ? implode("\n", $parts) : "La instalación queda sujeta a cobertura y factibilidad técnica de la zona.";
}

function asegurarTablaWhatsappContactIntakes($conn)
{
    static $checked = false;
    if ($checked) {
        return;
    }
    $checked = true;

    $sql = "CREATE TABLE IF NOT EXISTS whatsapp_contact_intakes (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        phone VARCHAR(30) COLLATE utf8mb4_unicode_ci NOT NULL,
        customer_id BIGINT(20) DEFAULT NULL,
        intake_type VARCHAR(40) COLLATE utf8mb4_unicode_ci NOT NULL,
        status VARCHAR(30) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'open',
        source_message TEXT COLLATE utf8mb4_unicode_ci,
        normalized_text TEXT COLLATE utf8mb4_unicode_ci,
        payload_json MEDIUMTEXT COLLATE utf8mb4_unicode_ci,
        human_required TINYINT(1) NOT NULL DEFAULT 1,
        created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT NULL,
        PRIMARY KEY (id),
        KEY idx_wci_phone_created (phone, created_at),
        KEY idx_wci_customer (customer_id),
        KEY idx_wci_type_status (intake_type, status),
        KEY idx_wci_human_created (human_required, created_at)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";

    if (!$conn->query($sql)) {
        log_file('error_log_custom.txt', 'Error creando whatsapp_contact_intakes: ' . $conn->error);
    }
}

function whatsappTablaExiste($conn, $table)
{
    $stmt = $conn->prepare("SHOW TABLES LIKE ?");
    if (!$stmt) {
        return false;
    }
    $stmt->bind_param('s', $table);
    $stmt->execute();
    $exists = (bool)$stmt->get_result()->fetch_row();
    $stmt->close();
    return $exists;
}

function registrarIntakeWhatsapp($conn, $phone, $customer_id, $type, $status, $message, $payload = [], $human_required = 1)
{
    asegurarTablaWhatsappContactIntakes($conn);

    $normalized = normalizarTextoBot($message);
    $payload_json = json_encode(is_array($payload) ? $payload : [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $human_required = (int)$human_required;

    $stmt = $conn->prepare("\n        INSERT INTO whatsapp_contact_intakes\n        (phone, customer_id, intake_type, status, source_message, normalized_text, payload_json, human_required, created_at, updated_at)\n        VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())\n    ");

    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error registrarIntakeWhatsapp prepare: ' . $conn->error);
        return null;
    }

    $stmt->bind_param('sisssssi', $phone, $customer_id, $type, $status, $message, $normalized, $payload_json, $human_required);
    if (!$stmt->execute()) {
        log_file('error_log_custom.txt', 'Error registrarIntakeWhatsapp execute: ' . $stmt->error);
        $stmt->close();
        return null;
    }

    $id = $stmt->insert_id;
    $stmt->close();
    return $id ?: null;
}

function actualizarSesionComprobanteAsociadoSeguro($conn, $phone, $cliente, $receipt_id, $last_message)
{
    $customer_id = isset($cliente['id']) ? (int)$cliente['id'] : 0;
    $nombre = $cliente['full_name'] ?? 'Cliente';

    if ($customer_id > 0 && $receipt_id > 0) {
        actualizarCustomerComprobante($conn, $receipt_id, $customer_id);
    }

    if ($customer_id > 0 && telefonoClienteCoincideWhatsapp($cliente, $phone)) {
        actualizarSesion($conn, $phone, $customer_id, $customer_id, 'comprobante_pago', null, 'comprobante #' . $receipt_id, 0, null);
        return [
            'registered_phone' => true,
            'message' => "Comprobante asociado correctamente ✅\n\n" .
                         "Cuenta: " . $nombre . "\n" .
                         "Estado: pendiente de conciliación administrativa.\n\n" .
                         "Cuando el pago sea confirmado, se impactará en tu cuenta."
        ];
    }

    registrarIntakeWhatsapp($conn, $phone, $customer_id, 'third_party_payment', 'needs_review', $last_message, [
        'receipt_id' => $receipt_id,
        'customer_id' => $customer_id,
        'customer_name' => $nombre,
        'telefono_whatsapp_no_registrado' => $phone,
    ], 1);

    actualizarSesion(
        $conn,
        $phone,
        null,
        null,
        'pago_tercero',
        'comprobante_asociado',
        'comprobante #' . $receipt_id,
        1,
        json_encode(['receipt_id' => $receipt_id, 'target_customer_id' => $customer_id, 'target_customer_name' => $nombre], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
    );

    return [
        'registered_phone' => false,
        'message' => "Comprobante asociado para revisión ✅\n\n" .
                     "Cuenta indicada: " . $nombre . "\n" .
                     "Estado: pendiente de conciliación administrativa.\n\n" .
                     "Importante: este WhatsApp no figura como número registrado de la cuenta. Por seguridad no se mostrarán saldo, facturas ni datos del servicio hasta validación administrativa."
    ];
}


function respuestaComprobanteAsociadoManual($conn, $phone, $receipt_id, $cliente)
{
    $resultado = actualizarSesionComprobanteAsociadoSeguro($conn, $phone, $cliente, $receipt_id, 'comprobante #' . $receipt_id);
    return $resultado['message'];
}


/* ================= INTENCIONES ================= */

function detectarIntentFacturacion($text)
{
    $text = mb_strtolower(trim($text), 'UTF-8');

    if (
        $text === '1' ||
        strpos($text, 'ultima factura') !== false ||
        strpos($text, 'última factura') !== false ||
        strpos($text, 'factura') !== false ||
        strpos($text, 'vencimiento') !== false ||
        strpos($text, 'suspension') !== false ||
        strpos($text, 'suspensión') !== false
    ) {
        return 'ultima_factura';
    }

    if (
        $text === '2' ||
        strpos($text, 'ultimo pago') !== false ||
        strpos($text, 'último pago') !== false ||
        strpos($text, 'pago') !== false
    ) {
        return 'ultimo_pago';
    }

    if (
        $text === '3' ||
        strpos($text, 'formas de pago') !== false ||
        strpos($text, 'forma de pago') !== false ||
        strpos($text, 'alias') !== false ||
        strpos($text, 'transferencia') !== false ||
        strpos($text, 'efectivo') !== false ||
        strpos($text, 'donde pago') !== false ||
        strpos($text, 'dónde pago') !== false
    ) {
        return 'formas_pago';
    }

    if ($text === 'facturacion' || $text === 'facturación') {
        return 'facturacion';
    }

    return 'facturacion';
}

function detectarIntent($text)
{
    $text = mb_strtolower(trim($text), 'UTF-8');

    if ($text === 'menu' || $text === 'menú' || $text === 'inicio' || $text === '0') {
        return 'menu';
    }

    if (strpos($text, 'cambiar cuenta') !== false || strpos($text, 'otra cuenta') !== false) {
        return 'cambiar_cuenta';
    }

    // Menú principal reducido.
    if ($text === '1' || strpos($text, 'facturacion') !== false || strpos($text, 'facturación') !== false) {
        return 'facturacion';
    }

    if (
        $text === '2' ||
        strpos($text, 'soporte') !== false ||
        strpos($text, 'tecnico') !== false ||
        strpos($text, 'técnico') !== false ||
        strpos($text, 'no tengo internet') !== false ||
        strpos($text, 'sin internet') !== false ||
        strpos($text, 'no anda') !== false ||
        strpos($text, 'no funciona') !== false ||
        strpos($text, 'internet lento') !== false ||
        strpos($text, 'antena') !== false ||
        strpos($text, 'fibra') !== false ||
        strpos($text, 'wisp') !== false
    ) {
        return 'soporte';
    }

    if (
        $text === '3' ||
        strpos($text, 'instalacion') !== false ||
        strpos($text, 'instalación') !== false ||
        strpos($text, 'quiero internet') !== false ||
        strpos($text, 'nuevo servicio') !== false
    ) {
        return 'instalacion';
    }

    if (
        $text === '4' ||
        strpos($text, 'operador') !== false ||
        strpos($text, 'persona') !== false ||
        strpos($text, 'humano') !== false ||
        strpos($text, 'asesor') !== false
    ) {
        return 'humano';
    }

    // Accesos directos por texto, aunque no estén en el menú principal.
    if (
        strpos($text, 'deuda') !== false ||
        strpos($text, 'debo') !== false ||
        strpos($text, 'saldo') !== false ||
        strpos($text, 'cuanto debo') !== false ||
        strpos($text, 'cuánto debo') !== false
    ) {
        return 'deuda';
    }

    if (
        strpos($text, 'ultima factura') !== false ||
        strpos($text, 'última factura') !== false ||
        strpos($text, 'vencimiento') !== false ||
        strpos($text, 'suspension') !== false ||
        strpos($text, 'suspensión') !== false
    ) {
        return 'ultima_factura';
    }

    if (strpos($text, 'ultimo pago') !== false || strpos($text, 'último pago') !== false) {
        return 'ultimo_pago';
    }

    if (
        strpos($text, 'formas de pago') !== false ||
        strpos($text, 'forma de pago') !== false ||
        strpos($text, 'alias') !== false ||
        strpos($text, 'transferencia') !== false ||
        strpos($text, 'efectivo') !== false ||
        strpos($text, 'donde pago') !== false ||
        strpos($text, 'dónde pago') !== false
    ) {
        return 'formas_pago';
    }

    return 'saludo';
}

function procesarMenuFacturacion($conn, $phone, $text, $session, $intent)
{
    $t = mb_strtolower(trim($text), 'UTF-8');

    if ($intent === 'menu') {
        actualizarSesion($conn, $phone, obtenerCustomerIdSesion($session), obtenerSelectedCustomerIdSesion($session), null, null, $text, 0, null);
        return menuPrincipal($conn, $phone, $session);
    }

    if ($t === '1' || $intent === 'ultima_factura') {
        return ejecutarIntentConCliente($conn, $phone, $text, $session, 'ultima_factura');
    }

    if ($t === '2' || $intent === 'ultimo_pago') {
        return ejecutarIntentConCliente($conn, $phone, $text, $session, 'ultimo_pago');
    }

    if ($t === '3' || $intent === 'formas_pago') {
        actualizarSesion($conn, $phone, obtenerCustomerIdSesion($session), obtenerSelectedCustomerIdSesion($session), 'formas_pago', null, $text, 0, null);
        return respuestaFormasPago();
    }

    return menuFacturacion();
}


/* ================= CLIENTES / SALDO / FACTURAS / PAGOS ================= */

function normalizarTelefonoCrm($phone_whatsapp)
{
    return substr(preg_replace('/\D+/', '', $phone_whatsapp), -10);
}

function buscarClientesPorTelefono($conn, $phone_whatsapp)
{
    $phone_crm = normalizarTelefonoCrm($phone_whatsapp);

    $stmt = $conn->prepare("SELECT id, full_name, phone FROM customers WHERE phone = ? ORDER BY full_name ASC, id ASC");

    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error buscarClientesPorTelefono: ' . $conn->error);
        return [];
    }

    $stmt->bind_param("s", $phone_crm);
    $stmt->execute();

    $result = $stmt->get_result();
    $clientes = [];

    while ($row = $result->fetch_assoc()) {
        $clientes[] = $row;
    }

    return $clientes;
}

function buscarClientesParaComprobante($conn, $query)
{
    $query = trim((string)$query);
    if ($query === '') {
        return [];
    }

    $digits = preg_replace('/\D+/', '', $query);
    $like = '%' . $query . '%';
    $digitsLike = '%' . $digits . '%';

    $where = [];
    $params = [];
    $types = '';

    $where[] = '(full_name LIKE ? OR customer_code LIKE ? OR address LIKE ? OR neighborhood LIKE ? OR city LIKE ?)';
    array_push($params, $like, $like, $like, $like, $like);
    $types .= 'sssss';

    if ($digits !== '') {
        $where[] = '(REPLACE(REPLACE(REPLACE(COALESCE(dni_cuit, ""), ".", ""), "-", ""), " ", "") LIKE ? OR REPLACE(REPLACE(REPLACE(COALESCE(phone, ""), "+", ""), "-", ""), " ", "") LIKE ?)';
        array_push($params, $digitsLike, $digitsLike);
        $types .= 'ss';
    }

    $sql = "SELECT id, customer_code, full_name, dni_cuit, phone, address, neighborhood, city
            FROM customers
            WHERE " . implode(' OR ', $where) . "
            ORDER BY
              CASE
                WHEN customer_code = ? THEN 0
                WHEN full_name LIKE ? THEN 1
                ELSE 2
              END,
              full_name ASC,
              id ASC
            LIMIT 8";
    $params[] = $query;
    $params[] = $like;
    $types .= 'ss';

    $stmt = $conn->prepare($sql);
    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error buscarClientesParaComprobante: ' . $conn->error);
        return [];
    }

    $stmt->bind_param($types, ...$params);
    $stmt->execute();
    $res = $stmt->get_result();
    $items = [];
    while ($row = $res->fetch_assoc()) {
        $items[] = $row;
    }
    $stmt->close();

    return $items;
}

function buscarClientePorId($conn, $customer_id)
{
    if (!$customer_id) {
        return null;
    }

    $stmt = $conn->prepare("SELECT id, full_name, phone FROM customers WHERE id = ? LIMIT 1");

    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error buscarClientePorId: ' . $conn->error);
        return null;
    }

    $stmt->bind_param("i", $customer_id);
    $stmt->execute();

    return $stmt->get_result()->fetch_assoc();
}

function telefonoClienteCoincideWhatsapp($cliente, $phone)
{
    if (!$cliente || empty($cliente['phone'])) {
        return false;
    }

    return normalizarTelefonoCrm((string)$cliente['phone']) === normalizarTelefonoCrm((string)$phone);
}

function limpiarSesionSiClienteNoCoincide($conn, $phone, $session, $motivo = 'telefono_reasignado')
{
    if (!$session) {
        return;
    }

    $customer_id = null;
    if (!empty($session['selected_customer_id'])) {
        $customer_id = (int)$session['selected_customer_id'];
    } elseif (!empty($session['customer_id'])) {
        $customer_id = (int)$session['customer_id'];
    }

    if (!$customer_id) {
        return;
    }

    $cliente = buscarClientePorId($conn, $customer_id);
    if ($cliente && telefonoClienteCoincideWhatsapp($cliente, $phone)) {
        return;
    }

    log_file('webhook_log.txt', 'Sesion WhatsApp limpiada por ' . $motivo . '. Phone: ' . $phone . ' customer_id_anterior: ' . $customer_id);

    // Importante: no se borra el historial de mensajes. Solo se limpia la vinculación/sesión activa.
    actualizarSesion($conn, $phone, null, null, null, null, 'sesion limpiada: ' . $motivo, 0, null);
}

function resolverClienteActual($conn, $phone, $session)
{
    if (!$session) {
        return null;
    }

    $customer_id = null;

    if (!empty($session['selected_customer_id'])) {
        $customer_id = (int)$session['selected_customer_id'];
    } elseif (!empty($session['customer_id'])) {
        $customer_id = (int)$session['customer_id'];
    }

    if (!$customer_id) {
        return null;
    }

    $cliente = buscarClientePorId($conn, $customer_id);

    // Seguridad: si el número fue reasignado a otro cliente, no usar la sesión vieja.
    if ($cliente && telefonoClienteCoincideWhatsapp($cliente, $phone)) {
        return $cliente;
    }

    limpiarSesionSiClienteNoCoincide($conn, $phone, $session);
    return null;
}

function consultarSaldoClientePorId($conn, $customer_id)
{
    $sql = "
        SELECT 
            c.id,
            c.full_name,
            c.phone,
            COALESCE(inv.total_facturado, 0) AS total_facturado,
            COALESCE(pay.total_pagado, 0) AS total_pagado,
            COALESCE(inv.total_facturado, 0) - COALESCE(pay.total_pagado, 0) AS saldo
        FROM customers c
        LEFT JOIN (
            SELECT customer_id, SUM(total) AS total_facturado
            FROM invoices
            GROUP BY customer_id
        ) inv ON inv.customer_id = c.id
        LEFT JOIN (
            SELECT customer_id, SUM(amount) AS total_pagado
            FROM payments
            GROUP BY customer_id
        ) pay ON pay.customer_id = c.id
        WHERE c.id = ?
        LIMIT 1
    ";

    $stmt = $conn->prepare($sql);

    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error consultarSaldoClientePorId: ' . $conn->error);
        return null;
    }

    $stmt->bind_param("i", $customer_id);
    $stmt->execute();

    return $stmt->get_result()->fetch_assoc();
}

function consultarUltimasFacturasPorCliente($conn, $customer_id, $limit = 3)
{
    $limit = (int)$limit;
    if ($limit < 1) {
        $limit = 1;
    }
    if ($limit > 5) {
        $limit = 5;
    }

    $sql = "
        SELECT id, customer_id, issue_date, due_date, suspend_date, total
        FROM invoices
        WHERE customer_id = ?
        ORDER BY issue_date DESC, id DESC
        LIMIT " . $limit;

    $stmt = $conn->prepare($sql);

    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error consultarUltimasFacturasPorCliente: ' . $conn->error);
        return [];
    }

    $stmt->bind_param("i", $customer_id);
    $stmt->execute();

    $result = $stmt->get_result();
    $facturas = [];

    while ($row = $result->fetch_assoc()) {
        $facturas[] = $row;
    }

    return $facturas;
}

function consultarUltimoPagoPorCliente($conn, $customer_id)
{
    $sql = "
        SELECT id, customer_id, amount, method, reference, paid_at
        FROM payments
        WHERE customer_id = ?
        ORDER BY paid_at DESC, id DESC
        LIMIT 1
    ";

    $stmt = $conn->prepare($sql);

    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error consultarUltimoPagoPorCliente: ' . $conn->error);
        return null;
    }

    $stmt->bind_param("i", $customer_id);
    $stmt->execute();

    return $stmt->get_result()->fetch_assoc();
}



/* ================= SERVICIOS / MIKROTIK ================= */

function consultarServiciosCliente($conn, $customer_id)
{
    $stmt = $conn->prepare("\n        SELECT id, service_label, status, ipv4, mac_address, dhcp_reservation, onu_sn, onu_model, olt_name, pon_port, onu_id\n        FROM services\n        WHERE customer_id = ?\n        ORDER BY id ASC\n    ");

    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error consultarServiciosCliente: ' . $conn->error);
        return [];
    }

    $stmt->bind_param("i", $customer_id);
    $stmt->execute();

    $result = $stmt->get_result();
    $servicios = [];

    while ($row = $result->fetch_assoc()) {
        $servicios[] = $row;
    }

    return $servicios;
}

function textoEstadoServiciosCliente($conn, $customer_id)
{
    $servicios = consultarServiciosCliente($conn, $customer_id);

    if (count($servicios) === 0) {
        return "Estado del servicio: no encontré servicios cargados en CRM.";
    }

    if (count($servicios) === 1) {
        $s = $servicios[0];
        $linea = "Estado del servicio: " . traducirEstadoServicio($s['status'] ?? '');
        if (!empty($s['service_label'])) {
            $linea .= " - " . $s['service_label'];
        }
        return $linea;
    }

    $lineas = ["Servicios registrados:"];
    $n = 1;
    foreach ($servicios as $s) {
        $linea = $n . ") " . traducirEstadoServicio($s['status'] ?? '');
        if (!empty($s['service_label'])) {
            $linea .= " - " . $s['service_label'];
        }
        $lineas[] = $linea;
        $n++;
    }

    return implode("\n", $lineas);
}

function traducirEstadoServicio($status)
{
    $status = strtolower(trim((string)$status));

    $map = [
        'active' => 'Activo',
        'activo' => 'Activo',
        'suspended' => 'Suspendido',
        'suspendido' => 'Suspendido',
        'inactive' => 'Inactivo',
        'inactivo' => 'Inactivo',
        'cancelled' => 'Cancelado',
        'canceled' => 'Cancelado',
        'pending' => 'Pendiente',
    ];

    return $map[$status] ?? ($status !== '' ? ucfirst($status) : 'No informado');
}

function diagnosticoServiciosSoporte($conn, $customer_id)
{
    $servicios = consultarServiciosCliente($conn, $customer_id);

    if (count($servicios) === 0) {
        return "Servicio técnico: no encontré servicios cargados para esta cuenta. Un operador puede verificarlo manualmente.";
    }

    $lineas = [];
    $n = 1;

    foreach ($servicios as $servicio) {
        $label = trim((string)($servicio['service_label'] ?? ''));
        $status = traducirEstadoServicio($servicio['status'] ?? '');

        $linea = "Servicio " . $n . ": " . $status;
        if ($label !== '') {
            $linea .= " - " . $label;
        }

        $lineas[] = $linea;
        $n++;
    }

    return implode("\n", $lineas);
}

function estadoAdministrativoSoporte($conn, $customer_id)
{
    $saldo = consultarSaldoClientePorId($conn, $customer_id);

    if (!$saldo) {
        return [
            'ok' => false,
            'tiene_deuda' => false,
            'saldo' => null,
            'texto' => 'Estado administrativo: no pude verificar el estado de cuenta en este momento.'
        ];
    }

    $monto = (float)($saldo['saldo'] ?? 0);

    if ($monto > 0.01) {
        return [
            'ok' => true,
            'tiene_deuda' => true,
            'saldo' => $monto,
            'texto' => 'Estado administrativo: registramos saldo pendiente.'
        ];
    }

    return [
        'ok' => true,
        'tiene_deuda' => false,
        'saldo' => $monto,
        'texto' => 'Estado administrativo: la cuenta no registra saldo pendiente.'
    ];
}

function textoEstadoAdministrativoSoporte($conn, $customer_id)
{
    $estado = estadoAdministrativoSoporte($conn, $customer_id);
    return $estado['texto'];
}

function obtenerRouterActivo($conn)
{
    $stmt = $conn->prepare("\n        SELECT *\n        FROM routers\n        WHERE is_active = 1\n        ORDER BY id ASC\n        LIMIT 1\n    ");

    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error obtenerRouterActivo: ' . $conn->error);
        return null;
    }

    $stmt->execute();
    return $stmt->get_result()->fetch_assoc();
}

function obtenerPasswordRouteros($router)
{
    if (defined('MIKROTIK_API_PASSWORD') && MIKROTIK_API_PASSWORD !== '') {
        return MIKROTIK_API_PASSWORD;
    }

    if (defined('ROUTEROS_API_PASSWORD') && ROUTEROS_API_PASSWORD !== '') {
        return ROUTEROS_API_PASSWORD;
    }

    /*
      No uso routers.password_enc directamente porque no confirmamos el método
      de desencriptado. Para activar la consulta MikroTik sin tocar tu cifrado,
      agregá en config.php:
      define('MIKROTIK_API_PASSWORD', 'clave_api_del_router');
    */
    return null;
}

function consultarMikrotikDhcpBound($conn, $ip)
{
    $ip = trim((string)$ip);

    if ($ip === '') {
        return ['ok' => false, 'bound' => null, 'message' => 'sin IP para consultar'];
    }

    $router = obtenerRouterActivo($conn);

    if (!$router) {
        return ['ok' => false, 'bound' => null, 'message' => 'sin router activo configurado'];
    }

    $host = trim((string)($router['api_host'] ?? ''));
    $port = (int)($router['api_port'] ?? 8728);
    $ssl = (int)($router['api_ssl'] ?? 0) === 1;
    $user = trim((string)($router['username'] ?? ''));
    $pass = obtenerPasswordRouteros($router);
    $timeout = (int)($router['timeout_sec'] ?? 4);

    if ($host === '' || $user === '' || $pass === null) {
        return [
            'ok' => false,
            'bound' => null,
            'message' => 'consulta no disponible: falta api_host/usuario o clave API en config.php'
        ];
    }

    $api = new SimpleRouterOSApi($host, $user, $pass, $port, $ssl, $timeout);

    try {
        if (!$api->connect()) {
            return ['ok' => false, 'bound' => null, 'message' => 'no conectó al router'];
        }

        $rows = $api->comm('/ip/dhcp-server/lease/print', [
            '?address' => $ip,
            '=.proplist' => '.id,address,mac-address,status,host-name,dynamic,comment,last-seen'
        ]);

        $api->disconnect();

        if (!is_array($rows) || count($rows) === 0) {
            return ['ok' => true, 'bound' => false, 'message' => 'IP no encontrada en leases'];
        }

        $lease = $rows[0];
        $status = strtolower((string)($lease['status'] ?? ''));
        $bound = ($status === 'bound');

        $msg = $bound ? 'BOUND activo' : ('lease encontrado, estado: ' . ($status ?: 'sin estado'));

        if (!empty($lease['mac-address'])) {
            $msg .= ' - MAC: ' . $lease['mac-address'];
        }

        return ['ok' => true, 'bound' => $bound, 'message' => $msg];

    } catch (Exception $e) {
        log_file('error_log_custom.txt', 'Error consultarMikrotikDhcpBound: ' . $e->getMessage());
        return ['ok' => false, 'bound' => null, 'message' => 'error consultando MikroTik'];
    }
}

class SimpleRouterOSApi
{
    private $host;
    private $user;
    private $pass;
    private $port;
    private $ssl;
    private $timeout;
    private $socket = null;

    public function __construct($host, $user, $pass, $port = 8728, $ssl = false, $timeout = 4)
    {
        $this->host = $host;
        $this->user = $user;
        $this->pass = $pass;
        $this->port = (int)$port;
        $this->ssl = (bool)$ssl;
        $this->timeout = (int)$timeout;
    }

    public function connect()
    {
        $target = ($this->ssl ? 'ssl://' : '') . $this->host;
        $errno = 0;
        $errstr = '';

        $this->socket = @fsockopen($target, $this->port, $errno, $errstr, $this->timeout);

        if (!$this->socket) {
            log_file('error_log_custom.txt', "RouterOS connect failed: {$errno} {$errstr}");
            return false;
        }

        stream_set_timeout($this->socket, $this->timeout);

        $this->writeSentence(['/login', '=name=' . $this->user, '=password=' . $this->pass]);
        $reply = $this->readReply();

        return $this->isDone($reply);
    }

    public function disconnect()
    {
        if ($this->socket) {
            @fclose($this->socket);
            $this->socket = null;
        }
    }

    public function comm($command, $params = [])
    {
        $sentence = [$command];
        foreach ($params as $key => $value) {
            $sentence[] = $key . '=' . $value;
        }

        $this->writeSentence($sentence);
        $reply = $this->readReply();

        $rows = [];
        foreach ($reply as $sentenceReply) {
            if (!isset($sentenceReply[0]) || $sentenceReply[0] !== '!re') {
                continue;
            }

            $row = [];
            foreach ($sentenceReply as $word) {
                if (strpos($word, '=') !== 0) {
                    continue;
                }
                $parts = explode('=', substr($word, 1), 2);
                if (count($parts) === 2) {
                    $row[$parts[0]] = $parts[1];
                }
            }
            $rows[] = $row;
        }

        return $rows;
    }

    private function isDone($reply)
    {
        foreach ($reply as $sentence) {
            if (isset($sentence[0]) && $sentence[0] === '!done') {
                return true;
            }
        }
        return false;
    }

    private function writeSentence($words)
    {
        foreach ($words as $word) {
            $this->writeWord($word);
        }
        $this->writeWord('');
    }

    private function writeWord($word)
    {
        $len = strlen($word);
        $this->writeLength($len);
        if ($len > 0) {
            fwrite($this->socket, $word);
        }
    }

    private function readReply()
    {
        $reply = [];
        $sentence = [];

        while (true) {
            $word = $this->readWord();

            if ($word === null) {
                break;
            }

            if ($word === '') {
                if (count($sentence) > 0) {
                    $reply[] = $sentence;
                    if (isset($sentence[0]) && in_array($sentence[0], ['!done', '!fatal'], true)) {
                        break;
                    }
                    $sentence = [];
                }
                continue;
            }

            $sentence[] = $word;
        }

        return $reply;
    }

    private function readWord()
    {
        $len = $this->readLength();

        if ($len === null) {
            return null;
        }

        if ($len === 0) {
            return '';
        }

        $data = '';
        while (strlen($data) < $len && !feof($this->socket)) {
            $chunk = fread($this->socket, $len - strlen($data));
            if ($chunk === false) {
                return null;
            }
            $data .= $chunk;
        }

        return $data;
    }

    private function writeLength($len)
    {
        if ($len < 0x80) {
            fwrite($this->socket, chr($len));
        } elseif ($len < 0x4000) {
            fwrite($this->socket, chr(($len >> 8) | 0x80) . chr($len & 0xFF));
        } elseif ($len < 0x200000) {
            fwrite($this->socket, chr(($len >> 16) | 0xC0) . chr(($len >> 8) & 0xFF) . chr($len & 0xFF));
        } elseif ($len < 0x10000000) {
            fwrite($this->socket, chr(($len >> 24) | 0xE0) . chr(($len >> 16) & 0xFF) . chr(($len >> 8) & 0xFF) . chr($len & 0xFF));
        } else {
            fwrite($this->socket, chr(0xF0) . chr(($len >> 24) & 0xFF) . chr(($len >> 16) & 0xFF) . chr(($len >> 8) & 0xFF) . chr($len & 0xFF));
        }
    }

    private function readLength()
    {
        $c = fread($this->socket, 1);
        if ($c === false || $c === '') {
            return null;
        }

        $c = ord($c);

        if (($c & 0x80) === 0x00) {
            return $c;
        }
        if (($c & 0xC0) === 0x80) {
            $c2 = ord(fread($this->socket, 1));
            return (($c & ~0xC0) << 8) + $c2;
        }
        if (($c & 0xE0) === 0xC0) {
            $c2 = ord(fread($this->socket, 1));
            $c3 = ord(fread($this->socket, 1));
            return (($c & ~0xE0) << 16) + ($c2 << 8) + $c3;
        }
        if (($c & 0xF0) === 0xE0) {
            $c2 = ord(fread($this->socket, 1));
            $c3 = ord(fread($this->socket, 1));
            $c4 = ord(fread($this->socket, 1));
            return (($c & ~0xF0) << 24) + ($c2 << 16) + ($c3 << 8) + $c4;
        }

        $c2 = ord(fread($this->socket, 1));
        $c3 = ord(fread($this->socket, 1));
        $c4 = ord(fread($this->socket, 1));
        $c5 = ord(fread($this->socket, 1));
        return ($c2 << 24) + ($c3 << 16) + ($c4 << 8) + $c5;
    }
}

/* ================= CONFIGURACIÓN RUNTIME WHATSAPP ================= */

function obtenerWhatsappRuntimeConfig($conn = null)
{
    $cfg = [
        'token' => defined('WHATSAPP_TOKEN') ? (string)WHATSAPP_TOKEN : '',
        'phone_number_id' => defined('PHONE_NUMBER_ID') ? (string)PHONE_NUMBER_ID : '',
        'api_version' => defined('WHATSAPP_API_VERSION') ? (string)WHATSAPP_API_VERSION : 'v22.0',
    ];

    if ($conn instanceof mysqli) {
        $res = @$conn->query("SHOW TABLES LIKE 'whatsapp_settings'");
        if ($res && $res->fetch_row()) {
            $rowRes = @$conn->query("SELECT token_enc, phone_number_id, api_version FROM whatsapp_settings WHERE id = 1 LIMIT 1");
            if ($rowRes) {
                $row = $rowRes->fetch_assoc();
                if ($row) {
                    if (!empty($row['token_enc'])) {
                        $cfg['token'] = trim((string)$row['token_enc']);
                    }
                    if (!empty($row['phone_number_id'])) {
                        $cfg['phone_number_id'] = trim((string)$row['phone_number_id']);
                    }
                    if (!empty($row['api_version'])) {
                        $cfg['api_version'] = trim((string)$row['api_version']);
                    }
                }
            }
        }
    }

    $cfg['api_version'] = normalizarApiVersionWhatsapp($cfg['api_version']);

    return $cfg;
}

function normalizarApiVersionWhatsapp($version)
{
    $version = trim((string)$version);
    if ($version === '') {
        return 'v22.0';
    }
    if (!preg_match('/^v(\d+)\.(\d+)$/', $version, $m)) {
        return 'v22.0';
    }
    // El dump original del CRM podía traer v19.0. Esa versión dejó de estar disponible
    // el 21/05/2026, por eso forzamos mínimo v22.0 para no cortar envíos.
    if ((int)$m[1] < 22) {
        return 'v22.0';
    }
    return $version;
}

/* ================= MENSAJES / WHATSAPP ================= */

function guardarMensaje($conn, $phone, $direction, $message, $raw_response = null, $wa_message_id = null, $intent = null)
{
    $stmt = $conn->prepare("INSERT INTO whatsapp_messages_api (wa_message_id, phone, direction, message, intent, raw_response, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())");

    if (!$stmt) {
        log_file('error_log_custom.txt', 'Error guardarMensaje: ' . $conn->error);
        return false;
    }

    $stmt->bind_param("ssssss", $wa_message_id, $phone, $direction, $message, $intent, $raw_response);
    return $stmt->execute();
}

function responder($to, $message)
{
    global $conn;
    $cfg = obtenerWhatsappRuntimeConfig($conn ?? null);
    $version = normalizarApiVersionWhatsapp($cfg['api_version'] ?? 'v22.0');
    $phoneNumberId = trim((string)($cfg['phone_number_id'] ?? ''));
    $token = trim((string)($cfg['token'] ?? ''));

    if ($phoneNumberId === '' || $token === '') {
        $result = [
            'ok' => false,
            'http_code' => 0,
            'error' => 'Falta token o Phone Number ID en configuración WhatsApp.',
            'to' => $to,
            'api_version' => $version,
        ];
        log_file('response_log.txt', json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        return $result;
    }

    $url = "https://graph.facebook.com/" . $version . "/" . rawurlencode($phoneNumberId) . "/messages";

    $data = [
        "messaging_product" => "whatsapp",
        "to" => $to,
        "type" => "text",
        "text" => [
            "preview_url" => false,
            "body" => $message
        ]
    ];

    $headers = [
        "Authorization: Bearer " . $token,
        "Content-Type: application/json"
    ];

    $ch = curl_init($url);

    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);

    $response = curl_exec($ch);
    $httpcode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $error = curl_error($ch);

    curl_close($ch);

    $decoded = json_decode((string)$response, true);
    $ok = $httpcode >= 200 && $httpcode < 300 && isset($decoded['messages'][0]['id']);

    $result = [
        'ok' => $ok,
        'http_code' => $httpcode,
        'error' => $ok ? null : ($error ?: ($decoded['error']['message'] ?? 'Error enviando mensaje.')),
        'provider_message_id' => $decoded['messages'][0]['id'] ?? null,
        'to' => $to,
        'api_version' => $version,
        'phone_number_id' => $phoneNumberId,
        'response' => $decoded ?: $response,
    ];

    log_file(
        'response_log.txt',
        "HTTP: " . $httpcode . PHP_EOL .
        "TO: " . $to . PHP_EOL .
        "API_VERSION: " . $version . PHP_EOL .
        "OK: " . ($ok ? '1' : '0') . PHP_EOL .
        "ERROR: " . ($result['error'] ?: '') . PHP_EOL .
        "RESPONSE: " . (is_array($decoded) ? json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $response)
    );

    return $result;
}

function normalizarNumeroSalida($from)
{
    if ($from === '5493874891734') {
        return '543874891734';
    }

    return $from;
}


/* ================= UTILIDADES ================= */

function respuestaFormasPago()
{
    return "Formas de pago disponibles:\n\n" .
           "Transferencia bancaria:\n" .
           "Alias: soluciones.dc\n" .
           "CBU: 2850106840094091240018\n" .
           "Titular: TIMOTEO DAVID CHAVEZ\n" .
           "CUIT: 20-27749674-8\n\n" .
           "Efectivo:\n" .
           "Calle Salta 570\n" .
           "Oficina Central: Mitre 215, Apolinario Saravia.";
}

function formatearFecha($fecha)
{
    if (!$fecha || $fecha === '0000-00-00' || $fecha === '0000-00-00 00:00:00') {
        return 'No registrada';
    }

    return date('d/m/Y', strtotime($fecha));
}

function menuFacturacion()
{
    return "FACTURACIÓN\n\n" .
           "Respondé con el número o escribí la opción:\n\n" .
           "1️⃣ Última factura\n" .
           "2️⃣ Último pago\n" .
           "3️⃣ Forma de pago\n\n" .
           "Para volver al menú principal, escribí: menú";
}

function menuPrincipal($conn = null, $phone = null, $session = null)
{
    $saludo = "Hola 👋 Soy el asistente automático de INTERLINK.";

    if ($conn && $phone) {
        $cliente = resolverClienteActual($conn, $phone, $session);

        if (!$cliente) {
            $clientes = buscarClientesPorTelefono($conn, $phone);
            if (count($clientes) === 0) {
                return menuContactoNoRegistrado();
            }
            if (count($clientes) === 1) {
                $cliente = $clientes[0];
            } elseif (count($clientes) > 1) {
                $saludo = "Hola 👋 Encontré más de una cuenta asociada a este WhatsApp.";
            }
        }

        if ($cliente) {
            $saludo = "Hola! " . ($cliente['full_name'] ?? 'Cliente') . " 👋";
            $saludo .= "\n" . textoEstadoServiciosCliente($conn, (int)$cliente['id']);
        }
    }

    return $saludo . "\n\n" .
           "Podés responder con el número o escribir el motivo:\n\n" .
           "1️⃣ FACTURACIÓN\n" .
           "2️⃣ SOPORTE TÉCNICO\n" .
           "3️⃣ NUEVA INSTALACIÓN\n" .
           "4️⃣ OPERADOR (8hs a 20hs)\n\n" .
           "Si tenés más de una cuenta, también podés escribir: cambiar cuenta";
}


function log_file($file, $content)
{
    file_put_contents(
        __DIR__ . '/' . $file,
        date('Y-m-d H:i:s') . PHP_EOL .
        $content . PHP_EOL .
        str_repeat('-', 60) . PHP_EOL,
        FILE_APPEND
    );
}
