<?php
session_start();
require_once 'config/config.php';

// Verificar autenticación igual que el resto del sistema
if (!isUserLoggedIn()) {
    header('Location: login.php');
    exit;
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
    <title>💬 Conversaciones - WhatsApp Bot</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <style>
        :root {
            --whatsapp-green: #25d366;
            --whatsapp-green-dark: #128c7e;
            --whatsapp-light: #dcf8c6;
            --whatsapp-dark: #075e54;
            --message-bg: #f0f0f0;
            --sidebar-bg: #f8f9fa;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
            background: linear-gradient(135deg, #f0f4f7 0%, #e5ddd5 100%);
            margin: 0;
            min-height: 100vh;
            overflow: hidden;
            color: #222;
            font-size: 14px;
        }

        .chat-container {
            display: flex;
            height: 100vh;
            background: transparent;
            gap: 12px;
            padding: 12px;
        }

        .chat-sidebar {
            width: 360px;
            background: var(--sidebar-bg);
            border-radius: 8px;
            border: 1px solid #e9eef2;
            display: flex;
            flex-direction: column;
            box-shadow: 0 4px 14px rgba(16,24,40,0.04);
            overflow: hidden;
        }

        .sidebar-header {
            padding: 14px 16px;
            background: linear-gradient(180deg, var(--whatsapp-dark) 0%, #054f45 100%);
            color: white;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }

        .sidebar-search {
            padding: 10px 12px;
            background: white;
            border-bottom: 1px solid #eef2f5;
        }

        .conversation-list {
            flex: 1;
            overflow-y: auto;
            padding: 6px;
            scroll-behavior: smooth;
        }

        /* Scrollbar personalizado estilo WhatsApp (fino y discreto) */
        .conversation-list::-webkit-scrollbar { width: 4px; }
        .conversation-list::-webkit-scrollbar-track { background: transparent; }
        .conversation-list::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.15); border-radius: 4px; }
        .conversation-list::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.28); }

        /* Buscador mejorado */
        .search-wrapper { position: relative; }
        .search-spinner { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); display: none; color: #999; font-size: 13px; }
        .search-clear { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); display: none; background: none; border: none; padding: 0; color: #999; cursor: pointer; font-size: 14px; line-height: 1; }
        .search-clear:hover { color: #333; }

        .conversation-item {
            padding: 12px 14px;
            border-bottom: 1px solid #f1f5f8;
            cursor: pointer;
            transition: background-color 0.12s, transform 0.08s;
            display: flex;
            align-items: center;
        }

        .conversation-item:hover {
            background: #f6fbff;
            transform: translateY(-1px);
        }

        .conversation-item.active {
            background: #e9fbf1;
            border-right: 3px solid var(--whatsapp-green);
        }

        .conversation-avatar {
            width: 46px;
            height: 46px;
            border-radius: 50%;
            background: var(--whatsapp-green);
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-weight: 700;
            margin-right: 12px;
            flex-shrink: 0;
        }

        .conversation-info {
            flex: 1;
            min-width: 0;
        }

        .conversation-name {
            font-weight: 700;
            font-size: 15px;
            color: #262626;
            margin-bottom: 4px;
        }

        .conversation-preview {
            color: #6b7280;
            font-size: 13px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        .conversation-time {
            color: #9aa4af;
            font-size: 12px;
            text-align: right;
            flex-shrink: 0;
            margin-left: 10px;
        }

        .chat-main {
            flex: 1;
            display: flex;
            flex-direction: column;
            background: transparent;
        }

        .chat-header {
            padding: 12px 18px;
            background: linear-gradient(180deg, var(--whatsapp-dark) 0%, #0a5347 100%);
            color: white;
            border-radius: 8px;
            border-bottom-left-radius: 0;
            border-bottom-right-radius: 0;
            display: flex;
            align-items: center;
            gap: 12px;
            box-shadow: 0 2px 6px rgba(15,23,42,0.06);
        }

        .chat-header-avatar {
            width: 44px;
            height: 44px;
            border-radius: 50%;
            background: var(--whatsapp-green);
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-weight: 700;
            margin-right: 12px;
        }

        .chat-header-info h6 { margin: 0; font-size: 15px; }
        .chat-header-info small { color: rgba(255,255,255,0.85); }

        .chat-conversations {
            flex: 1;
            overflow-y: auto;
            padding: 12px 16px;
            background: #f8f9fb;
            border: 1px solid #eef2f5;
            border-radius: 0 0 8px 8px;
        }

        .message {
            width: 100%;
            margin-bottom: 8px;
            display: flex;
            align-items: flex-end;
        }

        .message.outgoing {
            justify-content: flex-end;
        }

        .message.incoming {
            justify-content: flex-start;
        }

        .message-bubble {
            padding: 8px 12px;
            border-radius: 18px;
            position: relative;
            word-wrap: break-word;
            box-shadow: 0 3px 10px rgba(2,6,23,0.06);
            max-width: 72%;
            line-height: 1.35;
            transition: transform 0.08s ease, box-shadow 0.12s ease;
        }

        .message.outgoing .message-bubble {
            background: var(--whatsapp-light);
            color: #222;
            border-bottom-right-radius: 6px;
        }

        .message.incoming .message-bubble {
            background: white;
            color: #222;
            border-bottom-left-radius: 6px;
        }

        /* Cola tipo WhatsApp */
        .message.outgoing .message-bubble::after {
            content: '';
            position: absolute;
            right: -6px;
            bottom: 6px;
            width: 12px;
            height: 12px;
            background: var(--whatsapp-light);
            transform: rotate(45deg);
            z-index: 0;
            box-shadow: 0 1px 2px rgba(0,0,0,0.04);
        }
        .message.incoming .message-bubble::after {
            content: '';
            position: absolute;
            left: -6px;
            bottom: 6px;
            width: 12px;
            height: 12px;
            background: white;
            transform: rotate(45deg);
            z-index: 0;
            box-shadow: 0 1px 2px rgba(0,0,0,0.04);
        }

        /* Mensajes no leídos nuevos */
        .message.unread .message-bubble {
            background: #fffbea;
            border: 1px solid rgba(255, 193, 7, 0.3);
            animation: highlightNew 0.6s ease;
        }
        
        .message.unread.incoming .message-bubble::after {
            background: #fffbea;
        }
        
        @keyframes highlightNew {
            0% { background: #fff9c4; transform: scale(1.02); }
            100% { background: #fffbea; transform: scale(1); }
        }

        /* Timestamp badge shown at the corner of each bubble (WhatsApp-like) */
        .message-bubble { padding-bottom: 20px; }
        .message-bubble .message-time {
            position: absolute;
            right: 8px;
            bottom: 6px;
            font-size: 11px;
            line-height: 1;
            color: #666;
            opacity: 1; /* always visible */
            transition: opacity 0.12s ease-in-out, transform 0.12s ease;
            white-space: nowrap;
            font-weight: 500;
            background: transparent;
            padding: 0 4px;
        }
        /* Different color for outgoing (light text over green bubble) */
        .message.outgoing .message-bubble .message-time { color: rgba(255,255,255,0.92); }
        .message.incoming .message-bubble .message-time { color: rgba(32,37,41,0.7); }
        /* reduce prominence on very small screens */
        @media (max-width:420px) {
            .message-bubble .message-time { font-size:10px; right:6px; bottom:4px; }
        }

        /* Preserve user whitespace and line breaks like WhatsApp */
        .message-content {
            white-space: pre-wrap;       /* preserve newlines and wrap long lines */
            white-space: -moz-pre-wrap;
            white-space: -pre-wrap;
            white-space: -o-pre-wrap;
            word-break: break-word;
        }

        /* WhatsApp-like inline formatting and interactive messages */
        .wa-interactive { margin-top: 8px; border: 1px solid rgba(0,0,0,0.06); padding: 8px; border-radius: 8px; background: #fff; }
        .wa-interactive .wa-title { font-weight:700; margin-bottom:6px; }
        .wa-interactive .wa-buttons { display:flex; gap:6px; flex-wrap:wrap; }
        .wa-interactive .wa-buttons .wa-interactive-btn { white-space:nowrap; }
        .wa-interactive .wa-list { margin-top:6px; }
        .wa-interactive .wa-list-item { padding:8px; border-radius:6px; cursor:pointer; border:1px solid transparent; }
        .wa-interactive .wa-list-item:hover { background:#f6f8fb; border-color:rgba(0,0,0,0.04); }
        .wa-code { background:#0b0b0b; color:#fff; padding:8px; border-radius:6px; font-family: monospace; white-space: pre-wrap; }
        .wa-inline-code { background:#f4f4f4; padding:2px 6px; border-radius:4px; font-family: monospace; }
        .message-bubble:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 20px rgba(2,6,23,0.06);
        }
        .message-bubble:hover .message-time,
        .message-bubble:focus-within .message-time {
            opacity: 1;
        }

        .message-status {
            display: inline-block;
            margin-left: 5px;
            font-size: 12px;
        }

        .status-sent { color: #999; }
        .status-delivered { color: #999; }
        .status-read { color: var(--whatsapp-green); }

        .chat-input {
            padding: 15px 20px;
            background: #f0f0f0;
            border-top: 1px solid #ddd;
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .chat-input input[type="text"] {
            flex: 1;
            padding: 10px 15px;
            border: 1px solid #ddd;
            border-radius: 25px;
            outline: none;
            font-size: 14px;
        }

        .chat-input input:focus {
            border-color: var(--whatsapp-green);
        }

        .chat-input .btn {
            border-radius: 50%;
            width: 45px;
            height: 45px;
            display: flex;
            align-items: center;
            justify-content: center;
            background: var(--whatsapp-green);
            border: none;
            color: white;
        }

        .chat-input .btn:hover {
            background: var(--whatsapp-green-dark);
        }
        
        #attach-btn {
            background: #075E54;
        }
        
        #attach-btn:hover {
            background: #128C7E;
        }
        
        /* Estilos para mensajes multimedia */
        .message-media {
            max-width: 300px;
            border-radius: 8px;
            overflow: hidden;
            margin-bottom: 5px;
        }
        
        .message-media img,
        .message-media video {
            width: 100%;
            display: block;
            cursor: pointer;
        }
        
        .message-media audio {
            width: 100%;
        }
        
        .message-document {
            background: rgba(0,0,0,0.05);
            padding: 10px;
            border-radius: 8px;
            display: flex;
            align-items: center;
            gap: 10px;
            cursor: pointer;
        }
        
        .message-document:hover {
            background: rgba(0,0,0,0.1);
        }
        
        .message-document i {
            font-size: 24px;
            color: var(--whatsapp-green);
        }
        
        .document-info {
            flex: 1;
        }
        
        .document-name {
            font-weight: 500;
            font-size: 14px;
        }
        
        .document-size {
            font-size: 12px;
            color: #666;
        }
        
        #media-preview {
            animation: slideUp 0.3s ease;
        }
        
        @keyframes slideUp {
            from {
                transform: translateY(20px);
                opacity: 0;
            }
            to {
                transform: translateY(0);
                opacity: 1;
            }
        }
        
        .upload-progress {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 3px;
            background: var(--whatsapp-green);
            transform-origin: left;
            transition: transform 0.3s;
        }

        .no-conversation {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100%;
            color: #999;
            text-align: center;
        }

        .no-conversation i {
            font-size: 64px;
            margin-bottom: 20px;
            opacity: 0.3;
        }

        .loading {
            text-align: center;
            padding: 20px;
            color: #666;
        }

        /* Notification toasts (compact by default) */
        #notification-toasts {
            position: fixed;
            top: 12px;
            right: 12px;
            z-index: 10000;
            display: flex;
            flex-direction: column;
            gap: 8px;
            align-items: flex-end;
            pointer-events: none;
        }

        .notification-toast {
            pointer-events: auto;
            background: #fff;
            color: #222;
            padding: 8px 10px;
            border-radius: 8px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.12);
            display: flex;
            align-items: center;
            gap: 10px;
            min-width: 160px;
            max-width: 260px;
            font-size: 13px;
        }

        .notification-toast.small {
            padding: 6px 8px;
            font-size: 12px;
            min-width: 140px;
            max-width: 220px;
        }

        .notification-toast.cool {
            background: linear-gradient(90deg,#6a11cb,#2575fc);
            color: white;
            box-shadow: 0 6px 16px rgba(37,117,252,0.28);
            transform-origin: right;
            animation: pop 320ms ease;
        }

        @keyframes pop { from { transform: translateY(-6px) scale(0.98); opacity:0 } to { transform: translateY(0) scale(1); opacity:1 } }

        .notification-toast .nt-icon { font-size: 16px; margin-right: 6px; opacity: 0.95; }
        .notification-toast .nt-actions { margin-left: auto; display:flex; gap:6px; }
        .notification-toast .btn { font-size: 12px; padding: 4px 8px; }

        /* UI improvements for chat area */
        .quick-replies .btn {
            border-radius: 20px;
            padding: 6px 10px;
            font-size: 12px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        /* Quick replies compact panel */
        .quick-replies-wrapper { position: relative; }

        /* Improve audio control visibility and color in supporting browsers */
        .message-media audio { background: #fff; border-radius: 8px; padding: 4px; accent-color: var(--whatsapp-green); }
        
        /* Mic button styles */
        #mic-btn {
            margin-left: 6px;
            background: var(--whatsapp-green);
            border: none;
            color: white;
            width: 40px;
            height: 40px;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            border-radius: 50%;
            box-shadow: 0 2px 6px rgba(3, 102, 80, 0.12);
        }
        #mic-btn.recording { background: #c0392b; color: white; }
        #mic-btn i { color: white; font-size: 14px; }

        /* Attach '+' style and menu */
        #attach-btn { background: transparent; border-radius: 6px; padding: 4px 10px; border: 1px solid transparent; font-weight:700; }
        #attach-btn:hover { background: rgba(0,0,0,0.03); border-color: rgba(0,0,0,0.06); }
        .attach-menu { background:#fff; border:1px solid #ddd; box-shadow:0 6px 18px rgba(0,0,0,0.08); border-radius:6px; padding:6px; display:none; position:absolute; z-index:1500; }
        .attach-menu .attach-option { display:block; width:100%; text-align:left; padding:6px 10px; border:none; background:transparent; font-size:14px; }
        .attach-menu .attach-option:hover { background:#f6f6f6; }

        /* Show mic in place of send when input is empty */
        #send-btn { display: inline-block; }
        #mic-btn { display: none; }

        .chat-conversations {
            background-repeat: repeat;
            background-color: #e9efe9;
            padding-bottom: 20px; /* space for reply preview and input */
        }

        .message-bubble { transition: transform 0.12s ease, box-shadow 0.12s ease; }
        .message-bubble:hover { transform: translateY(-1px); box-shadow: 0 4px 10px rgba(0,0,0,0.06); }

        #reply-preview { box-shadow: 0 1px 2px rgba(0,0,0,0.04); }
        #reply-preview button { border: none; color: #888; }
        #reply-preview button:hover { color: #333; }

        .notification-toast.urgent { border-left: 4px solid #e74c3c; }

        /* Badge de usuario bloqueado en el header */
        #blocked-indicator {
            display: none;
            margin-left: 8px;
            background: #dc3545;
            color: #fff;
            font-size: 10px;
            font-weight: 700;
            padding: 2px 8px;
            border-radius: 10px;
            vertical-align: middle;
            letter-spacing: .3px;
        }
        /* Botón bloquear — estados */
        #block-user-btn.is-blocked { background: rgba(220,53,69,.18); border-color: #dc3545; color: #dc3545; }
        #block-user-btn.is-blocked:hover { background: rgba(220,53,69,.28); }
        /* Ítem bloqueado en sidebar */
        .conversation-item.user-blocked { opacity: .6; }
        .conversation-item.user-blocked .conversation-name::after {
            content: ' 🚫';
            font-size: 10px;
        }

        /* Visual destacado para notificaciones de tipo "attention" (ej. usuario subió documentos) */
        .notification-toast.attention {
            background: linear-gradient(90deg, #fff7e6, #fff3e0);
            border: 1px solid rgba(255,165,0,0.18);
            color: #333;
            box-shadow: 0 8px 24px rgba(255,165,0,0.06);
            min-width: 240px;
        }
        .notification-toast.attention .nt-icon {
            font-size: 18px;
            margin-right: 8px;
            transform: translateY(-1px);
            color: #ff9900;
        }
        .notification-toast.attention .nt-actions .btn { min-width: 70px; }

        .message-template {
            background: #fffacd;
            border: 1px solid #f0e68c;
            padding: 10px;
            border-radius: 8px;
            margin-bottom: 10px;
            font-size: 13px;
        }
        .message-template.system {
            background: linear-gradient(90deg,#fff4e6,#fffaf0);
            border: 1px solid rgba(255,165,0,0.18);
            color: #5c3a00;
            font-weight: 600;
            display: flex;
            gap: 8px;
            align-items: center;
            animation: systemPop 320ms ease;
        }
        .message-template.system .mt-icon { font-size:16px; margin-right:6px; }
        @keyframes systemPop { from { transform: translateY(6px); opacity:0 } to { transform: translateY(0); opacity:1 } }

        /* Highlight animation for newly inserted system messages */
        .message-template.system.system-highlight {
            box-shadow: 0 10px 30px rgba(255,165,0,0.12);
            border-color: rgba(255,165,0,0.28);
            transform-origin: center left;
            animation: pulseHighlight 1s ease both;
        }

        /* Date separator between message days */
        .date-sep {
            text-align: center;
            font-size: 12px;
            color: #666;
            padding: 6px 0;
            margin: 12px 0;
            position: relative;
        }
        .date-sep::before, .date-sep::after { content: ''; display: inline-block; vertical-align: middle; width: 20%; height: 1px; background: rgba(0,0,0,0.06); margin: 0 8px; }
        .date-sep { background: transparent; font-weight:600; }

        @keyframes pulseHighlight {
            0% { transform: translateY(6px) scale(.995); opacity: 0.95; }
            50% { transform: translateY(0) scale(1.01); opacity: 1; }
            100% { transform: translateY(0) scale(1); opacity: 1; }
        }

        .unread-count {
            background: var(--whatsapp-green);
            color: white;
            border-radius: 50%;
            font-size: 12px;
            min-width: 20px;
            height: 20px;
            display: flex;
            align-items: center;
            justify-content: center;
            margin-left: auto;
        }

        /* Media thumbnails and players */
        .message-media img, .message-media .message-media-image { max-width: 320px; border-radius: 8px; display: block; height: auto; }
        @media (max-width: 768px) {
            .message-media img, .message-media .message-media-image { max-width: 60vw; }
        }
        .message-media video, .message-media audio { max-width: 100%; border-radius: 8px; }
        .message-media { margin-bottom: 6px; }

        /* Quick replies: make them rectangular, full-width in panel, readable */
        .quick-replies-panel { 
            background: #fff; 
            border-radius: 8px; 
            padding: 8px; 
            box-shadow: 0 6px 18px rgba(0,0,0,0.08);
            display: none;
            position: absolute;
            z-index: 1400;
            max-height: 180px;
            overflow-y: auto;
        }
        .quick-replies-panel .btn {
            border-radius: 6px;
            display: block;
            text-align: left;
            width: 100%;
            padding: 8px 10px;
            margin-bottom: 6px;
            white-space: normal;
            overflow: hidden;
            text-overflow: ellipsis;
            min-width: 72px;
            max-width: 200px;
            font-size: 13px;
        }
        #quick-replies-toggle { background: transparent; border-radius: 20px; }
        @media (max-width: 768px) {
            .quick-replies-panel { left: 8px; right: 8px; bottom: 56px; min-width: auto; }
        }

        /* Indicador de nuevos mensajes */
        #new-messages-indicator {
            animation: bounceIn 0.4s ease, pulse 2s ease-in-out infinite;
        }
        
        @keyframes bounceIn {
            0% { transform: translateX(-50%) translateY(20px); opacity: 0; }
            60% { transform: translateX(-50%) translateY(-10px); opacity: 1; }
            100% { transform: translateX(-50%) translateY(-6px); opacity: 1; }
        }
        
        @keyframes pulse {
            0%, 100% { box-shadow: 0 6px 20px rgba(37, 211, 102, 0.35); }
            50% { box-shadow: 0 6px 30px rgba(37, 211, 102, 0.55); }
        }

        /* Recording controls: rectangular buttons and clearer contrast */
        #recording-indicator .btn {
            border-radius: 6px;
            padding: 6px 10px;
            min-width: 84px;
        }

        /* Audio control visibility: stronger border and accent color */
        .message-media audio {
            background: #fff;
            border: 1px solid rgba(0,0,0,0.06);
            padding: 6px;
            border-radius: 8px;
            accent-color: var(--whatsapp-green);
            box-shadow: 0 1px 3px rgba(0,0,0,0.04);
            height: 40px;
        }

        @media (max-width: 768px) {
            /* Fullscreen sidebar that can slide in/out */
            .chat-container { padding: 0; gap: 0; }
            .chat-sidebar {
                width: 100%;
                position: fixed;
                top: 0;
                left: 0;
                z-index: 1100;
                height: 100vh;
                transform: translateX(0);
                transition: transform 240ms ease;
                box-shadow: none;
            }

            /* Hidden by default on mobile. .open will show it */
            .chat-sidebar.mobile-hidden { transform: translateX(-110%); }

            .chat-main {
                width: 100%;
                margin-left: 0;
                display: flex;
                flex-direction: column;
                height: 100vh;
            }

            /* Header adjustment */
            .chat-header { padding: 10px 12px; gap: 8px; }
            .chat-header-avatar { width: 38px; height: 38px; }

            /* Fix input to bottom to emulate mobile chat apps */
            .chat-input {
                position: fixed;
                left: 0;
                right: 0;
                bottom: 0;
                padding: 10px calc(12px + env(safe-area-inset-left, 0px)) calc(12px + env(safe-area-inset-bottom, 0px));
                background: #fff;
                z-index: 1400;
                border-top: 1px solid #e9eef2;
                box-shadow: 0 -6px 20px rgba(0,0,0,0.06);
            }

            /* improve touch targets inside input bar */
            .chat-input input[type="text"] { font-size: 16px; padding: 12px 14px; }
            .chat-input .btn { width:48px; height:48px; }

            /* Give chat area bottom padding so messages are not hidden under input */
            .chat-conversations { padding-bottom: 120px; }

            /* Compact avatars and message bubble width */
            .conversation-avatar { width: 40px; height: 40px; }
            .message-bubble { max-width: 85%; font-size: 15px; }

            /* Quick replies panel adapt */
            .quick-replies-panel { left: 8px; right: 8px; bottom: 70px; max-height: 160px; }

            /* Show a small back button in header to open sidebar */
            #sidebar-toggle { display: inline-flex; }
        }

        .reply-preview {
            background: #f4f6f8;
            border-left: 3px solid #d0d7de;
            padding: 6px 8px;
            margin-bottom: 6px;
            font-size: 12px;
            color: #555;
            border-radius: 4px;
        }

        .reaction-badge { background:#fff; border:1px solid rgba(0,0,0,0.06); display:inline-flex; align-items:center; justify-content:center; padding:4px 6px; border-radius:14px; font-size:13px; margin-top:6px; }

        /* Reaction picker */
        #reaction-picker { position:fixed; display:none; z-index:9000; background:#fff; border-radius:10px; box-shadow:0 6px 24px rgba(0,0,0,0.16); padding:8px; min-width:160px; transition:transform .12s ease, opacity .12s ease; transform:scale(.96); opacity:0; }
        #reaction-picker.show { transform:scale(1); opacity:1; }
        #reaction-picker .emoji { font-size:18px; padding:6px; cursor:pointer; border-radius:6px; margin:4px; display:inline-flex; align-items:center; justify-content:center; }
        #reaction-picker .emoji:hover { background: rgba(0,0,0,0.04); }

        .conversation-avatar img, .conversation-avatar-img { max-width: 44px; max-height:44px; border-radius:50%; }
        .conversation-avatar-wrapper { width:48px; display:flex; align-items:center; justify-content:center }
        .conversation-item.unread { background: rgba(255, 248, 220, 0.9); }
        .unread-badge { margin-left: 8px; font-size: 12px; }

        /* Tooltip «Copiado» al hacer doble clic en un mensaje */
        .copy-feedback {
            position: absolute;
            top: -26px; left: 50%;
            transform: translateX(-50%);
            background: rgba(0,0,0,0.72);
            color: #fff;
            padding: 3px 10px;
            border-radius: 12px;
            font-size: 12px; font-weight: 600;
            white-space: nowrap; pointer-events: none;
            animation: cfadeIn .15s ease;
            z-index: 100;
        }
        @keyframes cfadeIn { from { opacity:0; transform: translateX(-50%) translateY(4px); } to { opacity:1; transform: translateX(-50%) translateY(0); } }

    </style>
</head>
<body>
    <div class="chat-container">
        <!-- Sidebar con lista de conversaciones -->
        <div class="chat-sidebar">
            <div class="sidebar-header">
                <div>
                    <h5 class="mb-0">💬 Conversaciones 
                        <span class="badge bg-warning text-dark ms-2" style="font-size: 9px; padding: 2px 6px; animation: pulse 2s infinite;">
                            v1.4.0-<?php echo substr(time(), -4); ?>
                        </span>
                    </h5>
                    <!-- Toggle disponibilidad de asesor -->
                    <div style="margin-top:6px; display:flex; align-items:center; gap:8px;">
                        <span style="font-size:11px; color:rgba(255,255,255,0.8);">Asesor:</span>
                        <button id="advisor-mode-btn"
                            onclick="toggleAdvisorMode()"
                            style="font-size:11px; padding:2px 10px; border-radius:20px; border:none; cursor:pointer; font-weight:600; transition:all 0.2s;"
                            title="Activar/desactivar menú de asesor ausente">
                            …
                        </button>
                        <span id="advisor-mode-label" style="font-size:10px; color:rgba(255,255,255,0.7);"></span>
                    </div>
                </div>
                <div>
                    <a href="index.php" class="text-white text-decoration-none">
                        <i class="fas fa-arrow-left"></i>
                    </a>
                </div>
            </div>
            
            <div class="sidebar-search">
                <div class="input-group search-wrapper" style="position:relative;">
                    <span class="input-group-text bg-transparent border-0">
                        <i class="fas fa-search text-muted" id="search-icon"></i>
                    </span>
                    <input type="text" class="form-control border-0" placeholder="Buscar por nombre, teléfono o mensaje..." id="search-input" autocomplete="off" style="padding-right:28px;">
                    <span class="search-spinner" id="search-spinner"><i class="fas fa-circle-notch fa-spin"></i></span>
                    <button class="search-clear" id="search-clear" title="Limpiar búsqueda">&#x2715;</button>
                </div>
            </div>

            <!-- Filtro de conversaciones: Todos / No leídos -->
            <div style="padding:8px 12px; display:flex; gap:8px; align-items:center;">
                <select id="conversation-filter" class="form-select form-select-sm" style="width:auto;">
                    <option value="all">Todos</option>
                    <option value="unread">No leídos</option>
                </select>
                <small class="text-muted">Mostrar sólo conversaciones con mensajes no leídos</small>
            </div>
            
            <div class="conversation-list" id="conversation-list">
                <div class="loading">
                    <i class="fas fa-spinner fa-spin"></i>
                    <div>Cargando conversaciones...</div>
                </div>
            </div>
            <!--div id="load-more-container" style="padding:10px; text-align:center; display:none;">
                <button id="load-more-btn" class="btn btn-sm btn-outline-primary">Cargar más</button>
            </div-->
        </div>

        <!-- Área principal del chat -->
        <div class="chat-main">
            <div id="no-conversation" class="no-conversation">
                <i class="fab fa-whatsapp"></i>
                <h4>WhatsApp Bot Manager</h4>
                <p>Selecciona una conversación para comenzar a chatear</p>
            </div>

            <div id="chat-area" style="display: none; height: 100%; flex-direction: column;">
                <div class="chat-header">
                        <button id="sidebar-toggle" class="btn btn-sm btn-outline-light d-md-none me-2" style="border-radius:8px; padding:6px 8px;" title="Volver a conversaciones"><i class="fas fa-bars"></i></button>
                    <div class="chat-header-avatar" id="chat-avatar">
                        <i class="fas fa-user"></i>
                    </div>
                    <div class="chat-header-info">
                        <h6 id="chat-name"><span id="chat-name-text">Usuario</span> <button class="btn btn-sm btn-link" id="edit-user-btn" title="Editar usuario"><i class="fas fa-user-edit"></i></button>
                            <span id="hold-indicator" style="display:none; margin-left:8px; color:#b85; font-weight:600">EN ESPERA</span>
                            <span id="blocked-indicator"><i class="fas fa-ban me-1"></i>BLOQUEADO</span>
                            <span id="terms-pending-badge" title="Este usuario aún no ha aceptado los Términos y Condiciones"
                                style="display:none; margin-left:8px; background:#dc3545; color:#fff; font-size:10px; font-weight:600; padding:2px 7px; border-radius:10px; vertical-align:middle; cursor:default;">
                                <i class="fas fa-file-contract me-1"></i>T&amp;C Pendiente
                            </span>
                        </h6>
                        <small id="chat-phone">+1234567890</small>
                        <div style="font-size:12px; margin-top:4px;">
                            <button id="attend-toggle-btn" class="btn btn-sm btn-outline-light" style="display:none; margin-right:6px;">Atender</button>
                            <small id="attend-status" class="text-white me-3" style="font-size:0.9rem;"></small>
                            <button id="release-hold-btn" class="btn btn-sm btn-outline-primary" style="display:none">Liberar espera</button>
                        </div>
                    </div>
                    <div style="margin-left: auto; display:flex; gap:8px; align-items:center;">
                        <div id="bot-toggle-container" style="display:flex;align-items:center;gap:6px;margin-right:8px;">
                            <button class="btn btn-sm btn-outline-secondary" id="bot-toggle">Bot: On</button>
                        </div>
                        <!-- Botón ver citas del paciente -->
                        <button class="btn btn-sm btn-outline-warning" id="ver-citas-btn" title="Ver citas de este paciente" onclick="citasModal.abrir()">
                            <i class="fas fa-calendar-alt"></i>
                        </button>
                        <!-- Botón agendar domicilio rápido (sin imagen) -->
                        <button class="btn btn-sm btn-outline-success" id="agendar-rapido-btn" title="Agendar domicilio" onclick="labDomicilio.abrirRapido()">
                            <i class="fas fa-house-medical"></i>
                        </button>
                        <!-- Botón ir a gestión de domicilios (asignar enfermeras) -->
                        <a href="lab_domicilios.php" target="_blank" class="btn btn-sm btn-outline-primary" title="Gestión de domicilios — asignar enfermeras">
                            <i class="fas fa-user-nurse"></i>
                        </a>
                        <!-- Botón solicitar archivo grande -->
                        <button class="btn btn-sm btn-outline-info" id="request-large-file-btn" title="Solicitar archivo grande al cliente">
                            <i class="fas fa-cloud-upload-alt"></i>
                        </button>
                        <!-- Botón programar recordatorio -->
                        <button class="btn btn-sm btn-success" id="schedule-reminder-btn" title="Programar recordatorio">
                            <i class="fas fa-calendar-plus"></i>
                        </button>
                        <!-- Botón bloquear/desbloquear usuario -->
                        <button class="btn btn-sm btn-outline-danger" id="block-user-btn" title="Bloquear usuario">
                            <i class="fas fa-ban"></i>
                        </button>
                        <!-- Botón actualizar mensajes -->
                        <button class="btn btn-sm btn-outline-light" id="refresh-messages-btn" title="Actualizar mensajes"><i class="fas fa-sync-alt"></i></button>
                        <button class="btn btn-sm btn-outline-secondary" id="mark-unread-btn" title="Marcar conversación como no leída" style="display:none;">Marcar no leído</button>
                        <button class="btn btn-sm btn-outline-danger" id="delete-conversation-btn" title="Eliminar conversación" style="display:none;"><i class="fas fa-trash"></i></button>
                    </div>
                </div>

                <div class="chat-conversations" id="chat-conversations">
                    <!-- Los mensajes se cargan aquí -->
                </div>

                <!-- Preview de archivo multimedia -->
                <div id="media-preview" style="display: none; padding: 10px; background: #f0f0f0; border-top: 1px solid #ddd;">
                    <div style="display: flex; align-items: center; justify-content: space-between;">
                        <div style="display: flex; align-items: center; gap: 10px;">
                            <img id="preview-thumbnail" style="max-width: 60px; max-height: 60px; border-radius: 5px; display: none;">
                            <div>
                                <div id="preview-filename" style="font-weight: bold; font-size: 14px;"></div>
                                <div id="preview-filesize" style="font-size: 12px; color: #666;"></div>
                            </div>
                        </div>
                        <button class="btn btn-sm btn-danger" onclick="cancelMediaUpload()">
                            <i class="fas fa-times"></i>
                        </button>
                    </div>
                    <input type="text" id="media-caption" class="form-control mt-2" placeholder="Agregar un comentario (opcional)" maxlength="1024">
                </div>

                <div id="reply-preview" class="reply-preview" style="display:none; align-items:center; justify-content:space-between;">
                    <div style="flex:1; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;" id="reply-preview-text">En respuesta a: (selecciona un mensaje)</div>
                    <button class="btn btn-sm btn-link" id="cancel-reply-btn" title="Cancelar respuesta">✕</button>
                </div>

                <div class="chat-input">
                    <input type="file" id="file-input" accept="image/*,video/*,audio/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx" style="display: none;">
                    <button class="btn" id="attach-btn" title="Adjuntar">
                        📎
                    </button>
                    <div id="attach-menu" class="attach-menu" style="display:none;">
                        <button class="attach-option" data-action="file"><i class="fas fa-file me-2"></i>Enviar archivo</button>
                        <button class="attach-option" data-action="template"><i class="fas fa-file-alt me-2"></i>Enviar plantilla</button>
                        <hr style="margin:4px 0; border-color:#eee;">
                        <button class="attach-option" data-action="large-file" style="color:#075e54; font-weight:600;"><i class="fas fa-cloud-upload-alt me-2"></i>Solicitar archivo grande</button>
                    </div>
                    <div style="display:flex; gap:8px; align-items:center;">
                  <!--   <select id="message-type" class="form-select form-select-sm" style="width:auto; margin-right:8px;">
                        <option value="text">Texto</option>
                        <option value="template">Plantilla</option>
                    </select> -->
                  <!--   <div id="templateSelectContainer" style="display:none; margin-right:8px;">
                        <select id="templateSelect" class="form-select form-select-sm">
                            <option value="">Seleccionar plantilla...</option>
                        </select>
                    </div> -->
                    <div class="quick-replies-wrapper" style="position:relative;">
                        <button id="quick-replies-toggle" class="btn btn-sm btn-outline-secondary" title="Respuestas rápidas" aria-expanded="false">⚡️</button>
                        <div id="quick-replies-panel" class="quick-replies-panel" style="display:none; position:absolute; bottom:48px; left:0; z-index:1200; min-width:220px; max-width:420px;">
                            <div style="padding:6px;">
                                <input id="quick-replies-search" class="form-control form-control-sm" placeholder="Buscar respuestas o plantillas..." autocomplete="off" />
                            </div>
                            <div class="qr-sections" style="max-height:260px; overflow:auto; padding:6px;">
                                <div class="qr-section">
                                    <div style="display:flex; justify-content:space-between; align-items:center;">
                                        <strong>Respuestas rápidas</strong>
                                        <small id="qr-count" class="text-muted">0</small>
                                    </div>
                                    <div class="qr-list" style="display:flex; gap:6px; flex-wrap:wrap; margin-top:6px;"></div>
                                </div>
                                <hr style="margin:8px 0; border-color:#eee;">
                                <div class="tpl-section">
                                    <div style="display:flex; justify-content:space-between; align-items:center;">
                                        <strong>Plantillas</strong>
                                        <small id="tpl-count" class="text-muted">0</small>
                                    </div>
                                    <div class="tpl-list" style="display:flex; gap:6px; flex-wrap:wrap; margin-top:6px;"></div>
                                </div>
                            </div>
                            <div class="text-muted" id="qr-empty" style="padding:8px; display:none;">No hay respuestas rápidas ni plantillas que coincidan.</div>
                        </div>
                    </div>
                    <button class="btn" id="mic-btn" title="Grabar audio" style="margin-left:6px;  border:1px solid #ddd;">
                        <i class="fas fa-microphone"></i>
                    </button>
                </div>
                <div id="recording-indicator" style="display:none; padding:8px; background:#fff3cd; border-radius:6px; margin-top:8px;">
                    <i class="fas fa-microphone text-danger"></i> Grabando... <span id="recording-time">0:00</span>
                    <button class="btn btn-sm btn-danger" id="stop-recording" style="margin-left:8px;">Detener</button>
                    <button class="btn btn-sm btn-secondary" id="cancel-recording" style="margin-left:6px;">Cancelar</button>
                </div>
                    <input type="text" placeholder="Escribe un mensaje..." id="message-input" maxlength="4096">
                    <button class="btn" id="send-btn">
                        <i class="fas fa-paper-plane"></i>
                    </button>
                    <button class="btn btn-danger" id="delete-file-btn" title="Eliminar" style="display:none; margin-left:6px;">
                        <i class="fas fa-trash"></i>
                    </button>
                </div>
            </div>
        </div>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
    
    <!-- DETECCIÓN DE VERSIÓN - NO BORRAR -->
    <script>
        // ESTE LOG DEBE APARECER PRIMERO
        console.clear();
        console.log('%c═══════════════════════════════════════════', 'color: #25d366; font-weight: bold;');
        console.log('%c🚀 WHATSAPP BOT v1.4.0 - Desarrollado por U-Site.app', 'background: #25d366; color: white; padding: 10px 20px; font-size: 18px; font-weight: bold; border-radius: 5px;');
        console.log('%c═══════════════════════════════════════════', 'color: #25d366; font-weight: bold;');
        window.__APP_VERSION__ = '2.0.0';
    </script>
    
    <?php 
    // Forzar recarga con timestamp actual + microtime para desarrollo
    $asset_v = time() . '.' . rand(10000, 99999) . '.' . substr(microtime(true) * 1000, -4);
    try {
        $chat_common_path = __DIR__ . '/assets/js/chat-common.js';
        if (file_exists($chat_common_path)) {
            $asset_v = filemtime($chat_common_path) . '.' . rand(10000, 99999) . '.' . substr(microtime(true) * 1000, -4);
        }
    } catch (Exception $e) {
        error_log('Error getting filemtime for chat-common.js: ' . $e->getMessage());
    }
    ?>
    <script src="assets/js/chat-common.js?v=<?php echo $asset_v; ?>"></script>
    <script>
        // ============================================
        // 🚀 VERSIÓN ACTUALIZADA - 20 FEBRERO 2026
        // ============================================
        console.log('%c📦 Asset Version:', 'font-weight: bold; color: #2575fc;', '<?php echo $asset_v; ?>');
        console.log('%c✨ Cambios: Descarga automática de media, cache local en media-url.php, parser de sticker, fix extensiones MIME', 'color: #666;');
        console.log('============================================');
        
        // Fallback ligero para showAlert (si no existe una implementación global)
        if (typeof showAlert === 'undefined') {
            function showAlert(message, type = 'info') {
                try {
                    let container = document.getElementById('notification-toasts');
                    if (!container) {
                        container = document.createElement('div');
                        container.id = 'notification-toasts';
                        document.body.appendChild(container);
                    }
                    const toast = document.createElement('div');
                    toast.className = 'notification-toast small' + (type === 'success' ? ' cool' : '');
                    const icon = type === 'success' ? '✅' : (type === 'danger' ? '⚠️' : (type === 'warning' ? '⚠' : 'ℹ️'));
                    toast.innerHTML = `<span class="nt-icon">${icon}</span><div style="flex:1; font-size:13px">${message}</div>`;
                    container.appendChild(toast);
                    // Ensure a sensible minimum duration (1s) so very short toasts don't disappear instantly
                    const _minToastDuration = 1000;
                    let _dur = (type === 'success') ? 1000 : 1000;
                    _dur = Math.max(_dur, _minToastDuration);
                    setTimeout(() => { if (toast.parentNode) toast.remove(); }, _dur);
                } catch (e) {
                    // fallback to alert if DOM fails
                    try { alert(message); } catch (e2) { /* ignore */ }
                }
            }
        }

        class WhatsAppChat {
            constructor() {
                console.log('%c✅ WhatsAppChat v1.4.0 - Constructor iniciado', 'background: #10b981; color: white; padding: 4px 8px; font-weight: bold; border-radius: 3px;');
                this.currentConversationId = null;
                this.currentUserId = null;
                this.conversations = []; // Lista de usuarios/conversaciones en el sidebar
                console.log('📋 conversations array inicializado:', this.conversations);
                this.currentMessages = []; // Mensajes de la conversación activa
                // Track shown notifications to avoid duplicates from polling
                this._shownNotifications = new Set();
                // Track notifications the user dismissed/read locally so they don't reappear
                this._dismissedNotifications = new Set();
                // Track processed notifications to avoid SSE duplicates
                this._processedNotifications = new Set();
                // Map of pending ack notifications to retry marking as read (nid => notification)
                this._pendingAck = new Map();
                // Message pagination / loading state
                this.messageLimit = 50;
                this.loadingMessages = false;
                this.hasMoreMessages = false;
                this.earliestMessage = null; // timestamp of earliest loaded message
                this.earliestMessageId = null; // id of the earliest loaded message (for stable pagination)
                // Filter state: 'all' or 'unread'
                this.conversationFilter = 'all';
                // Pagination state for conversation list
                this.conversationsPage = 1;
                this.conversationsLimit = 50;
                this.hasMoreConversations = true;
                this.loadingConversations = false;

                // Track whether the user is actively scrolling/reading to avoid forcing scroll-to-bottom
                this._userScrolling = false;
                this._userScrollTimer = null;

                // Conversation list (sidebar) scroll tracking to preserve position on updates
                this._conversationsScrolling = false;
                this._conversationsScrollTimer = null;
                // Suspend periodic refresh while media playback or recording is active
                this._suspendAutoRefresh = false;
                this._pendingReloadAfterMedia = false;
                // Flag to request a reload after user stops scrolling
                this._pendingReloadAfterScroll = false;
                // Staged incoming messages when the user is reading history (not near bottom)
                this._stagedMessageIds = new Set();
                this._stagedCount = 0;
                // Store actual staged message objects (for applying later)
                this._stagedMessages = [];
                // Last message timestamp seen (for delta polling)
                this._latestMessage = null;
                // Preloaded messages cache used when we explicitly fetch before opening a conversation
                this._preloadedMessages = null;
                // Removed: automatic "Nuevos mensajes" indicator — we always reload full conversation now
                // this._lastRenderMessageCount = 0;

                console.log('✅ WhatsAppChat inicializado - conversations array:', this.conversations);
                this.init();
            }

            init() {
                console.log('🎯 Iniciando WhatsAppChat v1.4.0...');
                this.loadConversations();
                this.setupEventListeners();
                this.setupAutoRefresh();
                this.setupNotificationPolling();
                // start background retry loop to ack dismissed notifications on server
                this.setupNotificationAckRetry && this.setupNotificationAckRetry();
                // Conectar a SSE para notificaciones en tiempo real
                this.connectSSE();
                
                // Mostrar confirmación de versión cargada
                this.showVersionNotification();
            }
            
            showVersionNotification() {
                console.log('📢 Mostrando notificación de versión');
                const toast = document.createElement('div');
                toast.className = 'notification-toast cool';
                toast.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 10000;';
                toast.innerHTML = `
                    <span class="nt-icon">🚀</span>
                    <div>
                        <strong>Versión 1.4.0 Cargada</strong><br>
                        <small style="opacity: 0.9;">20 Feb 2026</small>
                    </div>
                `;
                document.body.appendChild(toast);
                
                // Auto-hide después de 4 segundos
                setTimeout(() => {
                    toast.style.transition = 'opacity 0.3s, transform 0.3s';
                    toast.style.opacity = '0';
                    toast.style.transform = 'translateX(100%)';
                    setTimeout(() => toast.remove(), 300);
                }, 4000);
            }

            setupNotificationPolling() {
                // 🎯 OPTIMIZADO: Las notificaciones ahora solo llegan por SSE
                // No necesitamos polling ni carga inicial
                console.debug('⚡ setupNotificationPolling: DESHABILITADO - SSE maneja todo en tiempo real');
            }

            /**
             * Conectar a Server-Sent Events para recibir notificaciones en tiempo real
             * Esto reemplaza el polling constante y mejora la performance
             */
            connectSSE() {
                if (this.eventSource) {
                    try { this.eventSource.close(); } catch(e) {}
                }

                console.log('Conectando a SSE para eventos en tiempo real...');

                try {
                    // Detectar URL base automáticamente (funciona en cualquier entorno)
                    const baseUrl = window.location.origin;
                    const sseUrl = `${baseUrl}/api/sse_events.php?token=demo_token&t=${Date.now()}`;
                    
                    console.log('SSE URL:', sseUrl);
                    this.eventSource = new EventSource(sseUrl);

                    // Evento: conexión establecida
                    this.eventSource.addEventListener('connected', (e) => {
                        const data = JSON.parse(e.data);
                        const mode = data.mode === 'authenticated' ? '🔐 autenticado' : '🌐 global';
                        console.log(`✅ SSE conectado (${mode}):`, data);
                        
                        // Mostrar notificación discreta de conexión
                        if (typeof showAlert === 'function' && !this._sseConnectedNotified) {
                            showAlert('Notificaciones en tiempo real activadas', 'success');
                            this._sseConnectedNotified = true;
                        }
                    });

                    // Evento: nuevo mensaje entrante
                    this.eventSource.addEventListener('new_message', (e) => {
                        console.log('📨 SSE new_message recibido:', e.data);
                        try {
                            const data = JSON.parse(e.data);
                            console.log('📨 Datos parseados del mensaje:', data);
                            
                            // Extraer user_id del mensaje
                            const userId = data.user_id || data.from_user_id || data.sender_id;
                            console.log('👤 User ID del mensaje:', userId, '| Conversación actual:', this.currentUserId);
                            
                            // Si la conversación del mensaje es la actualmente abierta
                            if (this.currentUserId && String(this.currentUserId) === String(userId)) {
                                console.log('♻️ Mensaje para conversación ACTIVA');
                                
                                // Verificar si el usuario está cerca del final del chat
                                const container = document.getElementById('chat-conversations');
                                const nearBottom = container ? ((container.scrollHeight - container.scrollTop - container.clientHeight) < 150) : true;
                                
                                if (nearBottom && !this._userScrolling) {
                                    // Usuario está en el final: recargar mensajes automáticamente
                                    console.log('🔄 Usuario cerca del final, recargando mensajes...');
                                    this.loadMessages(this.currentUserId, true, true).catch(err => {
                                        console.error('Error recargando mensajes:', err);
                                    });
                                } else {
                                    // Usuario está leyendo historial: usar staged messages
                                    console.log('📜 Usuario leyendo historial, guardando en staging...');
                                    if (!this._stagedMessages) this._stagedMessages = [];
                                    if (!this._stagedMessageIds) this._stagedMessageIds = new Set();
                                    
                                    // Crear key única para evitar duplicados
                                    const msgKey = data.message_id || data.id || `${data.created_at}_${data.content?.substring(0,20)}`;
                                    
                                    if (!this._stagedMessageIds.has(msgKey)) {
                                        this._stagedMessageIds.add(msgKey);
                                        this._stagedMessages.push(data);
                                        console.log('💾 Mensaje guardado en staging. Total:', this._stagedMessages.length);
                                        
                                        // Mostrar indicador de nuevos mensajes
                                        this.showNewMessagesIndicator(this._stagedMessages.length);
                                    } else {
                                        console.log('⏭️ Mensaje duplicado, omitiendo');
                                    }
                                }
                            } else {
                                console.log('📋 Mensaje para OTRA conversación, solo actualizando lista');
                            }
                            
                            // Actualizar la lista de conversaciones para mostrar el nuevo mensaje
                            console.log('📋 Actualizando lista de conversaciones...');
                            this.updateConversationInList(data);
                            
                            // Reproducir sonido de notificación solo si no es la conversación activa
                            if (!this.currentUserId || String(this.currentUserId) !== String(userId)) {
                                console.log('🔔 Reproduciendo sonido de notificación');
                                this.playNotificationSound();
                            }
                        } catch (err) {
                            console.error('❌ Error procesando new_message:', err);
                        }
                    });

                    // Evento: nueva conversación detectada
                    this.eventSource.addEventListener('new_conversation', (e) => {
                        console.log('💬 Nueva conversación (SSE):', e.data);
                        const data = JSON.parse(e.data);
                        
                        // Agregar la conversación a la lista sin recargar todo
                        this.addConversationToList(data);
                        
                        // Reproducir sonido
                        this.playNotificationSound();
                    });

                    // Evento: notificación del sistema
                    this.eventSource.addEventListener('notification', (e) => {
                        console.log('🔔 Nueva notificación (SSE):', e.data);
                        try {
                            const notification = JSON.parse(e.data);
                            
                            // Deduplicación: evitar procesar la misma notificación múltiples veces
                            const notificationKey = `notif_${notification.id}_${notification.created_at}`;
                            if (this._processedNotifications.has(notificationKey)) {
                                console.debug('⏭️ Notificación ya procesada, omitiendo:', notification.id);
                                return;
                            }
                            this._processedNotifications.add(notificationKey);
                            
                            // Actualizar la lista de conversaciones con los datos de la notificación
                            if (notification.user_id && notification.message) {
                                console.log('📋 Actualizando conversación desde notificación...');
                                this.updateConversationInList({
                                    user_id: notification.user_id,
                                    message: notification.message,
                                    timestamp: notification.created_at
                                });
                            }
                            
                            // Mostrar toast de notificación
                            this.showNotificationToast(notification);
                        } catch (err) {
                            console.error('Error procesando notificación SSE:', err);
                        }
                    });

                    // Evento: heartbeat (mantener conexión viva)
                    this.eventSource.addEventListener('heartbeat', (e) => {
                        // Silencioso, solo mantiene la conexión
                    });

                    // Manejo de errores
                    this.eventSource.onerror = (error) => {
                        const state = this.eventSource.readyState;
                        const stateNames = {
                            0: 'CONNECTING',
                            1: 'OPEN',
                            2: 'CLOSED'
                        };
                        
                        console.warn('❌ Error en SSE:');
                        console.warn('   Estado:', stateNames[state] || state);
                        console.warn('   Error:', error);
                        
                        // Si está intentando conectar, dejar que EventSource lo maneje automáticamente
                        if (state === EventSource.CONNECTING) {
                            console.log('⏳ Reconectando automáticamente...');
                            return;
                        }
                        
                        // Si está cerrado, intentar reconectar manualmente
                        if (state === EventSource.CLOSED) {
                            console.log('🔄 Conexión cerrada, reconectando en 5 segundos...');
                            
                            // Cerrar completamente
                            try { 
                                this.eventSource.close(); 
                                this.eventSource = null;
                            } catch(e) {
                                console.warn('Error cerrando EventSource:', e);
                            }
                            
                            // Reconectar después de delay
                            if (this._sseReconnectTimeout) {
                                clearTimeout(this._sseReconnectTimeout);
                            }
                            
                            this._sseReconnectTimeout = setTimeout(() => {
                                if (!this._sseReconnecting) {
                                    console.log('🔌 Intentando reconectar SSE...');
                                    this._sseReconnecting = true;
                                    try {
                                        this.connectSSE();
                                    } catch(e) {
                                        console.error('Error al reconectar SSE:', e);
                                    } finally {
                                        this._sseReconnecting = false;
                                    }
                                }
                            }, 5000);
                        }
                    };
                    
                    // Detectar evento de error explícito
                    this.eventSource.addEventListener('error', (e) => {
                        if (e.data) {
                            try {
                                const errorData = JSON.parse(e.data);
                                console.error('❌ SSE Error:', errorData.message);
                                // Mostrar notificación al usuario
                                if (typeof showAlert === 'function') {
                                    showAlert('Error de conexión: ' + errorData.message, 'warning');
                                }
                            } catch(err) {
                                console.error('❌ SSE Error (raw):', e.data);
                            }
                        }
                    });

                } catch (error) {
                    console.error('Error conectando SSE:', error);
                }
            }

            /**
             * Actualizar una conversación en la lista (sin recargar todo)
             */
            updateConversationInList(data) {
                try {
                    console.log('📝 updateConversationInList INICIADO:', data);
                    
                    // Asegurar que conversations esté inicializado
                    if (!this.conversations || !Array.isArray(this.conversations)) {
                        console.warn('⚠️ conversations array vacío, recargando lista completa...');
                        this.loadConversations();
                        return;
                    }
                    
                    // Extraer user_id de diferentes formatos posibles
                    const userId = data.user_id || data.from_user_id || data.sender_id;
                    if (!userId) {
                        console.warn('⚠️ No se pudo obtener user_id de los datos:', data);
                        return;
                    }
                    
                    console.log('👤 User ID extraído:', userId);
                    
                    // Si no viene el mensaje, recargar la lista completa para obtener datos actualizados
                    if (!data.message && !data.content && !data.text && !data.last_message) {
                        console.log('⚠️ Evento SSE sin contenido de mensaje, recargando lista completa...');
                        // Forzar recarga temporal (sin await, ejecutar en background)
                        const wasLoaded = this._conversationsLoaded;
                        this._conversationsLoaded = false;
                        this.loadConversations().then(() => {
                            this._conversationsLoaded = wasLoaded;
                        });
                        return;
                    }
                    
                    // Extraer mensaje de diferentes formatos
                    const message = data.message || data.content || data.text || data.last_message || 'Nuevo mensaje';
                    const timestamp = data.timestamp || data.created_at || new Date().toISOString();
                    
                    console.log('💬 Mensaje extraído:', message);
                    console.log('⏰ Timestamp:', timestamp);
                    
                    // Buscar la conversación en el array
                    const existingIndex = this.conversations.findIndex(c => String(c.user_id) === String(userId));
                    
                    if (existingIndex !== -1) {
                        console.log('✅ Conversación encontrada en índice:', existingIndex);
                        // Actualizar conversación existente
                        const conv = this.conversations[existingIndex];
                        conv.last_message = message;
                        conv.last_time = timestamp;
                        
                        // Solo incrementar unread_count si no es la conversación activa
                        if (String(this.currentUserId) !== String(userId)) {
                            conv.unread_count = (conv.unread_count || 0) + 1;
                            console.log('📬 unread_count incrementado a:', conv.unread_count);
                        } else {
                            // Si es la conversación activa, mantener unread_count en 0
                            conv.unread_count = 0;
                            console.log('✅ Conversación activa, unread_count = 0');
                        }
                        
                        // Mover al inicio de la lista
                        this.conversations.splice(existingIndex, 1);
                        this.conversations.unshift(conv);
                        console.log('⬆️ Conversación movida al inicio');
                    } else {
                        console.log('➕ Conversación no existe, agregando nueva...');
                        // Nueva conversación, agregar al inicio
                        this.conversations.unshift({
                            user_id: userId,
                            name: data.name || data.sender_name || data.phone_number || `Usuario ${userId}`,
                            phone_number: data.phone_number || data.phone || '',
                            last_message: message,
                            last_time: timestamp,
                            unread_count: String(this.currentUserId) !== String(userId) ? 1 : 0
                        });
                        console.log('✅ Nueva conversación agregada');
                    }
                    
                    // Re-renderizar solo la lista de conversaciones
                    console.log('🔄 Re-renderizando lista de conversaciones...');
                    console.log('📊 Total conversaciones:', this.conversations.length);
                    console.log('🔍 Filtro activo:', this.conversationFilter);
                    
                    if (typeof this.renderConversations === 'function') {
                        console.log('✅ Llamando a renderConversations()...');
                        this.renderConversations();
                        console.log('✅ renderConversations() ejecutado correctamente');
                    } else {
                        console.error('❌ renderConversations no es una función!');
                    }
                    
                } catch (error) {
                    console.error('❌ Error updateConversationInList:', error);
                    console.error('Stack trace:', error.stack);
                }
            }

            /**
             * Agregar una conversación nueva a la lista
             */
            addConversationToList(data) {
                try {
                    console.log('➕ Agregando nueva conversación:', data);
                    
                    // Asegurar que conversations esté inicializado
                    if (!this.conversations || !Array.isArray(this.conversations)) {
                        console.warn('⚠️ conversations array no estaba inicializado, recargando...');
                        this.loadConversations();
                        return;
                    }
                    
                    // Extraer user_id
                    const userId = data.user_id || data.from_user_id || data.sender_id;
                    if (!userId) {
                        console.warn('⚠️ No se pudo obtener user_id');
                        return;
                    }
                    
                    // Verificar si ya existe
                    const exists = this.conversations.some(c => String(c.user_id) === String(userId));
                    if (exists) {
                        console.log('🔄 Conversación ya existe, actualizando...');
                        return this.updateConversationInList(data);
                    }
                    
                    // Extraer datos
                    const message = data.message || data.content || data.text || 'Nueva conversación';
                    const timestamp = data.timestamp || data.created_at || new Date().toISOString();
                    const name = data.name || data.sender_name || data.phone_number || `Usuario ${userId}`;
                    
                    // Agregar al inicio
                    this.conversations.unshift({
                        user_id: userId,
                        name: name,
                        phone_number: data.phone_number || data.phone || '',
                        last_message: message,
                        last_time: timestamp,
                        unread_count: String(this.currentUserId) !== String(userId) ? (data.message_count || 1) : 0
                    });
                    
                    console.log('✅ Conversación agregada, re-renderizando...');
                    // Re-renderizar lista
                    this.renderConversations();
                    
                } catch (error) {
                    console.error('❌ Error agregando conversación:', error);
                }
            }

            async loadNotifications() {
                // 🎯 ELIMINADO: Las notificaciones ahora solo llegan por SSE en tiempo real
                // No se hace fetch a get_notifications.php
                console.debug('⚡ loadNotifications: SSE maneja todas las notificaciones en tiempo real');
                return;
            }

            // SIMPLIFICADO: Las notificaciones ahora son solo en tiempo real
            // No se guardan en BD ni necesitan marcarse como leídas

            async showNotificationToast(notification) {
                // Create or reuse container
                const containerId = 'notification-toasts';
                let container = document.getElementById(containerId);
                if (!container) {
                    container = document.createElement('div');
                    container.id = containerId;
                    document.body.appendChild(container);
                }

                // Determine an id to dedupe notifications (prefer server id)
                const nid = notification && notification.id ? String(notification.id) : ('msg:' + String((notification && notification.message) || '').slice(0,200));
                // If the user already dismissed/acked this notification earlier, skip it
                if (this._dismissedNotifications.has(nid)) {
                    console.debug('Notification previously dismissed, skipping', nid);
                    return;
                }
                if (this._shownNotifications.has(nid)) {
                    // already shown in this session
                    console.debug('Notification already shown, skipping', nid);
                    return;
                }

                // mark as shown immediately (solo en memoria, no en servidor)
                this._shownNotifications.add(nid);

                const toast = document.createElement('div');
                // compact by default
                toast.className = 'notification-toast small';
                // Detect special "attention" notifications (e.g. user sent documents or similar system events)
                const isAttention = (
                    // explicit attention flags
                    notification.type === 'attention' || notification.system === 'attention' || notification.level === 'attention' || notification.attention ||
                    // known event types/tags for user-sent documents
                    notification.type === 'usersentdocuments' || notification.tag === 'usersentdocuments' || notification.system === 'usersentdocuments' ||
                    // file upload notifications (large file request feature)
                    notification.type === 'file_uploaded' || notification.type === 'file_request_sent'
                );
                if (notification.cool || notification.type === 'cool') toast.classList.add('cool');
                if (notification.level === 'urgent' || notification.urgent) toast.classList.add('urgent');
                if (isAttention) toast.classList.add('attention');

                const iconSpan = document.createElement('span');
                iconSpan.className = 'nt-icon';
                iconSpan.textContent = notification.icon || (isAttention ? '📎' : (notification.cool ? '✨' : (notification.level === 'urgent' ? '⚠️' : '🔔')));
                toast.appendChild(iconSpan);

                const msg = document.createElement('div');
                msg.style.flex = '1';
                msg.style.fontSize = '13px';
                msg.style.lineHeight = '1.2';
                msg.textContent = notification.message || 'Nuevo evento';
                toast.appendChild(msg);

                const actions = document.createElement('div');
                actions.className = 'nt-actions';

                const removeToastLocal = () => {
                    if (toast.parentNode) toast.remove();
                    if (this._shownNotifications.has(nid)) this._shownNotifications.delete(nid);
                };

                const openBtn = document.createElement('button');
                openBtn.className = notification.cool ? 'btn btn-sm btn-light' : (isAttention ? 'btn btn-sm btn-warning' : 'btn btn-sm btn-primary');
                openBtn.textContent = (notification.open_label && notification.open_label !== 'Ver') ? notification.open_label : 'Abrir';
                openBtn.onclick = async () => {
                    // Marcar como descartada localmente
                    this._dismissedNotifications.add(nid);
                    removeToastLocal();
                    // navigate to conv or open media if present
                    let data = {};
                    try { data = notification.data ? JSON.parse(notification.data) : {}; } catch(e) {}
                    const userId = data.user_id || notification.user_id;
                    const fileUrl = data.file_url || data.url || null;
                    if (fileUrl && this.currentUserId && String(this.currentUserId) === String(userId)) {
                        try { openMediaLightbox(fileUrl, data.file_name || ''); } catch (e) { console.warn('openMediaLightbox failed', e); }
                    } else if (userId) {
                        const conv = this.conversations.find(c => c.user_id == userId);
                        if (conv) {
                            // Prefetch messages then open conversation using preloaded data to avoid double-fetch
                            try {
                                const url = `get_user_messages.php?user_id=${encodeURIComponent(userId)}&limit=${this.messageLimit}`;
                                const resp = await this.apiCall(url);
                                if (resp && resp.success) {
                                    this._preloadedMessages = { userId: userId, resp: resp };
                                }
                            } catch (e) { console.warn('Prefetch messages failed', e); }

                            this.openConversation(conv.user_id, conv.name, conv.phone_number);
                        } else {
                            try {
                                const url = `get_user_messages.php?user_id=${encodeURIComponent(userId)}&limit=${this.messageLimit}`;
                                const resp = await this.apiCall(url);
                                if (resp && resp.success) {
                                    this._preloadedMessages = { userId: userId, resp: resp };
                                }
                            } catch (e) { console.warn('Prefetch messages failed', e); }

                            this.openConversation(userId, 'Usuario', '');
                        }
                    }
                }; 

                const dismiss = document.createElement('button');
                dismiss.className = 'btn btn-sm btn-outline-secondary';
                dismiss.textContent = '×';
                dismiss.title = 'Descartar';
                dismiss.onclick = async () => {
                    // Marcar como descartada localmente
                    this._dismissedNotifications.add(nid);
                    removeToastLocal();
                };

                actions.appendChild(openBtn);
                actions.appendChild(dismiss);
                toast.appendChild(actions);

                container.appendChild(toast);

                // If attention notification and related to current open conversation, insert a system message into the chat view
                try {
                    const dataObj = notification.data ? (typeof notification.data === 'string' ? JSON.parse(notification.data) : notification.data) : {};
                    const userId = dataObj.user_id || notification.user_id;
                    if (isAttention && userId && this.currentUserId && String(this.currentUserId) === String(userId) && typeof this.showSystemNotificationInChat === 'function') {
                        this.showSystemNotificationInChat(notification);
                    }
                } catch (e) { console.warn('showNotificationToast -> showSystemNotificationInChat failed', e); }

                // Auto remove (shorter for compact notifications) with minimum enforced
                // Reducir mínimo y valores por defecto a 1 segundo (1000 ms)
                const _minToastDuration = 1000;
                let timeout = notification.duration ? Number(notification.duration) : (notification.cool ? 1000 : 1000);
                if (!isFinite(timeout) || timeout <= 0) timeout = (notification.cool ? 1000 : 1000);
                timeout = Math.max(timeout, _minToastDuration);
                setTimeout(() => {
                    // Auto-descartar: solo limpiar localmente
                    this._dismissedNotifications.add(nid);
                    removeToastLocal();
                }, timeout);
            }

            showSystemNotificationInChat(notification) {
                try {
                    const data = notification.data ? (typeof notification.data === 'string' ? JSON.parse(notification.data) : notification.data) : {};
                    const userId = data.user_id || notification.user_id;
                    // Only show in currently open conversation
                    if (!userId || String(this.currentUserId) !== String(userId)) return;

                    const container = document.getElementById('chat-conversations');
                    if (!container) return;

                    const el = document.createElement('div');
                    el.className = 'message incoming';
                    el.dataset.notificationId = String(notification.id || '');

                    const bubble = document.createElement('div');
                    bubble.className = 'message-bubble message-template system';

                    // Friendly title and message (prefer explicit fields if present)
                    const title = notification.title || data.title || 'Documentos recibidos';
                    const msg = notification.message || data.text || data.message || (data.summary || 'El usuario envió documentos.');

                    // File info (if available)
                    const fileName = data.file_name || data.filename || data.file || null;
                    const fileUrl = data.file_url || data.url || null;
                    const fileSize = data.file_size || data.size || null;

                    // Build inner HTML
                    const parts = [];
                    parts.push(`<span class="mt-icon">📎</span>`);
                    parts.push(`<div style="flex:1">`);
                    parts.push(`<div style="font-weight:700; margin-bottom:6px">${escapeHtml(String(title))}</div>`);
                    parts.push(`<div style="font-size:13px; color:#444">${escapeHtml(String(msg))}</div>`);
                    if (fileName) {
                        parts.push(`<div style="margin-top:8px; display:flex; gap:8px; align-items:center;">
                            <i class="${getFileIcon(fileName)}"></i>
                            <div style="display:flex; flex-direction:column;">
                                <div class="document-name">${escapeHtml(fileName)}</div>
                                <div class="document-size text-muted" style="font-size:12px">${fileSize ? this.formatFileSize(Number(fileSize)) : ''}</div>
                            </div>
                        </div>`);
                    }
                    parts.push(`</div>`);

                    // Actions
                    parts.push(`<div style="margin-left:8px; display:flex; gap:6px; align-items:center">`);
                    if (fileUrl) {
                        parts.push(`<a href="${escapeHtml(fileUrl)}" target="_blank" class="btn btn-sm btn-outline-primary">Abrir</a>`);
                        parts.push(`<button class="btn btn-sm btn-primary download-doc-btn">Descargar</button>`);
                    }
                    parts.push(`</div>`);

                    bubble.innerHTML = parts.join('');
                    el.appendChild(bubble);

                    container.appendChild(el);
                    this.scrollToBottom();

                    // Attach handlers
                    
                    const downloadBtn = bubble.querySelector('.download-doc-btn');
                    if (downloadBtn) downloadBtn.addEventListener('click', () => {
                        if (fileUrl) {
                            const a = document.createElement('a');
                            a.href = fileUrl;
                            a.download = fileName || '';
                            document.body.appendChild(a);
                            a.click();
                            a.remove();
                        }
                    });

                } catch (e) {
                    console.warn('showSystemNotificationInChat failed', e);
                }
            }

            // Mostrar mensaje de estado (persistente) en la conversación activa
            showStatusMessageInChat(userId, text) {
                try {
                    if (!userId || !text) return;
                    if (String(this.currentUserId) !== String(userId)) return;
                    const container = document.getElementById('chat-conversations');
                    if (!container) return;

                    // eliminar status previos para evitar duplicados
                    Array.from(container.querySelectorAll('.message-template.system.system-status')).forEach(el => el.parentNode && el.parentNode.removeChild(el));

                    const el = document.createElement('div');
                    el.className = 'message incoming';
                    el.dataset.status = 'service';

                    const bubble = document.createElement('div');
                    bubble.className = 'message-bubble message-template system system-status';
                    bubble.innerHTML = `<div style="display:flex; gap:8px; align-items:center"><span class="mt-icon">👩‍⚕️</span><div style="flex:1"><div style="font-weight:700">${escapeHtml(String(text))}</div></div></div>`;

                    el.appendChild(bubble);
                    container.appendChild(el);
                    this.scrollToBottom();
                } catch (e) { console.warn('showStatusMessageInChat failed', e); }
            }

            setupEventListeners() {
                // Búsqueda de conversaciones con debounce
                let searchTimeout;
                const searchInput = document.getElementById('search-input');
                const searchClear = document.getElementById('search-clear');
                const searchSpinner = document.getElementById('search-spinner');

                searchInput.addEventListener('input', (e) => {
                    clearTimeout(searchTimeout);
                    const val = e.target.value;
                    // Mostrar/ocultar botón X
                    if (searchClear) searchClear.style.display = val ? 'block' : 'none';
                    // Mostrar spinner mientras espera debounce
                    if (searchSpinner && val) searchSpinner.style.display = 'block';
                    searchTimeout = setTimeout(async () => {
                        if (searchSpinner) searchSpinner.style.display = 'none';
                        await this.searchConversations(val);
                    }, 300);
                });

                if (searchClear) {
                    searchClear.addEventListener('click', () => {
                        searchInput.value = '';
                        searchClear.style.display = 'none';
                        if (searchSpinner) searchSpinner.style.display = 'none';
                        clearTimeout(searchTimeout);
                        this.searchConversations('');
                        searchInput.focus();
                    });
                }

                // Envío de mensajes
                document.getElementById('send-btn').addEventListener('click', () => {
                    this.sendMessage();
                });

                // Botón actualizar mensajes
                const refreshBtn = document.getElementById('refresh-messages-btn');
                if (refreshBtn) {
                    refreshBtn.addEventListener('click', async () => {
                        if (!this.currentUserId) return;
                        refreshBtn.disabled = true;
                        refreshBtn.innerHTML = '<i class="fas fa-sync-alt fa-spin"></i>';
                        try {
                            await this.loadMessages(this.currentUserId, true, true);
                            console.log('✅ Mensajes actualizados manualmente');
                        } catch (e) {
                            console.error('Error actualizando mensajes:', e);
                        } finally {
                            refreshBtn.disabled = false;
                            refreshBtn.innerHTML = '<i class="fas fa-sync-alt"></i>';
                        }
                    });
                }

                // Forward handler: open preview when user chooses to forward an existing message
                this.forwardMessage = function(messageId) {
                    try {
                        const input = document.getElementById('message-input');
                        if (input) {
                            input.dataset.forwardTo = messageId;
                        }
                        this.showReplyPreview(messageId, 'forward');
                        // Optionally focus the recipient selection by showing a hint or opening the sidebar
                    } catch (e) {
                        console.warn('forwardMessage failed', e);
                    }
                };

                // Edit user name button
                const editUserBtn = document.getElementById('edit-user-btn');
                if (editUserBtn) editUserBtn.addEventListener('click', () => this.promptEditUser());

                // Reply prompt helper: when user clicks reply on a message
                this.promptReply = function(messageId) {
                    const input = document.getElementById('message-input');
                    if (!input) return;
                    input.dataset.replyTo = messageId;
                    input.dataset.forwardTo = null;
                    this.showReplyPreview(messageId, 'reply');
                    input.focus();
                };

                // Reaction picker: open a small emoji palette near the click
                this.openReactionPicker = function(ev, messageId) {
                    if (ev && ev.stopPropagation) ev.stopPropagation();
                    const picker = document.getElementById('reaction-picker');
                    if (!picker) return;
                    this._pendingReactionMessage = messageId;

                    // position picker near event or center
                    picker.style.display = 'block';
                    picker.classList.remove('show');
                    // compute position
                    let left = (window.innerWidth / 2) - (picker.offsetWidth / 2);
                    let top = (window.innerHeight / 2) - (picker.offsetHeight / 2);
                    if (ev && ev.clientX) {
                        left = Math.min(Math.max(8, ev.clientX - 40), window.innerWidth - picker.offsetWidth - 8);
                        top = Math.min(Math.max(8, ev.clientY - 24), window.innerHeight - picker.offsetHeight - 8);
                    }
                    picker.style.left = left + 'px';
                    picker.style.top = top + 'px';

                    // small delay to allow CSS transition
                    setTimeout(() => picker.classList.add('show'), 10);
                };

                // Hide reaction picker on document click
                document.addEventListener('click', (ev) => {
                    const picker = document.getElementById('reaction-picker');
                    if (picker && picker.style.display === 'block' && !picker.contains(ev.target)) {
                        picker.classList.remove('show');
                        setTimeout(()=>{ picker.style.display='none'; },160);
                    }
                });

                // Attach click handler for emojis (event delegation)
                document.addEventListener('click', async (ev) => {
                    const t = ev.target;
                    if (t && t.dataset && t.dataset.emoji) {
                        ev.stopPropagation();
                        const emoji = t.dataset.emoji;
                        const mid = this._pendingReactionMessage;
                        if (!mid) return;
                        try {
                            const resp = await fetch('api/react_message.php', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ message_id: mid, emoji: emoji }), cache: 'no-store' });
                            const j = await resp.json();
                            if (j && j.success) {
                                // update local model and UI
                                const m = this.currentMessages.find(x => x.message_id == mid || x.id == mid);
                                if (m) m.reaction_emoji = emoji;
                                this.updateMessageReactionInView && this.updateMessageReactionInView(mid, emoji);
                            } else {
                                showAlert && showAlert('Error aplicando reacción', 'danger');
                            }
                        } catch (err) {
                            console.error('Reaction error', err);
                            showAlert && showAlert('Error aplicando reacción', 'danger');
                        } finally {
                            const picker = document.getElementById('reaction-picker');
                            if (picker) {
                                picker.classList.remove('show');
                                setTimeout(()=>{ picker.style.display='none'; },160);
                            }
                            this._pendingReactionMessage = null;
                        }
                    }
                });

                // Message type (text/template)
                const typeSelect = document.getElementById('message-type');
                if (typeSelect) {
                    typeSelect.addEventListener('change', (e) => {
                        const tplContainer = document.getElementById('templateSelectContainer');
                        tplContainer.style.display = (e.target.value === 'template') ? 'inline-block' : 'none';
                        if (typeof updateSendMicVisibility === 'function') updateSendMicVisibility();
                    });
                }

                // Quick replies toggle behavior: close on outside click
                const qrToggle = document.getElementById('quick-replies-toggle');
                const qrPanel = document.getElementById('quick-replies-panel');
                if (qrToggle && qrPanel) {
                    qrToggle.addEventListener('click', (ev) => {
                        ev.stopPropagation();
                        const isOpen = qrPanel.style.display === 'block';
                        qrPanel.style.display = isOpen ? 'none' : 'block';
                        qrToggle.setAttribute('aria-expanded', String(!isOpen));
                        // Focus search when panel opens
                        if (!isOpen) {
                            setTimeout(() => {
                                const s = document.getElementById('quick-replies-search');
                                if (s) { s.value = ''; s.focus(); s.dispatchEvent(new Event('input')); }
                            }, 80);
                        }
                    });

                    qrPanel.addEventListener('click', (ev) => { ev.stopPropagation(); });
                    document.addEventListener('click', () => { qrPanel.style.display = 'none'; qrToggle.setAttribute('aria-expanded','false'); });

                    // Quick replies search/filter behavior
                    const qrSearch = document.getElementById('quick-replies-search');
                    if (qrSearch) {
                        const filterLists = () => {
                            const q = (qrSearch.value || '').trim().toLowerCase();
                            const qrList = qrPanel.querySelector('.qr-list');
                            const tplList = qrPanel.querySelector('.tpl-list');
                            let qrMatches = 0, tplMatches = 0;
                            if (qrList) Array.from(qrList.children).forEach(b => {
                                const t = (b.textContent || '').toLowerCase();
                                const ok = q === '' || t.indexOf(q) !== -1;
                                b.style.display = ok ? 'inline-block' : 'none';
                                if (ok) qrMatches++;
                            });
                            if (tplList) Array.from(tplList.children).forEach(b => {
                                const t = (b.textContent || '').toLowerCase();
                                const ok = q === '' || t.indexOf(q) !== -1;
                                b.style.display = ok ? 'inline-block' : 'none';
                                if (ok) tplMatches++;
                            });
                            const qrCount = document.getElementById('qr-count'); if (qrCount) qrCount.textContent = qrMatches;
                            const tplCount = document.getElementById('tpl-count'); if (tplCount) tplCount.textContent = tplMatches;
                            const empty = document.getElementById('qr-empty'); if (empty) empty.style.display = (qrMatches + tplMatches) ? 'none' : 'block';
                        };
                        qrSearch.addEventListener('input', filterLists);
                    }
                }

                // Mic / recording
                const micBtn = document.getElementById('mic-btn');
                const sendBtn = document.getElementById('send-btn');
                const messageInput = document.getElementById('message-input');

                // Move mic button to be next to send (so it appears in the send slot)
                try {
                    if (micBtn && sendBtn && micBtn.parentNode) {
                        sendBtn.parentNode.insertBefore(micBtn, sendBtn);
                    }
                } catch (e) { /* ignore */ }

                // Use lexical captured self to avoid 'this' binding issues in event handlers
                const self = this;

                if (micBtn) {
                    micBtn.addEventListener('click', (ev) => {
                        ev.preventDefault();
                        // Ensure recording functions are available (they are initialized when opening a conversation
                        // but can be invoked earlier; initialize lazily if needed)
                        if (typeof self.startRecording !== 'function' && typeof self.initRecordingHandlers === 'function') {
                            try { self.initRecordingHandlers(); } catch(e) { console.warn('initRecordingHandlers failed', e); }
                        }

                        // While recording: clicking mic cancels the recording (as requested)
                        if (self._mediaRecorder && self._mediaRecorder.state === 'recording') {
                            try { self.cancelRecording(); } catch (e) { console.warn('cancelRecording failed', e); }
                        } else {
                            try { self.startRecording(); } catch (e) { console.warn('startRecording failed', e); alert('No se pudo iniciar la grabación.'); }
                        }
                    });
                }

                // Toggle visibility: show mic when input empty, show send when there's text, a file selected, or template mode
                const updateSendMicVisibility = () => {
                    const text = (messageInput && messageInput.value) ? messageInput.value.trim() : '';
                    const typeSelect = document.getElementById('message-type');
                    const isTemplate = typeSelect && typeSelect.value === 'template';
                    // Consider both selectedFile and visible media-preview as indicators of media pending send
                    const previewEl = document.getElementById('media-preview');
                    const fileSelected = !!this.selectedFile || (previewEl && previewEl.style.display && previewEl.style.display !== 'none');

                    // If currently recording, show mic and don't switch to send
                    if (this._mediaRecorder && this._mediaRecorder.state === 'recording') {
                        if (sendBtn) { sendBtn.style.display = 'none'; }
                        if (micBtn) { micBtn.style.display = 'inline-flex'; }
                        return;
                    }

                    // If there's a file selected or there's text/template, show send button (media overrides mic)
                    if (fileSelected || text.length > 0 || isTemplate) {
                        if (sendBtn) { sendBtn.style.display = 'inline-block'; }
                        if (micBtn) { micBtn.style.display = 'none'; }
                    } else {
                        // Force mic visible when input empty and no file selected
                        if (sendBtn) { sendBtn.style.display = 'none'; }
                        if (micBtn) { micBtn.style.display = 'inline-flex'; }
                    }
                };

                if (messageInput) {
                    messageInput.addEventListener('input', updateSendMicVisibility);
                }

                // Expose small helper to other methods
                this._updateSendMicVisibility = updateSendMicVisibility;

                // Doble clic en burbuja de mensaje → copiar texto al portapapeles
                const chatConvContainer = document.getElementById('chat-conversations');
                if (chatConvContainer) {
                    chatConvContainer.addEventListener('dblclick', (e) => {
                        const bubble = e.target.closest('.message-bubble');
                        if (!bubble) return;
                        const contentEl = bubble.querySelector('.message-content');
                        if (!contentEl) return;
                        const text = (contentEl.innerText || contentEl.textContent || '').trim();
                        if (!text) return;
                        const doFeedback = () => this._showCopyToast(bubble);
                        if (navigator.clipboard && navigator.clipboard.writeText) {
                            navigator.clipboard.writeText(text).then(doFeedback).catch(() => {
                                try { document.execCommand('copy'); doFeedback(); } catch(e2) {}
                            });
                        } else {
                            try {
                                const sel = window.getSelection();
                                const range = document.createRange();
                                range.selectNodeContents(contentEl);
                                sel.removeAllRanges(); sel.addRange(range);
                                document.execCommand('copy');
                                sel.removeAllRanges();
                                doFeedback();
                            } catch(e2) {}
                        }
                    });
                }

                // File input selected -> show send button
                const fileInput = document.getElementById('file-input');
                if (fileInput) {
                    fileInput.addEventListener('change', (ev) => {
                        if (ev.target.files && ev.target.files.length) {
                            // existing showMediaPreview handles storing this.selectedFile
                            this.showMediaPreview(ev.target.files[0]);
                        }
                        updateSendMicVisibility();
                    });
                }

                // Initialize visibility
                setTimeout(updateSendMicVisibility, 10);

                // Attach '+' menu handling
                const attachBtn = document.getElementById('attach-btn');
                const attachMenu = document.getElementById('attach-menu');
                if (attachBtn && attachMenu) {
                    attachBtn.addEventListener('click', (ev) => {
                        ev.stopPropagation();
                        const isOpen = attachMenu.style.display === 'block';
                        // position menu under the button
                        const rect = attachBtn.getBoundingClientRect();
                        attachMenu.style.left = (rect.left) + 'px';
                        attachMenu.style.top = (rect.bottom + 8) + 'px';
                        attachMenu.style.display = isOpen ? 'none' : 'block';
                    });

                    // Option clicks
                    attachMenu.addEventListener('click', (ev) => {
                        const action = ev.target && ev.target.dataset ? ev.target.dataset.action : null;
                        if (!action) return;
                        ev.stopPropagation();
                        attachMenu.style.display = 'none';
                        if (action === 'file') {
                            if (fileInput) fileInput.click();
                        } else if (action === 'large-file') {
                            self.requestLargeFile();
                        } else if (action === 'template') {
                            // switch to template mode and focus selector
                            const typeSelect = document.getElementById('message-type');
                            if (typeSelect) {
                                typeSelect.value = 'template';
                                const evt = new Event('change');
                                typeSelect.dispatchEvent(evt);
                                const tplSel = document.getElementById('templateSelect');
                                if (tplSel) tplSel.focus();
                                updateSendMicVisibility();
                            }
                        }
                    });

                    // close when clicking outside
                    document.addEventListener('click', (ev) => {
                        if (attachMenu && attachMenu.style.display === 'block' && !attachMenu.contains(ev.target) && ev.target !== attachBtn) {
                            attachMenu.style.display = 'none';
                        }
                    });
                }

                // Mobile: sidebar toggle
                const sidebar = document.querySelector('.chat-sidebar');
                const sidebarToggle = document.getElementById('sidebar-toggle');
                if (sidebarToggle && sidebar) {
                    sidebarToggle.addEventListener('click', (ev) => {
                        ev.preventDefault();
                        // toggle mobile-hidden class
                        sidebar.classList.toggle('mobile-hidden');
                    });
                    // ensure sidebar hidden by default on small screens
                    const applyInitialMobile = () => {
                        const chatAreaEl = document.getElementById('chat-area');
                        const sidebarToggleBtn = document.getElementById('sidebar-toggle');
                        if (window.innerWidth <= 768) {
                            // On small screens, show the conversations list by default
                            sidebar.classList.remove('mobile-hidden');
                            // hide chat area if no conversation selected; otherwise show chat and hide sidebar
                            if (chatAreaEl) {
                                if (!this.currentUserId) {
                                    chatAreaEl.style.display = 'none';
                                } else {
                                    chatAreaEl.style.display = 'flex';
                                    sidebar.classList.add('mobile-hidden');
                                }
                            }
                            if (sidebarToggleBtn) sidebarToggleBtn.style.display = 'inline-block';
                        } else {
                            // Desktop: show both panels
                            sidebar.classList.remove('mobile-hidden');
                            if (chatAreaEl) chatAreaEl.style.display = this.currentUserId ? 'flex' : 'none';
                            if (sidebarToggleBtn) sidebarToggleBtn.style.display = 'none';
                        }
                    };
                    applyInitialMobile();
                    window.addEventListener('resize', applyInitialMobile);
                }

                const stopRecBtn = document.getElementById('stop-recording');
                if (stopRecBtn) stopRecBtn.addEventListener('click', () => this.stopRecording());
                const cancelRecBtn = document.getElementById('cancel-recording');
                if (cancelRecBtn) cancelRecBtn.addEventListener('click', () => this.cancelRecording());

                document.getElementById('message-input').addEventListener('keypress', (e) => {
                    if (e.key === 'Enter') {
                        this.sendMessage();
                    }
                });

                // Permitir pegar imágenes desde el portapapeles: convierte el contenido pegado en File y muestra el preview
                document.getElementById('message-input').addEventListener('paste', (e) => {
                    try {
                        const cd = (e.clipboardData || window.clipboardData);
                        if (!cd) return;

                        // ─── Comprimir imagen pegada a JPEG ─────────────────────────────────────
                        // El contenedor usa OpenSSL 3.x que hace renegociación TLS con Facebook
                        // para payloads >~300KB → errno 55 "Send failure". Solución: forzar
                        // que la imagen quede bajo 250KB mediante calidad progresiva + escala.
                        const TARGET_BYTES = 250 * 1024; // 250KB
                        const MAX_DIM      = 1920;       // máximo ancho/alto antes de escalar

                        const compressToJpeg = (blob) => new Promise((resolve) => {
                            // Si ya es pequeño JPEG/WebP, no recomprimir
                            if ((blob.type === 'image/jpeg' || blob.type === 'image/webp') && blob.size <= TARGET_BYTES) {
                                resolve(blob);
                                return;
                            }
                            const url = URL.createObjectURL(blob);
                            const img = new Image();
                            img.onload = () => {
                                URL.revokeObjectURL(url);

                                // Escalar si la imagen es más grande que MAX_DIM
                                let w = img.naturalWidth, h = img.naturalHeight;
                                if (w > MAX_DIM || h > MAX_DIM) {
                                    const ratio = Math.min(MAX_DIM / w, MAX_DIM / h);
                                    w = Math.round(w * ratio);
                                    h = Math.round(h * ratio);
                                }

                                const canvas = document.createElement('canvas');
                                canvas.width = w; canvas.height = h;
                                const ctx = canvas.getContext('2d');
                                ctx.fillStyle = '#ffffff'; // fondo blanco para PNGs con transparencia
                                ctx.fillRect(0, 0, w, h);
                                ctx.drawImage(img, 0, 0, w, h);

                                // Calidades a intentar hasta quedar bajo TARGET_BYTES
                                const qualities = [0.85, 0.70, 0.55, 0.40, 0.25];
                                let idx = 0;
                                const tryNext = () => {
                                    if (idx >= qualities.length) { resolve(blob); return; } // fallback
                                    canvas.toBlob((b) => {
                                        if (!b) { resolve(blob); return; }
                                        if (b.size <= TARGET_BYTES || idx === qualities.length - 1) {
                                            console.log(`Paste compress: ${(blob.size/1024).toFixed(0)}KB → ${(b.size/1024).toFixed(0)}KB (q=${qualities[idx]} ${w}×${h}px)`);
                                            resolve(b);
                                        } else {
                                            idx++;
                                            tryNext();
                                        }
                                    }, 'image/jpeg', qualities[idx]);
                                };
                                tryNext();
                            };
                            img.onerror = () => { URL.revokeObjectURL(url); resolve(blob); };
                            img.src = url;
                        });

                        // Helper: convertir base64 a Blob
                        const b64ToBlob = (b64, mime) => {
                            const bytes = atob(b64);
                            const len = bytes.length;
                            const arr = new Uint8Array(len);
                            for (let i = 0; i < len; i++) arr[i] = bytes.charCodeAt(i);
                            return new Blob([arr], { type: mime });
                        };

                        // Helper: procesar un blob de imagen (comprimir + preview)
                        const handleImageBlob = async (blob, originalName) => {
                            const compressed = await compressToJpeg(blob);
                            const name = (originalName || 'pasted_image').replace(/\.[^.]+$/, '') + '.jpg';
                            const file = new File([compressed], name, { type: 'image/jpeg' });
                            const max = this.getMaxFileSize(file.type);
                            if (file.size > max) {
                                alert('El archivo es demasiado grande. Máximo: ' + (max / 1024 / 1024).toFixed(0) + 'MB');
                                return;
                            }
                            console.log(`Imagen pegada comprimida: ${(blob.size/1024).toFixed(0)}KB → ${(file.size/1024).toFixed(0)}KB (JPEG 0.85)`);
                            this.showMediaPreview(file);
                        };

                        // 1) Si hay archivos directos en clipboard (Chrome/Edge)
                        if (cd.files && cd.files.length) {
                            for (const file of cd.files) {
                                if (file && file.type && file.type.startsWith('image/')) {
                                    e.preventDefault && e.preventDefault();
                                    handleImageBlob(file, file.name);
                                    return;
                                }
                            }
                        }

                        // 2) Items (Safari / otros) - buscar imagen o HTML con data URI
                        if (cd.items && cd.items.length) {
                            for (const item of cd.items) {
                                try {
                                    if (item.kind === 'file' && item.type && item.type.startsWith('image/')) {
                                        e.preventDefault && e.preventDefault();
                                        const blob = item.getAsFile();
                                        if (blob) {
                                            handleImageBlob(blob, blob.name || 'pasted_image.png');
                                            return;
                                        }
                                    } else if (item.kind === 'string' && (item.type === 'text/html' || item.type === 'text/plain')) {
                                        // Extraer data URI desde HTML si existe
                                        item.getAsString((s) => {
                                            const m = s && s.match ? s.match(/src=["']data:(image\/[^;]+);base64,([^"']+)["']/i) : null;
                                            if (m) {
                                                try {
                                                    const blob = b64ToBlob(m[2], m[1]);
                                                    handleImageBlob(blob, 'pasted_image.png');
                                                } catch (err) { console.warn('paste image convert failed', err); }
                                            }
                                        });
                                        // no hacer return inmediato: seguir buscando otros items
                                    }
                                } catch (inner) { console.warn('clipboard item parse failed', inner); }
                            }
                        }
                    } catch (err) {
                        console.warn('paste handler failed', err);
                    }
                });
                
                // Adjuntar archivos
                document.getElementById('attach-btn').addEventListener('click', () => {
                    document.getElementById('file-input').click();
                });
                
                document.getElementById('file-input').addEventListener('change', (e) => {
                    this.handleFileSelect(e);
                });

                // Carga paginada: botón "Cargar más" y scroll infinito
                const loadMoreBtn = document.getElementById('load-more-btn');
                if (loadMoreBtn) {
                    loadMoreBtn.addEventListener('click', () => this.loadMoreConversations());
                }
                const convList = document.getElementById('conversation-list');
                if (convList) {
                    convList.addEventListener('scroll', () => {
                        // mark user scrolling in sidebar so updates don't jump their view
                        try {
                            this._conversationsScrolling = true;
                            if (this._conversationsScrollTimer) clearTimeout(this._conversationsScrollTimer);
                            this._conversationsScrollTimer = setTimeout(() => { this._conversationsScrolling = false; this._conversationsScrollTimer = null; }, 600);
                        } catch (e) { /* ignore */ }

                        if (this.hasMoreConversations && !this.loadingConversations && (convList.scrollTop + convList.clientHeight >= convList.scrollHeight - 60)) {
                            this.loadMoreConversations();
                        }
                    });
                }

                // Filtro: Todos / No leídos
                const filterSelect = document.getElementById('conversation-filter');
                if (filterSelect) {
                    filterSelect.value = this.conversationFilter;
                    filterSelect.addEventListener('change', (e) => {
                        this.conversationFilter = e.target.value || 'all';
                        console.log('🔍 Filtro cambiado a:', this.conversationFilter);
                        // Re-renderizar la lista actual sin recargar desde el servidor
                        this.renderConversations();
                        // También recargar desde el servidor para asegurar datos actualizados
                        // (pero esto es secundario y puede ser en background)
                        this.loadConversations(1, false).catch(err => {
                            console.warn('Error recargando conversaciones después de cambiar filtro:', err);
                        });
                    });
                }
                
                // Botón de programar recordatorio
                const reminderBtn = document.getElementById('schedule-reminder-btn');
                if (reminderBtn) {
                    reminderBtn.addEventListener('click', () => this.showReminderModal());
                }

                // Botón solicitar archivo grande (header)
                const requestLargeFileBtn = document.getElementById('request-large-file-btn');
                if (requestLargeFileBtn) {
                    requestLargeFileBtn.addEventListener('click', () => this.requestLargeFile());
                }
                
                // Botón de guardar recordatorio
                const saveReminderBtn = document.getElementById('save-reminder-btn');
                if (saveReminderBtn) {
                    saveReminderBtn.addEventListener('click', () => this.saveReminder());
                }
                
                // Selector de plantilla en recordatorio
                const reminderTemplate = document.getElementById('reminder-template');
                if (reminderTemplate) {
                    reminderTemplate.addEventListener('change', () => this.handleReminderTemplateChange());
                }
                
                // Botón de enviar plantilla con variables
                const sendTemplateWithVarsBtn = document.getElementById('send-template-with-vars-btn');
                if (sendTemplateWithVarsBtn) {
                    sendTemplateWithVarsBtn.addEventListener('click', () => this.sendTemplateWithVariables());
                }
            }

            setupAutoRefresh() {
                // NOTA: Todos los sistemas de polling automático fueron ELIMINADOS
                // 
                // SSE maneja TODAS las actualizaciones en tiempo real:
                // 
                // 1. new_message → Recarga mensajes de conversación activa
                //    Ver línea ~1103: this.loadMessages(this.currentUserId, false, true)
                // 
                // 2. new_conversation → Agrega conversación a la lista
                //    Ver línea ~1122: this.addConversationToList(data)
                // 
                // 3. notification → Muestra toast de notificación
                //    Ver línea ~1119: this.showNotificationToast(notification)
                // 
                // Esto elimina TODO el polling HTTP y reduce la carga en 95%
                // Latencia: 3-30s → < 1s
                // 
                // Sin polling = Sin peticiones constantes = Servidor más eficiente 🚀
            }

            // New: periodic delta poller to fetch only messages newer than last seen timestamp
            async pollNewMessages() {
                if (!this.currentUserId) return;
                // Do not run delta polling when a full load is in progress (avoids races/clearing)
                if (this._fullLoadInProgress) {
                    if (console && console.debug) console.debug('pollNewMessages skipped due to full load in progress');
                    return;
                }
                try {
                    // If we don't yet have a latest message timestamp, fall back to full load
                    if (!this._latestMessage) {
                        return await this.loadMessages(this.currentUserId, true, true);
                    }

                    const url = `get_user_messages.php?user_id=${this.currentUserId}&since=${encodeURIComponent(this._latestMessage)}&limit=${this.messageLimit}`;
                    const resp = await this.apiCall(url);
                    if (!resp || !resp.success || !Array.isArray(resp.data) || resp.data.length === 0) return;

                    const messages = resp.data;
                    // Deduplicate and process
                    const container = document.getElementById('chat-conversations');
                    const nearBottomNow = container ? ((container.scrollHeight - container.scrollTop - container.clientHeight) < 150) : true;

                    let appended = 0;
                    for (const m of messages) {
                        try {
                            // skip duplicates
                            const exists = this.currentMessages && this.currentMessages.some(x => (x.message_id && m.message_id && String(x.message_id) === String(m.message_id)) || (x.id && m.id && Number(x.id) === Number(m.id)));
                            if (exists) continue;

                            if (nearBottomNow && !this._userScrolling) {
                                // append directly and mark for render
                                this.currentMessages.push(m);
                                appended++;
                            } else {
                                // stage for later (user is reading history)
                                this._stagedMessages.push(m);
                                if (this._stagedMessageIds) this._stagedMessageIds.add(m.message_id || m.id || ('local_' + Date.now()));
                                this._stagedCount = this._stagedMessages.length;
                                this.showNewMessagesIndicator(this._stagedCount);
                            }
                        } catch (e) { console.warn('pollNewMessages process failed for msg', e); }
                    }

                    if (appended > 0) {
                        // render the new appended messages without disrupting playback/scroll; follow only if near bottom
                        this.renderMessagesIncremental(false, 0, nearBottomNow && !this._userScrolling);
                        if (nearBottomNow && !this._userScrolling) this.scrollToBottom();
                    }

                    // If we received any delta messages, perform an immediate full fetch to sync any server-side batching
                    if (messages.length > 0) {
                        if (!this._fullLoadInProgress) {
                            try {
                                if (console && console.debug) console.debug('pollNewMessages: delta returned, triggering immediate full reload');
                                await this.loadMessages(this.currentUserId, true, nearBottomNow && !this._userScrolling);
                            } catch (e) {
                                console.warn('Immediate full reload after delta failed', e);
                            }
                        } else {
                            if (console && console.debug) console.debug('Immediate full reload skipped because another full load is in progress');
                        }
                    }

                    // update latest timestamp from the last message
                    try {
                        const last = messages[messages.length - 1];
                        if (last && last.created_at) this._latestMessage = last.created_at;
                    } catch (e) { /* ignore */ }

                } catch (e) {
                    console.warn('pollNewMessages failed', e);
                }
            }

            // Helper para llamadas a la API desde esta clase
            async apiCall(endpoint, options = {}) {
                const defaultOptions = {
                    method: 'GET',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    credentials: 'same-origin', // Incluir cookies de sesión
                    cache: 'no-store' // Evitar caché del navegador
                };

                // Si se pasa body, asumimos POST (y serializamos)
                const finalOptions = { ...defaultOptions, ...options };
                if (options.body && typeof options.body === 'object') {
                    finalOptions.method = 'POST';
                    finalOptions.body = JSON.stringify(options.body);
                }

                // Normalizar endpoint: prefijar 'api/' si no es URL completa y no empieza con 'api/'
                let url = endpoint;
                if (!/^https?:\/\//i.test(url) && !url.startsWith('api/')) {
                    url = 'api/' + url;
                }

                const response = await fetch(url, finalOptions);

                if (response.status === 401) {
                    // Sesión expirada: redirigir al login
                    console.warn('apiCall: unauthorized, redirecting to login');
                    window.location.href = 'login.php';
                    return null;
                }

                const text = await response.text();

                if (!response.ok) {
                    // Si es 401, intentar parsear el JSON para obtener el mensaje real
                    if (response.status === 401) {
                        try {
                            const errorData = JSON.parse(text);
                            if (errorData.error) {
                                alert('Sesión expirada: ' + errorData.error + '. Recargando página...');
                            }
                        } catch (e) {
                            alert('Sesión expirada. Recargando página...');
                        }
                        // Forzar recarga para restablecer sesión
                        setTimeout(() => window.location.reload(), 1000);
                        return null;
                    }
                    // Incluir el cuerpo de la respuesta (truncado) para diagnóstico
                    const snippet = text && text.length ? (text.length > 2000 ? text.substr(0, 2000) + '... (truncated)' : text) : '<no body>';
                    console.error('apiCall HTTP error', response.status, url, snippet);
                    throw new Error('HTTP error! status: ' + response.status + ' -- ' + snippet);
                }

                if (!text || text.trim() === '') return null;

                try {
                    return JSON.parse(text);
                } catch (err) {
                    console.error('apiCall parse error for', url, err);
                    const snippet = text.length > 1000 ? text.substr(0, 1000) : text;
                    throw new Error('Invalid JSON response from ' + url + ': ' + err.message + ' -- response snippet: ' + snippet);
                }
            }

            async loadConversations(page = 1, append = false) {
                // 🎯 OPTIMIZACIÓN: Solo cargar del servidor si es la primera vez O si es paginación
                // Después SSE se encarga de actualizar automáticamente
                if (this._conversationsLoaded && !append) {
                    console.log('⚡ Conversaciones ya cargadas, SSE se encarga de actualizaciones');
                    return;
                }
                
                if (this.loadingConversations) return;
                this.loadingConversations = true;
                const loadMoreContainer = document.getElementById('load-more-container');
                const loadMoreBtn = document.getElementById('load-more-btn');
                if (loadMoreBtn) loadMoreBtn.disabled = true;
                try {
                    const url = `api/get_conversations.php?page=${page}&limit=${this.conversationsLimit}&filter=${encodeURIComponent(this.conversationFilter)}`;
                    const resp = await fetch(url, { credentials: 'same-origin', cache: 'no-store' });
                    
                    if (!resp.ok) {
                        throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
                    }
                    
                    const data = await resp.json();
                    console.log('Respuesta get_conversations:', data); // Debug

                    let items = [];
                    let hasMore = false;
                    if (data && data.success && Array.isArray(data.data)) {
                        items = data.data;
                        hasMore = !!data.has_more || (page * this.conversationsLimit) < (data.total || 0);
                    } else if (Array.isArray(data)) {
                        items = data;
                        hasMore = items.length === this.conversationsLimit;
                    } else {
                        console.error('Error cargando conversaciones: formato de datos inválido', data);
                        alert('Error cargando conversaciones: ' + (data.error || 'formato de datos inválido'));
                        this.loadingConversations = false;
                        if (loadMoreBtn) loadMoreBtn.disabled = false;
                        return;
                    }

                    if (append) {
                        this.conversations = this.conversations.concat(items);
                        this._justLoadedMore = true; // evita que renderConversations baje el scroll y dispare otro loadMore
                    } else {
                        this.conversations = items;
                    }

                    this.hasMoreConversations = hasMore;
                    this.conversationsPage = page;
                    
                    // Marcar como cargadas después de la primera carga exitosa
                    if (!append) {
                        this._conversationsLoaded = true;
                        console.log('✅ Primera carga de conversaciones completada, SSE tomará el control');
                    }
                    
                    this.renderConversations();

                    if (loadMoreContainer) {
                        loadMoreContainer.style.display = this.hasMoreConversations ? 'block' : 'none';
                    }
                } catch (error) {
                    console.error('Error loading conversations:', error);
                    alert('Error cargando conversaciones: ' + error.message);
                } finally {
                    this.loadingConversations = false;
                    if (loadMoreBtn) loadMoreBtn.disabled = false;
                }
            }

            async loadMoreConversations() {
                if (!this.hasMoreConversations || this.loadingConversations) return;
                await this.loadConversations(this.conversationsPage + 1, true);
            }



            renderConversations() {
                console.log('🎨 renderConversations INICIADO');
                console.log('📊 Total conversaciones:', this.conversations?.length || 0);
                
                const container = document.getElementById('conversation-list');
                
                if (!container) {
                    console.error('❌ No se encontró el elemento #conversation-list en el DOM');
                    return;
                }

                console.log('✅ Container encontrado:', container);
                console.log('🔍 Filtro activo:', this.conversationFilter);
                
                // Aplicar filtro de no leídos
                let conversationsToShow = this.conversations;
                if (this.conversationFilter === 'unread') {
                    conversationsToShow = this.conversations.filter(c => (c.unread_count || 0) > 0);
                    console.log(`🔍 Filtro 'unread' aplicado: ${conversationsToShow.length} de ${this.conversations.length} conversaciones`);
                }
                
                if (conversationsToShow.length === 0) {
                    const emptyMsg = this.conversationFilter === 'unread' ? 'No hay conversaciones no leídas' : 'No hay conversaciones aún';
                    container.innerHTML = `
                        <div class="text-center p-4">
                            <i class="fas fa-comments fa-3x text-muted mb-3"></i>
                            <p class="text-muted">${emptyMsg}</p>
                        </div>
                    `;
                    console.log('📭 Lista vacía, mostrando mensaje');
                    return;
                }
                
                console.log('📋 Generando HTML para', conversationsToShow.length, 'conversaciones');

                // Ahora el backend devuelve por usuario: last_message, last_time, unread_count y avatar_url
                const html = conversationsToShow.map(conv => {
                    const isActive = conv.user_id == this.currentUserId ? 'active' : '';
                    const time = this.formatTime(conv.last_time);
                    
                    // Mostrar miniatura si el último mensaje es multimedia y tiene archivo local
                    let preview = '';
                    const hasLocalMedia = conv.last_local_file || conv.last_local_thumb;
                    const isMediaType = conv.message_type && ['image', 'video', 'audio', 'document'].includes(conv.message_type);
                    
                    if (hasLocalMedia && isMediaType) {
                        const thumbSrc = conv.last_local_thumb || conv.last_local_file;
                        const mediaIcon = {
                            'image': '🖼️',
                            'video': '🎥',
                            'audio': '🎵',
                            'document': '📎'
                        }[conv.message_type] || '📎';
                        
                        if (conv.message_type === 'image' || conv.message_type === 'video') {
                            preview = `<img src="/${thumbSrc}" alt="media" style="width:40px; height:40px; object-fit:cover; border-radius:4px; margin-right:8px; vertical-align:middle;"> ${mediaIcon} ${conv.last_message || 'Multimedia'}`;
                        } else {
                            preview = `${mediaIcon} ${conv.last_message || 'Archivo'}`;
                        }
                    } else if (conv.matched_content) {
                        // Mostrar snippet del mensaje que coincidió en la búsqueda
                        const snippet = this.truncateText(conv.matched_content, 55);
                        preview = `<span style="color:#888;font-size:11px;">💬 </span>${escapeHtml(snippet)}`;
                    } else {
                        preview = this.truncateText(conv.last_message || 'Sin mensajes', 50);
                    }

                    const avatar = conv.avatar_url ? `<img src="${conv.avatar_url}" alt="avatar" class="conversation-avatar-img">` : `<div class="conversation-avatar">${this.getInitials(conv.name)}</div>`;

                    const unreadBadge = conv.unread_count && conv.unread_count > 0 ? `<span class="badge bg-danger unread-badge">${conv.unread_count}</span>` : '';
                    const attentionBadge = conv.advisor_requested ? `<span class="badge bg-danger ms-1">¡Atención!</span>` : '';

                    // different color if has unread and not active
                    const unreadClass = (conv.unread_count && conv.unread_count > 0 && !isActive) ? 'unread' : '';
                    const attentionClass = conv.advisor_requested ? ' attention' : '';
                    const blockedClass = conv.user_status === 'blocked' ? ' user-blocked' : '';

                    // Escapar caracteres especiales en nombre y teléfono para no romper el onclick
                    const safeName  = (conv.name        || '').replace(/\r?\n/g, ' ').replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '&quot;').replace(/`/g, '\\`');
                    const safePhone = (conv.phone_number || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'");
                    return `
                        <div class="conversation-item ${isActive} ${unreadClass}${attentionClass}${blockedClass}" data-user-id="${conv.user_id}" onclick="chat.openConversation(${conv.user_id}, '${safeName}', '${safePhone}')">
                            <div class="conversation-avatar-wrapper">
                                ${avatar}
                            </div>
                            <div class="conversation-info">
                                <div class="conversation-name">${conv.name} ${unreadBadge} ${attentionBadge}</div>
                                <div class="conversation-preview">
                                    ${conv.direction === 'outgoing' ? '✓ ' : ''}${preview}
                                </div>
                            </div>
                            <div class="conversation-time">
                                ${time}
                            </div>
                        </div>
                    `;
                }).join('');
                
                console.log('📄 HTML generado, longitud:', html.length, 'caracteres');

                // Detectar nuevas notificaciones: comparar unread counts previos (usar todas las conversaciones, no solo las filtradas)
                if (!this._prevConversations) this._prevConversations = {};
                this.conversations.forEach(c => {
                    const prev = this._prevConversations[c.user_id] || { unread_count: 0 };
                    if (c.unread_count > prev.unread_count && c.user_id != this.currentUserId) {
                        // nueva notificación
                        this.playNotificationSound();
                    }
                    this._prevConversations[c.user_id] = { unread_count: c.unread_count };
                });

                // Preservar posición visual del scroll del sidebar
                try {
                    const prevScrollTop    = container.scrollTop;
                    const prevScrollHeight = container.scrollHeight;
                    const wasAtTop         = prevScrollTop <= 4;
                    // Flag: si veníamos de un loadMore (items appended al fondo) NO compensar
                    const wasLoadingMore   = !!this._justLoadedMore;
                    this._justLoadedMore   = false;

                    container.innerHTML = html;
                    void container.offsetHeight; // forzar repaint

                    if (wasAtTop) {
                        container.scrollTop = 0;
                    } else if (wasLoadingMore) {
                        // Items se agregaron al FONDO — mantener posición exacta sin compensar
                        // (compensar causaría que el scroll baje y disparara otro loadMore)
                        container.scrollTop = prevScrollTop;
                    } else {
                        // Re-render normal (SSE update): compensar cambio de altura
                        const scrollDelta = container.scrollHeight - prevScrollHeight;
                        container.scrollTop = Math.max(0, prevScrollTop + scrollDelta);
                    }
                } catch (e) {
                    console.warn('renderConversations: scroll preservation failed', e);
                    container.innerHTML = html;
                    void container.offsetHeight;
                }
            }

            getInitials(name) {
                if (!name || typeof name !== 'string') return '?';
                const parts = name.trim().split(/\s+/);
                if (parts.length >= 2 && parts[0] && parts[1]) {
                    return ((parts[0][0] || '') + (parts[1][0] || '')).toUpperCase();
                }
                if (parts[0]) {
                    return parts[0].substring(0, 2).toUpperCase();
                }
                return '?';
            }

            formatTime(dateString) {
                const date = new Date(dateString);
                const now = new Date();
                const diffInHours = (now - date) / (1000 * 60 * 60);
                
                if (diffInHours < 24) {
                    return date.toLocaleTimeString('es-ES', { 
                        hour: '2-digit', 
                        minute: '2-digit' 
                    });
                } else if (diffInHours < 48) {
                    return 'Ayer';
                } else {
                    return date.toLocaleDateString('es-ES', { 
                        day: '2-digit', 
                        month: '2-digit' 
                    });
                }
            }

            truncateText(text, maxLength) {
                if (text.length <= maxLength) return text;
                return text.substring(0, maxLength) + '...';
            }

            // Cache ligero del estado del usuario (in_service, advisor_requested, on_hold)
            async getUserState(userId, force = false) {
                if (!userId) return null;
                this._userStateCache = this._userStateCache || {};
                const now = Date.now();
                const cached = this._userStateCache[userId];
                const TTL = 15000; // 15 segundos
                if (!force && cached && (now - cached.ts) < TTL) {
                    return cached.state;
                }

                try {
                    const resp = await this.apiCall(`get_conversation_detail.php?user_id=${userId}`);
                    if (resp && resp.success && resp.user) {
                        const s = {
                            in_service: !!resp.user.in_service,
                            advisor_requested: !!resp.user.advisor_requested,
                            on_hold: !!resp.user.on_hold
                        };
                        this._userStateCache[userId] = { state: s, ts: Date.now() };
                        return s;
                    }
                } catch (e) {
                    console.warn('getUserState failed for', userId, e);
                }

                // fallback: keep previous cached or return null
                if (cached) return cached.state;
                this._userStateCache[userId] = { state: null, ts: Date.now() };
                return null;
            }

            getConversationPhone(userId) {
                const conv = this.conversations.find(c => c.user_id === userId);
                if (conv) return conv.phone_number || conv.phone || conv.user_phone || null;
                const el = document.getElementById('chat-phone');
                return el ? el.textContent.trim() : null;
            }

            playNotificationSound() {
                try {
                    // Small beep using Web Audio API (no external file needed)
                    const ctx = new (window.AudioContext || window.webkitAudioContext)();
                    const o = ctx.createOscillator();
                    const g = ctx.createGain();
                    o.type = 'sine';
                    o.frequency.value = 880;
                    o.connect(g);
                    g.connect(ctx.destination);
                    g.gain.value = 0.05;
                    o.start();
                    setTimeout(() => { o.stop(); ctx.close(); }, 180);
                } catch (e) {
                    console.warn('Notification sound failed', e);
                }
            }

            async openConversation(userId, userName, phoneNumber) {
                this.currentUserId = userId;
                // Clear any staged messages from a previous conversation and hide indicator
                try { if (this._stagedMessageIds) { this._stagedMessageIds.clear(); this._stagedCount = 0; this.hideNewMessagesIndicator(); } } catch(e) {}
                
                // Actualizar inmediatamente el contador de no leídos a 0 en la lista local
                const conv = this.conversations.find(c => c.user_id === userId);
                if (conv) {
                    conv.unread_count = 0;
                    // Re-renderizar la lista para reflejar el cambio inmediatamente
                    this.renderConversations();
                }
                
                // Actualizar UI
                document.getElementById('no-conversation').style.display = 'none';
                document.getElementById('chat-area').style.display = 'flex';
                
                // Actualizar header
                const nameEl = document.getElementById('chat-name-text') || document.getElementById('chat-name');
                nameEl.textContent = userName;
                document.getElementById('chat-phone').textContent = phoneNumber;
                document.getElementById('chat-avatar').textContent = this.getInitials(userName);
                
                // Marcar conversación como activa
                document.querySelectorAll('.conversation-item').forEach(item => {
                    item.classList.remove('active');
                });
                const activeItem = document.querySelector(`[data-user-id="${userId}"]`);
                if (activeItem) {
                    activeItem.classList.add('active');
                    // Scroll al ítem activo solo al abrirlo (no en cada re-render)
                    activeItem.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
                }
                
                // Actualizar estado del toggle del bot según datos de la conversación (ya obtuvimos conv arriba)
                if (conv) {
                    const btn = document.getElementById('bot-toggle');
                    const holdIndicator = document.getElementById('hold-indicator');
                    const releaseBtn = document.getElementById('release-hold-btn');
                    const markUnreadBtn = document.getElementById('mark-unread-btn');

                    // On mobile, hide the sidebar when opening a conversation so chat occupies full screen
                    const sidebar = document.querySelector('.chat-sidebar');
                    if (sidebar && window.innerWidth <= 768) {
                        sidebar.classList.add('mobile-hidden');
                    }

                    if (markUnreadBtn) {
                        markUnreadBtn.style.display = 'inline-block';
                        markUnreadBtn.onclick = async () => {
                            try {
                                const resp = await fetch('api/mark_conversation_unread.php', {
                                    method: 'POST',
                                    headers: { 'Content-Type': 'application/json' },
                                    body: JSON.stringify({ user_id: userId }),
                                    cache: 'no-store'
                                });
                                if (resp.status === 401) {
                                    alert('Sesión expirada. Por favor inicie sesión de nuevo.');
                                    return;
                                }
                                const json = await resp.json();
                                if (json && json.success) {
                                    // Actualizar la conversación local para mostrar badge de no leído
                                    const conv = this.conversations.find(c => String(c.user_id) === String(userId));
                                    if (conv) {
                                        conv.unread_count = Math.max(1, conv.unread_count || 1);
                                    }
                                    // Refrescar la lista visual
                                    this.renderConversations();
                                    console.log('✅ Conversación marcada como no leída');
                                } else {
                                    console.error('Error marcando conversación como no leída:', json);
                                    alert('Error marcando conversación como no leída');
                                }
                            } catch (e) {
                                console.error('Error marking conversation unread', e);
                                alert('Error marcando conversación como no leída');
                            }
                        };
                    }

                    if (holdIndicator) {
                        if (conv.on_hold) {
                            holdIndicator.textContent = 'EN ESPERA';
                            holdIndicator.style.color = '#b85';
                            holdIndicator.style.display = 'inline';
                        } else if (conv.advisor_requested) {
                            holdIndicator.textContent = 'SOLICITUD PENDIENTE';
                            holdIndicator.style.color = '#f39c12';
                            holdIndicator.style.display = 'inline';
                        } else {
                            holdIndicator.style.display = 'none';
                        }
                    } else {
                        console.warn('holdIndicator element not found in DOM');
                    }

                    // Badge T&C pendiente
                    const termsBadge = document.getElementById('terms-pending-badge');
                    if (termsBadge) {
                        termsBadge.style.display = conv.terms_pending ? 'inline' : 'none';
                    }

                    if (releaseBtn) {
                        // show button only when conversation is explicitly 'on_hold' (do NOT show during advisor request)
                        releaseBtn.style.display = (conv.on_hold) ? 'inline-block' : 'none';
                        releaseBtn.onclick = async () => {
                            try {
                                const resp = await fetch('api/release_hold.php', {
                                    method: 'POST',
                                    headers: { 'Content-Type': 'application/json' },
                                    body: JSON.stringify({ user_id: userId }),
                                    cache: 'no-store'
                                });
                                if (resp.status === 401) {
                                    alert('Sesión expirada. Por favor inicie sesión de nuevo.');
                                    return;
                                }
                                const json = await resp.json();
                                if (json && json.success) {
                                    conv.on_hold = false;
                                    conv.advisor_requested = 0;
                                    if (holdIndicator) {
                                        holdIndicator.style.display = 'none';
                                    } else {
                                        console.warn('holdIndicator missing when releasing hold');
                                    }
                                    releaseBtn.style.display = 'none';
                                    // show bot toggle again
                                    if (btn) btn.style.display = 'inline-block';
                                    await this.loadConversations();
                                } else {
                                    alert('Error al liberar la espera');
                                }
                            } catch (e) {
                                console.error('Error releasing hold', e);
                                alert('Error liberando espera');
                            }
                        };

                        // Auxiliary attend button is hidden: we use the main Atender/Finalizar control and show status in-chat
                        try {
                            const attendToggleBtn = document.getElementById('attend-toggle-btn');
                            if (attendToggleBtn) {
                                attendToggleBtn.style.display = 'none';
                                attendToggleBtn.onclick = null;
                            }
                            if (conv && conv.advisor_requested && !conv.in_service) {
                                try { this.showStatusMessageInChat(userId, 'Solicitud de asesor pendiente'); } catch(e) { /* ignore */ }
                            }
                        } catch (e) { console.warn('ensure attend button visibility failed', e); }

                        // Render attend controls (single toggle + status) — compatible con comportamiento de chat_window.php
                        const attendToggleBtn = document.getElementById('attend-toggle-btn');
                        const attendStatus = document.getElementById('attend-status');

                        const renderAttendControls = () => {
                            if (!attendToggleBtn || !attendStatus) return;
                            // Hide auxiliary attend button to avoid having two 'Finalizar' controls
                            attendToggleBtn.style.display = 'none';
                            attendToggleBtn.onclick = null;
                            attendStatus.textContent = '';

                            // refresh conv reference
                            const c = this.conversations.find(x => x.user_id === userId) || conv;

                            // Update only the in-chat status message based on server/local state
                            this.getUserState(userId).then(serverState => {
                                const s = serverState || { in_service: c.in_service, advisor_requested: c.advisor_requested };
                                if (s.in_service) {
                                    try { this.showStatusMessageInChat(userId, 'Un asesor te está atendiendo'); } catch(e) {}
                                } else if (s.advisor_requested || c.advisor_requested) {
                                    try { this.showStatusMessageInChat(userId, 'Solicitud de asesor pendiente'); } catch(e) {}
                                }
                            }).catch(e => {
                                if (c.in_service) {
                                    try { this.showStatusMessageInChat(userId, 'Un asesor te está atendiendo'); } catch(e) {}
                                } else if (c.advisor_requested) {
                                    try { this.showStatusMessageInChat(userId, 'Solicitud de asesor pendiente'); } catch(e) {}
                                }
                            });
                        }; 

                        const toggleAttend = async () => {
                            try {
                                const c = this.conversations.find(x => x.user_id === userId) || conv;

                                // Preflight: obtener estado canonico del servidor con cache corta
                                const serverState = await this.getUserState(userId, true);
                                const currentlyInService = serverState ? !!serverState.in_service : !!c.in_service;

                                if (currentlyInService) {
                                    if (!confirm('¿Confirmas finalizar la atención?')) return;
                                    const resp = await this.apiCall('finish_attend.php', { body: { user_id: userId } });
                                    if (resp && resp.success) {
                                        showAlert('Finalizada la atención', 'success');
                                        c.in_service = false;
                                        c.advisor_requested = 0;
                                        // hide hold indicator
                                        if (holdIndicator) holdIndicator.style.display = 'none';

                                        // release_hold is performed by finish_attend.php; avoid extra client-side call to prevent duplicate notifications.

                                        // actualizar cache local
                                        this._userStateCache = this._userStateCache || {};
                                        this._userStateCache[userId] = { state: { in_service: false, advisor_requested: false, on_hold: false }, ts: Date.now() };

                                        await this.loadConversations();
                                        await this.loadMessages(userId, true);
                                    } else {
                                        throw new Error(resp && resp.error ? resp.error : 'Error finalizando');
                                    }
                                } else {
                                    if (!confirm('¿Confirmas tomar la atención de esta conversación?')) return;
                                    const resp = await this.apiCall('attend.php', { body: { user_id: userId } });
                                    if (resp && resp.success) {
                                        showAlert('Atención iniciada', 'success');
                                        c.in_service = true;
                                        c.advisor_requested = 0;
                                        if (holdIndicator) {
                                            holdIndicator.textContent = 'EN SERVICIO';
                                            holdIndicator.style.color = '#28a745';
                                            holdIndicator.style.display = 'inline';
                                        }
                                        // hide bot toggle while in service
                                        if (btn) btn.style.display = 'none';

                                        // Add status message in chat to inform the user
                                        try { this.showStatusMessageInChat(userId, 'Un asesor te está atendiendo'); } catch(e) { console.warn('showStatusMessageInChat failed', e); }

                                        // actualizar cache local
                                        this._userStateCache = this._userStateCache || {};
                                        this._userStateCache[userId] = { state: { in_service: true, advisor_requested: false, on_hold: false }, ts: Date.now() };

                                        await this.loadConversations();
                                        await this.loadMessages(userId, true);
                                    } else {
                                        throw new Error(resp && resp.error ? resp.error : 'Error iniciando atención');
                                    }
                                }
                            } catch (err) {
                                console.error('Error toggling attend', err);
                                showAlert('Error al cambiar estado de atención: ' + (err.message || err), 'danger');
                            } finally {
                                // re-render controls after any operation
                                renderAttendControls();
                            }
                        };

                        if (attendToggleBtn) {
                            // Auxiliary button intentionally disabled; main control handles attend/finish
                            attendToggleBtn.style.display = 'none';
                            attendToggleBtn.onclick = null;
                        }

                        // inicializar estado
                        renderAttendControls();
                    }

                    if (btn) {
                        // Usar este botón como control único de Atender / Finalizar
                        btn.textContent = conv.in_service ? 'Finalizar' : 'Atender';
                        btn.title = conv.in_service ? 'Finalizar atención' : 'Atender';
                        // Estilos según estado
                        btn.classList.toggle('btn-light', !!conv.in_service);
                        btn.classList.toggle('btn-outline-light', !conv.in_service);

                        // Mostrar el botón por defecto (se oculta en casos puntuales de on_hold si se desea)
                        btn.style.display = 'inline-block';
                        btn.disabled = false;

                        btn.onclick = async () => {
                            try {
                                btn.disabled = true;

                                // Si ya está en servicio -> finalizar
                                if (conv.in_service) {
                                    if (!confirm('¿Confirmas finalizar la atención?')) { btn.disabled = false; return; }
                                    const resp = await this.apiCall('finish_attend.php', { body: { user_id: userId } });
                                    if (resp && resp.success) {
                                        showAlert('Finalizada la atención', 'success');
                                        conv.in_service = false;
                                        conv.advisor_requested = 0;
                                        conv.bot_enabled = true;
                                        if (holdIndicator) holdIndicator.style.display = 'none';

                                        // release_hold is performed by finish_attend.php; avoid extra client-side call to prevent duplicate notifications.

                                        // actualizar cache local
                                        this._userStateCache = this._userStateCache || {};
                                        this._userStateCache[userId] = { state: { in_service: false, advisor_requested: false, on_hold: false }, ts: Date.now() };

                                        await this.loadConversations();
                                        await this.loadMessages(userId, true);
                                    } else {
                                        throw new Error(resp && resp.error ? resp.error : 'Error finalizando');
                                    }

                                } else {
                                    // Tomar la atención
                                    if (!confirm('¿Confirmas tomar la atención de esta conversación?')) { btn.disabled = false; return; }
                                    const resp = await this.apiCall('attend.php', { body: { user_id: userId } });
                                    if (resp && resp.success) {
                                        showAlert('Atención iniciada', 'success');
                                        conv.in_service = true;
                                        conv.advisor_requested = 0;
                                        conv.bot_enabled = false;
                                        if (holdIndicator) {
                                            holdIndicator.textContent = 'EN SERVICIO';
                                            holdIndicator.style.color = '#28a745';
                                            holdIndicator.style.display = 'inline';
                                        }

                                        // Add status message in chat to inform the user
                                        try { this.showStatusMessageInChat(userId, 'Un asesor te está atendiendo'); } catch(e) { console.warn('showStatusMessageInChat failed', e); }

                                        // actualizar cache local
                                        this._userStateCache = this._userStateCache || {};
                                        this._userStateCache[userId] = { state: { in_service: true, advisor_requested: false, on_hold: false }, ts: Date.now() };

                                        await this.loadConversations();
                                        await this.loadMessages(userId, true);
                                    } else {
                                        throw new Error(resp && resp.error ? resp.error : 'Error iniciando atención');
                                    }
                                }
                            } catch (err) {
                                console.error('Error toggling attend via main button', err);
                                showAlert('Error al cambiar estado de atención: ' + (err.message || err), 'danger');
                            } finally {
                                btn.disabled = false;
                                // Actualizar label y clases
                                btn.textContent = conv.in_service ? 'Finalizar' : 'Atender';
                                btn.title = conv.in_service ? 'Finalizar atención' : 'Atender';
                                btn.classList.toggle('btn-light', !!conv.in_service);
                                btn.classList.toggle('btn-outline-light', !conv.in_service);
                                // Re-render controls auxiliares
                                try { renderAttendControls(); } catch(e) { /* ignore */ }
                            }
                        };
                    }

                    // ── Botón Bloquear / Desbloquear ────────────────────────────
                    const blockBtn = document.getElementById('block-user-btn');
                    const blockedIndicator = document.getElementById('blocked-indicator');
                    if (blockBtn) {
                        const isBlocked = conv.user_status === 'blocked';
                        // Actualizar apariencia
                        blockBtn.classList.toggle('is-blocked', isBlocked);
                        blockBtn.title = isBlocked ? 'Desbloquear usuario' : 'Bloquear usuario';
                        blockBtn.innerHTML = isBlocked
                            ? '<i class="fas fa-lock-open"></i>'
                            : '<i class="fas fa-ban"></i>';
                        if (blockedIndicator) {
                            blockedIndicator.style.display = isBlocked ? 'inline' : 'none';
                        }

                        blockBtn.onclick = async () => {
                            const currentlyBlocked = conv.user_status === 'blocked';
                            const action = currentlyBlocked ? 'desbloquear' : 'bloquear';
                            if (!confirm(`¿Confirmas ${action} a este usuario?\n${currentlyBlocked ? 'Podrá volver a interactuar con el bot.' : 'El bot ignorará sus mensajes.'}`)) return;
                            blockBtn.disabled = true;
                            try {
                                const resp = await fetch('api/block_user.php', {
                                    method: 'POST',
                                    headers: { 'Content-Type': 'application/json' },
                                    body: JSON.stringify({ user_id: userId, blocked: !currentlyBlocked }),
                                    cache: 'no-store'
                                });
                                if (resp.status === 401) { alert('Sesión expirada.'); return; }
                                const json = await resp.json();
                                if (json && json.success) {
                                    conv.user_status = json.status;
                                    const nowBlocked = json.blocked;
                                    blockBtn.classList.toggle('is-blocked', nowBlocked);
                                    blockBtn.title = nowBlocked ? 'Desbloquear usuario' : 'Bloquear usuario';
                                    blockBtn.innerHTML = nowBlocked
                                        ? '<i class="fas fa-lock-open"></i>'
                                        : '<i class="fas fa-ban"></i>';
                                    if (blockedIndicator) {
                                        blockedIndicator.style.display = nowBlocked ? 'inline' : 'none';
                                    }
                                    // Actualizar ítem en sidebar
                                    const sideItem = document.querySelector(`.conversation-item[data-user-id="${userId}"]`);
                                    if (sideItem) sideItem.classList.toggle('user-blocked', nowBlocked);
                                    showAlert(nowBlocked ? '🚫 Usuario bloqueado' : '✅ Usuario desbloqueado', nowBlocked ? 'warning' : 'success');
                                } else {
                                    alert('Error: ' + (json && json.error ? json.error : 'Error desconocido'));
                                }
                            } catch (e) {
                                console.error('Error toggling block', e);
                                alert('Error al cambiar estado de bloqueo');
                            } finally {
                                blockBtn.disabled = false;
                            }
                        };
                    }
                }

                // Cargar mensajes (con paginación)
                // Scroll inmediato al fondo para que el usuario vea el final mientras cargamos el bloque completo
                try {
                    const immedContainer = document.getElementById('chat-conversations');
                    if (immedContainer) {
                        immedContainer.scrollTop = immedContainer.scrollHeight;
                        // clear any pending reloads and temporarily suspend auto-refresh to avoid races
                        this._pendingReloadAfterScroll = false;
                        this._pendingReloadAfterMedia = false;
                        this._suspendAutoRefresh = true;
                        if (console && console.debug) console.debug('openConversation: scrolled to bottom and suspended auto-refresh prior to full load');
                    }
                } catch(e) { /* ignore */ }

                await this.loadMessages(userId, true, true);

                // Resume auto-refresh after the load
                try { this._suspendAutoRefresh = false; } catch(e) {}

                // Añadir listener para scroll arriba (cargar más historial)
                const chatContainer = document.getElementById('chat-conversations');
                if (chatContainer) {
                    if (!chatContainer._infiniteScrollAdded) {
                        chatContainer.addEventListener('scroll', async () => {
                            // Mark that the user is actively scrolling/reading so we don't yank the viewport to bottom
                            try {
                                this._userScrolling = true;
                                if (this._userScrollTimer) clearTimeout(this._userScrollTimer);
                                this._userScrollTimer = setTimeout(() => { 
                                    this._userScrolling = false; 
                                    this._userScrollTimer = null; 
                                    if (this._pendingReloadAfterScroll && this.currentUserId) {
                                        this._pendingReloadAfterScroll = false;
                                        try { this.loadMessages(this.currentUserId, true, false).catch(e => console.warn('reload after scroll failed', e)); } catch(e) { console.warn('reload after scroll schedule failed', e); }
                                    }
                                }, 1500);
                            } catch(e) { /* ignore */ }

                            if (chatContainer.scrollTop <= 60 && this.hasMoreMessages && !this.loadingMessages && this.currentUserId == userId) {
                                // User scrolled to top: trigger a full reload (initial=true) but don't jump to bottom (follow=false)
                                try { if (console && console.debug) console.debug('User scrolled to top - triggering full load (follow=false)'); } catch(e) {}
                                await this.loadMessages(userId, true, false);
                            }

                            // If the user scrolled near the bottom and there are staged messages, apply them
                            try {
                                const nearBottomNow = (chatContainer.scrollHeight - chatContainer.scrollTop - chatContainer.clientHeight) < 150;
                                if (nearBottomNow && this._stagedMessages && this._stagedMessages.length) {
                                    this.applyStagedMessages();
                                }
                            } catch(e) { /* ignore */ }
                        });
                        chatContainer._infiniteScrollAdded = true;
                    }

                    // Media playback listeners: suspend auto-refresh while media is playing and resume when it stops
                    if (!chatContainer._mediaListenersAdded) {
                        const onMediaPlay = (ev) => {
                            try {
                                this._suspendAutoRefresh = true;
                                console.debug('Media play detected, suspending periodic reload');
                            } catch (e) { /* ignore */ }
                        };
                        const onMediaEnd = async (ev) => {
                            try {
                                this._suspendAutoRefresh = false;
                                console.debug('Media paused/ended, resuming periodic reload');
                                if (this._pendingReloadAfterMedia && this.currentUserId) {
                                    this._pendingReloadAfterMedia = false;
                                    await this.loadMessages(this.currentUserId, true, false);
                                }
                            } catch (e) { console.warn('media end handler failed', e); }
                        };
                        chatContainer.addEventListener('play', onMediaPlay, true);
                        chatContainer.addEventListener('pause', onMediaEnd, true);
                        chatContainer.addEventListener('ended', onMediaEnd, true);
                        chatContainer._mediaListenersAdded = true;
                    }
                }
                // Cargar respuestas rápidas y plantillas
                await this.loadQuickReplies();
                // templates loaded into select by loadQuickReplies
                // show template container only if templates exist
                const tplContainer = document.getElementById('templateSelectContainer');
                const tplSelect = document.getElementById('templateSelect');
                const typeSelect = document.getElementById('message-type');
                if (tplContainer && tplSelect && tplSelect.children.length) {
                    // If message-type selector exists, show only when 'template' is selected. Otherwise show (legacy/simple UI).
                    const showTpl = typeSelect ? (typeSelect.value === 'template') : true;
                    tplContainer.style.display = showTpl ? 'inline-block' : 'none';
                }

                // Initialize recording handlers so the mic works even before opening a conversation
                if (typeof this.initRecordingHandlers === 'function') {
                    try { this.initRecordingHandlers(); } catch(e) { console.warn('initRecordingHandlers() failed', e); }
                } else {
                    // fallback: define minimal state
                    this._mediaRecorder = null;
                    this._recordingInterval = null;
                    this._recordingStart = null;
                }
            }

            async loadconversations(userId, showLoading = true) {
                // Compatibilidad: ahora delegamos en loadMessages
                if (showLoading) {
                    document.getElementById('chat-conversations').innerHTML = `
                        <div class="loading">
                            <i class="fas fa-spinner fa-spin"></i> Cargando mensajes...
                        </div>
                    `;
                }

                try {
                    await this.loadMessages(userId, true);
                } catch (error) {
                    console.error('Error loading conversations via loadMessages:', error);
                    document.getElementById('chat-conversations').innerHTML = `
                        <div class="text-center p-4">
                            <i class="fas fa-exclamation-triangle text-warning"></i>
                            <p>Error al cargar los mensajes</p>
                        </div>
                    `;
                }
            }

            /**
             * Cargar mensajes con paginación. Si initial=true, carga el bloque más reciente; si initial=false, carga mensajes anteriores (before=this.earliestMessage).
             */
            async loadMessages(userId, initial = false, follow = true) {
                if (this.loadingMessages) return;
                this.loadingMessages = true;
                // Mark a full load is in progress to avoid race with delta polling
                this._fullLoadInProgress = true;
                const container = document.getElementById('chat-conversations');

                // Guardar posición de scroll y determinar si el usuario estaba en el fondo
                let prevScrollHeight = container ? container.scrollHeight : 0;
                let prevScrollTop = container ? container.scrollTop : 0;
                const wasAtBottom = container ? ((container.scrollHeight - container.scrollTop - container.clientHeight) < 150) : true;

                // indicador top cuando cargamos anteriores
                let topLoader = null;
                if (!initial && container) {
                    container.insertAdjacentHTML('afterbegin', `<div class="loading loading-top" style="text-align:center; padding:8px; font-size:12px;">Cargando mensajes anteriores...</div>`);
                    topLoader = container.querySelector('.loading-top');
                }

                try {
                    // Not sending 'before' param due to unstable server behavior in some cases.
                    // The server-side pagination/fallback and client-side dedupe should handle continuity.
                    // If we prefetched data (e.g. from notification click), use it to avoid double fetch
                    let data = null;
                    if (initial && this._preloadedMessages && this._preloadedMessages.userId == userId && this._preloadedMessages.resp) {
                        data = this._preloadedMessages.resp;
                        // clear preloaded cache
                        this._preloadedMessages = null;
                        if (console && console.debug) console.debug('Using preloaded messages for user', userId);
                    } else {
                        let url = `get_user_messages.php?user_id=${userId}&limit=${this.messageLimit}`;
                        data = await this.apiCall(url);
                    }

                    if (!data) throw new Error('No autorizado o error en la petición');

                    let messages = [];
                    let hasMore = false;
                    let earliest = null;
                    let earliestId = null; // local var for earliest message id
                    if (data && data.success && Array.isArray(data.data)) {
                        messages = data.data;
                        // Filtrar mensajes de tipo 'reaction' para que no aparezcan como elementos en la conversación
                        messages = messages.filter(m => !(m && m.message_type === 'reaction'));
                        hasMore = !!data.has_more;
                        earliest = data.earliest || (messages[0] && messages[0].created_at) || null;
                        earliestId = data.earliest_id || (messages[0] && messages[0].id) || null;
                    } else if (Array.isArray(data)) {
                        messages = data;
                        messages = messages.filter(m => !(m && m.message_type === 'reaction'));
                    }

                    // Filtrar mensajes vacíos (sin texto y sin media) para evitar que aparezcan placeholders "[Mensaje vacío]"
                    if (Array.isArray(messages) && messages.length > 0) {
                        const beforeCount = messages.length;
                        messages = messages.filter(m => {
                            const content = (typeof m.content !== 'undefined' && m.content !== null) ? String(m.content).trim() : '';
                            const hasContent = content.length > 0;
                            const hasMedia = !!(m.local_thumb || m.local_file || m.media_url_external || m.media_url);
                            const nonText = m.message_type && m.message_type !== 'text';
                            if (!hasContent && !hasMedia && !nonText) {
                                // Drop purely empty text message
                                if (console && console.warn) console.warn('Dropping empty message in pagination:', m && (m.id || m.message_id), m && m.created_at);
                                return false;
                            }
                            return true;
                        });
                        const dropped = beforeCount - messages.length;
                        if (dropped > 0) {
                            // Indicar de forma no intrusiva en caso de debug o visibilidad limitada
                            if (typeof showAlert === 'function') {
                                showAlert(dropped + ' mensajes vacíos omitidos', 'warning');
                            } else {
                                if (console && console.info) console.info('Omitidos ' + dropped + ' mensajes vacíos durante paginación');
                            }
                        }
                    }

                    // Guard: cuando paginamos (initial=false) y la respuesta no trae mensajes,
                    // no reemplazar ni limpiar la conversación actual; solo indicar que no hay más.
                    if (!initial && Array.isArray(messages) && messages.length === 0) {
                        try { if (topLoader && topLoader.parentNode) topLoader.remove(); } catch(e) {}
                        // Asegurar que no quedemos en estado de loading
                        this.loadingMessages = false;
                        this.hasMoreMessages = false;
                        // show hint and preserve current view
                        this.showNoMoreMessagesHint();
                        // preserve current view and stop further processing
                        return;
                    }

                    // Si es carga inicial y el servidor devolvió vacío, pero ya teníamos mensajes, preservarlos.
                    if (initial && Array.isArray(messages) && messages.length === 0 && Array.isArray(this.currentMessages) && this.currentMessages.length > 0) {
                        try { if (topLoader && topLoader.parentNode) topLoader.remove(); } catch(e) {}
                        // Ensure container visibility is restored if it was hidden during render
                        try { if (container && container.dataset && container.dataset.renderHidden) { container.style.visibility = 'visible'; delete container.dataset.renderHidden; } } catch(e) {}
                        this.loadingMessages = false;
                        if (typeof showAlert === 'function') showAlert('No se pudieron recargar mensajes; manteniendo historial actual', 'warning');
                        console.warn('Initial load returned empty but existing view contains messages - preserving current conversation');
                        return;
                    }

                    if (initial) {
                        // Reemplazar sólo en carga inicial válida (puede estar vacía si no hay nada en DB)
                        this.currentMessages = messages;
                    } else {
                        // Prepend sólo si hay mensajes nuevos
                        if (Array.isArray(messages) && messages.length > 0) {
                            // Evitar introducir duplicados que ya existen en la vista actual
                            try {
                                const existingKeys = new Set((this.currentMessages || []).map(m => this.messageKey(m)));
                                const beforeLen = messages.length;
                                messages = messages.filter(m => {
                                    const k = this.messageKey(m);
                                    const seen = existingKeys.has(k);
                                    if (!seen) existingKeys.add(k); // mark as seen to avoid duplicates within the batch
                                    else {
                                        if (console && console.debug) console.debug('Skipping duplicate message from server (already in view)', m && (m.id || m.message_id), m && m.created_at);
                                    }
                                    return !seen;
                                });
                                const dropped = beforeLen - messages.length;
                                if (dropped > 0) {
                                    if (console && console.info) console.info(`Omitted ${dropped} messages because they already exist in view`);
                                }
                            } catch (e) { console.warn('duplicate filtering failed', e); }

                            // Prepend mensajes antiguos
                            this.currentMessages = messages.concat(this.currentMessages || []);
                        } else {
                            // No hay mensajes nuevos para añadir; nada que hacer
                        }
                    }

                    // Actualizar paginación (earliest timestamp y id)
                    if (earliest) this.earliestMessage = earliest;
                    if (typeof earliestId !== 'undefined' && earliestId !== null) this.earliestMessageId = earliestId;

                    // Update last/latest message timestamp so we can poll deltas later
                    try {
                        if (Array.isArray(this.currentMessages) && this.currentMessages.length > 0) {
                            const last = this.currentMessages[this.currentMessages.length - 1];
                            if (last && last.created_at) this._latestMessage = last.created_at;
                        }
                    } catch(e) { /* ignore */ }

                    // Improved dedupe: pick the most "readable" message when duplicates exist
                    try {
                        if (!this.currentMessages || this.currentMessages.length === 0) {
                            // Nothing to dedupe
                        } else {
                            const best = new Map(); // key => { index, score }
                            const toRemove = [];

                            const scoreFor = (m) => {
                                let s = 0;
                                if (!m) return s;
                                // Prefer explicit media type messages
                                if (m.message_type && m.message_type !== 'text') s += 50;
                                // Prefer messages with thumbnails or local files
                                if (m.local_thumb || m.local_file || m.media_url_external || m.media_url) s += 30;
                                // Prefer human text content (avoid raw JSON blobs)
                                if (m.content) {
                                    const c = String(m.content).trim();
                                    const looksJson = c.startsWith('{') && c.endsWith('}');
                                    if (looksJson) {
                                        s -= 20; // penalize raw JSON-looking content
                                    } else {
                                        s += 10;
                                        if (c.length < 200) s += 5;
                                    }
                                }
                                if (m.message_id) s += 3;
                                if (m.reply_to_message_id) s += 2;
                                return s;
                            };

                            for (let i = this.currentMessages.length - 1; i >= 0; i--) {
                                const m = this.currentMessages[i];
                                if (!m) continue;
                                const mid = m.message_id || m.id || null;
                                let key = null;
                                if (mid) key = String(mid);
                                else {
                                    const partMedia = m.media_url || m.media_url_external || m.local_file || m.local_thumb || m.content || '';
                                    const ts = m.created_at ? String(Math.floor(new Date(m.created_at).getTime() / 1000)) : '';
                                    key = `${m.direction||'?'}|${m.message_type||m.media_type||'text'}|${partMedia}|${ts}`;
                                }

                                const s = scoreFor(m);
                                if (!best.has(key)) {
                                    best.set(key, { index: i, score: s });
                                } else {
                                    const prev = best.get(key);
                                    if (s > prev.score) {
                                        // keep current, remove previous
                                        toRemove.push(prev.index);
                                        best.set(key, { index: i, score: s });
                                    } else {
                                        // remove current
                                        toRemove.push(i);
                                    }
                                }
                            }

                            if (toRemove.length) {
                                // dedupe unique indices
                                const uniq = Array.from(new Set(toRemove)).sort((a,b) => a - b);
                                // remove from highest index down
                                for (let j = uniq.length - 1; j >= 0; j--) {
                                    const idx = uniq[j];
                                    const removed = this.currentMessages.splice(idx, 1)[0];
                                    console.debug('Dedupe removed message at index', idx, 'removed=', removed && (removed.message_id || removed.id || removed.content || removed.media_url) );
                                }
                            }
                        }
                    } catch (e) {
                        console.warn('Dedupe failed', e);
                    }

                    // Actualizar estado de paginación
                    this.hasMoreMessages = hasMore;
                    // Mostrar o remover el hint "No hay más mensajes anteriores" según corresponda
                    if (this.hasMoreMessages) {
                        this.removeNoMoreMessagesHint();
                    } else {
                        this.showNoMoreMessagesHint();
                    }
                    if (earliest) this.earliestMessage = earliest;

                    // Renderizar incrementalmente para evitar parpadeos y preservar reproducción de media
                    // Si cargamos paginación (initial=false), pasar la cantidad de mensajes recién obtenidos para insertarlos al inicio
                    const newlyFetched = (!initial && Array.isArray(messages)) ? messages.length : 0;
                    if (this.currentMessages && this.currentMessages.length > 0) {
                        this.renderMessagesIncremental(initial, newlyFetched, follow);
                    } else if (initial) {
                        // Si es carga inicial y no tenemos mensajes, mostrar una vista vacía
                        const container = document.getElementById('chat-conversations');
                        if (container) container.innerHTML = `<div class="no-conversation"><i class="fab fa-whatsapp"></i><h4>No hay mensajes</h4><p>Envía un mensaje para iniciar la conversación.</p></div>`;
                    }

                    // Replace media rendering uses window.renderMediaMessage inside element creation/update

                    // Full load finished - unset flag so polling can resume
                    this._fullLoadInProgress = false;

                    if (initial) {
                        // Scroll to bottom SOLO si: (1) follow fue solicitado Y (2) el usuario estaba en el fondo O (3) es el primer mensaje
                        const shouldScrollToBottom = follow && (wasAtBottom || this.currentMessages.length === messages.length);
                        if (shouldScrollToBottom) { 
                            this.scrollToBottom(); 
                        } else {
                            // Preservar la posición relativa del scroll
                            if (container && prevScrollHeight > 0) {
                                const scrollPercentage = prevScrollTop / prevScrollHeight;
                                container.scrollTop = container.scrollHeight * scrollPercentage;
                            }
                        }
                        // Re-show the container (we hid it before rendering to avoid jump-to-top)
                        try {
                            if (container && container.dataset && container.dataset.renderHidden) {
                                container.style.visibility = 'visible';
                                delete container.dataset.renderHidden;
                            }
                        } catch(e) { /* ignore */ }
                    } else if (container) {
                        // Mantener posición: desplazar por la diferencia de heights
                        const newScrollHeight = container.scrollHeight;
                        container.scrollTop = newScrollHeight - prevScrollHeight + prevScrollTop;
                    }

                    // remover loader top si existía
                    if (topLoader && topLoader.parentNode) topLoader.remove();

                    // Marcar como leídos (comportamiento previo: marcar todos los entrantes como leídos al abrir)
                    if (initial) {
                        try {
                            await this.apiCall('mark_conversation_read.php', { method: 'POST', body: { user_id: userId } });
                            // Recargar lista de conversaciones para refrescar contadores
                            await this.loadConversations();
                        } catch (e) {
                            console.error('Error marking conversation as read', e);
                        }
                    }

                } catch (error) {
                    console.error('Error en loadMessages:', error);

                    // Si la carga es paginada (no "initial"), preservamos la vista actual en vez de borrar el DOM.
                    if (!initial) {
                        try { if (topLoader && topLoader.parentNode) topLoader.remove(); } catch (e) {}
                        this.hasMoreMessages = false;
                        // Mostrar hint visual
                        this.showNoMoreMessagesHint();
                        // Notificar de forma no intrusiva
                        if (typeof showAlert === 'function') {
                            showAlert('No se pudieron cargar mensajes anteriores: ' + (error.message || error), 'warning');
                        } else {
                            console.warn('No se pudieron cargar mensajes anteriores:', error);
                        }
                    } else {
                        // En cargas iniciales mostramos el mensaje de error y botón de reintento (comportamiento previo)
                        if (container) {
                            // Make sure it's visible again
                            try { if (container && container.dataset && container.dataset.renderHidden) { container.style.visibility = 'visible'; delete container.dataset.renderHidden; } } catch(e) {}

                            container.innerHTML = `
                                <div class="text-center p-4">
                                    <i class="fas fa-exclamation-triangle text-warning fa-2x"></i>
                                    <p class="mt-3">Error al cargar los mensajes: ${error.message || error}</p>
                                    <button class="btn btn-sm btn-primary" id="retry-load-messages">Reintentar</button>
                                </div>
                            `;
                            const retryBtn = document.getElementById('retry-load-messages');
                            if (retryBtn) retryBtn.addEventListener('click', async () => { await this.loadMessages(userId, true); });
                        }
                    }
                } finally {
                    this.loadingMessages = false;
                    // Ensure the full-load-in-progress flag is cleared even on errors/early returns
                    try { this._fullLoadInProgress = false; } catch(e) { /* noop */ }
                    if (console && console.debug) console.debug('loadMessages completed, _fullLoadInProgress cleared');
                }
            }

            // Incremental rendering to avoid DOM replacement that interrupts media playback or scroll
            renderMessagesIncremental(initial = false, prependCount = 0, follow = true) {
                const container = document.getElementById('chat-conversations');
                if (!container) return;

                // Safety: remove any empty text-only messages that may have slipped into the model
                try {
                    const before = this.currentMessages ? this.currentMessages.length : 0;
                    if (Array.isArray(this.currentMessages) && this.currentMessages.length > 0) {
                        this.currentMessages = this.currentMessages.filter(m => !this.isEmptyMessage(m));
                        const removed = before - this.currentMessages.length;
                        if (removed > 0 && console && console.info) console.info('Removed', removed, 'empty messages before render');
                    }
                } catch (e) { console.warn('Failed to trim empty messages before render', e); }

                if (initial) {
                    // Hide the container while we rebuild the DOM to avoid a visible jump to the top
                    try { container.style.visibility = 'hidden'; container.dataset.renderHidden = '1'; } catch(e) {}
                    container.innerHTML = '';
                }

                // Snapshot scroll metrics to preserve user visual position when updating DOM
                // prevScrollHeight: total height BEFORE we mutate the DOM
                // prevScrollTop: scrollTop BEFORE update (so we can reapply offset)
                const prevScrollHeight = container.scrollHeight;
                const prevScrollTop = container.scrollTop;
                // wasNearBottomBefore: were we following the bottom before update?
                const wasNearBottomBefore = (prevScrollHeight - prevScrollTop - container.clientHeight) < 150;

                // Map existing nodes
                const existing = new Map();
                Array.from(container.querySelectorAll('[data-message-id]')).forEach(el => {
                    existing.set(String(el.dataset.messageId), el);
                });

                // Compute maximum timestamp among existing DOM messages. This helps detect
                // which incoming messages are strictly newer than what the user currently sees.
                let maxExistingTs = 0;
                existing.forEach(el => {
                    try {
                        const d = el.dataset && el.dataset.createdAt ? Date.parse(el.dataset.createdAt) : 0;
                        if (d && d > maxExistingTs) maxExistingTs = d;
                    } catch (e) { /* ignore parse errors */ }
                });

                // helper: format date separators and compare same day
                const sameDay = (a,b) => {
                    try {
                        const da = new Date(a); const db = new Date(b);
                        return da.getFullYear() === db.getFullYear() && da.getMonth() === db.getMonth() && da.getDate() === db.getDate();
                    } catch (e) { return false; }
                };
                const formatDateLabel = (d) => {
                    try {
                        const date = new Date(d);
                        const today = new Date();
                        const y = new Date(); y.setDate(today.getDate()-1);
                        if (sameDay(date, today)) return 'Hoy';
                        if (sameDay(date, y)) return 'Ayer';
                        // within last 7 days -> weekday name
                        const diff = Math.floor((today - date) / (1000*60*60*24));
                        if (diff < 7) return date.toLocaleDateString('es-ES', { weekday: 'long', day: '2-digit', month: '2-digit' });
                        return date.toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit', year: 'numeric' });
                    } catch(e) { return ''; }
                };

                const prependEls = []; // elements to insert at top if we loaded older messages

                // calculate near-bottom late to decide scrolling after DOM updates (avoids stale value)

                for (let i = 0; i < this.currentMessages.length; i++) {
                    const msg = this.currentMessages[i];
                    const mid = String(msg.message_id || msg.id || ('local_' + i));
                    const existingEl = existing.get(mid);

                    // Skip truly empty messages (safety net) and remove any existing placeholder nodes
                    if (this.isEmptyMessage(msg)) {
                        if (existingEl && existingEl.parentNode) {
                            existingEl.parentNode.removeChild(existingEl);
                            existing.delete(mid);
                            if (console && console.info) console.info('Removed empty DOM message', mid);
                        }
                        continue;
                    }

                    if (existingEl) {
                        // update in place
                        try {
                            const rp = existingEl.querySelector('.reply-preview');
                            if (msg.reply_to_message_id) {
                                const previewSrc = (this.currentMessages.find(m => m.message_id == msg.reply_to_message_id) || {}).content || ('Mensaje ' + msg.reply_to_message_id);
                                if (rp) rp.textContent = 'En respuesta a: ' + previewSrc.substring(0,140);
                                else {
                                    const div = document.createElement('div'); div.className = 'reply-preview'; div.textContent = 'En respuesta a: ' + previewSrc.substring(0,140); existingEl.insertBefore(div, existingEl.firstChild);
                                }
                            } else if (rp) rp.remove();

                            const body = existingEl.querySelector('.message-content');
                            if (body) {
                                const mediaPresent = (msg.local_thumb || msg.local_file || msg.media_url_external || msg.media_url);
                                const existingMedia = body.querySelector('audio,video,img');
                                let existingSrc = null;
                                if (existingMedia) existingSrc = existingMedia.getAttribute('src') || existingMedia.getAttribute('data-src');
                                const newMediaUrl = mediaPresent ? (msg.local_thumb || msg.local_file || msg.media_url_external || msg.media_url) : null;

                                // If media url unchanged, keep existing element (preserve playback)
                                if (existingMedia && newMediaUrl && existingSrc && String(existingSrc).includes(newMediaUrl)) {
                                    // nothing to change
                                } else if (existingMedia && newMediaUrl && (!existingSrc || !String(existingSrc).includes(newMediaUrl))) {
                                    // Replace src but preserve playback state for audio/video
                                    try {
                                        const tag = existingMedia.tagName && existingMedia.tagName.toLowerCase();
                                        if (tag === 'audio' || tag === 'video') {
                                            const currentTime = existingMedia.currentTime || 0;
                                            const wasPaused = existingMedia.paused;
                                            existingMedia.src = newMediaUrl;
                                            existingMedia.addEventListener('loadedmetadata', () => {
                                                try {
                                                    if (typeof existingMedia.duration === 'number' && !isNaN(existingMedia.duration)) {
                                                        existingMedia.currentTime = Math.min(currentTime, existingMedia.duration || currentTime);
                                                    }
                                                } catch (e) { /* ignore */ }
                                                try { if (!wasPaused) existingMedia.play().catch(()=>{}); } catch(e){}
                                            }, { once: true });
                                        } else {
                                            // image or other: just replace src
                                            try { existingMedia.src = newMediaUrl; } catch(e) { body.innerHTML = window.renderMediaMessage ? window.renderMediaMessage(msg) : window.escapeHtml(msg.content || '[Mensaje]'); }
                                        }
                                    } catch (e) {
                                        console.warn('preserve media update failed', e);
                                        body.innerHTML = window.renderMediaMessage ? window.renderMediaMessage(msg) : window.escapeHtml(msg.content || '[Mensaje]');
                                    }
                                } else if (!existingMedia && newMediaUrl) {
                                    // No existing media element, render new media block without touching other parts
                                    try {
                                    body.innerHTML = window.renderMediaMessage ? window.renderMediaMessage(msg) : window.escapeHtml(msg.content || '[Mensaje]');
                                    // If after inserting media/text the body appears empty and there's no media element, replace with placeholder
                                    try {
                                        const hasMediaNode = !!body.querySelector && !!body.querySelector('img,video,audio,source,.message-media');
                                        if ((body.textContent || '').trim() === '' && !hasMediaNode) {
                                            body.innerHTML = '<em>(Mensaje sin texto)</em>';
                                            console.warn('Post-update: media/text produced empty body, set placeholder for', msg && (msg.id || msg.message_id));
                                        }
                                    } catch (e) { /* ignore DOM query errors */ }
                                } catch(e) { /* ignore */ }
                                } else if (existingMedia && !newMediaUrl) {
                                    // Media removed in new message: replace body with text/content
                                    try { body.innerHTML = window.escapeHtml(msg.content || '[Mensaje]'); } catch(e) { /* ignore */ }
                                }
                            }

                            const timeEl = existingEl.querySelector('.message-time');
                            if (timeEl && msg.created_at) {
                                const full = new Date(msg.created_at).toLocaleString('es-ES');
                                const short = new Date(msg.created_at).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' });
                                timeEl.textContent = short;
                                timeEl.dataset.full = full;
                                timeEl.dataset.short = short;
                                // preserve a machine-readable timestamp on the element so we can detect newer messages
                                try { if (existingEl) existingEl.dataset.createdAt = msg.created_at; } catch(e) {}
                                if (!timeEl._hasToggleListener) {
                                    timeEl.addEventListener('click', (e) => {
                                        e.stopPropagation();
                                        if (timeEl.dataset._toggled === '1') {
                                            timeEl.textContent = timeEl.dataset.short;
                                            timeEl.dataset._toggled = '0';
                                        } else {
                                            timeEl.textContent = timeEl.dataset.full;
                                            timeEl.dataset._toggled = '1';
                                            setTimeout(() => {
                                                if (timeEl.dataset._toggled === '1') {
                                                    timeEl.textContent = timeEl.dataset.short;
                                                    timeEl.dataset._toggled = '0';
                                                }
                                            }, 4000);
                                        }
                                    });
                                    timeEl._hasToggleListener = true;
                                }
                            } else if (timeEl) {
                                timeEl.textContent = '';
                            }
                            const statusEl = existingEl.querySelector('.message-status');
                            if (statusEl) statusEl.innerHTML = this.getStatusIcon(msg.status);

                            const reactBadge = existingEl.querySelector('.reaction-badge');
                            if (msg.reaction_emoji) {
                                if (reactBadge) reactBadge.textContent = msg.reaction_emoji; else {
                                    const b = document.createElement('div'); b.className = 'reaction-badge'; b.textContent = msg.reaction_emoji; const bubble = existingEl.querySelector('.message-bubble') || existingEl; bubble.insertBefore(b, bubble.querySelector('.message-actions'));
                                }
                            } else if (reactBadge) reactBadge.remove();

                        } catch (e) { console.warn('update message failed', e); }
                        existing.delete(mid);
                    } else {
                        // create new element
                        try {
                            // If this message is strictly newer than anything already in the DOM and the user was
                            // not near the bottom (i.e., reading history), stage it instead of appending to avoid
                            // moving the user's viewport.
                            try {
                                const msgTs = msg.created_at ? Date.parse(msg.created_at) : 0;
                                const isNewerThanExisting = msgTs && (msgTs > maxExistingTs);
                                if (isNewerThanExisting && !wasNearBottomBefore && !this._userScrolling) {
                                    this._stagedMessageIds.add(mid);
                                    this._stagedCount = this._stagedMessageIds.size;
                                    this.showNewMessagesIndicator(this._stagedCount);
                                    // skip creation for now; message will be rendered when user clicks the indicator
                                    continue;
                                }
                            } catch (e) { /* ignore staging errors */ }

                            // insert date separator if this message is the first of a new day
                            const prev = (i>0) ? this.currentMessages[i-1] : null;
                            if (!prev || !sameDay(prev.created_at, msg.created_at)) {
                                const sep = document.createElement('div');
                                sep.className = 'date-sep';
                                sep.textContent = formatDateLabel(msg.created_at || Date.now());
                                // For prepended older messages, collect to insert at top
                                if (prependCount && i < prependCount) prependEls.push(sep);
                                else container.appendChild(sep);
                            }

                            const div = document.createElement('div');
                            div.className = 'message ' + (msg.direction || 'incoming');
                            div.dataset.messageId = mid;
                            div.dataset.createdAt = msg.created_at || '';
                            
                            // Marcar mensajes nuevos como no leídos si es el último mensaje y viene de SSE
                            if (this._nextMessageUnread && i === messagesToRender.length - 1 && msg.direction === 'incoming') {
                                div.classList.add('unread');
                                div.dataset.isNewUnread = 'true';
                            }

                            let replyHtml = '';
                            if (msg.reply_to_message_id) {
                                const target = this.currentMessages.find(m => m.message_id == msg.reply_to_message_id || m.id == msg.reply_to_message_id);
                                const previewText = target ? (target.content || target.message_text || '').substring(0,140) : ('Mensaje ' + msg.reply_to_message_id);
                                replyHtml = `<div class="reply-preview">En respuesta a: ${window.escapeHtml(previewText)}</div>`;
                            }
                            const mediaPresent = (msg.local_thumb || msg.local_file || msg.media_url_external || msg.media_url);

                            // Try to render system-style messages when the message content is a JSON payload
                            let content = '';
                            let isSystemJson = false;
                            try {
                                const raw = (typeof msg.content === 'string') ? msg.content.trim() : '';
                                if (!mediaPresent && raw && (raw.startsWith('{') || raw.startsWith('['))) {
                                    try {
                                        const parsed = JSON.parse(raw);
                                        const isSys = parsed && (parsed.type === 'usersentdocuments' || parsed.system === 'usersentdocuments' || parsed.type === 'attention' || parsed.system === 'attention' || parsed.attention);
                                        if (isSys) {
                                            isSystemJson = true;
                                            const title = parsed.title || parsed.message || parsed.text || parsed.summary || 'Documentos recibidos';
                                            const msgText = parsed.message || parsed.text || parsed.summary || '';
                                            const fileName = parsed.file_name || parsed.filename || parsed.file || null;
                                            const fileUrl = parsed.file_url || parsed.url || null;
                                            const fileSize = parsed.file_size || parsed.size || null;

                                            const parts = [];
                                            parts.push(`<div class="message-template system" style="display:flex; gap:8px; align-items:center">`);
                                            parts.push(`<span class="mt-icon">📎</span>`);
                                            parts.push(`<div style="flex:1">`);
                                            parts.push(`<div style="font-weight:700; margin-bottom:6px">${escapeHtml(String(title))}</div>`);
                                            if (msgText) parts.push(`<div style="font-size:13px; color:#444">${escapeHtml(String(msgText))}</div>`);
                                            if (fileName) {
                                                parts.push(`<div style="margin-top:8px; display:flex; gap:8px; align-items:center;">
                                                    <i class="${getFileIcon(fileName)}"></i>
                                                    <div style="display:flex; flex-direction:column;">
                                                        <div class="document-name">${escapeHtml(fileName)}</div>
                                                        <div class="document-size text-muted" style="font-size:12px">${fileSize ? this.formatFileSize(Number(fileSize)) : ''}</div>
                                                    </div>
                                                </div>`);
                                            }
                                            parts.push(`</div>`);
                                            parts.push(`<div style="margin-left:8px; display:flex; gap:6px; align-items:center">`);
                                            if (fileUrl) {
                                                parts.push(`<a href="${escapeHtml(fileUrl)}" target="_blank" class="btn btn-sm btn-outline-primary">Abrir</a>`);
                                                parts.push(`<button class="btn btn-sm btn-primary download-doc-btn">Descargar</button>`);
                                            }
                                            parts.push(`</div>`);
                                            parts.push(`</div>`);

                                            content = parts.join('');
                                        }
                                    } catch (e) {
                                        // not JSON or parse error — fall back to normal rendering
                                    }
                                }
                            } catch (e) { /* ignore */ }

                            if (!isSystemJson) {
                                content = mediaPresent
                                    ? (window.renderMediaMessage ? window.renderMediaMessage(msg) : (window.formatMessageContent ? window.formatMessageContent(msg.content || '[Mensaje]') : window.escapeHtml(msg.content || '[Mensaje]')))
                                    : (window.formatMessageContent ? window.formatMessageContent(msg.content || '') : window.escapeHtml(msg.content || ''));

                                // If after formatting the content is empty (spaces or nothing), show a clearer placeholder.
                                // But allow pure media-only content (images/audio/video) to render instead of being treated as empty.
                                try {
                                    const tmp = (content || '').replace(/<[^>]*>/g, '').trim(); // strip tags and check
                                    const hasMediaTag = /<(img|audio|video|source|a[^>]*download|div\s+class=["']?message-media)/i.test(content || '');
                                    if (!tmp && !hasMediaTag) {
                                        console.warn('Rendered message content empty, replacing with placeholder', mid, msg.id, msg.created_at);
                                        content = '<em>(Mensaje sin texto)</em>';
                                    }
                                } catch (e) { /* ignore */ }
                            } else {
                                // If it is the system JSON, we may want to highlight it briefly after insertion later
                            }

                            const time = msg.created_at ? new Date(msg.created_at).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' }) : '';
                            const statusIcon = this.getStatusIcon(msg.status);
                            const reactionHtml = msg.reaction_emoji ? `<div class="reaction-badge">${msg.reaction_emoji}</div>` : '';

                            div.innerHTML = `
                                <div class="message-bubble">
                                    ${replyHtml}
                                    <div class="message-content">${content}</div>
                                    ${reactionHtml}
                                    <div class="message-actions mt-1">
                                        <button class="btn btn-sm btn-link" onclick="chat.promptReply('${window.escapeHtml(msg.message_id || msg.id || '')}')" title="Responder"><i class="fas fa-reply"></i></button>
                                        <button class="btn btn-sm btn-link" onclick="chat.openReactionPicker(event, '${window.escapeHtml(msg.message_id || msg.id || '')}')" title="Reaccionar"><i class="far fa-grin"></i></button>
                                        ${(['image','document','video','audio','sticker'].includes(msg.message_type) && msg.direction !== 'outgoing') ? `<button class="btn btn-sm btn-link text-primary" data-msg-id="${msg.id||0}" data-local-file="${msg.local_file||msg.local_thumb||''}" data-conv-id="${chat ? chat.currentConversationId : 0}" onclick="labDomicilio.abrir(+this.dataset.msgId, this.dataset.localFile, +this.dataset.convId)" title="Agendar domicilio"><i class="fas fa-house-medical"></i></button>` : ''}${window._labAddrBtn ? window._labAddrBtn(msg, chat ? chat.currentConversationId : 0) : ''}
                                    </div>
                                    <div class="message-time">${time} ${msg.direction === 'outgoing' ? `<span class="message-status">${statusIcon}</span>` : ''}</div>
                                </div>
                            `;

                            // For prepended older messages, collect to insert at top later
                            if (prependCount && i < prependCount) {
                                prependEls.push(div);
                            } else {
                                container.appendChild(div);
                            }

                            // attach small behavior: allow tapping the time to reveal full date/time briefly
                            try {
                                const timeEl = div.querySelector('.message-time');
                                if (timeEl && msg.created_at) {
                                    const full = new Date(msg.created_at).toLocaleString('es-ES');
                                    const short = new Date(msg.created_at).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' });
                                    timeEl.dataset.full = full;
                                    timeEl.dataset.short = short;
                                    timeEl.addEventListener('click', (e) => {
                                        e.stopPropagation();
                                        if (timeEl.dataset._toggled === '1') {
                                            timeEl.textContent = timeEl.dataset.short;
                                            timeEl.dataset._toggled = '0';
                                        } else {
                                            timeEl.textContent = timeEl.dataset.full;
                                            timeEl.dataset._toggled = '1';
                                            setTimeout(() => {
                                                if (timeEl.dataset._toggled === '1') {
                                                    timeEl.textContent = timeEl.dataset.short;
                                                    timeEl.dataset._toggled = '0';
                                                }
                                            }, 4000);
                                        }
                                    });
                                }
                            } catch (e) { /* ignore */ }

                            // Attach handlers for system-like messages rendered from JSON (view/download buttons + highlight)
                            try {
                                const raw = (typeof msg.content === 'string') ? msg.content.trim() : '';
                                if (raw && (raw.startsWith('{') || raw.startsWith('['))) {
                                    const parsed = JSON.parse(raw);
                                    const isSys = parsed && (parsed.type === 'usersentdocuments' || parsed.system === 'usersentdocuments' || parsed.type === 'attention' || parsed.system === 'attention' || parsed.attention);
                                    if (isSys) {
                                        const bubble = div.querySelector('.message-template.system') || div.querySelector('.message-template');
                                        if (bubble) {
                                            const viewBtn = bubble.querySelector('.view-docs-btn');
                                            const downloadBtn = bubble.querySelector('.download-doc-btn');
                                            const fileUrl = parsed.file_url || parsed.url || null;
                                            const userId = parsed.user_id || parsed.data && parsed.data.user_id || null;
                                            
                                            if (downloadBtn) downloadBtn.addEventListener('click', () => {
                                                if (fileUrl) {
                                                    const a = document.createElement('a'); a.href = fileUrl; a.download = parsed.file_name || ''; document.body.appendChild(a); a.click(); a.remove();
                                                }
                                            });

                                            // brief visual highlight
                                            bubble.classList.add('system-highlight');
                                            setTimeout(() => bubble.classList.remove('system-highlight'), 4200);
                                        }
                                    }
                                }
                            } catch (e) { /* ignore */ }
                        } catch (e) { console.warn('create message failed', e); }
                    }
                }

                // insert prepended elements (keep order)
                if (prependEls.length) {
                    const frag = document.createDocumentFragment();
                    prependEls.forEach(e => frag.appendChild(e));
                    container.insertBefore(frag, container.firstChild);
                }

                // remove any remaining old elements that weren't updated
                existing.forEach((el) => el.remove());

                // After DOM changes, compute new heights and preserve user's visual position when appropriate
                const newScrollHeight = container.scrollHeight;
                const scrollDelta = newScrollHeight - prevScrollHeight; // positive when content grew

                if (initial) {
                    if (follow) {
                        // Initial load and follow requested: show latest messages
                        this.scrollToBottom();
                    } else {
                        // Initial load but DO NOT follow: preserve visual offset like we do for non-initial updates
                        if (!wasNearBottomBefore && !this._userScrolling) {
                            const newTop = Math.max(0, prevScrollTop + scrollDelta);
                            container.scrollTop = newTop;
                        } else {
                            const nearBottomNow = (newScrollHeight - container.scrollTop - container.clientHeight) < 150;
                            if (nearBottomNow && !this._userScrolling) {
                                this.scrollToBottom();
                            }
                        }
                    }
                } else {
                    // If the user was reading history (not near bottom before) and is not actively scrolling,
                    // keep the viewport anchored to the same messages by adjusting scrollTop by the delta.
                    if (!wasNearBottomBefore && !this._userScrolling) {
                        // Keep the same visual offset (don't jump)
                        const newTop = Math.max(0, prevScrollTop + scrollDelta);
                        container.scrollTop = newTop;
                    } else {
                        // If we were near bottom and still not actively scrolling, follow new messages
                        const nearBottomNow = (newScrollHeight - container.scrollTop - container.clientHeight) < 150;
                        if (nearBottomNow && !this._userScrolling) {
                            this.scrollToBottom();
                        }
                        // Otherwise (user actively scrolling) do nothing and let user's action control view
                    }
                }


            }

            // Mostrar / quitar hint "No hay más mensajes anteriores"
            showNoMoreMessagesHint() {
                try {
                    const container = document.getElementById('chat-conversations');
                    if (!container) return;
                    if (container.querySelector('.no-more-messages-hint')) return; // ya existe
                    const hint = document.createElement('div');
                    hint.className = 'no-more-messages-hint text-center text-muted';
                    hint.style.fontSize = '12px';
                    hint.style.padding = '6px';
                    hint.textContent = 'No hay más mensajes anteriores';
                    container.insertBefore(hint, container.firstChild);
                } catch (e) { console.warn('showNoMoreMessagesHint failed', e); }
            }

            removeNoMoreMessagesHint() {
                try {
                    const container = document.getElementById('chat-conversations');
                    if (!container) return;
                    const el = container.querySelector('.no-more-messages-hint');
                    if (el && el.parentNode) el.parentNode.removeChild(el);
                } catch (e) { console.warn('removeNoMoreMessagesHint failed', e); }
            }

            // Helper: generate stable key for a message to detect duplicates
            messageKey(m) {
                if (!m) return '';
                const mid = m.message_id || m.id || '';
                if (mid) return String(mid);
                const partMedia = m.media_url || m.media_url_external || m.local_file || m.local_thumb || (m.content || '');
                const ts = m.created_at ? String(Math.floor(new Date(m.created_at).getTime() / 1000)) : '';
                return `${m.direction||'?'}|${m.message_type||m.media_type||'text'}|${partMedia}|${ts}`;
            }

            // Helper: determine if a message is effectively empty (no text, no media, and is text type)
            isEmptyMessage(m) {
                if (!m) return true;
                const content = (typeof m.content !== 'undefined' && m.content !== null) ? String(m.content).trim() : '';
                const hasContent = content.length > 0;
                const hasMedia = !!(m.local_thumb || m.local_file || m.media_url_external || m.media_url);
                const nonText = m.message_type && m.message_type !== 'text';
                return !(hasContent || hasMedia || nonText);
            }

            // Helper: detect active media playback or recording to avoid interrupting with reloads
            isMediaActive() {
                // Recording in progress -> active
                if (this._mediaRecorder && this._mediaRecorder.state === 'recording') return true;
                // Explicit suspend flag
                if (this._suspendAutoRefresh) return true;
                try {
                    const container = document.getElementById('chat-conversations');
                    if (!container) return false;
                    const mediaEls = container.querySelectorAll('audio,video');
                    for (const el of mediaEls) {
                        if (!el.paused && !el.ended) return true;
                        if (el.readyState > 2 && !el.paused) return true;
                    }
                } catch (e) {
                    console.warn('isMediaActive: failed to inspect media elements', e);
                }
                return false;
            }

            // Initialize recording handlers (so mic works before openConversation) and helper to manage recording state
            initRecordingHandlers() {
                if (this._recordingHandlersInitialized) return;
                this._recordingHandlersInitialized = true;
                this._mediaRecorder = null;
                this._recordingInterval = null;
                this._recordingStart = null;

                this.startRecording = async function() {
                    try {
                        // Verificar que el navegador soporta MediaDevices
                        if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
                            throw new Error('Tu navegador no soporta grabación de audio');
                        }

                        // Suspend periodic reload while recording
                        this._suspendAutoRefresh = true;
                        
                        // Intentar obtener acceso al micrófono con diferentes configuraciones
                        let stream = null;
                        try {
                            // Intento 1: Configuración básica sin especificar dispositivo
                            stream = await navigator.mediaDevices.getUserMedia({ 
                                audio: {
                                    echoCancellation: true,
                                    noiseSuppression: true,
                                    autoGainControl: true
                                }
                            });
                        } catch (err1) {
                            console.warn('Intento 1 falló, probando configuración más simple:', err1.message);
                            try {
                                // Intento 2: Configuración minimalista
                                stream = await navigator.mediaDevices.getUserMedia({ audio: true });
                            } catch (err2) {
                                console.error('Todos los intentos fallaron');
                                throw err2;
                            }
                        }
                        
                        if (!stream) {
                            throw new Error('No se pudo obtener acceso al micrófono');
                        }
                        
                        this._mediaRecorder = new MediaRecorder(stream);
                        const chunks = [];
                        this._mediaRecorder.ondataavailable = (e) => { if (e.data && e.data.size) chunks.push(e.data); };
                        this._mediaRecorder.onstop = async () => {
                            const blob = new Blob(chunks, { type: 'audio/webm' });
                            let file = new File([blob], `record_${Date.now()}.webm`, { type: 'audio/webm' });
                            // Try convert if FFmpeg available
                            try {
                                if (window && window.ensureFFmpeg) {
                                    await window.ensureFFmpeg();
                                    const converted = await window.convertWebmToOgg(file);
                                    file = converted;
                                }
                            } catch (convErr) { console.warn('conversion failed', convErr); }

                            // set as selected file and show preview
                            this.selectedFile = file;
                            // Marcar como grabación de voz para enviar como nota de voz (ptt=true)
                            this._isVoiceRecording = true;
                            this.showMediaPreview(file);
                            // restore mic UI to default (not recording)
                            try {
                                const micEl2 = document.getElementById('mic-btn');
                                if (micEl2) {
                                    micEl2.innerHTML = micEl2.dataset._wa_prev || '<i class="fas fa-microphone"></i>';
                                    micEl2.title = 'Grabar audio';
                                    micEl2.classList.remove('recording-cancel');
                                }
                            } catch(e) { /* ignore */ }
                            // Clear recorder reference to indicate stopped
                            this._mediaRecorder = null;

                            // Resume auto-refresh and trigger a reload if one was pending
                            try {
                                this._suspendAutoRefresh = false;
                                if ((this._pendingReloadAfterMedia || this._pendingReloadAfterScroll) && this.currentUserId) {
                                    this._pendingReloadAfterMedia = false;
                                    this._pendingReloadAfterScroll = false;
                                    await this.loadMessages(this.currentUserId, true, false);
                                }
                            } catch (e) { console.warn('Failed to resume reload after recording', e); }
                        };

                        this._mediaRecorder.start();
                        this._recordingStart = Date.now();
                        const ind = document.getElementById('recording-indicator'); if (ind) ind.style.display = 'block';
                        const micEl = document.getElementById('mic-btn'); if (micEl) {
                            // Show cancel on same button
                            micEl.dataset._wa_prev = micEl.innerHTML;
                            micEl.innerHTML = '<i class="fas fa-times"></i>';
                            micEl.title = 'Cancelar';
                            micEl.classList.add('recording');
                            micEl.classList.add('recording-cancel');
                        }
                        // Ensure UI shows mic and hides send while recording
                        if (this._updateSendMicVisibility) this._updateSendMicVisibility();
                        const update = () => {
                            if (!this._recordingStart) return;
                            const elapsed = Math.floor((Date.now() - this._recordingStart) / 1000);
                            const m = Math.floor(elapsed/60); const s = elapsed % 60;
                            const rt = document.getElementById('recording-time'); if (rt) rt.textContent = `${m}:${String(s).padStart(2,'0')}`;
                        };
                        update();
                        this._recordingInterval = setInterval(update, 1000);
                    } catch (err) {
                        console.error('startRecording error', err.name, err.message);
                        
                        // Mensajes de error más específicos
                        let errorMsg = 'No se pudo acceder al micrófono';
                        
                        if (err.name === 'NotFoundError') {
                            errorMsg = 'No se encontró ningún micrófono. Conecta un micrófono e intenta de nuevo.';
                        } else if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
                            errorMsg = 'Permiso denegado. Permite el acceso al micrófono en la configuración del navegador.';
                        } else if (err.name === 'NotReadableError') {
                            errorMsg = 'El micrófono está siendo usado por otra aplicación.';
                        } else if (err.message) {
                            errorMsg += ': ' + err.message;
                        }
                        
                        alert(errorMsg);
                        
                        // Limpiar estado
                        this._suspendAutoRefresh = false;
                        const ind = document.getElementById('recording-indicator'); 
                        if (ind) ind.style.display = 'none';
                        const micEl = document.getElementById('mic-btn'); 
                        if (micEl) micEl.classList.remove('recording', 'recording-cancel');
                    }
                };

                this.stopRecording = function() {
                    if (this._mediaRecorder && this._mediaRecorder.state !== 'inactive') {
                        this._mediaRecorder.stop();
                    }
                    if (this._recordingInterval) clearInterval(this._recordingInterval);
                    this._recordingInterval = null;
                    this._recordingStart = null;
                    const ind = document.getElementById('recording-indicator'); if (ind) ind.style.display = 'none';
                    const micEl = document.getElementById('mic-btn'); if (micEl) micEl.classList.remove('recording');
                    if (this._updateSendMicVisibility) this._updateSendMicVisibility();

                    // Ensure we resume periodic reloads and trigger pending reload
                    try {
                        this._suspendAutoRefresh = false;
                        if ((this._pendingReloadAfterMedia || this._pendingReloadAfterScroll) && this.currentUserId) {
                            this._pendingReloadAfterMedia = false;
                            this._pendingReloadAfterScroll = false;
                            this.loadMessages(this.currentUserId, true, false).catch(e => console.warn('Reload after stopRecording failed', e));
                        }
                    } catch (e) { console.warn('stopRecording resume failed', e); }
                };

                this.cancelRecording = function() {
                    if (this._mediaRecorder && this._mediaRecorder.state !== 'inactive') {
                        this._mediaRecorder.stop();
                    }
                    this._mediaRecorder = null;
                    this.selectedFile = null;
                    if (this._recordingInterval) clearInterval(this._recordingInterval);
                    this._recordingInterval = null;
                    this._recordingStart = null;
                    const ind = document.getElementById('recording-indicator'); if (ind) ind.style.display = 'none';
                    const micEl = document.getElementById('mic-btn'); if (micEl) {
                        micEl.classList.remove('recording');
                        micEl.classList.remove('recording-cancel');
                        micEl.innerHTML = micEl.dataset._wa_prev || '<i class="fas fa-microphone"></i>';
                        micEl.title = 'Grabar audio';
                    }
                    // hide delete button if present
                    const delBtn = document.getElementById('delete-file-btn'); if (delBtn) delBtn.style.display = 'none';
                    if (this._updateSendMicVisibility) this._updateSendMicVisibility();

                    // Resume auto-refresh if it was suspended and trigger pending reload
                    try {
                        this._suspendAutoRefresh = false;
                        if ((this._pendingReloadAfterMedia || this._pendingReloadAfterScroll) && this.currentUserId) {
                            this._pendingReloadAfterMedia = false;
                            this._pendingReloadAfterScroll = false;
                            this.loadMessages(this.currentUserId, true, false).catch(e => console.warn('Reload after cancelRecording failed', e));
                        }
                    } catch (e) { console.warn('cancelRecording resume failed', e); }
                };

                return true;
            }

// --- New messages indicator helpers ---
            showNewMessagesIndicator(count) {
                try {
                    if (this._applyingStaged) return; // avoid toggling while applying
                    const container = document.getElementById('chat-area') || document.body;
                    let el = document.getElementById('new-messages-indicator');
                    if (!el) {
                        el = document.createElement('div');
                        el.id = 'new-messages-indicator';
                        el.style.position = 'absolute';
                        el.style.left = '50%';
                        el.style.transform = 'translateX(-50%)';
                        el.style.bottom = '84px';
                        el.style.zIndex = '1500';
                        el.className = 'btn btn-success';
                        el.style.padding = '10px 18px';
                        el.style.borderRadius = '24px';
                        el.style.boxShadow = '0 6px 20px rgba(37, 211, 102, 0.35)';
                        el.style.cursor = 'pointer';
                        el.style.fontWeight = '600';
                        el.style.fontSize = '14px';
                        el.innerHTML = '<i class="fas fa-arrow-down me-2"></i>Nuevo mensaje';
                        el.onclick = async () => {
                            try {
                                console.log('🔄 Clic en indicador de nuevos mensajes');
                                
                                // Mostrar loading en el botón
                                el.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Cargando...';
                                el.style.pointerEvents = 'none';
                                
                                // Limpiar staged messages
                                this._stagedMessages = [];
                                if (this._stagedMessageIds) this._stagedMessageIds.clear();
                                this._stagedCount = 0;
                                
                                // Suspender auto-refresh para evitar conflictos
                                this._suspendAutoRefresh = true;
                                
                                if (this.currentUserId) {
                                    // Reset loading flag para permitir la carga
                                    this.loadingMessages = false;
                                    
                                    // Cargar mensajes
                                    await this.loadMessages(this.currentUserId, true, true);
                                    
                                    // Scroll al fondo
                                    this.scrollToBottom();
                                    console.log('✅ Mensajes cargados correctamente');
                                }
                                
                                // Ocultar indicador
                                this.hideNewMessagesIndicator();
                                
                            } catch (e) {
                                console.error('❌ Error al cargar mensajes:', e);
                                // En caso de error, intentar ocultar el indicador y restaurar estado
                                this.hideNewMessagesIndicator();
                            } finally {
                                this._suspendAutoRefresh = false;
                            }
                        };
                        // subtle fade-in
                        el.style.opacity = '0';
                        el.style.transition = 'opacity 220ms ease, transform 220ms ease';
                        container.appendChild(el);
                        setTimeout(() => { try { el.style.opacity = '1'; el.style.transform = 'translateX(-50%) translateY(-6px)'; } catch(e) {} }, 20);
                    }
                    const c = (typeof count === 'number') ? count : (this._stagedMessages ? this._stagedMessages.length : 0);
                    el.innerHTML = (c && c > 1) 
                        ? `<i class="fas fa-arrow-down me-2"></i>Nuevos mensajes (${c})` 
                        : '<i class="fas fa-arrow-down me-2"></i>Nuevo mensaje';
                    el.style.display = 'inline-block';
                    el.style.pointerEvents = 'auto'; // Restaurar por si estaba deshabilitado
                } catch (e) { console.warn('showNewMessagesIndicator failed', e); }
            }

            hideNewMessagesIndicator() {
                try {
                    // remove any instances of the indicator (handle duplicates)
                    const els = document.querySelectorAll('#new-messages-indicator');
                    els.forEach(el => { try { if (el && el.parentNode) el.parentNode.removeChild(el); } catch(e){} });
                } catch (e) { /* ignore */ }
            }
            
            _removeUnreadHighlights() {
                try {
                    console.log('🎨 Quitando highlights de mensajes no leídos');
                    const unreadMessages = document.querySelectorAll('.message.unread');
                    unreadMessages.forEach(msg => {
                        msg.classList.remove('unread');
                        delete msg.dataset.isNewUnread;
                    });
                    // Reset flag
                    this._nextMessageUnread = false;
                } catch (e) {
                    console.warn('Error quitando highlights:', e);
                }
            }

            applyStagedMessages() {
                try {
                    // If nothing to apply, ensure indicator is gone
                    if (!this._stagedMessages || this._stagedMessages.length === 0) {
                        this.hideNewMessagesIndicator();
                        return;
                    }

                    // Mark as applying so we don't re-show indicator mid-apply
                    this._applyingStaged = true;

                    // Immediately hide indicator to provide instant feedback
                    this.hideNewMessagesIndicator();

                    const toApplyCount = this._stagedMessages.length;
                    console.log('✅ applyStagedMessages: aplicando', toApplyCount, 'mensajes');
                    
                    // ENFOQUE SEGURO: En lugar de renderizar incrementalmente,
                    // simplemente recargar los mensajes desde el servidor.
                    // Esto es más seguro y evita problemas de renderizado.
                    const userId = this.currentUserId;
                    
                    // Limpiar staged messages primero
                    this._stagedMessages = [];
                    if (this._stagedMessageIds) this._stagedMessageIds.clear();
                    this._stagedCount = 0;
                    
                    // Recargar mensajes de forma segura
                    if (userId) {
                        this.loadingMessages = false; // Reset flag para permitir la carga
                        this.loadMessages(userId, true, true).then(() => {
                            console.log('✅ Mensajes recargados después de aplicar staged');
                            this.scrollToBottom();
                        }).catch(err => {
                            console.error('❌ Error recargando mensajes:', err);
                            // Fallback: intentar scroll al fondo de lo que ya hay
                            this.scrollToBottom();
                        });
                    }

                } catch (e) {
                    console.warn('applyStagedMessages failed', e);
                } finally {
                    // allow indicator to show again for future arrivals after short delay
                    setTimeout(() => { this._applyingStaged = false; }, 500);
                }
            }

            getStatusIcon(status) {
                switch (status) {
                    case 'sent': return '<i class="fas fa-check status-sent"></i>';
                    case 'delivered': return '<i class="fas fa-check-double status-delivered"></i>';
                    case 'read': return '<i class="fas fa-check-double status-read"></i>';
                    default: return '<i class="fas fa-clock status-sent"></i>';
                }
            }

            // Update reaction badge for a single message in the DOM
            updateMessageReactionInView(messageId, emoji) {
                try {
                    const container = document.getElementById('chat-conversations');
                    if (!container) return;
                    const el = container.querySelector(`[data-message-id="${messageId}"]`);
                    if (!el) return;
                    let badge = el.querySelector('.reaction-badge');
                    if (emoji) {
                        if (badge) {
                            badge.textContent = emoji;
                        } else {
                            const div = document.createElement('div');
                            div.className = 'reaction-badge';
                            div.textContent = emoji;
                            const bubble = el.querySelector('.message-bubble');
                            if (bubble) bubble.insertBefore(div, bubble.querySelector('.message-actions'));
                        }
                    } else {
                        if (badge && badge.parentNode) badge.parentNode.removeChild(badge);
                    }
                } catch (e) {
                    console.warn('updateMessageReactionInView failed', e);
                }
            }
            
            // ========== FUNCIONES DE RECORDATORIO ==========
            
            async showReminderModal() {
                console.log('🔵 showReminderModal - currentUserId:', this.currentUserId);
                console.log('📋 conversations:', this.conversations);
                
                if (!this.currentUserId) {
                    alert('Selecciona una conversación primero');
                    return;
                }
                
                // Buscar usuario en conversaciones - probar múltiples formas
                let user = this.conversations.find(c => c.id == this.currentUserId);
                if (!user) {
                    user = this.conversations.find(c => c.user_id == this.currentUserId);
                }
                
                console.log('👤 Usuario encontrado:', user);
                
                if (user) {
                    const userName = user.name || user.user_name || 'Sin nombre';
                    const phoneNumber = user.phone_number || user.phone || 'Sin teléfono';
                    document.getElementById('reminder-user-name').value = `${userName} (${phoneNumber})`;
                    console.log('✅ Usuario establecido:', userName, phoneNumber);
                } else {
                    console.error('❌ Usuario no encontrado en conversaciones');
                    // Intentar obtener desde el DOM
                    const nameEl = document.getElementById('chat-name-text') || document.getElementById('chat-name');
                    const phoneEl = document.getElementById('chat-phone');
                    if (nameEl && phoneEl) {
                        document.getElementById('reminder-user-name').value = `${nameEl.textContent} (${phoneEl.textContent})`;
                        console.log('✅ Usuario obtenido del DOM');
                    } else {
                        document.getElementById('reminder-user-name').value = 'Usuario actual';
                        console.warn('⚠️ No se pudo obtener nombre del usuario');
                    }
                }
                
                // Establecer fecha mínima (hoy)
                const today = new Date().toISOString().split('T')[0];
                document.getElementById('reminder-date').setAttribute('min', today);
                document.getElementById('reminder-date').value = today;
                
                // Cargar plantillas aprobadas
                await this.loadReminderTemplates();
                
                // Mostrar modal
                const modal = new bootstrap.Modal(document.getElementById('reminderModal'));
                modal.show();
            }
            
            async loadReminderTemplates() {
                try {
                    const response = await this.apiCall('get_templates.php?approved_only=1');
                    const select = document.getElementById('reminder-template');
                    
                    if (response && response.success && response.data) {
                        select.innerHTML = '<option value="">Seleccionar plantilla...</option>';
                        response.data.forEach(template => {
                            const option = document.createElement('option');
                            option.value = template.id;
                            option.textContent = `${template.name} (${template.language_code})`;
                            option.dataset.templateId = template.id;
                            option.dataset.templateName = template.template_name;
                            option.dataset.language = template.language_code;
                            option.dataset.bodyText = template.body_text || '';
                            // Detectar variables tanto numéricas como con nombres
                            option.dataset.hasVariables = template.body_text && /\{\{[^\}]+\}\}/.test(template.body_text);
                            select.appendChild(option);
                        });
                    }
                } catch (error) {
                    console.error('Error loading templates:', error);
                }
            }
            
            async handleReminderTemplateChange() {
                const select = document.getElementById('reminder-template');
                const selectedOption = select.options[select.selectedIndex];
                
                console.log('🔵 handleReminderTemplateChange llamado');
                console.log('📋 Option seleccionada:', selectedOption);
                
                if (!selectedOption || !selectedOption.value) {
                    document.getElementById('reminder-variables-container').style.display = 'none';
                    document.getElementById('reminder-preview').style.display = 'none';
                    return;
                }
                
                const hasVariables = selectedOption.dataset.hasVariables === 'true';
                const bodyText = selectedOption.dataset.bodyText || '';
                
                console.log('🔍 hasVariables:', hasVariables);
                console.log('📝 bodyText:', bodyText);
                
                if (hasVariables) {
                    // Usar get_template_details.php para obtener variables con sus metadatos
                    try {
                        const templateId = selectedOption.dataset.templateId;
                        const detailsResp = await this.apiCall(`get_template_details.php?id=${templateId}`);
                        
                        if (detailsResp && detailsResp.success && detailsResp.template) {
                            const variables = detailsResp.template.variables || [];
                            console.log('✅ Variables obtenidas de API:', variables);
                            
                            // Generar campos
                            const fieldsContainer = document.getElementById('reminder-variables-fields');
                            fieldsContainer.innerHTML = '';
                            
                            variables.forEach(v => {
                                const div = document.createElement('div');
                                div.className = 'mb-2';
                                div.innerHTML = `
                                    <label class="form-label small">${v.label}</label>
                                    <input type="text" 
                                           class="form-control form-control-sm reminder-variable-input" 
                                           data-index="${v.index}"
                                           data-placeholder="${v.placeholder || ''}"
                                           placeholder="${v.example || 'Ingrese valor...'}"
                                           required>
                                `;
                                fieldsContainer.appendChild(div);
                            });
                            
                            // Event listeners para actualizar preview
                            fieldsContainer.querySelectorAll('.reminder-variable-input').forEach(input => {
                                input.addEventListener('input', () => this.updateReminderPreview());
                            });
                        }
                    } catch (error) {
                        console.error('Error obteniendo detalles de plantilla:', error);
                    }
                    
                    document.getElementById('reminder-variables-container').style.display = 'block';
                } else {
                    document.getElementById('reminder-variables-container').style.display = 'none';
                }
                
                // Actualizar preview
                this.updateReminderPreview();
            }
            
            updateReminderPreview() {
                const select = document.getElementById('reminder-template');
                const selectedOption = select.options[select.selectedIndex];
                
                if (!selectedOption || !selectedOption.value) {
                    document.getElementById('reminder-preview').style.display = 'none';
                    return;
                }
                
                let bodyText = selectedOption.dataset.bodyText || '';
                const hasVariables = selectedOption.dataset.hasVariables === 'true';
                
                if (hasVariables) {
                    const inputs = document.querySelectorAll('.reminder-variable-input');
                    inputs.forEach(input => {
                        const index = input.dataset.index;
                        const value = input.value || `{{${index}}}`;
                        bodyText = bodyText.replace(new RegExp(`\\{\\{${index}\\}\\}`, 'g'), value);
                    });
                }
                
                const previewContainer = document.getElementById('reminder-preview-content');
                previewContainer.innerHTML = bodyText.replace(/\n/g, '<br>');
                document.getElementById('reminder-preview').style.display = 'block';
            }
            
            async saveReminder() {
                if (!this.currentUserId) return;
                
                const date = document.getElementById('reminder-date').value;
                const time = document.getElementById('reminder-time').value;
                const select = document.getElementById('reminder-template');
                const selectedOption = select.options[select.selectedIndex];
                
                if (!date || !time) {
                    alert('Por favor completa fecha y hora');
                    return;
                }
                
                if (!selectedOption || !selectedOption.value) {
                    alert('Por favor selecciona una plantilla');
                    return;
                }
                
                // Recopilar parámetros si hay variables
                const hasVariables = selectedOption.dataset.hasVariables === 'true' || selectedOption.dataset.hasVariables === true;
                console.log('🔍 hasVariables:', hasVariables, 'tipo:', typeof selectedOption.dataset.hasVariables);
                let parameters = null;
                
                if (hasVariables) {
                    const inputs = document.querySelectorAll('.reminder-variable-input');
                    console.log('📝 Inputs encontrados:', inputs.length);
                    
                    if (inputs.length === 0) {
                        console.log('⚠️ No hay inputs de variables');
                    } else {
                        // Detectar tipo de variables usando el placeholder del primer input
                        const firstInput = inputs[0];
                        const placeholder = firstInput?.dataset?.placeholder || '';
                        const isNumericVar = /^\{\{\d+\}\}$/.test(placeholder);
                        
                        console.log('🔍 Detectando tipo de variables en recordatorio:');
                        console.log('   - Placeholder ejemplo:', placeholder);
                        console.log('   - Es numérica?', isNumericVar);
                        
                        let allFilled = true;
                        
                        if (isNumericVar) {
                            // Variables numéricas: crear array ordenado
                            const tempObj = {};
                            inputs.forEach(input => {
                                const value = input.value.trim();
                                const index = parseInt(input.dataset.index || input.dataset.var);
                                if (!value) {
                                    allFilled = false;
                                    input.classList.add('is-invalid');
                                } else {
                                    input.classList.remove('is-invalid');
                                    tempObj[index] = value;
                                }
                            });
                            
                            // Convertir a array ordenado
                            const sortedKeys = Object.keys(tempObj).map(Number).sort((a, b) => a - b);
                            parameters = sortedKeys.map(key => tempObj[key]);
                            console.log('📊 Enviando como array ordenado:', parameters);
                        } else {
                            // Variables con nombres: crear objeto
                            parameters = {};
                            inputs.forEach(input => {
                                const value = input.value.trim();
                                const placeholder = input.dataset.placeholder || '';
                                const varName = placeholder.replace(/\{\{|\}\}/g, '');
                                
                                if (!value) {
                                    allFilled = false;
                                    input.classList.add('is-invalid');
                                } else {
                                    input.classList.remove('is-invalid');
                                    parameters[varName] = value;
                                }
                            });
                            console.log('📦 Enviando como objeto con nombres:', parameters);
                        }
                        
                        if (!allFilled) {
                            alert('Por favor completa todas las variables');
                            return;
                        }
                    }
                } else {
                    console.log('⚠️ Plantilla sin variables');
                }
                
                const templateId = selectedOption.dataset.templateId;
                const templateName = selectedOption.dataset.templateName;
                const language = selectedOption.dataset.language;
                
                const payload = {
                    user_id: parseInt(this.currentUserId),
                    message_type: 'template',
                    template_id: parseInt(templateId),
                    template_name: templateName,
                    template_language: language,
                    template_parameters: parameters,
                    scheduled_date: date,
                    scheduled_time: time
                };
                
                console.log('💾 Guardando recordatorio:', payload);
                console.log('📊 Parámetros detalle:', {
                    esNull: parameters === null,
                    esArray: Array.isArray(parameters),
                    longitud: parameters ? parameters.length : 0,
                    valores: parameters
                });
                
                try {
                    const response = await this.apiCall('schedule_message.php', {
                        body: payload
                    });
                    
                    console.log('✅ Respuesta del servidor:', response);
                    
                    if (response && response.success) {
                        alert('✅ Recordatorio programado exitosamente');
                        bootstrap.Modal.getInstance(document.getElementById('reminderModal')).hide();
                        
                        // Limpiar formulario
                        document.getElementById('reminder-template').value = '';
                        document.getElementById('reminder-variables-container').style.display = 'none';
                        document.getElementById('reminder-preview').style.display = 'none';
                    } else {
                        alert('Error: ' + (response.error || 'Error desconocido'));
                    }
                } catch (error) {
                    console.error('Error saving reminder:', error);
                    alert('Error al programar recordatorio: ' + error.message);
                }
            }

            async loadQuickReplies() {
                const panel = document.getElementById('quick-replies-panel');
                if (!panel) return;
                // Clear both lists
                const qrList = panel.querySelector('.qr-list');
                const tplList = panel.querySelector('.tpl-list');
                const qrCount = panel.querySelector('#qr-count');
                const tplCount = panel.querySelector('#tpl-count');
                if (qrList) qrList.innerHTML = '';
                if (tplList) tplList.innerHTML = '';
                if (qrCount) qrCount.textContent = '0';
                if (tplCount) tplCount.textContent = '0';

                try {
                    // Load autoresponses (text quick replies)
                    const respText = await this.apiCall('get_autoresponses.php');
                    if (respText && respText.success && Array.isArray(respText.data)) {
                        let added = 0;
                        respText.data.forEach(r => {
                            if (!r.is_active) return;
                            const btn = document.createElement('button');
                            btn.className = 'btn btn-sm btn-outline-primary';
                            let text = (r.response_text || '').replace(/\n/g,' ');
                            text = this.stripEmoji(text);
                            btn.textContent = text.length > 30 ? text.substring(0,30) + '...' : text;
                            btn.title = r.response_text;
                            btn.addEventListener('click', () => {
                                // send as text message and close panel
                                this.sendQuickReply(r.response_text);
                                panel.style.display = 'none';
                                document.getElementById('quick-replies-toggle').setAttribute('aria-expanded','false');
                            });
                            if (qrList) qrList.appendChild(btn);
                            added++;
                        });
                        if (qrCount) qrCount.textContent = String(added);
                    }
                } catch (e) {
                    console.error('Error loading quick replies (text)', e);
                }

                try {
                    const resp = await this.apiCall('get_templates.php?approved_only=1&limit=30');
                    if (resp && resp.success && Array.isArray(resp.data)) {
                        let added = 0;
                        resp.data.forEach(t => {
                            const btn = document.createElement('button');
                            btn.className = 'btn btn-sm btn-outline-secondary';
                            let text = (t.body_text || t.name || t.template_name || '').replace(/\n/g,' ');
                            text = this.stripEmoji(text);
                            btn.textContent = text.length > 30 ? text.substring(0,30) + '...' : text;
                            btn.title = t.name;
                            btn.addEventListener('click', () => { this.sendTemplateQuick(t.template_name, t.language_code || 'es'); panel.style.display='none'; document.getElementById('quick-replies-toggle').setAttribute('aria-expanded','false'); });
                            if (tplList) tplList.appendChild(btn);
                            added++;
                        });
                        if (tplCount) tplCount.textContent = String(added);
                    }
                } catch (e) {
                    console.error('Error loading quick replies (templates)', e);
                }

                // Also populate the template select
                try { await loadTemplatesInto('#templateSelect'); } catch(e){console.warn('Could not load templates into select', e);} 

                // Show a small hint if there are no quick replies and no templates
                const empty = panel.querySelector('#qr-empty');
                const total = (qrList ? qrList.children.length : 0) + (tplList ? tplList.children.length : 0);
                if (empty) empty.style.display = total ? 'none' : 'block';

                // Trigger an initial filter (useful when search has an active value)
                const sq = panel.querySelector('#quick-replies-search'); if (sq) sq.dispatchEvent(new Event('input'));

            }

            async sendTemplateQuick(templateName, language) {
                console.log('🔵 sendTemplateQuick:', templateName, language);
                // Primero, cargar detalles de la plantilla para verificar si tiene variables
                try {
                    // Buscar el template_id desde la caché o hacer una petición
                    const templatesResp = await this.apiCall('get_templates.php?approved_only=1');
                    const template = templatesResp.data.find(t => t.template_name === templateName);
                    
                    if (!template) {
                        throw new Error('Plantilla no encontrada');
                    }
                    
                    console.log('📋 Template encontrado:', template);
                    
                    // Verificar si tiene variables (tanto numéricas como con nombres)
                    const hasVariables = template.body_text && /\{\{[^\}]+\}\}/.test(template.body_text);
                    console.log('🔍 Tiene variables?', hasVariables);
                    
                    if (hasVariables) {
                        // Mostrar modal para pedir variables
                        await this.showTemplateVariablesModal(template);
                    } else {
                        // Enviar directamente sin variables
                        await this.sendTemplateMessage(templateName, language, null);
                    }
                } catch (error) {
                    console.error('❌ Error en sendTemplateQuick:', error);
                    alert('Error al cargar plantilla: ' + error.message);
                }
            }

            async showTemplateVariablesModal(template) {
                console.log('🔵 showTemplateVariablesModal:', template);
                
                // Guardar template actual para usarlo al enviar
                this.currentTemplate = template;
                
                // Establecer nombre de plantilla en modal
                document.getElementById('template-modal-name').textContent = template.name || template.template_name;
                
                try {
                    // Usar get_template_details.php para obtener variables con metadatos
                    const detailsResp = await this.apiCall(`get_template_details.php?id=${template.id}`);
                    
                    if (!detailsResp || !detailsResp.success) {
                        throw new Error('No se pudieron cargar los detalles de la plantilla');
                    }
                    
                    const variables = detailsResp.template.variables || [];
                    console.log('📝 Variables obtenidas:', variables);
                    
                    // Guardar template completo con variables para usarlo después
                    this.currentTemplate = { ...template, variables: variables };
                    
                    // Generar campos de entrada
                    const container = document.getElementById('template-variables-container');
                    let html = '';
                    
                    variables.forEach((variable) => {
                        html += `
                            <div class="mb-3">
                                <label class="form-label"><strong>${variable.label}</strong>
                                    ${variable.example ? `<small class="text-muted">(ej: ${variable.example})</small>` : ''}
                                </label>
                                <input 
                                    type="text" 
                                    class="form-control template-var-input" 
                                    data-var="${variable.index}"
                                    data-placeholder="${variable.placeholder}"
                                    placeholder="${variable.example || 'Ingrese valor...'}"
                                    onkeyup="window.chatApp.updateTemplatePreview()"
                                    required
                                >
                            </div>
                        `;
                    });
                    
                    container.innerHTML = html;
                    
                    // Generar vista previa inicial
                    this.updateTemplatePreview();
                    
                    // Mostrar modal
                    const modal = new bootstrap.Modal(document.getElementById('templateVariablesModal'));
                    modal.show();
                } catch (error) {
                    console.error('❌ Error cargando detalles de plantilla:', error);
                    alert('Error al cargar plantilla: ' + error.message);
                }
            }

            async updateTemplatePreview() {
                if (!this.currentTemplate) return;
                
                const previewContainer = document.getElementById('template-preview');
                const previewContent = document.getElementById('template-preview-content');
                
                // Recopilar parámetros
                const inputs = document.querySelectorAll('.template-var-input');
                const parameters = [];
                
                inputs.forEach(input => {
                    const varIndex = parseInt(input.dataset.var) - 1;
                    parameters[varIndex] = input.value || '';
                });
                
                try {
                    // Usar preview_template.php para renderizar correctamente
                    const response = await this.apiCall('preview_template.php', {
                        body: {
                            template_id: this.currentTemplate.id,
                            parameters: parameters
                        }
                    });
                    
                    if (response && response.success && response.preview) {
                        previewContent.innerHTML = response.preview.html || response.preview.body.replace(/\n/g, '<br>');
                        previewContainer.style.display = 'block';
                    }
                } catch (error) {
                    console.error('Error generando preview:', error);
                    // Fallback: mostrar body_text sin procesar
                    previewContent.innerHTML = this.currentTemplate.body_text.replace(/\n/g, '<br>');
                    previewContainer.style.display = 'block';
                }
            }

            async sendTemplateWithVariables() {
                console.log('🔵 sendTemplateWithVariables');
                
                if (!this.currentTemplate) {
                    alert('No hay plantilla seleccionada');
                    return;
                }
                
                // Recopilar variables
                const inputs = document.querySelectorAll('.template-var-input');
                let allFilled = true;
                
                // Detectar si son variables numéricas o con nombres usando el placeholder
                const firstInput = inputs[0];
                const placeholder = firstInput?.dataset?.placeholder || '';
                // Si el placeholder es {{1}}, {{2}}, etc. -> numéricas
                // Si es {{nombre_tema}}, {{fecha}}, etc. -> con nombres
                const isNumericVar = /^\{\{\d+\}\}$/.test(placeholder);
                
                console.log('🔍 Detectando tipo de variables:');
                console.log('   - Placeholder ejemplo:', placeholder);
                console.log('   - Es numérica?', isNumericVar);
                
                let parameters;
                if (isNumericVar) {
                    // Variables numéricas {{1}}, {{2}} - usar array
                    parameters = [];
                    inputs.forEach(input => {
                        const value = input.value.trim();
                        if (!value) {
                            allFilled = false;
                            input.classList.add('is-invalid');
                        } else {
                            input.classList.remove('is-invalid');
                            parameters.push(value);
                        }
                    });
                    console.log('📊 Enviando como array:', parameters);
                } else {
                    // Variables con nombres {{fecha}}, {{motivo}} - usar objeto
                    parameters = {};
                    inputs.forEach(input => {
                        const value = input.value.trim();
                        const placeholder = input.dataset.placeholder || '';
                        // Extraer nombre de {{fecha}} -> "fecha"
                        const varName = placeholder.replace(/\{\{|\}\}/g, '');
                        
                        if (!value) {
                            allFilled = false;
                            input.classList.add('is-invalid');
                        } else {
                            input.classList.remove('is-invalid');
                            parameters[varName] = value;
                        }
                    });
                    console.log('📦 Enviando como objeto:', parameters);
                }
                
                if (!allFilled) {
                    alert('Por favor completa todas las variables');
                    return;
                }
                
                console.log('📝 Parámetros finales:', parameters);
                
                // Cerrar modal
                const modal = bootstrap.Modal.getInstance(document.getElementById('templateVariablesModal'));
                if (modal) modal.hide();
                
                // Enviar plantilla con parámetros
                await this.sendTemplateMessage(
                    this.currentTemplate.template_name,
                    this.currentTemplate.language_code || 'es',
                    parameters
                );
                
                // Limpiar template actual
                this.currentTemplate = null;
            }

            async sendTemplateMessage(templateName, language = 'es', parameters = null) {
                if (!this.currentUserId) return;
                const recipient = this.getConversationPhone(this.currentUserId);
                if (!recipient) {
                    alert('No se pudo determinar el número de destino para la plantilla');
                    console.error('sendTemplateMessage: missing recipient for user', this.currentUserId);
                    return;
                }

                try {
                    // Build parameters: support NAMED parameters for certain templates
                    let paramsPayload = parameters || [];

                    // If the template expects a named parameter `name`, provide it using user name or phone
                    if (templateName === 'contacto_nuevo') {
                        // Try to find conversation info
                        let conv = this.conversations.find(c => c.user_id === this.currentUserId) || {};
                        let resolvedName = (conv.name || conv.full_name || '').trim();
                        if (!resolvedName) {
                            const nameEl = document.getElementById('chat-name-text') || document.getElementById('chat-name');
                            if (nameEl && nameEl.textContent) resolvedName = nameEl.textContent.trim();
                        }

                        // If no resolvedName and parameters don't include a name, prompt the operator for the name
                        const hasNameInParams = (paramsPayload && typeof paramsPayload === 'object' && (paramsPayload.name || (Array.isArray(paramsPayload) && paramsPayload.some(p => p.parameter_name === 'name' || p.name === 'name'))));
                        if (!resolvedName && !hasNameInParams) {
                            const inputName = prompt('Ingrese el nombre del paciente para la plantilla (dejar vacío para usar el número):');
                            if (inputName === null) {
                                // user cancelled
                                return;
                            }
                            resolvedName = inputName.trim() || '';
                        }

                        if (!resolvedName) resolvedName = recipient; // fallback to phone number

                        // Use an associative object so backend treats it as NAMED parameter
                        paramsPayload = { name: resolvedName };
                    }

                    const requestBody = {
                        recipient: recipient,
                        type: 'template',
                        template: templateName,
                        language: language || 'es',
                        parameters: paramsPayload
                    };

                    const resp = await this.apiCall('send_message.php', { body: requestBody });
                    // hide reply preview if any
                    this.hideReplyPreview();
                    if (resp && resp.success) {
                        const replyToTemplate = document.getElementById('message-input').dataset.replyTo || null;
                        this.addMessageToView(`Plantilla: ${templateName}`, 'outgoing', { reply_to_message_id: replyToTemplate });
                        typeof showAlert !== 'undefined' && showAlert('Mensaje de plantilla enviado correctamente', 'success');
                        await this.loadconversations(this.currentUserId, false);
                        // SSE actualizará las conversaciones automáticamente
                        // await this.loadConversations();
                    } else {
                        throw new Error(resp && resp.error ? resp.error : 'Error enviando plantilla');
                    }
                } catch (e) {
                    console.error('Error sending template message', e);
                    alert('Error enviando plantilla: ' + (e.message || e));
                }
            }

            async sendQuickReply(text) {
                if (!text) return;
                if (!this.currentUserId) return;
                
                const input = document.getElementById('message-input');
                if (!input) return;
                
                // No copiar al input para preservar saltos de línea
                // Enviar directamente usando la API
                input.disabled = true;
                const sendBtn = document.getElementById('send-btn');
                if (sendBtn) sendBtn.disabled = true;
                
                try {
                    const recipient = this.getConversationPhone(this.currentUserId);
                    if (!recipient) {
                        alert('No se pudo determinar el número de destino');
                        return;
                    }
                    
                    const requestBody = {
                        recipient: recipient,
                        type: 'text',
                        message: text
                    };
                    
                    const result = await this.apiCall('send_message.php', { body: requestBody });
                    
                    if (result && result.success) {
                        // Mostrar inmediatamente en la vista
                        this.addMessageToView(text, 'outgoing');
                        typeof showAlert !== 'undefined' && showAlert('Mensaje enviado correctamente', 'success');
                        // Recargar mensajes en background
                        this.loadconversations(this.currentUserId, false).catch(e=>console.warn(e));
                    } else {
                        throw new Error(result && result.error ? result.error : 'Error desconocido');
                    }
                } catch (error) {
                    console.error('Error sending quick reply:', error);
                    alert('Error al enviar respuesta rápida: ' + (error.message || error));
                } finally {
                    input.disabled = false;
                    if (sendBtn) sendBtn.disabled = false;
                    input.focus();
                }
            }

            scrollToBottom() {
                const container = document.getElementById('chat-conversations');
                container.scrollTop = container.scrollHeight;
            }

            _showCopyToast(bubble) {
                const existing = bubble.querySelector('.copy-feedback');
                if (existing) existing.remove();
                const tip = document.createElement('span');
                tip.className = 'copy-feedback';
                tip.textContent = '✓ Copiado';
                bubble.appendChild(tip);
                setTimeout(() => { if (tip.parentNode) tip.remove(); }, 1800);
            }

            // Insertar mensaje saliente en la vista inmediatamente (simular envío)
            addMessageToView(content, type = 'outgoing', options = {}) {
                const msg = {
                    message_id: 'local_' + Date.now(),
                    direction: type === 'outgoing' ? 'outgoing' : 'incoming',
                    content: content,
                    created_at: new Date().toISOString(),
                    status: 'sent'
                };
                // Soportar reply preview inmediato
                if (options && options.reply_to_message_id) {
                    msg.reply_to_message_id = options.reply_to_message_id;
                }
                // Soportar attachments/meta si es necesario
                if (options && options.message_type) {
                    msg.message_type = options.message_type;
                }

                this.currentMessages.push(msg);
                this.renderMessagesIncremental();
                this.scrollToBottom();
            }

            async showReplyPreview(messageId, type = 'reply') {
                try {
                    const preview = document.getElementById('reply-preview');
                    const previewText = document.getElementById('reply-preview-text');
                    const cancelBtn = document.getElementById('cancel-reply-btn');
                    let msg = this.currentMessages.find(m => m.message_id == messageId || m.id == messageId);

                    const setPreviewFromMsg = (m) => {
                        const text = m ? (m.content || m.message_text || (m.caption || '[Mensaje]')) : ('Mensaje ' + messageId);
                        if (preview && previewText) {
                            if (type === 'reply') previewText.textContent = 'En respuesta a: ' + (String(text).replace(/\n/g,' ')).substring(0,140);
                            else if (type === 'forward') previewText.textContent = 'Reenviando: ' + (String(text).replace(/\n/g,' ')).substring(0,140);
                            preview.style.display = 'flex';
                        }
                    };

                    if (!msg) {
                        // intentar obtener mensaje por API
                        try {
                            const resp = await fetch(`api/get_message.php?message_id=${encodeURIComponent(messageId)}`, { cache: 'no-store' });
                            if (resp.ok) {
                                const j = await resp.json();
                                if (j && j.success && j.data) {
                                    msg = j.data;
                                }
                            }
                        } catch (err) {
                            console.warn('Could not fetch referenced message', err);
                        }
                    }

                    setPreviewFromMsg(msg);

                    if (cancelBtn) {
                        cancelBtn.onclick = () => this.hideReplyPreview();
                    }
                } catch (e) {
                    console.warn('showReplyPreview failed', e);
                }
            }

            hideReplyPreview() {
                try {
                    const preview = document.getElementById('reply-preview');
                    if (preview) preview.style.display = 'none';
                    const input = document.getElementById('message-input');
                    if (input) {
                        input.dataset.replyTo = null;
                        input.dataset.forwardTo = null;
                    }
                } catch (e) {
                    console.warn('hideReplyPreview failed', e);
                }
            }

            async sendMessage() {
                // If a file is selected (media preview active), delegate to sendMediaMessage to avoid duplicate sends
                if (this.selectedFile) {
                    await this.sendMediaMessage();
                    return;
                }

                const input = document.getElementById('message-input');
                const message = input.value.trim();
                const messageType = document.getElementById('message-type') ? document.getElementById('message-type').value : 'text';
                
                if (!message && messageType === 'text') return;
                if (!this.currentUserId) return;
                
                // Deshabilitar input
                input.disabled = true;
                document.getElementById('send-btn').disabled = true;
                
                try {
                    const replyTo = input.dataset.replyTo || null;

                    if (messageType === 'template') {
                        const template = document.getElementById('templateSelect').value;
                        if (!template) {
                            alert('Seleccione una plantilla');
                            return;
                        }

                        const recipient = this.getConversationPhone(this.currentUserId);
                        if (!recipient) {
                            alert('No se pudo determinar el número de destino para la plantilla');
                            console.error('sendMessage(template): missing recipient for user', this.currentUserId);
                            return;
                        }

                        // send template via helper (match chat_window format)
                        const templateName = template;
                        const language = (document.getElementById('templateSelect').selectedOptions[0] ? document.getElementById('templateSelect').selectedOptions[0].dataset.language : 'es');
                        await this.sendTemplateMessage(templateName, language, null);
                    } else {
                        // send text message using recipient (phone number) to match chat_window behavior
                        const recipient = this.getConversationPhone(this.currentUserId);
                        if (!recipient) {
                            alert('No se pudo determinar el número de destino');
                            console.error('sendMessage(text): missing recipient for user', this.currentUserId);
                            return;
                        }

                        // prepare request body similar to chat_window
                        const requestBody = {
                            recipient: recipient,
                            type: 'text',
                            message: message
                        };
                        if (replyTo) requestBody.reply_to = replyTo;

                        try {
                            const result = await this.apiCall('send_message.php', { body: requestBody });
                            // Clear reply data attribute
                            input.dataset.replyTo = null;
                            this.hideReplyPreview();

                            if (result && result.success) {
                                input.value = '';
                                // Mostrar inmediatamente en la vista
                                this.addMessageToView(message, 'outgoing', { reply_to_message_id: replyTo });
                                typeof showAlert !== 'undefined' && showAlert('Mensaje enviado correctamente', 'success');
                                // Recargar mensajes y conversaciones en background
                                this.loadconversations(this.currentUserId, false).catch(e=>console.warn(e));
                                // SSE actualizará las conversaciones automáticamente
                                // this.loadConversations().catch(e=>console.warn(e));
                                // loadQuickReplies ya se cargó al abrir la conversación, no es necesario recargar después de cada mensaje
                            } else {
                                throw new Error(result && result.error ? result.error : 'Error desconocido');
                            }
                        } catch (err) {
                            console.error('Error sending text message', err);
                            alert('Error al enviar mensaje: ' + (err.message || err));
                        }
                    }
                } catch (error) {
                    console.error('Error sending message:', error);
                    alert('Error al enviar mensaje: ' + (error.message||error));
                } finally {
                    input.disabled = false;
                    document.getElementById('send-btn').disabled = false;
                    input.focus();
                    // Ensure send/mic visibility is recalculated (restore mic when input empty)
                    try { if (typeof this._updateSendMicVisibility === 'function') this._updateSendMicVisibility(); } catch (e) { console.warn('updateSendMicVisibility failed', e); }
                }
            }

            // loadQuickReplies is implemented earlier (combined autoresponses & templates)
            // kept here as a no-op to avoid accidentally overriding the real implementation.

            async promptEditUser() {
                const nameEl = document.getElementById('chat-name-text') || document.getElementById('chat-name');
                const currentName = nameEl ? nameEl.textContent.trim() : '';
                const newName = prompt('Nombre del usuario:', currentName);
                if (!newName) return;

                try {
                    const resp = await fetch('api/update_user.php', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ user_id: this.currentUserId, name: newName }),
                        cache: 'no-store'
                    });
                    const json = await resp.json();
                    if (json && json.success) {
                        if (nameEl) nameEl.textContent = newName;
                        this.showSuccess && this.showSuccess('Usuario actualizado');
                        this.loadConversations();
                    } else {
                        this.showError && this.showError('No se pudo actualizar usuario');
                    }
                } catch (err) {
                    console.error(err);
                    this.showError && this.showError('Error actualizando usuario');
                }
            }

            async deleteCurrentConversation() {
                if (!confirm('¿Eliminar esta conversación? Se borrará todo el historial.')) return;
                try {
                    const resp = await fetch('api/delete_conversation.php', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ user_id: this.currentUserId }),
                        cache: 'no-store'
                    });
                    const json = await resp.json();
                    if (json && json.success) {
                        this.showSuccess('Conversación eliminada');
                        // reset UI
                        document.getElementById('chat-area').style.display = 'none';
                        document.getElementById('no-conversation').style.display = 'block';
                        this.currentUserId = null;
                        this.loadConversations();
                    } else {
                        this.showError('No se pudo eliminar la conversación');
                    }
                } catch (err) {
                    console.error(err);
                    this.showError('Error eliminando la conversación');
                }
            }

            async deleteCurrentConversation() {
                if (!confirm('¿Eliminar esta conversación? Se borrará todo el historial.')) return;
                try {
                    const resp = await fetch('api/delete_conversation.php', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ user_id: this.currentUserId }),
                        cache: 'no-store'
                    });
                    const json = await resp.json();
                    if (json && json.success) {
                        this.showSuccess('Conversación eliminada');
                        // reset UI
                        document.getElementById('chat-area').style.display = 'none';
                        document.getElementById('no-conversation').style.display = 'block';
                        this.currentUserId = null;
                        this.loadConversations();
                    } else {
                        this.showError('No se pudo eliminar la conversación');
                    }
                } catch (err) {
                    console.error(err);
                    this.showError('Error eliminando la conversación');
                }
            }

            // ========== SOLICITAR ARCHIVO GRANDE ==========

            async requestLargeFile() {
                if (!this.currentUserId) {
                    showAlert('Selecciona una conversación primero', 'warning');
                    return;
                }

                const phoneNumber = this.getConversationPhone(this.currentUserId);
                if (!phoneNumber) {
                    showAlert('No se encontró el número de teléfono del cliente', 'danger');
                    return;
                }

                // Confirmar con el operador
                const userName = document.getElementById('chat-name-text')?.textContent?.trim() || phoneNumber;
                if (!confirm(`¿Enviar enlace de carga de archivos grandes a ${userName}?\n\nSe enviará un mensaje por WhatsApp con un enlace seguro donde el cliente podrá subir archivos de hasta 50 MB.`)) {
                    return;
                }

                // Deshabilitar botones mientras se procesa
                const headerBtn = document.getElementById('request-large-file-btn');
                if (headerBtn) {
                    headerBtn.disabled = true;
                    headerBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
                }

                try {
                    const response = await fetch('api/request_large_file.php', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        credentials: 'same-origin',
                        body: JSON.stringify({
                            user_id: this.currentUserId,
                            phone_number: phoneNumber,
                        }),
                        cache: 'no-store',
                    });

                    const result = await response.json();

                    if (result && result.success) {
                        showAlert('✅ Enlace de carga enviado al cliente por WhatsApp', 'success');
                        console.log('📎 File request created:', result);

                        // Agregar mensaje visual al chat con texto más detallado
                        this.addMessageToView(
                            `📎 Enlace para enviar archivos grandes\n\n` +
                            `Hola.\n\n` +
                            `Le enviamos este enlace seguro para que pueda subir archivos de gran tamaño (hasta 50 MB):\n\n` +
                            `👉 ${result.upload_url}\n\n` +
                            `✅ Puede subir imágenes o documentos.\n` +
                            `🔒 El enlace es seguro y exclusivo para usted.\n` +
                            `⏰ Válido por 24 horas.\n\n` +
                            `Si tiene alguna duda, estamos aquí para ayudarle.`,
                            'outgoing',
                            { message_type: 'text' }
                        );

                        // Recargar mensajes después de un breve delay
                        setTimeout(() => {
                            this.loadMessages(this.currentUserId, false, true);
                        }, 2000);

                    } else {
                        showAlert('Error: ' + (result.error || 'No se pudo enviar el enlace'), 'danger');
                    }
                } catch (error) {
                    console.error('Error en requestLargeFile:', error);
                    showAlert('Error al enviar la solicitud: ' + (error.message || error), 'danger');
                } finally {
                    if (headerBtn) {
                        headerBtn.disabled = false;
                        headerBtn.innerHTML = '<i class="fas fa-cloud-upload-alt"></i>';
                    }
                }
            }

            async getFileRequests() {
                if (!this.currentUserId) return [];
                try {
                    const resp = await fetch(`api/get_file_requests.php?user_id=${this.currentUserId}&status=active`, {
                        credentials: 'same-origin',
                        cache: 'no-store',
                    });
                    const data = await resp.json();
                    return data.success ? (data.uploads || []) : [];
                } catch (e) {
                    console.warn('Error loading file requests:', e);
                    return [];
                }
            }

            async searchConversations(query) {
                console.log('🔍 Buscando conversaciones:', query);
                
                // Limpiar búsqueda si el query está vacío
                if (!query || query.trim() === '') {
                    // Recargar conversaciones normales
                    await this.loadConversations(1, false);
                    return;
                }
                
                // Buscar en la base de datos
                try {
                    const response = await this.apiCall(`get_conversations.php?search=${encodeURIComponent(query.trim())}`);
                    
                    if (response && response.success) {
                        this.conversations = response.data || [];
                        this.currentPage = 1;
                        this.hasMore = false; // Desactivar paginación en búsqueda
                        this.renderConversations();
                        
                        console.log(`✅ Búsqueda completada: ${this.conversations.length} resultados`);
                        
                        if (this.conversations.length === 0) {
                            const container = document.getElementById('conversation-list');
                            container.innerHTML = `
                                <div style="padding: 20px; text-align: center; color: #999;">
                                    <i class="fas fa-search" style="font-size: 48px; margin-bottom: 10px; opacity: 0.3;"></i>
                                    <p>No se encontraron conversaciones con "${query}"</p>
                                    <small>Intenta buscar por nombre, teléfono o contenido del mensaje</small>
                                </div>
                            `;
                        }
                    } else {
                        console.error('Error en búsqueda:', response);
                        // Fallback a búsqueda local si falla el servidor
                        this.searchConversationsLocal(query);
                    }
                } catch (error) {
                    console.error('Error buscando conversaciones:', error);
                    // Fallback a búsqueda local
                    this.searchConversationsLocal(query);
                }
            }
            
            // Búsqueda local (fallback) - solo filtra las conversaciones ya cargadas
            searchConversationsLocal(query) {
                const items = document.querySelectorAll('.conversation-item');
                items.forEach(item => {
                    const name = item.querySelector('.conversation-name').textContent.toLowerCase();
                    const preview = item.querySelector('.conversation-preview').textContent.toLowerCase();
                    const matches = name.includes(query.toLowerCase()) || preview.includes(query.toLowerCase());
                    item.style.display = matches ? 'flex' : 'none';
                });
            }
            
            // ========== FUNCIONES MULTIMEDIA ==========
            
            handleFileSelect(event) {
                const file = event.target.files[0];
                if (!file) return;
                
                // NO es grabación de voz (es archivo subido)
                this._isVoiceRecording = false;
                
                // Validar tamaño
                const maxSize = this.getMaxFileSize(file.type);
                if (file.size > maxSize) {
                    alert(`El archivo es demasiado grande. Máximo: ${(maxSize / 1024 / 1024).toFixed(0)}MB`);
                    return;
                }
                
                // Mostrar preview
                this.showMediaPreview(file);
            }
            
            getMaxFileSize(fileType) {
                if (fileType.startsWith('image/')) return 5 * 1024 * 1024; // 5MB
                if (fileType.startsWith('application/')) return 100 * 1024 * 1024; // 100MB
                return 16 * 1024 * 1024; // 16MB para video/audio
            }
            
            showMediaPreview(file) {
                const preview = document.getElementById('media-preview');
                const thumbnail = document.getElementById('preview-thumbnail');
                const filename = document.getElementById('preview-filename');
                const filesize = document.getElementById('preview-filesize');
                
                // Establecer información del archivo
                filename.textContent = file.name;
                filesize.textContent = this.formatFileSize(file.size);
                
                // Mostrar thumbnail para imágenes
                if (file.type.startsWith('image/')) {
                    const reader = new FileReader();
                    reader.onload = (e) => {
                        thumbnail.src = e.target.result;
                        thumbnail.style.display = 'block';
                    };
                    reader.readAsDataURL(file);
                } else {
                    thumbnail.style.display = 'none';
                }
                
                // Guardar archivo temporalmente
                this.selectedFile = file;
                
                // Mostrar preview
                preview.style.display = 'block';
                
                // Show delete button next to send
                const delBtn = document.getElementById('delete-file-btn');
                if (delBtn) {
                    delBtn.style.display = 'inline-flex';
                    delBtn.onclick = () => { this.cancelMediaUpload(); };
                }

                // Use the main send handler: `sendMessage` will delegate to media send when a file is selected
                const sendBtn = document.getElementById('send-btn');
                // ensure send button is enabled
                if (sendBtn) sendBtn.disabled = false;
                // Update mic/send visibility (hide mic when a file is selected)
                if (this._updateSendMicVisibility) this._updateSendMicVisibility();
            }
            
            formatFileSize(bytes) {
                if (bytes === 0) return '0 Bytes';
                const k = 1024;
                const sizes = ['Bytes', 'KB', 'MB', 'GB'];
                const i = Math.floor(Math.log(bytes) / Math.log(k));
                return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
            }

            // Remove common emoji/pictograph characters for minimal quick reply labels
            stripEmoji(text) {
                if (!text) return '';
                try {
                    return String(text).replace(/[\u{1F300}-\u{1FAFF}\u{1F600}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu, '').trim();
                } catch (e) {
                    return String(text);
                }
            }
            
            async sendMediaMessage() {
                if (!this.selectedFile || !this.currentUserId) return;
                
                const caption = document.getElementById('media-caption').value.trim();
                const sendBtn = document.getElementById('send-btn');
                const attachBtn = document.getElementById('attach-btn');
                
                // Deshabilitar botones
                sendBtn.disabled = true;
                attachBtn.disabled = true;
                sendBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
                
                try {
                    // 1. Subir archivo
                    const formData = new FormData();
                    formData.append('file', this.selectedFile);
                    
                    const uploadResponse = await fetch('api/upload_media.php', {
                        method: 'POST',
                        credentials: 'same-origin', // Enviar cookies de sesión
                        body: formData
                    });
                    const uploadContentType = uploadResponse.headers.get('content-type') || '';
                    let uploadResult;
                    if (uploadContentType.indexOf('application/json') !== -1) {
                        uploadResult = await uploadResponse.json();
                    } else {
                        const text = await uploadResponse.text();
                        console.error('upload_media returned non-JSON response:\n', text);
                        throw new Error('Respuesta inválida de upload_media.php. Ver consola para más detalles.');
                    }

                    if (!uploadResult.success) {
                        throw new Error(uploadResult.error || 'Error subiendo archivo');
                    }

                    // 2. Enviar mensaje con el archivo
                    const phone = this.getConversationPhone(this.currentUserId);

                    if (!phone) {
                        throw new Error('No se encontró el número de teléfono del usuario');
                    }

                    const sendResponse = await fetch('api/send_media_message.php', {
                        method: 'POST',
                        credentials: 'same-origin', // Enviar cookies de sesión
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({
                            recipient: phone,
                            media_url: uploadResult.data.url,
                            media_type: uploadResult.data.type,
                            caption: caption || null,
                            filename: this.selectedFile.name,
                            // Para audio: is_voice = true si fue grabado desde micrófono (nota de voz)
                            // is_voice = false si fue subido como archivo (audio normal)
                            is_voice: (uploadResult.data.type === 'audio' && this._isVoiceRecording) ? true : false
                        })
                    });

                    const sendContentType = sendResponse.headers.get('content-type') || '';
                    let sendResult = null;

                    // Leer el body una sola vez para evitar error "body stream already read"
                    const sendResponseText = await sendResponse.text();
                    
                    if (sendContentType.indexOf('application/json') !== -1) {
                        try {
                            sendResult = JSON.parse(sendResponseText);
                        } catch (err) {
                            // Content-Type claims JSON but parsing failed: attempt to extract JSON from body
                            console.warn('send_media_message: Content-Type JSON but parse failed. Response body will be inspected for JSON.');
                            console.warn(sendResponseText);
                            const m = sendResponseText.match(/(\{[\s\S]*\})/);
                            if (m) {
                                try { sendResult = JSON.parse(m[1]); console.warn('send_media_message: extracted JSON from response.'); } catch (e) { console.warn('send_media_message: extracted JSON parse failed', e); }
                            }
                            if (!sendResult) {
                                // If HTTP status is OK, be forgiving: treat as success but log for investigation
                                if (sendResponse.ok) {
                                    console.warn('send_media_message: non-parseable JSON returned but HTTP ok; treating as success.');
                                    sendResult = { success: true };
                                } else {
                                    throw new Error('Respuesta JSON inválida de send_media_message.php. Ver consola para más detalles.');
                                }
                            }
                        }
                    } else {
                        // Non-JSON content-type: try to find embedded JSON; if HTTP 200, be forgiving
                        console.warn('send_media_message returned non-JSON response:');
                        console.warn(sendResponseText.slice(0, 2000));
                        const m = sendResponseText.match(/(\{[\s\S]*\})/);
                        if (m) {
                            try { sendResult = JSON.parse(m[1]); console.warn('send_media_message: extracted JSON from non-JSON response.'); } catch (e) { console.warn('send_media_message: failed to parse extracted JSON', e); }
                        }

                        if (!sendResult) {
                            if (sendResponse.ok) {
                                // Message was likely sent successfully despite odd response — avoid showing false error to user
                                console.warn('send_media_message: non-JSON response but HTTP OK. Treating as success.');
                                sendResult = { success: true };
                            } else {
                                console.error('send_media_message returned non-JSON response and HTTP not OK.');
                                throw new Error('Respuesta inválida de send_media_message.php. Revisa logs del servidor (send_media_message_debug.log y el servidor PHP) para más detalles.');
                            }
                        }
                    }

                    if (!sendResult.success) {
                        throw new Error(sendResult.error || 'Error enviando mensaje');
                    }
                    
                    // Éxito
                    console.log('✅ Archivo enviado exitosamente, recargando mensajes...');
                    this.cancelMediaUpload();
                    
                    // Recargar mensajes para mostrar el multimedia recién enviado
                    await this.loadMessages(this.currentUserId, true, true);
                    
                    // SSE actualizará las conversaciones en la lista automáticamente
                    // this.loadConversations();
                    
                } catch (error) {
                    console.error('Error sending media:', error);
                    alert('Error al enviar archivo: ' + error.message);
                } finally {
                    sendBtn.disabled = false;
                    attachBtn.disabled = false;
                    sendBtn.innerHTML = '<i class="fas fa-paper-plane"></i>';
                }
            }
            
            cancelMediaUpload() {
                document.getElementById('media-preview').style.display = 'none';
                document.getElementById('media-caption').value = '';
                document.getElementById('file-input').value = '';
                this.selectedFile = null;
                // Limpiar flag de grabación de voz
                this._isVoiceRecording = false;
                
                // Restaurar botón enviar
                const sendBtn = document.getElementById('send-btn');
                if (sendBtn) { sendBtn.onclick = null; sendBtn.disabled = false; sendBtn.innerHTML = '<i class="fas fa-paper-plane"></i>'; }
                const delBtn = document.getElementById('delete-file-btn'); if (delBtn) delBtn.style.display = 'none';

                // Ensure mic/send visibility reflects the current state
                if (this._updateSendMicVisibility) this._updateSendMicVisibility();
            }
            
            renderMediaMessage(message) {
                if (!message) return '';
                const mediaType = message.message_type || message.media_type || 'text';
                const mediaUrl = message.media_url || '';
                const mediaUrlExternal = message.media_url_external || '';
                const caption = message.content || '';
                const localFile = message.local_file || '';
                const localThumb = message.local_thumb || '';
                const messageId = message.id || message.message_id || '';
                
                // Obtener la URL base del sitio para URLs absolutas
                const baseUrl = window.location.origin;
                
                // Debug completo del mensaje
                if (mediaType === 'audio' || mediaType === 'image' || mediaType === 'video') {
                    console.log(`🎵 Media message (${mediaType}) - Datos completos:`, {
                        id: messageId,
                        localFile: localFile,
                        localThumb: localThumb,
                        mediaUrl: mediaUrl,
                        mediaUrlExternal: mediaUrlExternal,
                        hasLocalFile: !!localFile,
                        messageType: mediaType,
                        baseUrl: baseUrl
                    });
                }
                
                // Prioridad: archivo local > proxy con DB id > media_url_external > URL externa
                let full = '';
                let thumb = '';
                
                if (localFile) {
                    // ✅ PRIORIDAD 1: Archivo local guardado (usar URL absoluta)
                    full = localFile.startsWith('http') ? localFile : `${baseUrl}/${localFile.replace(/^\//, '')}`;
                    thumb = localThumb 
                        ? (localThumb.startsWith('http') ? localThumb : `${baseUrl}/${localThumb.replace(/^\//, '')}`)
                        : full;
                    console.log('✅ Usando archivo local (URL absoluta):', full);
                } else if (messageId && !mediaUrlExternal) {
                    // ⚠️ PRIORIDAD 2: Usar proxy con el ID de la base de datos (URL absoluta)
                    full = `${baseUrl}/api/version/media-url.php?id=${encodeURIComponent(messageId)}`;
                    thumb = full;
                    console.log('⚠️ No hay local_file, usando proxy con messageId:', messageId);
                } else if (mediaUrlExternal) {
                    // 🔄 PRIORIDAD 3: Usar media_url_external (convertir a URL absoluta si es relativa)
                    full = mediaUrlExternal.startsWith('http') 
                        ? mediaUrlExternal 
                        : `${baseUrl}/${mediaUrlExternal.replace(/^\//, '')}`;
                    thumb = full;
                    console.log('🔄 Usando media_url_external (URL absoluta):', full);
                } else if (mediaUrl && /^\d+$/.test(mediaUrl)) {
                    // 📱 PRIORIDAD 4: Es un ID de WhatsApp (solo números) - usar URL absoluta
                    full = `${baseUrl}/api/version/media-url.php?id=${encodeURIComponent(mediaUrl)}`;
                    thumb = full;
                    console.log('📱 Usando whatsapp_media_id con media-url.php:', mediaUrl);
                } else if (mediaUrl && /^https?:\/\//i.test(mediaUrl)) {
                    // 🌐 PRIORIDAD 5: URL externa directa (puede estar caducada)
                    full = mediaUrl;
                    thumb = mediaUrl;
                    console.log('🌐 Usando URL externa:', full);
                } else {
                    // ❌ Fallback: sin media disponible
                    console.warn('❌ No hay fuente de media disponible para el mensaje', messageId);
                    full = '';
                    thumb = '';
                }

                // Si no hay URL de media, mostrar placeholder con botón de descarga inmediata
                if (!full && mediaType !== 'text') {
                    const iconMap = {image: 'fa-image', video: 'fa-video', audio: 'fa-microphone', document: 'fa-file', sticker: 'fa-sticky-note'};
                    const icon = iconMap[mediaType] || 'fa-file';
                    const labelMap = {image: 'imagen', video: 'video', audio: 'audio', document: 'documento', sticker: 'sticker'};
                    const label = labelMap[mediaType] || mediaType;
                    const btnId = `dl-btn-${messageId}`;
                    const statusId = `dl-status-${messageId}`;
                    // Botón descarga on-demand
                    const hasId = messageId || (mediaUrl && /^\d+$/.test(mediaUrl));
                    const downloadBtn = hasId ? `
                        <button id="${escapeHtml(btnId)}" class="btn btn-sm btn-success mt-2" style="border-radius:20px; font-size:11px; padding:3px 12px;"
                            onclick="window.forceDownloadMedia(${escapeHtml(String(messageId))}, '${escapeHtml(btnId)}', '${escapeHtml(statusId)}'); return false;">
                            <i class='fas fa-download'></i> Descargar ahora
                        </button>
                        <div id="${escapeHtml(statusId)}" style="font-size:11px; color:#aaa; margin-top:4px;"></div>
                    ` : '';
                    return `
                        <div class="message-media" style="background:rgba(0,0,0,0.04); border-radius:8px; padding:12px; text-align:center; min-width:180px;">
                            <i class="fas ${icon}" style="font-size:28px; color:#999; margin-bottom:6px; display:block;"></i>
                            <div style="font-size:12px; color:#888;">Pendiente de descarga (${escapeHtml(label)})</div>
                            ${downloadBtn}
                        </div>
                        ${caption ? `<div>${escapeHtml(caption)}</div>` : ''}
                    `;
                }

                switch (mediaType) {
                    case 'image':
                        return `
                            <div class="message-media">
                                <a href="#" data-full="${escapeHtml(full)}" data-type="image" data-caption="${escapeHtml(caption)}" onclick="openMediaLightbox(this.dataset.full, this.dataset.type, this.dataset.caption); return false;" title="Abrir imagen">
                                    <img src="${escapeHtml(thumb)}" alt="Imagen" style="cursor:zoom-in" onerror="this.parentElement.parentElement.innerHTML='<div style=\\'padding:12px;text-align:center;background:rgba(0,0,0,0.04);border-radius:8px;\\'><i class=\\'fas fa-image\\' style=\\'font-size:28px;color:#ccc;\\'></i><div style=\\'font-size:12px;color:#999;margin-top:4px;\\'>Imagen no disponible</div><a href=\\'${escapeHtml(full)}\\' target=\\'_blank\\' class=\\'btn btn-sm btn-outline-primary mt-1\\'><i class=\\'fas fa-download\\'></i> Reintentar</a></div>'">
                                </a>
                            </div>
                            ${caption ? `<div>${escapeHtml(caption)}</div>` : ''}
                        `;
                    case 'video':
                        return `
                            <div class="message-media">
                                <a href="#" data-full="${escapeHtml(full)}" data-type="video" data-caption="${escapeHtml(caption)}" onclick="openMediaLightbox(this.dataset.full, this.dataset.type, this.dataset.caption); return false;" title="Abrir video">
                                    <div style="position:relative; display:inline-block;">
                                        <img src="${escapeHtml(thumb)}" alt="Video" style="cursor:zoom-in; max-width:100%; border-radius:6px;">
                                        <div style="position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); font-size:28px; color:white; text-shadow:0 1px 6px rgba(0,0,0,0.6);"><i class="fas fa-play-circle"></i></div>
                                    </div>
                                </a>
                            </div>
                            ${caption ? `<div>${escapeHtml(caption)}</div>` : ''}
                        `;
                    case 'audio': {
                        // Para audio, usar la misma lógica de full
                        let audioSrc = full;
                        
                        if (!audioSrc) {
                            return `
                                <div class="message-media">
                                    <div class="alert alert-warning mb-0" style="font-size: 13px;">
                                        <i class="fas fa-exclamation-triangle"></i> Audio no disponible
                                        <br><small>El audio puede haber expirado o no está disponible</small>
                                    </div>
                                </div>
                            `;
                        }
                        
                        return `
                            <div class="message-media">
                                <audio controls controlsList="nodownload" preload="metadata" onerror="this.parentElement.innerHTML='<div class=\\'alert alert-danger mb-0\\' style=\\'font-size:13px;\\'><i class=\\'fas fa-times-circle\\'></i> Error al cargar audio<br><small>Archivo no encontrado o no disponible</small></div>'">
                                    <source src="${escapeHtml(audioSrc)}" type="audio/mpeg">
                                    <source src="${escapeHtml(audioSrc)}" type="audio/ogg">
                                    <source src="${escapeHtml(audioSrc)}" type="audio/wav">
                                    Tu navegador no soporta reproducción de audio.
                                </audio>
                            </div>
                        `;
                    }
                    case 'document': {
                        let docUrl = full;
                        if (!docUrl) {
                            return `
                                <div class="message-document disabled">
                                    <i class="fas fa-file-pdf"></i>
                                    <div class="document-info">
                                        <div class="document-name">${escapeHtml(caption || 'Documento')}</div>
                                        <div class="document-size text-muted">No disponible para descargar</div>
                                    </div>
                                </div>
                            `;
                        }
                        return `
                            <div class="message-document">
                                <i class="fas fa-file-pdf"></i>
                                <div class="document-info">
                                    <div class="document-name">${escapeHtml(caption || 'Documento')}</div>
                                    <div class="document-size">
                                        <a href="${escapeHtml(docUrl)}" target="_blank" rel="noopener noreferrer" ${docUrl && docUrl.indexOf('download=1') !== -1 ? 'download' : ''}>Descargar</a>
                                    </div>
                                </div>
                                <i class="fas fa-download"></i>
                            </div>
                        `;
                    }
                    default:
                        return escapeHtml(caption) || '';
                }
            }
        }
        
        // Función global para cancelar
        function cancelMediaUpload() {
            if (window.chatApp) {
                window.chatApp.cancelMediaUpload();
            }
        }

        // Helper para renderizar WhatsApp-like formatted text and interactive messages
        function renderInteractiveMessage(obj) {
            try {
                const interactive = obj.interactive || obj;
                const t = interactive.type || (interactive.header && 'button');
                if (t === 'button' || interactive.button) {
                    const title = escapeHtml(interactive.title || interactive.body || '');
                    const buttons = interactive.buttons || interactive.button || [];
                    const html = [`<div class="wa-interactive">`, title ? `<div class="wa-title">${title}</div>` : ''];
                    html.push(`<div class="wa-buttons">`);
                    buttons.forEach(b => {
                        const label = escapeHtml(b.title || b.text || b.body || b.label || '');
                        html.push(`<button class="btn btn-sm btn-outline-primary wa-interactive-btn" data-value="${label}">${label}</button>`);
                    });
                    html.push(`</div></div>`);
                    return html.join('');
                }
                if (t === 'list' || interactive.sections) {
                    const title = escapeHtml(interactive.title || interactive.header || '');
                    const sections = interactive.sections || [];
                    const html = [`<div class="wa-interactive">`, title ? `<div class="wa-title">${title}</div>` : ''];
                    html.push(`<div class="wa-list">`);
                    sections.forEach(s => {
                        const secTitle = escapeHtml(s.title || '');
                        if (secTitle) html.push(`<div class="wa-section-title">${secTitle}</div>`);
                        (s.rows || []).forEach(r => {
                            const rowTitle = escapeHtml(r.title || r.name || r.id || '');
                            const rowDesc = escapeHtml(r.description || '');
                            html.push(`<div class="wa-list-item" data-value="${rowTitle}"><div class="wa-list-name">${rowTitle}</div>${rowDesc?`<div class="wa-list-desc small text-muted">${rowDesc}</div>`:''}</div>`);
                        });
                    });
                    html.push(`</div></div>`);
                    return html.join('');
                }
            } catch (e) { console.warn('renderInteractiveMessage failed', e); }
            return '';
        }

        function formatMessageInlineCode(s) {
            return s.replace(/`([^`]+)`/g, (m, g1) => `<span class="wa-inline-code">${escapeHtml(g1)}</span>`);
        }

        function formatMessageContent(text) {
            if (!text && text !== 0) return '';
            try {
                // If looks like JSON with interactive payload, render interactive UI
                const t = String(text).trim();
                if ((t.startsWith('{') && t.endsWith('}')) || (t.startsWith('[') && t.endsWith(']'))) {
                    try {
                        const parsed = JSON.parse(t);
                        if (parsed && (parsed.interactive || parsed.type === 'interactive')) {
                            return renderInteractiveMessage(parsed.interactive || parsed);
                        }
                    } catch (e) { /* not json */ }
                }

                // Escape HTML first
                let s = escapeHtml(String(text));

                // Code block (triple backticks) -> <pre>
                s = s.replace(/```([\s\S]*?)```/g, (m, g1) => `<div class="wa-code">${escapeHtml(g1)}</div>`);
                // Inline code
                s = formatMessageInlineCode(s);
                // Bold *text*
                s = s.replace(/\*([^*]+)\*/g, '<strong>$1</strong>');
                // Italic _text_
                s = s.replace(/_([^_]+)_/g, '<em>$1</em>');
                // Strikethrough ~text~
                s = s.replace(/~([^~]+)~/g, '<del>$1</del>');

                // Auto-link URLs
                s = s.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>');

                return s;
            } catch (e) {
                console.warn('formatMessageContent failed', e);
                return escapeHtml(String(text));
            }
        }

        window.formatMessageContent = formatMessageContent;

        /**
         * Descarga inmediata on-demand de un archivo multimedia pendiente.
         * Se llama desde el botón del placeholder de media pendiente.
         */
        window.forceDownloadMedia = async function(messageId, btnId, statusId) {
            const btn = document.getElementById(btnId);
            const statusEl = document.getElementById(statusId);
            if (!btn) return;

            // Mostrar estado de carga
            btn.disabled = true;
            btn.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i> Descargando...';
            if (statusEl) statusEl.textContent = 'Solicitando descarga...';

            try {
                const resp = await fetch('api/force_download_media.php', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ message_id: messageId })
                });
                const data = await resp.json();

                if (data.success) {
                    if (statusEl) statusEl.innerHTML = '<span style="color:#25d366">&#10003; Descargado. Recargando...</span>';
                    // Recargar los mensajes después de 1 segundo para mostrar el archivo
                    setTimeout(() => {
                        if (window.chatApp && window.chatApp.currentUserId) {
                            window.chatApp.loadMessages(window.chatApp.currentUserId, true, true);
                        }
                    }, 1200);
                } else {
                    const msg = data.message || data.error || 'No se pudo descargar ahora';
                    if (statusEl) statusEl.innerHTML = '<span style="color:#e74c3c">⚠️ ' + escapeHtml(msg) + '</span>';
                    btn.disabled = false;
                    btn.innerHTML = '<i class="fas fa-redo"></i> Reintentar';
                }
            } catch (err) {
                console.error('forceDownloadMedia error', err);
                if (statusEl) statusEl.innerHTML = '<span style="color:#e74c3c">⚠️ Error de conexión</span>';
                btn.disabled = false;
                btn.innerHTML = '<i class="fas fa-redo"></i> Reintentar';
            }
        };

        // Delegate clicks on interactive buttons/list items to send quick replies
        document.addEventListener('click', (ev) => {
            const btn = ev.target.closest('.wa-interactive-btn');
            if (btn) {
                ev.stopPropagation();
                const v = btn.dataset.value;
                if (v && window.chatApp && typeof window.chatApp.sendQuickReply === 'function') {
                    window.chatApp.sendQuickReply(v);
                }
            }
            const item = ev.target.closest('.wa-list-item');
            if (item) {
                ev.stopPropagation();
                const v = item.dataset.value;
                if (v && window.chatApp && typeof window.chatApp.sendQuickReply === 'function') {
                    window.chatApp.sendQuickReply(v);
                }
            }
        });

        // Helper para iconos de archivos (copiado de chat_window)
        function getFileIcon(filename) {
            if (!filename) return 'fas fa-file text-muted';
            const ext = String(filename).split('.').pop().toLowerCase();
            const iconMap = {
                'pdf': 'fas fa-file-pdf text-danger',
                'doc': 'fas fa-file-word text-primary',
                'docx': 'fas fa-file-word text-primary',
                'xls': 'fas fa-file-excel text-success',
                'xlsx': 'fas fa-file-excel text-success',
                'ppt': 'fas fa-file-powerpoint text-warning',
                'pptx': 'fas fa-file-powerpoint text-warning',
                'zip': 'fas fa-file-archive text-secondary',
                'rar': 'fas fa-file-archive text-secondary',
                'txt': 'fas fa-file-alt text-muted',
                'csv': 'fas fa-file-csv text-success'
            };
            return iconMap[ext] || 'fas fa-file text-muted';
        }

        // Media lightbox helper (image/video with download)
        function openMediaLightbox(url, type = 'image', caption = '') {
            if (!url) {
                showAlert && showAlert('Media no disponible', 'warning');
                return;
            }
            const body = document.getElementById('imageLightboxBody');
            const cap = document.getElementById('imageLightboxCaption');
            const downloadBtn = document.getElementById('imageLightboxDownload');
            if (!body || !cap) return;
            // clear existing content
            body.innerHTML = '';

            // Decide rendering by type or by URL extension
            const isVideo = (type === 'video') || /\.(mp4|webm|ogg)(\?|$)/i.test(url);
            if (isVideo) {
                body.innerHTML = `<video controls class="w-100" style="border-radius:10px;"><source src="${escapeHtml(url)}" type="video/mp4">Tu navegador no soporta video.</video>`;
            } else {
                body.innerHTML = `<img src="${escapeHtml(url)}" class="img-fluid w-100" style="border-radius:10px;">`;
            }

            cap.textContent = caption || '';
            if (downloadBtn) {
                downloadBtn.href = url;
                downloadBtn.style.display = 'inline-block';
            }

            const modalEl = document.getElementById('imageLightboxModal');
            const bsModal = new bootstrap.Modal(modalEl);
            bsModal.show();
        }

        // backward compatibility wrapper
        function openImageLightbox(url, caption = '') {
            openMediaLightbox(url, 'image', caption);
        }

        // Inicializar cuando la página cargue
        let chat;
        document.addEventListener('DOMContentLoaded', () => {
            chat = new WhatsAppChat();
            window.chatApp = chat; // Exponer globalmente para funciones auxiliares
            // Helpers para pruebas manuales y debugging
            window.__showNoMoreMessagesHint = () => chat.showNoMoreMessagesHint && chat.showNoMoreMessagesHint();
            window.__removeNoMoreMessagesHint = () => chat.removeNoMoreMessagesHint && chat.removeNoMoreMessagesHint();
        });
    </script>

    <!-- Lightbox Modal -->
    <div class="modal fade" id="imageLightboxModal" tabindex="-1" aria-hidden="true">
        <div class="modal-dialog modal-dialog-centered modal-lg">
            <div class="modal-content bg-transparent border-0">
                <div class="modal-body p-0 position-relative">
                    <button type="button" class="btn-close btn-close-white position-absolute top-0 end-0 m-3" data-bs-dismiss="modal" aria-label="Cerrar" style="z-index:10;"></button>
                    <div id="imageLightboxBody" style="max-height:75vh; overflow:auto;"></div>
                    <div id="imageLightboxCaption" class="mt-2 text-center text-white small"></div>
                    <div class="position-absolute bottom-0 end-0 m-3">
                        <a id="imageLightboxDownload" class="btn btn-sm btn-light" href="#" target="_blank" download style="display:none;"><i class="fas fa-download"></i> Descargar</a>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Reaction picker -->
    <div id="reaction-picker" aria-hidden="true">
        <div style="display:flex; flex-wrap:wrap; gap:6px;">
            <button class="emoji" data-emoji="👍">👍</button>
            <button class="emoji" data-emoji="❤️">❤️</button>
            <button class="emoji" data-emoji="😂">😂</button>
            <button class="emoji" data-emoji="😮">😮</button>
            <button class="emoji" data-emoji="😢">😢</button>
            <button class="emoji" data-emoji="👏">👏</button>
            <button class="emoji" data-emoji="🎉">🎉</button>
            <button class="emoji" data-emoji="🔥">🔥</button>
            <button class="emoji" data-emoji="🙏">🙏</button>
        </div>
    </div>

    <!-- Modal para programar recordatorio -->
    <div class="modal fade" id="reminderModal" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header bg-success text-white">
                    <h5 class="modal-title">
                        <i class="fas fa-calendar-plus"></i> Programar Recordatorio
                    </h5>
                    <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <div class="mb-3">
                        <label class="form-label">Cliente</label>
                        <input type="text" class="form-control" id="reminder-user-name" readonly>
                    </div>
                    
                    <div class="mb-3">
                        <label class="form-label">Fecha</label>
                        <input type="date" class="form-control" id="reminder-date" required>
                    </div>
                    
                    <div class="mb-3">
                        <label class="form-label">Hora</label>
                        <input type="time" class="form-control" id="reminder-time" value="09:00" required>
                    </div>
                    
                    <div class="mb-3">
                        <label class="form-label">Seleccionar Plantilla</label>
                        <select class="form-select" id="reminder-template" required>
                            <option value="">Cargando plantillas...</option>
                        </select>
                    </div>
                    
                    <div id="reminder-variables-container" style="display:none;">
                        <label class="form-label">Variables de la Plantilla</label>
                        <div id="reminder-variables-fields"></div>
                    </div>
                    
                    <div id="reminder-preview" class="mt-3" style="display:none;">
                        <h6 class="border-bottom pb-2">Vista Previa</h6>
                        <div id="reminder-preview-content" class="p-3 bg-light rounded"></div>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
                        Cancelar
                    </button>
                    <button type="button" class="btn btn-success" id="save-reminder-btn">
                        <i class="fas fa-save"></i> Programar Recordatorio
                    </button>
                </div>
            </div>
        </div>
    </div>

    <!-- Modal para variables de plantilla (envío normal) -->
    <div class="modal fade" id="templateVariablesModal" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header bg-primary text-white">
                    <h5 class="modal-title">
                        <i class="fas fa-edit"></i> Variables de la Plantilla
                    </h5>
                    <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <div class="mb-3">
                        <strong id="template-modal-name"></strong>
                    </div>
                    
                    <div id="template-variables-container">
                        <!-- Variables dinámicas -->
                    </div>
                    
                    <div id="template-preview" class="mt-3" style="display:none;">
                        <h6 class="border-bottom pb-2">Vista Previa</h6>
                        <div id="template-preview-content" class="p-3 bg-light rounded"></div>
                    </div>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
                        Cancelar
                    </button>
                    <button type="button" class="btn btn-primary" id="send-template-with-vars-btn">
                        <i class="fas fa-paper-plane"></i> Enviar Plantilla
                    </button>
                </div>
            </div>
        </div>
    </div>

<!-- ════════════════════════════════════════════════════════════════════════
     MÓDULO LAB — Modal "Agendar Domicilio"
     ════════════════════════════════════════════════════════════════════════ -->
<style>
#modalAgendarDomicilio .addr-chip {
    cursor:pointer; border:1px solid #0d6efd; color:#0d6efd;
    background:#f0f4ff; border-radius:20px; padding:3px 10px;
    font-size:.78rem; white-space:nowrap; transition:background .15s;
}
#modalAgendarDomicilio .addr-chip:hover { background:#0d6efd; color:#fff; }
#modalAgendarDomicilio .field-err { display:none; font-size:.8rem; color:#dc3545; margin-top:3px; }

/* ── Chips de archivos adjuntos (multi-orden) ── */
.labdom-orden-chip { position:relative; width:72px; flex-shrink:0; text-align:center; }
.labdom-orden-thumb { width:72px; height:72px; border:1px solid #dee2e6; border-radius:6px; background:#f8f9fa; display:flex; align-items:center; justify-content:center; overflow:hidden; }
.labdom-orden-name { display:block; font-size:.62rem; color:#666; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:72px; margin-top:3px; }
.labdom-orden-del { position:absolute; top:-5px; right:-5px; width:18px; height:18px; border-radius:50%; background:#dc3545; color:#fff; border:none; font-size:.7rem; line-height:1; cursor:pointer; display:flex; align-items:center; justify-content:center; padding:0; }
.labdom-orden-del:hover { background:#c82333; }
#modalAgendarDomicilio .is-invalid ~ .field-err,
#modalAgendarDomicilio .is-invalid + .field-err { display:block; }

/* ── Barra de sesiones minimizadas ── */
#labdom-pills-bar {
    position: fixed;
    bottom: 0;
    left: 0;
    z-index: 1060;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 8px 14px 8px;
    pointer-events: none;
    max-width: 80vw;
}
.labdom-pill {
    pointer-events: all;
    display: flex;
    align-items: center;
    gap: 5px;
    background: linear-gradient(135deg, #1565c0, #0d47a1);
    color: #fff;
    border-radius: 24px;
    padding: 5px 8px 5px 6px;
    cursor: pointer;
    font-size: .78rem;
    box-shadow: 0 4px 16px rgba(0,0,0,.28);
    max-width: 280px;
    transition: background .15s, transform .1s, box-shadow .1s;
    user-select: none;
    animation: pillIn .18s ease;
}
.labdom-pill:hover { background: linear-gradient(135deg,#1976d2,#1565c0); transform: translateY(-2px); box-shadow: 0 6px 22px rgba(0,0,0,.32); }
@keyframes pillIn { from { transform:translateY(10px); opacity:0 } to { transform:translateY(0); opacity:1 } }
.labdom-pill-num {
    background: rgba(255,255,255,.28);
    border-radius: 50%;
    width: 22px; height: 22px;
    display: flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: .72rem; flex-shrink: 0;
}
.labdom-pill-body { display:flex; flex-direction:column; overflow:hidden; }
.labdom-pill-chat { font-weight: 600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width: 110px; font-size:.78rem; }
.labdom-pill-pac  { font-size:.68rem; opacity:.85; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width: 110px; }
.labdom-pill-close {
    background: rgba(255,255,255,.18);
    border: none; color: #fff;
    border-radius: 50%;
    width: 17px; height: 17px;
    display: flex; align-items:center; justify-content:center;
    font-size: .6rem; cursor: pointer; padding: 0; flex-shrink: 0;
    transition: background .12s;
    line-height: 1;
}
.labdom-pill-close:hover { background: rgba(255,50,50,.55); }

/* Botón minimizar */
#labdom-btn-minimizar {
    background: rgba(255,255,255,.18);
    border: 1px solid rgba(255,255,255,.35);
    color: #fff;
    border-radius: 6px;
    padding: 2px 8px;
    font-size: .8rem;
    line-height: 1.4;
    cursor: pointer;
    transition: background .15s;
}
#labdom-btn-minimizar:hover { background: rgba(255,255,255,.32); }
</style>

<div class="modal fade" id="modalAgendarDomicilio" tabindex="-1">
    <div class="modal-dialog modal-lg modal-dialog-scrollable">
        <div class="modal-content">

            <div class="modal-header bg-primary text-white py-2">
                <div>
                    <h5 class="modal-title mb-0"><i class="fas fa-house-medical me-2"></i>Agendar Domicilio</h5>
                    <small id="labdom-header-pac" class="opacity-75"></small>
                </div>
                <div class="d-flex align-items-center gap-2">
                    <button id="labdom-btn-minimizar" title="Minimizar — guarda el formulario y crea una píldora en la barra inferior"
                            onclick="labDomicilio.minimizar()">
                        <i class="fas fa-window-minimize"></i> Minimizar
                    </button>
                    <button class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
                </div>
            </div>

            <!-- ── PANEL ÉXITO (se muestra al guardar) ── -->
            <div id="labdom-ok-panel" class="d-none">
                <div class="text-center py-4 px-3">
                    <div class="mb-3" style="font-size:3rem">✅</div>
                    <h5 class="fw-bold text-success" id="labdom-ok-msg">Domicilio agendado</h5>
                    <p class="text-muted mb-4" id="labdom-ok-sub"></p>
                    <div class="d-flex justify-content-center gap-2 flex-wrap">
                        <a id="labdom-ok-asignar" href="lab_domicilios.php" target="_blank" class="btn btn-warning">
                            <i class="fas fa-user-nurse me-1"></i>Asignar enfermera
                        </a>
                        <a id="labdom-ok-ver" href="#" target="_blank" class="btn btn-outline-primary">
                            <i class="fas fa-external-link-alt me-1"></i>Ver domicilio
                        </a>
                        <button type="button" class="btn btn-success" onclick="labDomicilio.agendarOtro()">
                            <i class="fas fa-plus me-1"></i>Agendar otro
                        </button>
                        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
                    </div>
                </div>
            </div>

            <!-- ── FORMULARIO ── -->
            <div id="labdom-form-panel" class="modal-body pb-2">

                <!-- Orden médica (múltiples archivos) -->
                <div class="mb-3">
                    <div class="d-flex align-items-center justify-content-between mb-1">
                        <span class="fw-semibold small text-secondary"><i class="fas fa-paperclip me-1"></i>Órdenes médicas <span class="fw-normal text-muted">(opcional)</span></span>
                        <button type="button" id="labdom-btn-add-orden" class="btn btn-sm btn-outline-primary py-0 px-2" onclick="labDomicilio._agregarOrden()">
                            <i class="fas fa-plus me-1"></i>Agregar
                        </button>
                    </div>
                    <div id="labdom-ordenes-list" class="d-flex flex-wrap gap-2 mt-1"></div>
                    <input type="file" id="labdom-orden-input" accept="image/*,.pdf,.doc,.docx" multiple style="display:none">
                </div>

                <!-- ── TIPO (particular/seguro) — PRIMERO y llamativo ── -->
                <div class="mb-3 p-2 rounded border bg-light">
                    <label class="fw-semibold small text-secondary mb-2 d-block">
                        <i class="fas fa-id-card me-1"></i>¿ES PARTICULAR O POR SEGURO?
                    </label>
                    <div class="d-flex gap-2">
                        <input type="radio" class="btn-check" name="labdom-tipo-cliente" id="tc-particular" value="particular" autocomplete="off"
                            onchange="labDomicilio._toggleSeguro()">
                        <label class="btn btn-sm btn-outline-secondary" for="tc-particular">
                            <i class="fas fa-wallet me-1"></i>Particular
                        </label>

                        <input type="radio" class="btn-check" name="labdom-tipo-cliente" id="tc-seguro" value="seguro" autocomplete="off"
                            onchange="labDomicilio._toggleSeguro()">
                        <label class="btn btn-sm btn-outline-info" for="tc-seguro">
                            <i class="fas fa-shield-alt me-1"></i>Seguro
                        </label>
                    </div>
                    <!-- Campos seguro (visibles solo cuando se elige Seguro) -->
                    <div id="labdom-seguro-row" class="mt-2 d-none">
                        <div class="mb-2">
                            <label class="form-label small mb-1">¿Qué seguro? <span class="text-danger">*</span></label>
                            <input type="text" class="form-control form-control-sm" id="labdom-seguro-nombre"
                                placeholder="Ej: Sura, Colsanitas, Compensar, Póliza SOAT…" autocomplete="off">
                            <div class="field-err" id="labdom-seguro-err">Indica el nombre del seguro</div>
                        </div>
                        <div class="row g-2">
                            <div class="col-6">
                                <label class="form-label small mb-1"><i class="fas fa-hashtag me-1 text-muted"></i>N.° de autorización <span class="text-muted">(opcional)</span></label>
                                <input type="text" class="form-control form-control-sm" id="labdom-autorizacion"
                                    placeholder="Ej: 12345678" autocomplete="off">
                            </div>
                            <div class="col-6">
                                <label class="form-label small mb-1"><i class="fas fa-hand-holding-usd me-1 text-muted"></i>Copago asume el laboratorio <span class="text-muted">(opcional)</span></label>
                                <div class="input-group input-group-sm">
                                    <span class="input-group-text">$</span>
                                    <input type="number" min="0" step="1" class="form-control form-control-sm" id="labdom-copago"
                                        placeholder="0">
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- ── PACIENTE ── -->
                <div class="card border-0 bg-light mb-3 p-2">
                    <div class="d-flex align-items-center justify-content-between mb-1">
                        <span class="fw-semibold small text-secondary"><i class="fas fa-user me-1"></i>PACIENTE</span>
                        <button id="labdom-btn-cambiar-pac" class="btn btn-xs btn-link text-primary p-0 small d-none" onclick="labDomicilio.limpiarPaciente()">Cambiar</button>
                    </div>

                    <!-- Buscador (visible cuando NO hay paciente seleccionado) -->
                    <div id="labdom-pac-buscar">
                        <div class="input-group input-group-sm">
                            <input type="text" id="labdom-pac-busq" class="form-control" placeholder="Buscar por nombre o documento…" autocomplete="off">
                            <button class="btn btn-outline-secondary" type="button" onclick="labDomicilio.buscarPaciente()"><i class="fas fa-search"></i></button>
                        </div>
                        <div id="labdom-pac-list" class="list-group mt-1 shadow" style="position:relative;z-index:1060;max-height:165px;overflow-y:auto;display:none"></div>
                        <div class="mt-2 d-flex gap-2">
                            <button id="labdom-btn-contacto" class="btn btn-sm btn-outline-success flex-grow-1" type="button" onclick="labDomicilio.usarContacto()">
                                <i class="fab fa-whatsapp me-1"></i> Usar contacto
                            </button>
                            <button class="btn btn-sm btn-outline-secondary" type="button" onclick="labDomicilio.crearNuevo()" title="Registrar paciente nuevo que no está en el sistema">
                                <i class="fas fa-user-plus me-1"></i>Nuevo
                            </button>
                        </div>
                        <div id="labdom-contacto-spin" class="text-center py-1 d-none">
                            <span class="spinner-border spinner-border-sm text-success"></span>
                            <span class="small text-muted ms-1">Buscando contacto…</span>
                        </div>
                    </div>

                    <!-- Tarjeta paciente seleccionado (visible cuando SÍ hay paciente) -->
                    <div id="labdom-pac-card" class="d-none">
                        <!-- Indicador modo nuevo paciente -->
                        <div id="labdom-nuevo-badge" class="d-none mb-2">
                            <span class="badge bg-warning text-dark"><i class="fas fa-user-plus me-1"></i>Nuevo paciente — se registrará al guardar</span>
                        </div>
                        <div class="d-flex align-items-start gap-2">
                            <div class="rounded-circle bg-primary text-white d-flex align-items-center justify-content-center flex-shrink-0 mt-1" style="width:36px;height:36px;font-size:1rem">
                                <i class="fas fa-user"></i>
                            </div>
                            <div class="flex-grow-1">
                                <input type="text" class="form-control form-control-sm fw-semibold mb-1" id="labdom-pac-nombre" placeholder="Nombre completo *" autocomplete="off">
                                <div class="row g-1">
                                    <div class="col-5">
                                        <select class="form-select form-select-sm" id="labdom-pac-tipodoc" title="Tipo de documento">
                                            <option value="CC">CC - Cédula</option>
                                            <option value="TI">TI - Tarjeta</option>
                                            <option value="CE">CE - Extran.</option>
                                            <option value="PA">PA - Pasap.</option>
                                            <option value="NIT">NIT</option>
                                            <option value="RC">RC - Nac.</option>
                                        </select>
                                    </div>
                                    <div class="col-7">
                                        <input type="text" class="form-control form-control-sm" id="labdom-pac-cedula" placeholder="N.° documento" autocomplete="off">
                                    </div>
                                </div>
                            </div>
                        </div>
                        <!-- Datos de contacto del paciente -->
                        <div id="labdom-pac-contacto" class="mt-2 d-none">
                            <div class="row g-1 small">
                                <div class="col-6">
                                    <span class="text-muted"><i class="fas fa-phone me-1"></i></span>
                                    <span id="labdom-pac-tel" class="fw-semibold">—</span>
                                </div>
                                <div class="col-6">
                                    <span class="text-muted"><i class="fas fa-envelope me-1"></i></span>
                                    <span id="labdom-pac-email" class="fw-semibold">—</span>
                                </div>
                                <div class="col-12" id="labdom-pac-eps-row" style="display:none">
                                    <span class="text-muted small"><i class="fas fa-hospital me-1"></i>EPS: </span>
                                    <span id="labdom-pac-eps" class="small fw-semibold"></span>
                                </div>
                            </div>
                        </div>
                        <!-- Historial de domicilios previos -->
                        <div id="labdom-hist-cont" class="mt-2 d-none">
                            <p class="mb-1 small text-muted"><i class="fas fa-history me-1"></i>Direcciones usadas anteriormente — toca para reusar:</p>
                            <div id="labdom-hist-chips" class="d-flex flex-wrap gap-1"></div>
                        </div>
                        <!-- Fecha de nacimiento y correo — editables, se pre-llenan si el paciente ya los tiene -->
                        <div class="mt-2 row g-2" id="labdom-pac-extra">
                            <div class="col-6">
                                <label class="form-label small mb-1"><i class="fas fa-birthday-cake me-1 text-muted"></i>Fecha de nacimiento</label>
                                <input type="date" class="form-control form-control-sm" id="labdom-pac-fnac">
                            </div>
                            <div class="col-6">
                                <label class="form-label small mb-1"><i class="fas fa-envelope me-1 text-muted"></i>Correo electrónico</label>
                                <input type="email" class="form-control form-control-sm" id="labdom-pac-correo" placeholder="correo@dominio.com">
                            </div>
                        </div>
                    </div>
                </div>

                <!-- ── FECHA · HORA · TIPO ── -->
                <div class="row g-2 mb-3">
                    <div class="col-5">
                        <label class="form-label fw-semibold small mb-1">Fecha <span class="text-danger">*</span></label>
                        <input type="date" class="form-control form-control-sm" id="labdom-fecha">
                        <div class="field-err" id="labdom-fecha-err">Selecciona una fecha</div>
                    </div>
                    <div class="col-3">
                        <label class="form-label small mb-1">Hora</label>
                        <input type="time" class="form-control form-control-sm" id="labdom-hora">
                    </div>
                    <div class="col-4">
                        <label class="form-label small mb-1">Tipo</label>
                        <select class="form-select form-select-sm" id="labdom-tipo">
                            <option value="domicilio">🏠 Domicilio</option>
                            <option value="urgencia">🚨 Urgencia</option>
                            <option value="control">🔬 Control</option>
                        </select>
                    </div>
                </div>

                <!-- ── DIRECCIÓN ── -->
                <div class="mb-3">
                    <label class="form-label fw-semibold small mb-1">Dirección <span class="text-danger">*</span></label>
                    <input type="text" class="form-control form-control-sm" id="labdom-direccion" placeholder="Ej: Cra 15 # 32-10">
                    <div class="field-err" id="labdom-dir-err">Ingresa la dirección</div>

                    <div class="row g-2 mt-1">
                        <div class="col-6">
                            <input type="text" class="form-control form-control-sm" id="labdom-barrio" placeholder="Barrio">
                        </div>
                        <div class="col-6">
                            <input type="text" class="form-control form-control-sm" id="labdom-ciudad" placeholder="Ciudad">
                        </div>
                        <div class="col-12">
                            <input type="text" class="form-control form-control-sm" id="labdom-indicaciones" placeholder="Indicaciones: piso, apto, portero, referencias…">
                        </div>
                    </div>
                </div>

                <!-- ── EXÁMENES SOLICITADOS ── -->
                <div class="mb-3">
                    <label class="form-label fw-semibold small mb-1">
                        <i class="fas fa-flask me-1 text-primary"></i>Exámenes solicitados
                        <span class="badge bg-secondary ms-1" style="font-size:.65rem">si no hay foto de orden</span>
                    </label>
                    <textarea class="form-control form-control-sm" id="labdom-examenes" rows="2"
                        placeholder="Ej: Hemograma completo, glucosa en ayunas, parcial de orina…"></textarea>
                    <div class="form-text text-muted" style="font-size:.73rem">
                        <i class="fas fa-info-circle me-1"></i>Si el cliente envió foto de la orden, este campo es opcional.
                    </div>
                </div>

                <!-- ── INFORMACIÓN DE COBRO ── -->
                <div class="mb-3 p-2 rounded border bg-light">
                    <label class="fw-semibold small text-secondary mb-2 d-block">
                        <i class="fas fa-file-invoice-dollar me-1"></i>Información de cobro <span class="fw-normal text-muted">(opcional)</span>
                    </label>
                    <div class="row g-2">
                        <div class="col-6">
                            <label class="form-label small mb-1">Valor del domicilio</label>
                            <div class="input-group input-group-sm">
                                <span class="input-group-text">$</span>
                                <input type="number" min="0" step="1" class="form-control form-control-sm" id="labdom-valor-domicilio"
                                    placeholder="0" oninput="labDomicilio._calcTotal()">
                            </div>
                        </div>
                        <div class="col-6">
                            <label class="form-label small mb-1">Valor del copago</label>
                            <div class="input-group input-group-sm">
                                <span class="input-group-text">$</span>
                                <input type="number" min="0" step="1" class="form-control form-control-sm" id="labdom-valor-copago"
                                    placeholder="0" oninput="labDomicilio._calcTotal()">
                            </div>
                        </div>
                        <!-- Total a cobrar al cliente -->
                        <div class="col-12" id="labdom-total-row" style="display:none">
                            <div class="d-flex align-items-center justify-content-between rounded px-3 py-2 mt-1"
                                 style="background:#d1fae5;border:1px solid #6ee7b7">
                                <span class="fw-semibold small text-success">
                                    <i class="fas fa-cash-register me-1"></i>Total a cobrar al cliente
                                </span>
                                <span class="fw-bold text-success" id="labdom-total-valor" style="font-size:1rem">$0</span>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- ── NOTAS ADMIN ── -->
                <div class="mb-2">
                    <label class="form-label small mb-1"><i class="fas fa-sticky-note me-1"></i>Notas internas del agendamiento</label>
                    <textarea class="form-control form-control-sm" id="labdom-notas" rows="2"
                        placeholder="Indicaciones especiales, observaciones del operador…"></textarea>
                </div>

            </div><!-- /form-panel -->

            <div id="labdom-footer" class="modal-footer py-2">
                <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancelar</button>
                <button type="button" class="btn btn-primary btn-sm" id="labdom-btn-guardar" onclick="labDomicilio.guardar()">
                    <i class="fas fa-calendar-check me-1"></i> Agendar Domicilio
                </button>
            </div>

        </div>
    </div>
</div>

<script>
// ══════════════════════════════════════════════════════════════════════════════
//  Módulo Lab — Agendar Domicilio desde conversations.php
//  Soporta múltiples domicilios al mismo paciente en la misma sesión.
// ══════════════════════════════════════════════════════════════════════════════
const labDomicilio = (() => {
    // Estado interno
    let _msgId        = null;
    let _localFile    = null;
    let _newOrderFiles    = [];  // archivos nuevos seleccionados por el usuario
    let _newOrderDataUrls = [];  // data-URLs para previsualizar (índice paralelo)
    let _localFiles       = [];  // filenames ya en servidor (del mensaje WA original)
    let _convId           = null;
    let _pacienteId   = null;
    let _isNewPaciente = false;  // true = paciente nuevo (no existe en BD aún)
    let _pacNombre    = '';
    let _histDirs     = [];   // direcciones únicas previas del paciente
    let _bsModal      = null;

    // Sesiones minimizadas (múltiples chats en paralelo)
    let _sesiones         = [];   // [{ id, chatUserId, chatName, chatPhone, convId, pacienteId, pacNombre, histDirs, msgId, localFile, cardVisible, pacInfo, fields }]
    let _sesionIdCounter  = 0;

    // ── helpers DOM ──────────────────────────────────────────────────────────
    const $  = id => document.getElementById(id);
    const fv = id => $( id).value.trim();

    function fechaManana() {
        const d = new Date();
        d.setDate(d.getDate() + 1);
        return d.toISOString().slice(0, 10);
    }

    function mostrarFormulario() {
        $('labdom-ok-panel').classList.add('d-none');
        $('labdom-form-panel').style.display = '';
        $('labdom-footer').style.display     = '';
    }

    function limpiarValidacion() {
        ['labdom-fecha','labdom-direccion','labdom-pac-nombre','labdom-pac-cedula','labdom-pac-correo'].forEach(id => {
            const el = $(id); if (el) el.classList.remove('is-invalid');
        });
    }

    function validar() {
        let ok = true;
        limpiarValidacion();
        if (!fv('labdom-fecha'))    { $('labdom-fecha').classList.add('is-invalid');    ok = false; }
        if (!fv('labdom-direccion')){ $('labdom-direccion').classList.add('is-invalid'); ok = false; }
        // Validar formato de dirección
        const dirVal = fv('labdom-direccion') || '';
        if (dirVal && dirVal.trim().length < 5) {
            $('labdom-direccion').classList.add('is-invalid'); ok = false;
        }
        const esSeguro = document.getElementById('tc-seguro')?.checked;
        if (esSeguro && !fv('labdom-seguro-nombre')) {
            $('labdom-seguro-nombre').classList.add('is-invalid');
            ok = false;
        }
        // Validaciones de paciente nuevo
        if (_isNewPaciente) {
            const nom = fv('labdom-pac-nombre') || '';
            if (!nom) { $('labdom-pac-nombre').classList.add('is-invalid'); ok = false; }
            else if (!/^[\p{L}\s'\-\.]+$/u.test(nom)) { $('labdom-pac-nombre').classList.add('is-invalid'); ok = false; }
            else if (nom.trim().split(/\s+/).length < 2) { $('labdom-pac-nombre').classList.add('is-invalid'); ok = false; }

            const ced = fv('labdom-pac-cedula') || '';
            if (ced) {
                const tipodoc = $('labdom-pac-tipodoc')?.value || 'CC';
                if (['CC','TI','RC','CE'].includes(tipodoc)) {
                    const digits = ced.replace(/[^0-9]/g, '');
                    if (digits.length < 4 || digits.length > 12) { $('labdom-pac-cedula').classList.add('is-invalid'); ok = false; }
                }
            }
            const cor = fv('labdom-pac-correo') || '';
            if (cor && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(cor)) { $('labdom-pac-correo').classList.add('is-invalid'); ok = false; }
        }
        return ok;
    }

    // ── cargar historial de direcciones del paciente ─────────────────────────
    async function cargarHistorial(pacienteId) {
        $('labdom-hist-cont').classList.add('d-none');
        $('labdom-hist-chips').innerHTML = '';
        _histDirs = [];
        try {
            const r = await fetch(`api/lab/get_domicilios.php?paciente_id=${pacienteId}&limit=30&no_stats=1`);
            const d = await r.json();
            if (!d.data?.length) return;

            // Deduplicar por dirección+barrio, mantener las más recientes primero
            const vistas = new Set();
            const dirs   = [];
            for (const dom of d.data) {
                const key = (dom.direccion||'').toLowerCase().trim();
                if (!key || vistas.has(key)) continue;
                vistas.add(key);
                dirs.push({
                    direccion:        dom.direccion    || '',
                    barrio:           dom.barrio       || '',
                    ciudad:           dom.ciudad       || '',
                    indicaciones_dir: dom.indicaciones_dir || '',
                });
                if (dirs.length >= 4) break;   // máximo 4 chips
            }
            if (!dirs.length) return;

            _histDirs = dirs;
            const chips = $('labdom-hist-chips');
            dirs.forEach((dir, i) => {
                const label = dir.barrio ? `${dir.direccion} (${dir.barrio})` : dir.direccion;
                const btn   = document.createElement('button');
                btn.type      = 'button';
                btn.className = 'addr-chip';
                btn.title     = [dir.direccion, dir.barrio, dir.ciudad].filter(Boolean).join(' · ');
                btn.innerHTML = `<i class="fas fa-map-marker-alt me-1"></i>${escLab(label)}`;
                btn.onclick   = () => labDomicilio.usarDireccion(i);
                chips.appendChild(btn);
            });
            $('labdom-hist-cont').classList.remove('d-none');
        } catch (_) { /* silencioso */ }
    }

    // ── API pública ──────────────────────────────────────────────────────────
    return {

        /* Abre el modal desde el botón del mensaje imagen */
        abrir(msgId, localFile, convId) {
            _msgId            = msgId || null;
            _localFiles       = localFile ? [localFile] : [];
            _newOrderFiles    = [];
            _newOrderDataUrls = [];
            _convId           = convId   ? parseInt(convId) : null;
            // Guardar también el userId actual para usarContacto cuando convId no esté disponible
            if (!_convId && typeof chatApp !== 'undefined' && chatApp.currentUserId) {
                _convId = '__user:' + chatApp.currentUserId;
            }

            mostrarFormulario();
            labDomicilio._resetFull();

            labDomicilio._updateOrdenPreview();

            if (!_bsModal) _bsModal = new bootstrap.Modal('#modalAgendarDomicilio', { backdrop: true, keyboard: true });
            _bsModal.show();

            // Pre-cargar contacto de la conversación de forma silenciosa
            if (_convId) labDomicilio.usarContacto();
        },

        /* Reset completo (nombre paciente + todos los campos) */
        _resetFull() {
            _pacienteId       = null;
            _pacNombre        = '';
            _histDirs         = [];
            _newOrderFiles    = [];
            _newOrderDataUrls = [];
            _localFiles       = [];
            $('labdom-pac-busq').value = '';
            $('labdom-pac-list').style.display = 'none';
            $('labdom-pac-buscar').classList.remove('d-none');
            $('labdom-pac-card').classList.add('d-none');
            $('labdom-btn-cambiar-pac').classList.add('d-none');
            $('labdom-pac-contacto').classList.add('d-none');
            $('labdom-hist-cont').classList.add('d-none');
            $('labdom-hist-chips').innerHTML = '';
            $('labdom-header-pac').textContent = '';
            if ($('labdom-pac-nombre'))  $('labdom-pac-nombre').value  = '';
            if ($('labdom-pac-cedula'))  $('labdom-pac-cedula').value  = '';
            if ($('labdom-pac-tipodoc')) $('labdom-pac-tipodoc').value = 'CC';
            _isNewPaciente = false;
            if ($('labdom-nuevo-badge')) $('labdom-nuevo-badge').classList.add('d-none');
            // Resetear tipo cliente a "particular" por defecto
            const tc = document.getElementById('tc-particular');
            if (tc) tc.checked = true;
            labDomicilio._toggleSeguro();
            labDomicilio._resetDomicilio();
        },

        /* Renderiza la lista de archivos de la orden médica */
        _updateOrdenPreview() {
            const list = $('labdom-ordenes-list');
            if (!list) return;

            const iconCls = ext => {
                const m = { pdf:'fas fa-file-pdf text-danger', doc:'fas fa-file-word text-primary', docx:'fas fa-file-word text-primary', mp4:'fas fa-file-video text-warning', mkv:'fas fa-file-video text-warning', mp3:'fas fa-file-audio text-success', ogg:'fas fa-file-audio text-success', webm:'fas fa-file-video text-warning' };
                return m[ext] || 'fas fa-file text-secondary';
            };
            const isImgExt = ext => /^(jpg|jpeg|png|gif|webp|bmp)$/.test(ext);
            const chip = (label, thumb, delCall) => `<div class="labdom-orden-chip" title="${escLab(label)}">
                <div class="labdom-orden-thumb">${thumb}</div>
                <span class="labdom-orden-name">${escLab(label.length > 13 ? label.slice(0,10)+'\u2026' : label)}</span>
                <button type="button" class="labdom-orden-del" onclick="${delCall}" title="Eliminar">&times;</button>
            </div>`;

            let html = '';
            _localFiles.forEach((fname, i) => {
                const ext = (fname||'').split('.').pop().toLowerCase();
                const thumb = isImgExt(ext)
                    ? `<img src="uploads/media/${escLab(fname)}" style="width:100%;height:100%;object-fit:cover;border-radius:4px">`
                    : `<i class="${iconCls(ext)}" style="font-size:1.6rem"></i>`;
                html += chip(fname, thumb, `labDomicilio._eliminarOrden('local',${i})`);
            });
            _newOrderFiles.forEach((file, i) => {
                const ext = (file.name||'').split('.').pop().toLowerCase();
                const dUrl = _newOrderDataUrls[i] || '';
                const thumb = (isImgExt(ext) && dUrl)
                    ? `<img src="${dUrl}" style="width:100%;height:100%;object-fit:cover;border-radius:4px">`
                    : `<i class="${iconCls(ext)}" style="font-size:1.6rem"></i>`;
                html += chip(file.name, thumb, `labDomicilio._eliminarOrden('new',${i})`);
            });
            list.innerHTML = html;
        },

        /* Abre el selector de archivo (soporta múltiples) y los agrega a la lista */
        _agregarOrden() {
            const inp = document.getElementById('labdom-orden-input');
            if (!inp) return;
            const handler = (e) => {
                inp.removeEventListener('change', handler);
                const files = e.target.files ? Array.from(e.target.files) : [];
                if (!files.length) return;
                let pending = files.length;
                files.forEach(file => {
                    const ext     = (file.name||'').split('.').pop().toLowerCase();
                    const isImage = /^(jpg|jpeg|png|gif|webp|bmp)$/.test(ext);
                    if (isImage) {
                        const reader = new FileReader();
                        reader.onload = ev => {
                            _newOrderFiles.push(file);
                            _newOrderDataUrls.push(ev.target.result);
                            if (--pending === 0) labDomicilio._updateOrdenPreview();
                        };
                        reader.readAsDataURL(file);
                    } else {
                        _newOrderFiles.push(file);
                        _newOrderDataUrls.push('');
                        if (--pending === 0) labDomicilio._updateOrdenPreview();
                    }
                });
                inp.value = '';
            };
            inp.addEventListener('change', handler);
            inp.click();
        },

        /* Elimina un archivo específico de la lista */
        _eliminarOrden(type, idx) {
            if (type === 'local') {
                _localFiles.splice(idx, 1);
                if (!_localFiles.length && !_newOrderFiles.length) _msgId = null;
            } else {
                _newOrderFiles.splice(idx, 1);
                _newOrderDataUrls.splice(idx, 1);
            }
            labDomicilio._updateOrdenPreview();
        },

        /* Muestra/oculta el campo «nombre del seguro» según la selección */
        _toggleSeguro() {
            const esSeguro = document.getElementById('tc-seguro')?.checked;
            const row = $('labdom-seguro-row');
            if (!row) return;
            if (esSeguro) {
                row.classList.remove('d-none');
            } else {
                row.classList.add('d-none');
                if ($('labdom-seguro-nombre')) $('labdom-seguro-nombre').value = '';
            }
        },

        /* Calcula y muestra el total a cobrar al cliente */
        _calcTotal() {
            const dom    = parseFloat($('labdom-valor-domicilio')?.value) || 0;
            const copago = parseFloat($('labdom-valor-copago')?.value)    || 0;
            const total  = dom + copago;
            const row    = $('labdom-total-row');
            const label  = $('labdom-total-valor');
            if (!row || !label) return;
            if (total > 0) {
                label.textContent = '$' + total.toLocaleString('es-CO');
                row.style.display = '';
            } else {
                row.style.display = 'none';
            }
        },

        /* Reset solo los campos del domicilio (mantiene paciente) */
        _resetDomicilio() {
            $('labdom-fecha').value       = fechaManana();
            $('labdom-hora').value        = '07:00';
            $('labdom-tipo').value        = 'domicilio';
            $('labdom-direccion').value   = '';
            $('labdom-barrio').value      = '';
            $('labdom-ciudad').value      = '';
            $('labdom-indicaciones').value= '';
            $('labdom-examenes').value    = '';
            $('labdom-notas').value       = '';
            if ($('labdom-seguro-nombre'))    $('labdom-seguro-nombre').value    = '';
            if ($('labdom-autorizacion'))       $('labdom-autorizacion').value       = '';
            if ($('labdom-copago'))             $('labdom-copago').value             = '';
            if ($('labdom-valor-domicilio'))    $('labdom-valor-domicilio').value    = '';
            if ($('labdom-valor-copago'))       $('labdom-valor-copago').value       = '';
            labDomicilio._calcTotal();
            limpiarValidacion();
        },

        /* ── Buscar paciente ── */
        async buscarPaciente() {
            const q = fv('labdom-pac-busq');
            if (!q) return;
            const r    = await fetch(`api/lab/get_pacientes.php?busqueda=${encodeURIComponent(q)}&limit=8`);
            const d    = await r.json();
            const list = $('labdom-pac-list');
            if (!d.data?.length) {
                list.innerHTML     = '<a class="list-group-item list-group-item-action text-muted disabled small py-2">Sin resultados</a>';
                list.style.display = 'block';
                return;
            }
            list.innerHTML = d.data.map(p =>
                `<button type="button" class="list-group-item list-group-item-action py-2"
                    data-pid="${p.id}"
                    data-nombre="${escAtr(p.nombre_completo)}"
                    data-dir="${escAtr(p.direccion||'')}"
                    data-ciudad="${escAtr(p.ciudad||'')}"
                    data-info="${escAtr((p.tipo_documento||'') + ' ' + (p.numero_documento||''))}"
                    data-tipodoc="${escAtr(p.tipo_documento||'')}"
                    data-numdoc="${escAtr(p.numero_documento||'')}"
                    data-tel="${escAtr(p.telefono||'')}"
                    data-email="${escAtr(p.email||'')}"
                    data-eps="${escAtr(p.eps||'')}"
                    data-fnac="${escAtr(p.fecha_nacimiento||'')}"
                    onclick="labDomicilio._clickPaciente(this)">
                    <strong class="small">${escLab(p.nombre_completo)}</strong>
                    <span class="text-muted ms-2 small">${escLab(p.tipo_documento||'')} ${escLab(p.numero_documento||'')}</span>
                    ${p.telefono ? `<span class="ms-2 small text-success"><i class="fas fa-phone me-1"></i>${escLab(p.telefono)}</span>` : ''}
                    ${p.direccion ? `<br><small class="text-primary"><i class="fas fa-map-marker-alt me-1"></i>${escLab(p.direccion)}</small>` : ''}
                 </button>`
            ).join('');
            list.style.display = 'block';
        },

        _clickPaciente(btn) {
            labDomicilio.selPaciente(
                +btn.dataset.pid,
                btn.dataset.nombre,
                btn.dataset.dir,
                btn.dataset.ciudad,
                btn.dataset.info,
                btn.dataset.tel     || '',
                btn.dataset.email   || '',
                btn.dataset.eps     || '',
                btn.dataset.fnac    || '',
                btn.dataset.tipodoc || '',
                btn.dataset.numdoc  || ''
            );
        },

        selPaciente(id, nombre, direccion, ciudad, info, tel = '', email = '', eps = '', fechaNac = '', tipoDoc = '', numDoc = '') {
            _pacienteId = id;
            _pacNombre  = nombre;

            // Extraer tipo/num del campo 'info' como fallback si no se pasan explícitamente
            const _infoMatch = (info||'').match(/^([A-Z]+)\s*(.*)/);
            const _tipoFinal = tipoDoc || (_infoMatch?.[1] || 'CC');
            const _numFinal  = numDoc  || (_infoMatch?.[2]?.trim() || '');

            // Ocultar buscador, mostrar tarjeta
            $('labdom-pac-list').style.display   = 'none';
            $('labdom-pac-buscar').classList.add('d-none');
            $('labdom-pac-card').classList.remove('d-none');
            $('labdom-btn-cambiar-pac').classList.remove('d-none');
            if ($('labdom-pac-nombre'))  $('labdom-pac-nombre').value  = nombre;
            if ($('labdom-pac-cedula'))  $('labdom-pac-cedula').value  = _numFinal;
            if ($('labdom-pac-tipodoc')) $('labdom-pac-tipodoc').value = _tipoFinal;
            $('labdom-header-pac').textContent = nombre;

            // Mostrar datos de contacto
            if (tel || email) {
                $('labdom-pac-tel').textContent   = tel   || '—';
                $('labdom-pac-email').textContent  = email || '—';
                if (eps) {
                    $('labdom-pac-eps').textContent = eps;
                    $('labdom-pac-eps-row').style.display = '';
                } else {
                    $('labdom-pac-eps-row').style.display = 'none';
                }
                $('labdom-pac-contacto').classList.remove('d-none');
            } else {
                $('labdom-pac-contacto').classList.add('d-none');
            }

            // Pre-llenar campos solo si están vacíos
            if (direccion && !fv('labdom-direccion')) $('labdom-direccion').value = direccion;
            if (ciudad    && !fv('labdom-ciudad'))    $('labdom-ciudad').value    = ciudad;

            // Pre-llenar fecha de nacimiento y correo desde el paciente
            if ($('labdom-pac-fnac'))   $('labdom-pac-fnac').value   = fechaNac || '';
            if ($('labdom-pac-correo')) $('labdom-pac-correo').value = email    || '';

            // Cargar historial de domicilios previos
            cargarHistorial(id);
        },

        limpiarPaciente() {
            _pacienteId    = null;
            _isNewPaciente = false;
            _pacNombre     = '';
            $('labdom-pac-buscar').classList.remove('d-none');
            $('labdom-pac-card').classList.add('d-none');
            $('labdom-btn-cambiar-pac').classList.add('d-none');
            $('labdom-pac-busq').value = '';
            $('labdom-pac-contacto').classList.add('d-none');
            $('labdom-hist-cont').classList.add('d-none');
            $('labdom-header-pac').textContent = '';
            if ($('labdom-nuevo-badge')) $('labdom-nuevo-badge').classList.add('d-none');
            if ($('labdom-pac-fnac'))   $('labdom-pac-fnac').value   = '';
            if ($('labdom-pac-correo')) $('labdom-pac-correo').value = '';
            if ($('labdom-pac-nombre'))  $('labdom-pac-nombre').value  = '';
            if ($('labdom-pac-cedula'))  $('labdom-pac-cedula').value  = '';
            if ($('labdom-pac-tipodoc')) $('labdom-pac-tipodoc').value = 'CC';
            // Mantener dirección/notas para no perder lo escrito
        },

        /* Modo nuevo paciente: oculta el buscador y muestra la tarjeta vacía para registrar */
        crearNuevo() {
            _pacienteId    = null;
            _isNewPaciente = true;
            _pacNombre     = '';
            _histDirs      = [];

            $('labdom-pac-buscar').classList.add('d-none');
            $('labdom-pac-card').classList.remove('d-none');
            $('labdom-btn-cambiar-pac').classList.remove('d-none');
            $('labdom-pac-contacto').classList.add('d-none');
            $('labdom-hist-cont').classList.add('d-none');
            $('labdom-pac-eps-row') && ($('labdom-pac-eps-row').style.display = 'none');
            if ($('labdom-nuevo-badge')) $('labdom-nuevo-badge').classList.remove('d-none');

            // Limpiar campos del paciente
            if ($('labdom-pac-nombre'))  { $('labdom-pac-nombre').value  = ''; $('labdom-pac-nombre').focus(); }
            if ($('labdom-pac-cedula'))  $('labdom-pac-cedula').value  = '';
            if ($('labdom-pac-tipodoc')) $('labdom-pac-tipodoc').value = 'CC';
            if ($('labdom-pac-fnac'))    $('labdom-pac-fnac').value    = '';
            if ($('labdom-pac-correo'))  $('labdom-pac-correo').value  = '';
            $('labdom-header-pac').textContent = 'Nuevo paciente';
        },

        /* Aplica una dirección histórica al formulario */
        usarDireccion(idx) {
            const dir = _histDirs[idx];
            if (!dir) return;
            $('labdom-direccion').value    = dir.direccion;
            $('labdom-barrio').value       = dir.barrio;
            $('labdom-ciudad').value       = dir.ciudad;
            $('labdom-indicaciones').value = dir.indicaciones_dir;
            limpiarValidacion();
            // Resaltar brevemente el campo
            $('labdom-direccion').classList.add('border-primary');
            setTimeout(() => $('labdom-direccion').classList.remove('border-primary'), 1200);
        },

        /* Contacto de WhatsApp → paciente automático */
        async usarContacto() {
            // Obtener convId o userId del contexto actual
            let effectiveConvId = _convId;
            if (!effectiveConvId && typeof chatApp !== 'undefined' && chatApp.currentUserId) {
                effectiveConvId = '__user:' + chatApp.currentUserId;
            }
            if (!effectiveConvId) return;

            const spin = $('labdom-contacto-spin');
            const btn  = $('labdom-btn-contacto');
            try {
                spin.classList.remove('d-none');
                btn.classList.add('d-none');
                // Determinar si es user_id o conversation_id
                let qparam;
                if (String(effectiveConvId).startsWith('__user:')) {
                    qparam = `user_id=${String(effectiveConvId).slice(7)}`;
                } else {
                    qparam = `conversation_id=${effectiveConvId}`;
                }
                const r = await fetch(`api/lab/crear_desde_whatsapp.php?solo_paciente=1&${qparam}`);
                const d = await r.json();
                if (d.success && d.paciente) {
                    labDomicilio.selPaciente(
                        d.paciente.id,
                        d.paciente.nombre_completo,
                        d.paciente.direccion || '',
                        d.paciente.ciudad    || '',
                        [(d.paciente.tipo_documento||''), (d.paciente.numero_documento||'')].filter(Boolean).join(' '),
                        d.paciente.telefono  || '',
                        d.paciente.email     || '',
                        d.paciente.eps       || '',
                        d.paciente.fecha_nacimiento  || '',
                        d.paciente.tipo_documento    || '',
                        d.paciente.numero_documento  || ''
                    );
                    return;
                }
            } catch (_) { /* silencioso */ } finally {
                spin.classList.add('d-none');
                if (!_pacienteId) btn.classList.remove('d-none');
            }
        },

        /* ── Guardar domicilio ── */
        async guardar() {
            // Validar paciente
            if (!_pacienteId && !_isNewPaciente) {
                $('labdom-pac-busq')?.focus();
                $('labdom-pac-busq')?.classList.add('is-invalid');
                setTimeout(() => $('labdom-pac-busq')?.classList.remove('is-invalid'), 2000);
                return;
            }
            if (_isNewPaciente) {
                const nom = fv('labdom-pac-nombre');
                if (!nom) {
                    $('labdom-pac-nombre').classList.add('is-invalid');
                    $('labdom-pac-nombre').focus();
                    setTimeout(() => $('labdom-pac-nombre').classList.remove('is-invalid'), 2200);
                    return;
                }
            }
            if (!validar()) return;

            const btn = $('labdom-btn-guardar');
            btn.disabled  = true;
            btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Guardando…';

            // ── Crear paciente nuevo si aplica ────────────────────────────
            if (_isNewPaciente) {
                try {
                    const tipodoc = $('labdom-pac-tipodoc')?.value || 'CC';
                    const cedula  = fv('labdom-pac-cedula');
                    const fnac    = fv('labdom-pac-fnac');
                    const correo  = fv('labdom-pac-correo');
                    const payload = {
                        nombre_completo: fv('labdom-pac-nombre'),
                        tipo_documento:  tipodoc,
                    };
                    if (cedula)  payload.numero_documento = cedula;
                    if (fnac)    payload.fecha_nacimiento = fnac;
                    if (correo)  payload.email            = correo;

                    const r = await fetch('api/lab/save_paciente.php', {
                        method:  'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body:    JSON.stringify(payload),
                    });
                    const d = await r.json();
                    if (d.success && d.id) {
                        _pacienteId    = d.id;
                        _isNewPaciente = false;
                        if ($('labdom-nuevo-badge')) $('labdom-nuevo-badge').classList.add('d-none');
                    } else {
                        const errDiv = document.createElement('div');
                        errDiv.className = 'alert alert-danger alert-dismissible mt-2 py-2';
                        errDiv.innerHTML = `<i class="fas fa-exclamation-circle me-1"></i>${escLab(d.error||'No se pudo registrar el paciente')}
                            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>`;
                        $('labdom-form-panel').prepend(errDiv);
                        btn.disabled  = false;
                        btn.innerHTML = '<i class="fas fa-calendar-check me-1"></i> Agendar Domicilio';
                        return;
                    }
                } catch (err) {
                    btn.disabled  = false;
                    btn.innerHTML = '<i class="fas fa-calendar-check me-1"></i> Agendar Domicilio';
                    return;
                }
            }

            const datos = {
                paciente_id:          _pacienteId,
                fecha_programada:     fv('labdom-fecha'),
                hora_programada:      fv('labdom-hora')        || null,
                tipo_servicio:        fv('labdom-tipo'),
                tipo_cliente:         (document.querySelector('input[name="labdom-tipo-cliente"]:checked')?.value) || 'particular',
                seguro_nombre:        (document.querySelector('input[name="labdom-tipo-cliente"]:checked')?.value === 'seguro') ? (fv('labdom-seguro-nombre') || null) : null,
                autorizacion:         (document.querySelector('input[name="labdom-tipo-cliente"]:checked')?.value === 'seguro') ? (fv('labdom-autorizacion') || null) : null,
                copago_laboratorio:   (document.querySelector('input[name="labdom-tipo-cliente"]:checked')?.value === 'seguro') ? ($('labdom-copago').value || null) : null,
                valor_domicilio:      $('labdom-valor-domicilio').value || null,
                valor_copago:         $('labdom-valor-copago').value    || null,
                examenes_solicitados: fv('labdom-examenes')    || null,
                direccion:            fv('labdom-direccion'),
                barrio:               fv('labdom-barrio')        || null,
                ciudad:               fv('labdom-ciudad')        || null,
                indicaciones_dir:     fv('labdom-indicaciones')  || null,
                notas_admin:          fv('labdom-notas')         || null,
            };

            // ── Subir archivos nuevos de orden ─────────────────────────────
            for (const file of _newOrderFiles) {
                try {
                    const fd = new FormData();
                    fd.append('file', file);
                    const upRes  = await fetch('api/lab/upload_orden.php', { method: 'POST', body: fd });
                    const upData = await upRes.json();
                    if (upData.success && upData.local_file) _localFiles.push(upData.local_file);
                } catch (_e) { /* silencioso */ }
            }
            _newOrderFiles    = [];
            _newOrderDataUrls = [];

            // ── Crear registro de orden por cada archivo ───────────────────
            if (_localFiles.length) {
                try {
                    const convIdNum = (_convId && !String(_convId).startsWith('__user:'))
                        ? parseInt(_convId) : null;
                    const allOrdenIds = [];
                    for (let _fi = 0; _fi < _localFiles.length; _fi++) {
                        const orRes = await fetch('api/lab/crear_desde_whatsapp.php', {
                            method:  'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body:    JSON.stringify({
                                paciente_id:          _pacienteId,
                                local_file:           _localFiles[_fi],
                                media_message_id:     _fi === 0 ? (_msgId || null) : null,
                                conversation_id:      convIdNum,
                                examenes_solicitados: datos.examenes_solicitados,
                                estado:               'pendiente',
                            }),
                        });
                        const orData = await orRes.json();
                        if (orData.success && orData.orden_id) {
                            allOrdenIds.push(orData.orden_id);
                            if (_fi === 0) datos.orden_id = orData.orden_id;
                        }
                    }
                    datos._allOrdenIds = allOrdenIds;
                } catch (_) { /* no bloquea */ }
            }

            try {
                const r = await fetch('api/lab/save_domicilio.php', {
                    method:  'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body:    JSON.stringify(datos),
                });
                const d = await r.json();

                if (d.success) {
                    // Vincular todas las órdenes al domicilio recién creado
                    const _oids = datos._allOrdenIds;
                    if (Array.isArray(_oids) && _oids.length > 0 && d.id) {
                        fetch('api/lab/link_ordenes_domicilio.php', {
                            method:  'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body:    JSON.stringify({ domicilio_id: d.id, orden_ids: _oids }),
                        }).catch(() => {});
                    }

                    // Actualizar _pacNombre si el usuario editó el nombre en el input
                    const _editedNombre = fv('labdom-pac-nombre');
                    if (_editedNombre) _pacNombre = _editedNombre;

                    // Actualizar datos del paciente (nombre, cédula, fecha nacimiento, correo)
                    const updFnac    = fv('labdom-pac-fnac');
                    const updCorreo  = fv('labdom-pac-correo');
                    const updNombre  = fv('labdom-pac-nombre');
                    const updCedula  = fv('labdom-pac-cedula');
                    const updTipodoc = $('labdom-pac-tipodoc')?.value || '';
                    if (_pacienteId && (updFnac || updCorreo || updNombre || updCedula)) {
                        const patch = { id: _pacienteId };
                        if (updFnac)    patch.fecha_nacimiento = updFnac;
                        if (updCorreo)  patch.email            = updCorreo;
                        if (updNombre)  patch.nombre_completo  = updNombre;
                        if (updCedula)  patch.numero_documento = updCedula;
                        if (updTipodoc) patch.tipo_documento   = updTipodoc;
                        fetch('api/lab/save_paciente.php', {
                            method:  'POST',
                            headers: { 'Content-Type': 'application/json' },
                            body:    JSON.stringify(patch),
                        }).catch(() => {}); // silencioso — no bloquea el flujo
                    }

                    // Mostrar panel de éxito DENTRO del modal (no cierra)
                    $('labdom-form-panel').style.display = 'none';
                    $('labdom-footer').style.display     = 'none';
                    const horaStr = datos.hora_programada ? ` a las ${datos.hora_programada}` : '';
                    $('labdom-ok-msg').textContent  = `✅  Domicilio #${d.id} agendado`;
                    const tipoLabel = { particular:'👤 Particular', seguro:'🛡️ Seguro' };
                    if (datos.tipo_cliente === 'seguro' && datos.seguro_nombre) tipoLabel.seguro = `🛡️ Seguro — ${datos.seguro_nombre}`;
                    const examStr   = datos.examenes_solicitados ? `<br><small class="text-muted">${escLab(datos.examenes_solicitados)}</small>` : '';
                    $('labdom-ok-sub').innerHTML    =
                        `<strong>${escLab(_pacNombre)}</strong> · <span class="badge bg-secondary">${tipoLabel[datos.tipo_cliente]||datos.tipo_cliente}</span><br>${escLab(datos.direccion)}${examStr}<br><span class="badge bg-primary">${datos.fecha_programada}${horaStr}</span>`;
                    $('labdom-ok-ver').href          = `lab_domicilios.php?id=${d.id}`;
                    $('labdom-ok-asignar').href      = `lab_domicilios.php?id=${d.id}`;
                    $('labdom-ok-panel').classList.remove('d-none');

                    // Actualizar historial (puede haber nueva dirección)
                    if (_pacienteId) cargarHistorial(_pacienteId);
                } else {
                    // Error inline
                    const errDiv = document.createElement('div');
                    errDiv.className = 'alert alert-danger alert-dismissible mt-2 py-2';
                    errDiv.innerHTML = `<i class="fas fa-exclamation-circle me-1"></i>${escLab(d.error||'Error al guardar')}
                        <button type="button" class="btn-close" data-bs-dismiss="alert"></button>`;
                    $('labdom-form-panel').prepend(errDiv);
                }
            } finally {
                btn.disabled  = false;
                btn.innerHTML = '<i class="fas fa-calendar-check me-1"></i> Agendar Domicilio';
            }
        },

        /* "Agendar otro" → mantiene paciente, resetea solo el domicilio */
        agendarOtro() {
            mostrarFormulario();
            labDomicilio._resetDomicilio();
            // Si hay historial, enfocar notas (la dirección ya tiene chips para elegir)
            if (_histDirs.length) {
                $('labdom-notas').focus();
            } else {
                $('labdom-direccion').focus();
            }
        },

        /* Cierra el modal de citas si está visible y llama cb() al terminar */
        _cerrarCitas(cb) {
            const elC = document.getElementById('modalCitasPaciente');
            if (elC && elC.classList.contains('show')) {
                const inst = bootstrap.Modal.getInstance(elC);
                if (inst) {
                    elC.addEventListener('hidden.bs.modal', cb, { once: true });
                    inst.hide();
                    return;
                }
            }
            cb();
        },

        /* Abrir modal sin imagen (botón header o "nueva cita" desde citasModal) */
        abrirRapido() {
            // currentConversationId no siempre está disponible; usamos currentUserId como fallback
            const convId = (typeof chatApp !== 'undefined' && chatApp.currentConversationId)
                ? chatApp.currentConversationId : 0;
            this._cerrarCitas(() => labDomicilio.abrir(0, '', convId));
        },

        /* Abrir modal y pre-rellenar dirección detectada en un mensaje */
        abrirConTexto(convId, address) {
            this._cerrarCitas(() => {
                labDomicilio.abrir(0, '', convId);
                setTimeout(() => {
                    const inp = document.getElementById('labdom-direccion');
                    if (inp && address) { inp.value = address; inp.dispatchEvent(new Event('input')); }
                }, 140);
            });
        },

        /* Usar una dirección del historial de citas (desde citasModal) */
        _usarDirHistorial(dir) {
            this._cerrarCitas(() => {
                const inp = document.getElementById('labdom-direccion');
                if (inp) { inp.value = dir; inp.dispatchEvent(new Event('input')); }
                const el = document.getElementById('modalAgendarDomicilio');
                if (el) bootstrap.Modal.getOrCreateInstance(el).show();
            });
        },

        /* Devuelve el ID del paciente actualmente seleccionado */
        getPacienteId() { return _pacienteId || 0; },
        getPacienteNombre() {
            const el = document.getElementById('labdom-pac-nombre');
            return el ? (el.value !== undefined ? el.value.trim() : el.textContent.trim()) : '';
        },

        /* ─────────────────────────────────────────────────────────────────
         * SISTEMA DE SESIONES MINIMIZADAS
         * Al minimizar: guarda estado del form en memoria y muestra una
         * píldora en la barra inferior con el nombre del chat + paciente.
         * Al hacer clic en la píldora: navega al chat y restaura el form.
         * ───────────────────────────────────────────────────────────────── */

        minimizar() {
            // Capturar nombre e identificadores del chat activo
            const chatName   = document.getElementById('chat-name-text')?.textContent?.trim()
                            || document.getElementById('chat-name')?.textContent?.trim()
                            || 'Chat';
            const chatPhone  = document.getElementById('chat-phone')?.textContent?.trim() || '';
            const chatUserId = (typeof chatApp !== 'undefined') ? (chatApp.currentUserId || 0) : 0;

            // Construir objeto de sesión
            const sesion = {
                id:          ++_sesionIdCounter,
                chatUserId,
                chatName,
                chatPhone,
                convId:      _convId,
                pacienteId:  _pacienteId,
                pacNombre:   _pacNombre,
                histDirs:    [..._histDirs],
                msgId:           _msgId,
                localFiles:       [..._localFiles],
                newOrderFiles:    [..._newOrderFiles],
                newOrderDataUrls: [..._newOrderDataUrls],
                cardVisible:  !document.getElementById('labdom-pac-card')?.classList.contains('d-none'),
                pacInfo: {
                    nombre: document.getElementById('labdom-pac-nombre')?.value || '',
                    info:   [document.getElementById('labdom-pac-tipodoc')?.value, document.getElementById('labdom-pac-cedula')?.value].filter(Boolean).join(' ') || '',
                    tel:    document.getElementById('labdom-pac-tel')?.textContent    || '',
                    email:  document.getElementById('labdom-pac-email')?.textContent  || '',
                    eps:    document.getElementById('labdom-pac-eps')?.textContent    || '',
                },
                fields: {},
            };

            // Capturar todos los campos del formulario
            ['labdom-fecha','labdom-hora','labdom-tipo','labdom-direccion','labdom-barrio',
             'labdom-ciudad','labdom-indicaciones','labdom-examenes','labdom-notas',
             'labdom-seguro-nombre','labdom-autorizacion','labdom-copago',
             'labdom-valor-domicilio','labdom-valor-copago','labdom-pac-fnac',
             'labdom-pac-correo','labdom-pac-busq','labdom-pac-nombre','labdom-pac-cedula',
             'labdom-pac-tipodoc'].forEach(id => {
                const el = document.getElementById(id);
                if (el) sesion.fields[id] = el.value;
            });
            const tcr = document.querySelector('input[name="labdom-tipo-cliente"]:checked');
            sesion.fields['labdom-tipo-cliente'] = tcr ? tcr.value : 'particular';

            _sesiones.push(sesion);

            // Cerrar el modal completamente
            if (_bsModal) _bsModal.hide();

            // Renderizar píldoras
            labDomicilio._renderPills();
        },

        /* Renderiza la barra de píldoras de sesiones minimizadas */
        _renderPills() {
            let bar = document.getElementById('labdom-pills-bar');
            if (!bar) {
                bar = document.createElement('div');
                bar.id = 'labdom-pills-bar';
                document.body.appendChild(bar);
            }
            bar.innerHTML = _sesiones.map((s, i) => {
                const num = i + 1;
                return `
                <div class="labdom-pill" onclick="labDomicilio._restaurarSesion(${s.id})"
                     title="Restaurar — ${escLab(s.chatName)}${s.pacNombre ? ' · ' + escLab(s.pacNombre) : ''}">
                    <span class="labdom-pill-num">${num}</span>
                    <span class="labdom-pill-body">
                        <span class="labdom-pill-chat"><i class="fas fa-comments me-1" style="font-size:.65rem;opacity:.75"></i>${escLab(s.chatName)}</span>
                        ${s.pacNombre ? `<span class="labdom-pill-pac"><i class="fas fa-user me-1" style="font-size:.6rem;opacity:.7"></i>${escLab(s.pacNombre)}</span>` : `<span class="labdom-pill-pac" style="opacity:.65">Sin paciente aún</span>`}
                    </span>
                    <button type="button" class="labdom-pill-close"
                            onclick="event.stopPropagation();labDomicilio._descartarSesion(${s.id})"
                            title="Descartar esta sesión">✕</button>
                </div>`;
            }).join('');
        },

        /* Restaura una sesión minimizada (y navega al chat si hace falta) */
        async _restaurarSesion(sesId) {
            const sesion = _sesiones.find(s => s.id === sesId);
            if (!sesion) return;

            // Quitar de la lista ANTES de abrir el modal
            _sesiones = _sesiones.filter(s => s.id !== sesId);
            labDomicilio._renderPills();

            // Navegar al chat correspondiente si no es el activo
            const chatUserId = (typeof chatApp !== 'undefined') ? (chatApp.currentUserId || 0) : 0;
            if (sesion.chatUserId && sesion.chatUserId != chatUserId && typeof chatApp !== 'undefined') {
                const conv = chatApp.conversations?.find(c => c.user_id == sesion.chatUserId);
                if (conv) {
                    await chatApp.openConversation(conv.user_id, conv.name || sesion.chatName, conv.phone_number || sesion.chatPhone);
                    await new Promise(r => setTimeout(r, 280));
                }
            }

            // Mostrar formulario y limpiar DOM
            mostrarFormulario();
            labDomicilio._resetFull();  // limpia el DOM

            // Restaurar estado interno DESPUÉS del reset (para que no se sobreescriba)
            _convId       = sesion.convId;
            _pacienteId   = sesion.pacienteId;
            _pacNombre    = sesion.pacNombre;
            _histDirs     = sesion.histDirs || [];
            _msgId            = sesion.msgId;
            _localFiles       = sesion.localFiles    || (sesion.localFile    ? [sesion.localFile]    : []);
            _newOrderFiles    = sesion.newOrderFiles || (sesion.newOrderFile ? [sesion.newOrderFile] : []);
            _newOrderDataUrls = sesion.newOrderDataUrls || [];

            // Restaurar campos del formulario
            const f = sesion.fields || {};
            ['labdom-fecha','labdom-hora','labdom-tipo','labdom-direccion','labdom-barrio',
             'labdom-ciudad','labdom-indicaciones','labdom-examenes','labdom-notas',
             'labdom-seguro-nombre','labdom-autorizacion','labdom-copago',
             'labdom-valor-domicilio','labdom-valor-copago','labdom-pac-fnac',
             'labdom-pac-correo','labdom-pac-busq','labdom-pac-nombre','labdom-pac-cedula',
             'labdom-pac-tipodoc'].forEach(id => {
                const el = document.getElementById(id);
                if (el && f[id] !== undefined) el.value = f[id];
            });
            const tcVal = f['labdom-tipo-cliente'] || 'particular';
            const tcEl  = document.querySelector(`input[name="labdom-tipo-cliente"][value="${tcVal}"]`);
            if (tcEl) { tcEl.checked = true; labDomicilio._toggleSeguro(); }

            // Restaurar tarjeta de paciente si había uno seleccionado
            if (sesion.cardVisible && sesion.pacienteId) {
                const $ = id => document.getElementById(id);
                $('labdom-pac-buscar')?.classList.add('d-none');
                $('labdom-pac-card')?.classList.remove('d-none');
                $('labdom-btn-cambiar-pac')?.classList.remove('d-none');
                if ($('labdom-pac-nombre')) $('labdom-pac-nombre').value = sesion.pacInfo.nombre || f['labdom-pac-nombre'] || '';
                if ($('labdom-header-pac')) $('labdom-header-pac').textContent = sesion.pacNombre;
                if ($('labdom-pac-tel'))    $('labdom-pac-tel').textContent    = sesion.pacInfo.tel;
                if ($('labdom-pac-email'))  $('labdom-pac-email').textContent  = sesion.pacInfo.email;
                if (sesion.pacInfo.eps && $('labdom-pac-eps')) {
                    $('labdom-pac-eps').textContent = sesion.pacInfo.eps;
                    if ($('labdom-pac-eps-row')) $('labdom-pac-eps-row').style.display = '';
                }
                if (sesion.pacInfo.tel || sesion.pacInfo.email) {
                    $('labdom-pac-contacto')?.classList.remove('d-none');
                }
                if (sesion.histDirs?.length) {
                    $('labdom-hist-cont')?.classList.remove('d-none');
                    if ($('labdom-hist-chips')) {
                        $('labdom-hist-chips').innerHTML = sesion.histDirs.map((d, i) =>
                            `<button type="button" class="addr-chip" onclick="labDomicilio.usarDireccion(${i})">${escLab(d.direccion)}${d.barrio ? ', '+escLab(d.barrio):''}</button>`
                        ).join('');
                    }
                }
            }

            labDomicilio._calcTotal();
            labDomicilio._updateOrdenPreview();

            // Abrir el modal
            if (!_bsModal) _bsModal = new bootstrap.Modal('#modalAgendarDomicilio', { backdrop: true, keyboard: true });
            _bsModal.show();
        },

        /* Descartar sesión con confirmación */
        _descartarSesion(sesId) {
            const s = _sesiones.find(x => x.id === sesId);
            const nam = s ? `"${s.chatName}"` : 'este agendamiento';
            if (!confirm(`¿Descartar el agendamiento en curso de ${nam}? Se perderán los datos.`)) return;
            _sesiones = _sesiones.filter(x => x.id !== sesId);
            labDomicilio._renderPills();
        },

    };
})();

// Enter en el buscador de paciente
document.addEventListener('keydown', e => {
    if (e.target.id === 'labdom-pac-busq' && e.key === 'Enter') labDomicilio.buscarPaciente();
});
// Cerrar dropdown de paciente al hacer clic afuera
document.addEventListener('click', e => {
    const list = document.getElementById('labdom-pac-list');
    if (list && !list.contains(e.target) && e.target.id !== 'labdom-pac-busq') {
        list.style.display = 'none';
    }
});

const escLab = s  => String(s||'').replace(/[<>&"'`]/g, c =>({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;',"'":'&#39;','`':'&#96;'}[c]));
const escAtr = s  => String(s||'').replace(/"/g, '&quot;');

/* ─── Detección de direcciones colombianas en mensajes ─── */
window._detectAddr = function(text) {
    if (!text) return null;
    const re = /(calle|cl\.?|carrera|cra\.?|kr\.?|avenida|av\.?|diagonal|dg\.?|transversal|tv\.?|manzana|mz\.?)\s*[\d\w#\-\.]+[\s,][\s\d\w#\-\.°barrioaptolocalnorte sur este oeste]{4,60}/gi;
    const m = String(text).match(re);
    return (m && m.length) ? m[0].trim() : null;
};

window._labAddrBtn = function(msg, convId) {
    if (msg.direction === 'outgoing') return '';
    const addr = window._detectAddr(msg.body || msg.message || '');
    if (!addr) return '';
    const safeAddr = addr.replace(/'/g, "\\'").replace(/"/g, '&quot;');
    return `<button class="btn btn-sm btn-link text-success" title="Agendar domicilio en esta dirección"
        onclick="labDomicilio.abrirConTexto(${convId}, '${safeAddr}')"
        ><i class="fas fa-map-marker-alt"></i> Agendar</button>`;
};

/* ─── Modal "Ver citas del paciente" ─── */
const citasModal = (() => {
    let _pacId = 0;

    function abrir() {
        /* Obtener paciente del contexto actual de la conversación */
        const convId = (typeof chatApp !== 'undefined' && chatApp.currentConversationId)
            ? chatApp.currentConversationId : 0;

        const el = document.getElementById('modalCitasPaciente');
        if (!el) return;
        const bsM = bootstrap.Modal.getOrCreateInstance(el);
        bsM.show();

        /* Usar el paciente activo en labDomicilio si existe */
        _pacId = (typeof labDomicilio !== 'undefined' && labDomicilio.getPacienteId)
            ? labDomicilio.getPacienteId() : 0;
        if (_pacId) {
            const nom = labDomicilio.getPacienteNombre ? labDomicilio.getPacienteNombre() : '';
            const h = document.getElementById('citas-pac-nombre');
            if (h && nom) h.textContent = nom;
            cargar(_pacId);
        } else {
            /* Si no hay paciente seleccionado, obtenerlo desde la conversación o usuario activo */
            const userId = typeof chatApp !== 'undefined' ? (chatApp.currentUserId || 0) : 0;
            const qparam = convId ? `conversation_id=${convId}` : (userId ? `user_id=${userId}` : '');
            if (!qparam) { mostrarVacio('Abre una conversación primero.'); return; }
            fetch(`api/lab/crear_desde_whatsapp.php?solo_paciente=1&${qparam}`)
                .then(r => r.json())
                .then(d => {
                    if (d.success && d.paciente && d.paciente.id) {
                        _pacId = d.paciente.id;
                        document.getElementById('citas-pac-nombre').textContent = d.paciente.nombre_completo || '';
                        cargar(_pacId);
                    } else {
                        mostrarVacio('No se encontró paciente asociado a esta conversación.');
                    }
                })
                .catch(() => mostrarVacio('Error al obtener datos del paciente.'));
        }
    }

    function cargar(pacId) {
        const tbody = document.getElementById('citas-tbody');
        const header = document.getElementById('citas-pac-nombre');
        if (!tbody) return;
        tbody.innerHTML = `<tr><td colspan="5" class="text-center py-3"><div class="spinner-border spinner-border-sm text-primary" role="status"></div> Cargando…</td></tr>`;

        fetch(`api/lab/get_domicilios.php?paciente_id=${pacId}&limit=20`)
            .then(r => r.json())
            .then(data => {
                const rows = (data.data || data.domicilios || data || []);
                if (!rows.length) { mostrarVacio('Este paciente no tiene citas registradas.'); return; }
                if (rows[0] && rows[0].paciente_nombre && header) header.textContent = rows[0].paciente_nombre;
                tbody.innerHTML = rows.map(c => {
                    const estado = c.estado || 'pendiente';
                    const colorMap = {pendiente:'warning',confirmado:'info',en_camino:'primary',completado:'success',cancelado:'danger'};
                    const badge = `<span class="badge bg-${colorMap[estado]||'secondary'}">${estado}</span>`;
                    const tipo = c.tipo_cliente ? `<span class="badge bg-light text-dark border">${c.tipo_cliente}</span>` : '';
                    const dir = escLab(c.direccion || '');
                    const fecha = c.fecha_programada ? c.fecha_programada.substring(0,16).replace('T',' ') : (c.fecha || '—');
                    return `<tr>
                        <td class="text-nowrap small">${escLab(fecha)}</td>
                        <td class="small">${dir}</td>
                        <td>${badge}</td>
                        <td>${tipo}</td>
                        <td class="text-nowrap">
                            <button class="btn btn-xs btn-outline-success py-0 px-2 small" title="Agendar con esta dirección"
                                onclick="labDomicilio._usarDirHistorial('${dir.replace(/'/g,"\\'")}')">
                                <i class="fas fa-location-arrow me-1"></i>Usar
                            </button>
                        </td>
                    </tr>`;
                }).join('');
            })
            .catch(() => mostrarVacio('Error al cargar las citas.'));
    }

    function recargar() { if (_pacId) cargar(_pacId); }

    function mostrarVacio(msg) {
        const tbody = document.getElementById('citas-tbody');
        if (tbody) tbody.innerHTML = `<tr><td colspan="5" class="text-center text-muted py-3">${escLab(msg)}</td></tr>`;
    }

    return { abrir, recargar };
})();
</script>

<!-- Modal: Citas del paciente -->
<div class="modal fade" id="modalCitasPaciente" tabindex="-1" aria-labelledby="modalCitasPacienteTitulo" aria-hidden="true">
    <div class="modal-dialog modal-lg modal-dialog-scrollable">
        <div class="modal-content">
            <div class="modal-header bg-warning bg-opacity-10">
                <h5 class="modal-title" id="modalCitasPacienteTitulo">
                    <i class="fas fa-calendar-alt me-2 text-warning"></i>
                    Citas — <span id="citas-pac-nombre" class="text-primary">paciente</span>
                </h5>
                <div class="ms-auto d-flex gap-2 me-2">
                    <button class="btn btn-sm btn-outline-secondary" onclick="citasModal.recargar()" title="Recargar">
                        <i class="fas fa-sync-alt"></i>
                    </button>
                    <button class="btn btn-sm btn-outline-success" onclick="labDomicilio.abrirRapido()" title="Nueva cita">
                        <i class="fas fa-plus"></i> Nueva
                    </button>
                </div>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
            </div>
            <div class="modal-body p-0">
                <table class="table table-sm table-hover mb-0">
                    <thead class="table-light">
                        <tr>
                            <th>Fecha / Hora</th>
                            <th>Dirección</th>
                            <th>Estado</th>
                            <th>Tipo</th>
                            <th></th>
                        </tr>
                    </thead>
                    <tbody id="citas-tbody">
                        <tr><td colspan="5" class="text-center text-muted py-4">–</td></tr>
                    </tbody>
                </table>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
            </div>
        </div>
    </div>
</div>

<!-- Barra de sesiones minimizadas (agendamientos en paralelo) -->
<div id="labdom-pills-bar"></div>

<script>
// ── Disponibilidad del asesor (menú secundario no_advisor) ────────────────
(function() {
    var btn   = document.getElementById('advisor-mode-btn');
    var label = document.getElementById('advisor-mode-label');

    function applyState(available) {
        if (available) {
            btn.textContent  = '🟢 Disponible';
            btn.style.background = '#25d366';
            btn.style.color  = '#fff';
            if (label) label.textContent = 'El bot usa el menú principal';
        } else {
            btn.textContent  = '🔴 Ausente';
            btn.style.background = '#dc3545';
            btn.style.color  = '#fff';
            if (label) label.textContent = 'El bot usa el menú de asesor ausente';
        }
    }

    // Cargar estado inicial
    fetch('api/set_advisor_mode.php', { credentials: 'same-origin' })
        .then(function(r){ return r.json(); })
        .then(function(d){ if (d && d.success !== undefined) applyState(d.available); })
        .catch(function(){});

    window.toggleAdvisorMode = function() {
        var current = btn.textContent.indexOf('Disponible') !== -1;
        var next    = !current;
        fetch('api/set_advisor_mode.php', {
            method: 'POST',
            credentials: 'same-origin',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ available: next })
        })
        .then(function(r){ return r.json(); })
        .then(function(d){
            if (d && d.success) {
                applyState(d.available);
                var msg = d.available
                    ? '✅ Modo asesor disponible activado. El bot usará el menú principal.'
                    : '⚠️ Asesor marcado como ausente. El bot usará el menú secundario.';
                if (window.showToast) showToast(msg);
                else alert(msg);
            }
        })
        .catch(function(){ alert('Error al cambiar el modo de asesor.'); });
    };
})();
</script>

</body>
</html>