1882 lines
99 KiB
PHP
1882 lines
99 KiB
PHP
<?php
|
||
|
||
/**
|
||
* Página principal del sistema WhatsApp Bot
|
||
* Desarrollado por U-Site.app
|
||
*/
|
||
|
||
require_once 'config/config.php';
|
||
|
||
// Verificar autenticación - redirigir al login si no está logueado
|
||
if (!isUserLoggedIn()) {
|
||
header('Location: login.php');
|
||
exit('Acceso denegado. <a href="login.php">Iniciar sesión</a>');
|
||
}
|
||
|
||
// Verificar conexión a la base de datos
|
||
try {
|
||
$db = Database::getInstance();
|
||
$dbStatus = 'Conectado';
|
||
} catch (Exception $e) {
|
||
$dbStatus = 'Error: ' . $e->getMessage();
|
||
}
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="es">
|
||
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>WhatsApp Bot Manager</title>
|
||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||
<link href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet" />
|
||
<link href="./assets/css/styles.css?v=11" rel="stylesheet">
|
||
<link href="./assets/css/diagnostic.css?v=11" rel="stylesheet">
|
||
</head>
|
||
|
||
<body>
|
||
<!-- Sidebar -->
|
||
<nav class="sidebar">
|
||
<div class="sidebar-header">
|
||
<h4><i class="fas fa-robot"></i> WhatsApp Bot</h4>
|
||
</div>
|
||
<ul class="sidebar-menu">
|
||
<li><a href="#dashboard" class="nav-link active" data-tab="dashboard"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
|
||
<li><a href="#conversations" class="nav-link" data-tab="conversations"><i class="fas fa-comments"></i> Conversaciones <span id="conversations-count" class="sidebar-badge badge bg-danger" style="display:none; font-size:0.72rem; padding:0.15rem 0.4rem;">0</span></a></li>
|
||
<li><a href="#users" class="nav-link" data-tab="users"><i class="fas fa-users"></i> Usuarios</a></li>
|
||
<li><a href="#menus" class="nav-link" data-tab="menus"><i class="fas fa-list"></i> Menús</a></li>
|
||
<li><a href="#admin_users" class="nav-link" data-tab="admin_users"><i class="fas fa-user-shield"></i> Usuarios del Sistema</a></li>
|
||
<li><a href="#send_message" class="nav-link" data-tab="send_message"><i class="fas fa-paper-plane"></i> Enviar Mensaje</a></li>
|
||
<li><a href="#broadcast" class="nav-link" data-tab="broadcast"><i class="fas fa-broadcast-tower"></i> Envío Masivo <span class="badge bg-success ms-1">NUEVO</span></a></li>
|
||
<li><a href="scheduled_messages.php" class="nav-link"><i class="fas fa-clock"></i> Mensajes Programados</a></li>
|
||
<li><a href="#templates" class="nav-link" data-tab="templates"><i class="fas fa-file-text"></i> Plantillas</a></li>
|
||
<li><a href="#autoresponses" class="nav-link" data-tab="autoresponses"><i class="fas fa-robot"></i> Respuestas Auto</a></li>
|
||
<li><a href="#system_config" class="nav-link" data-tab="system_config"><i class="fas fa-cog"></i> Configuración</a></li>
|
||
<li><a href="#logs" class="nav-link" data-tab="logs"><i class="fas fa-file-alt"></i> Logs</a></li>
|
||
<li class="sidebar-divider"></li>
|
||
<li><a href="logout.php" class="nav-link logout-link" onclick="return confirm('¿Está seguro que desea cerrar sesión?')"><i class="fas fa-sign-out-alt"></i> Cerrar Sesión</a></li>
|
||
</ul>
|
||
</nav>
|
||
|
||
<!-- Main Content -->
|
||
<main class="main-content">
|
||
|
||
<!-- Notification toasts container (global) -->
|
||
<div id="global-notification-toasts" style="position:fixed; top:12px; right:12px; z-index:9999;"></div>
|
||
<!-- Header -->
|
||
<header class="content-header">
|
||
<div>
|
||
<h1><i class="fas fa-robot"></i> Sistema de Gestión WhatsApp Bot</h1>
|
||
<small class="text-muted">
|
||
<i class="fas fa-user"></i> Conectado como <strong><?= htmlspecialchars(
|
||
|
||
// Preferir nombre completo del admin si existe
|
||
|
||
(
|
||
$_SESSION['admin_user']['full_name'] ?? $_SESSION['admin_user']['username'] ?? ADMIN_USERNAME
|
||
)
|
||
) ?></strong>
|
||
| <i class="fas fa-clock"></i> Sesión desde: <?= date('H:i:s', $_SESSION['login_time'] ?? time()) ?>
|
||
</small>
|
||
</div>
|
||
<div class="header-actions">
|
||
<!-- Sidebar toggle (visible en pantallas pequeñas) -->
|
||
<button id="sidebar-toggle" class="btn btn-light d-md-none me-2" title="Abrir menú">
|
||
<i class="fas fa-bars"></i>
|
||
</button>
|
||
<div class="status-badge">
|
||
<span class="status-dot status-online"></span>
|
||
<span class="status-text">En línea</span>
|
||
</div> <!-- Notification bell -->
|
||
<div class="me-2">
|
||
<button id="notification-bell" class="btn btn-light position-relative" title="Notificaciones">
|
||
<i class="fas fa-bell"></i>
|
||
<span id="notification-count" class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" style="display:none;">0</span>
|
||
</button>
|
||
</div> <button class="btn btn-primary me-2" onclick="refreshData()">
|
||
<i class="fas fa-sync-alt"></i> Actualizar
|
||
</button>
|
||
<a href="logout.php" class="btn btn-outline-danger" onclick="return confirm('¿Está seguro que desea cerrar sesión?')">
|
||
<i class="fas fa-sign-out-alt"></i> Cerrar Sesión
|
||
</a>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Dashboard Tab -->
|
||
<div id="dashboard" class="tab-content active">
|
||
<div class="row mb-4">
|
||
<div class="col-xl-3 col-md-6 mb-4">
|
||
<div class="card card-stat bg-primary text-white">
|
||
<div class="card-body">
|
||
<div class="row align-items-center">
|
||
<div class="col">
|
||
<h6 class="card-stat-title">Total Usuarios</h6>
|
||
<h3 class="card-stat-number" id="total-users">-</h3>
|
||
</div>
|
||
<div class="col-auto">
|
||
<i class="fas fa-users fa-2x"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-xl-3 col-md-6 mb-4">
|
||
<div class="card card-stat bg-success text-white">
|
||
<div class="card-body">
|
||
<div class="row align-items-center">
|
||
<div class="col">
|
||
<h6 class="card-stat-title">Mensajes Hoy</h6>
|
||
<h3 class="card-stat-number" id="conversations-today">-</h3>
|
||
</div>
|
||
<div class="col-auto">
|
||
<i class="fas fa-comment fa-2x"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-xl-3 col-md-6 mb-4">
|
||
<div class="card card-stat bg-info text-white">
|
||
<div class="card-body">
|
||
<div class="row align-items-center">
|
||
<div class="col">
|
||
<h6 class="card-stat-title">Usuarios Activos</h6>
|
||
<h3 class="card-stat-number" id="active-users">-</h3>
|
||
</div>
|
||
<div class="col-auto">
|
||
<i class="fas fa-user-check fa-2x"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-xl-3 col-md-6 mb-4">
|
||
<div class="card card-stat bg-warning text-white">
|
||
<div class="card-body">
|
||
<div class="row align-items-center">
|
||
<div class="col">
|
||
<h6 class="card-stat-title">Total Mensajes</h6>
|
||
<h3 class="card-stat-number" id="total-conversations">-</h3>
|
||
</div>
|
||
<div class="col-auto">
|
||
<i class="fas fa-envelope fa-2x"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Recent Activity -->
|
||
<div class="row">
|
||
<div class="col-lg-8">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5><i class="fas fa-chart-line"></i> Actividad Reciente</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<canvas id="conversationsChart" height="100"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-4">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5><i class="fas fa-clock"></i> Últimos Mensajes</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="recent-conversations" class="list-group">
|
||
<!-- Contenido dinámico -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Conversations Tab -->
|
||
<div id="conversations" class="tab-content">
|
||
<div class="card conversation-card">
|
||
<div class="card-header bg-gradient-primary text-white">
|
||
<div class="row align-items-center">
|
||
<div class="col">
|
||
<h5 class="mb-0 d-flex align-items-center">
|
||
<i class="fas fa-comments"></i> Conversaciones
|
||
<a href="conversations.php" class="btn btn-sm btn-light ms-3" style="font-size:0.75rem;">Ver nueva versión</a>
|
||
</h5>
|
||
<small class="text-white-50">Administra tus chats de WhatsApp</small>
|
||
</div>
|
||
<div class="col-auto">
|
||
<div class="input-group">
|
||
<span class="input-group-text bg-light border-0">
|
||
<i class="fas fa-search"></i>
|
||
</span>
|
||
<input type="text" class="form-control border-0"
|
||
placeholder="Buscar conversaciones..."
|
||
id="search-conversations">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<div class="conversation-list" id="conversations-container">
|
||
<!-- Contenido dinámico -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Users Tab -->
|
||
<div id="users" class="tab-content">
|
||
<div class="card">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<h5><i class="fas fa-users"></i> Gestión de Usuarios</h5>
|
||
<button class="btn btn-primary" onclick="exportUsers()">
|
||
<i class="fas fa-download"></i> Exportar
|
||
</button>
|
||
</div>
|
||
<div class="card-body">
|
||
<!-- Búsqueda de usuarios -->
|
||
<div class="mb-3">
|
||
<div class="input-group">
|
||
<span class="input-group-text">
|
||
<i class="fas fa-search"></i>
|
||
</span>
|
||
<input
|
||
type="text"
|
||
class="form-control"
|
||
id="users-search"
|
||
placeholder="Buscar por teléfono, nombre o email..."
|
||
onkeyup="searchUsers()"
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-responsive">
|
||
<table class="table table-hover">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Teléfono</th>
|
||
<th>Nombre</th>
|
||
<th>Estado</th>
|
||
<th>Menú Actual</th>
|
||
<th>Registro</th>
|
||
<th>Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="users-table">
|
||
<!-- Contenido dinámico -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Paginación -->
|
||
<div id="users-pagination"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Menus Tab -->
|
||
<div id="menus" class="tab-content">
|
||
<div class="card">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<h5><i class="fas fa-list"></i> Menús Configurados</h5>
|
||
<button class="btn btn-primary" onclick="openCreateMenuModal()">
|
||
<i class="fas fa-plus"></i> Nuevo Menú
|
||
</button>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="table-responsive">
|
||
<table class="table table-hover">
|
||
<thead>
|
||
<tr>
|
||
<th>Nombre</th>
|
||
<th>Clave</th>
|
||
<th>Descripción</th>
|
||
<th>Estado</th>
|
||
<th>Opciones</th>
|
||
<th>Fecha Creación</th>
|
||
<th>Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="menus-table">
|
||
<!-- Contenido dinámico -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Admin Users Tab -->
|
||
<div id="admin_users" class="tab-content">
|
||
<div class="card">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<h5><i class="fas fa-user-shield"></i> Usuarios del Sistema</h5>
|
||
<button class="btn btn-primary" onclick="showCreateAdminUserModal()">
|
||
<i class="fas fa-plus"></i> Nuevo Usuario
|
||
</button>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="alert alert-info mb-3">
|
||
<i class="fas fa-info-circle"></i>
|
||
Gestiona los usuarios administradores que tienen acceso al sistema.
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table table-hover">
|
||
<thead>
|
||
<tr>
|
||
<th>Usuario</th>
|
||
<th>Nombre Completo</th>
|
||
<th>Email</th>
|
||
<th>Estado</th>
|
||
<th>Último Acceso</th>
|
||
<th>Fecha Creación</th>
|
||
<th>Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="admin-users-table">
|
||
<tr>
|
||
<td colspan="7" class="text-center">
|
||
<i class="fas fa-spinner fa-spin"></i> Cargando usuarios...
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- conversations Tab -->
|
||
<div id="send_message" class="tab-content">
|
||
<div class="row">
|
||
<div class="col-lg-6">
|
||
<div class="card">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<h5><i class="fas fa-paper-plane"></i> Enviar Mensaje</h5>
|
||
<div>
|
||
<button class="btn btn-sm btn-secondary" onclick="whatsappManager.loadconversations()">🔄 Actualizar</button>
|
||
</div>
|
||
</div>
|
||
<div class="card-body">
|
||
<form id="send-message-form">
|
||
<div class="mb-3">
|
||
<label class="form-label">Tipo de Destinatario</label>
|
||
<select class="form-control" id="recipient-type" onchange="toggleRecipientType()">
|
||
<option value="existing">Usuario Registrado</option>
|
||
<option value="manual">Número Manual (Solo Plantillas)</option>
|
||
</select>
|
||
</div>
|
||
<div class="mb-3" id="existing-recipient-group">
|
||
<label class="form-label">Destinatario</label>
|
||
<select class="form-control" id="message-recipient">
|
||
<option value="">Seleccionar usuario...</option>
|
||
</select>
|
||
</div>
|
||
<div class="mb-3" id="manual-recipient-group" style="display: none;">
|
||
<label class="form-label">Número de WhatsApp</label>
|
||
<input type="text" class="form-control" id="manual-recipient" placeholder="+57XXXXXXXXX">
|
||
<small class="form-text text-warning">
|
||
⚠️ Solo se pueden enviar plantillas aprobadas a números nuevos
|
||
</small>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Tipo de Mensaje</label>
|
||
<select class="form-control" id="message-type" onchange="toggleMessageType()">
|
||
<option value="text">Texto</option>
|
||
<option value="template">Plantilla</option>
|
||
</select>
|
||
</div>
|
||
<div class="mb-3" id="message-text-group">
|
||
<label class="form-label">Mensaje</label>
|
||
<textarea class="form-control" id="message-text" rows="4" placeholder="Escribe tu mensaje aquí..."></textarea>
|
||
</div>
|
||
<div class="mb-3" id="template-group" style="display: none;">
|
||
<label class="form-label">Plantilla</label>
|
||
<select class="form-control" id="message-template" onchange="loadMessageTemplateDetails()">
|
||
<option value="">Seleccionar plantilla...</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Variables de la plantilla -->
|
||
<div id="message-template-variables" class="mb-3" style="display: none;">
|
||
<!-- Se llenarán dinámicamente -->
|
||
</div>
|
||
|
||
<!-- Preview de la plantilla -->
|
||
<div id="message-template-preview" class="mb-3" style="display: none;">
|
||
<label class="form-label"><i class="fas fa-eye"></i> Vista Previa</label>
|
||
<div class="card bg-light">
|
||
<div class="card-body">
|
||
<div id="message-preview-content"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-primary">
|
||
<i class="fas fa-paper-plane"></i> Enviar Mensaje
|
||
</button>
|
||
<input type="hidden" name="template_params" id="template-params" value="">
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Broadcast Tab -->
|
||
<div id="broadcast" class="tab-content">
|
||
<div class="row">
|
||
<div class="col-lg-8 mx-auto">
|
||
<div class="card shadow-lg">
|
||
<div class="card-header bg-warning text-white">
|
||
<h4 class="mb-0"><i class="fas fa-broadcast-tower"></i> Envío Masivo de Mensajes</h4>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="alert alert-success mb-4">
|
||
<i class="fas fa-check-circle"></i>
|
||
<strong>¡Envío Masivo Activo!</strong> Ahora puedes enviar mensajes a múltiples usuarios simultáneamente.
|
||
</div>
|
||
|
||
<form id="broadcast-form" onsubmit="return handleBroadcastSubmit(event)">
|
||
<div class="mb-4">
|
||
<label class="form-label fw-bold">
|
||
<i class="fas fa-filter"></i> Tipo de Selección
|
||
</label>
|
||
<select class="form-control form-control-lg" id="broadcast-selection-type" onchange="toggleBroadcastSelectionType()">
|
||
<option value="filter">📋 Por filtro de usuarios</option>
|
||
<option value="specific">👥 Usuarios específicos</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Filtro de usuarios -->
|
||
<div class="mb-4" id="broadcast-filter-container">
|
||
<label class="form-label fw-bold">
|
||
<i class="fas fa-filter"></i> Filtro de Usuarios
|
||
</label>
|
||
<select class="form-control form-control-lg" id="broadcast-filter">
|
||
<option value="all">📋 Todos los usuarios activos</option>
|
||
<option value="active">🔥 Solo usuarios con conversaciones recientes (7 días)</option>
|
||
<option value="recent">✨ Usuarios recientes (30 días)</option>
|
||
</select>
|
||
<small class="form-text text-muted">
|
||
<i class="fas fa-info-circle"></i> Selecciona qué usuarios recibirán el mensaje
|
||
</small>
|
||
</div>
|
||
|
||
<!-- Selección específica de usuarios -->
|
||
<div class="mb-4" id="broadcast-users-container" style="display: none;">
|
||
<label class="form-label fw-bold">
|
||
<i class="fas fa-users"></i> Seleccionar Usuarios
|
||
</label>
|
||
<select class="form-control" id="broadcast-users" multiple="multiple">
|
||
<!-- Opciones cargadas dinámicamente -->
|
||
</select>
|
||
<small class="form-text text-muted">
|
||
<i class="fas fa-info-circle"></i> Selecciona uno o más usuarios para enviar el mensaje
|
||
</small>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<label class="form-label fw-bold">
|
||
<i class="fas fa-envelope"></i> Tipo de Mensaje
|
||
</label>
|
||
<select class="form-control form-control-lg" id="broadcast-message-type" onchange="toggleBroadcastMessageType()">
|
||
<option value="text">📝 Mensaje de texto</option>
|
||
<option value="template">📋 Plantilla de WhatsApp</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Mensaje de texto -->
|
||
<div class="mb-4" id="broadcast-text-container">
|
||
|
||
<label class="form-label fw-bold">
|
||
<i class="fas fa-comment-dots"></i> Mensaje
|
||
</label>
|
||
<textarea
|
||
class="form-control"
|
||
id="broadcast-message"
|
||
rows="6"
|
||
placeholder="Escribe el mensaje que deseas enviar a todos los usuarios...
|
||
|
||
Ejemplo:
|
||
¡Hola! 👋 Tenemos una promoción especial para ti..."
|
||
maxlength="4096"></textarea>
|
||
<div class="d-flex justify-content-between mt-2">
|
||
<small class="form-text text-muted">
|
||
<i class="fas fa-keyboard"></i> Máximo 4096 caracteres
|
||
</small>
|
||
<small class="form-text text-muted">
|
||
<span id="char-count">0</span> / 4096
|
||
</small>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Selección de plantilla -->
|
||
<div class="mb-4" id="broadcast-template-container" style="display: none;">
|
||
<label class="form-label fw-bold">
|
||
<i class="fas fa-file-text"></i> Seleccionar Plantilla
|
||
</label>
|
||
<select class="form-control form-control-lg" id="broadcast-template" onchange="loadBroadcastTemplateDetails()">
|
||
<option value="">-- Selecciona una plantilla --</option>
|
||
</select>
|
||
<small class="form-text text-muted">
|
||
<i class="fas fa-info-circle"></i> Solo plantillas aprobadas
|
||
</small>
|
||
</div>
|
||
|
||
<!-- Variables de plantilla -->
|
||
<div id="broadcast-template-variables" class="mb-4" style="display: none;">
|
||
<!-- Variables dinámicas -->
|
||
</div>
|
||
|
||
<!-- Vista previa de plantilla -->
|
||
<div id="broadcast-template-preview" class="mb-4" style="display: none;">
|
||
<label class="form-label fw-bold">
|
||
<i class="fas fa-eye"></i> Vista Previa
|
||
</label>
|
||
<div class="alert alert-info" id="broadcast-preview-content">
|
||
<!-- Contenido de vista previa -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="alert alert-warning d-flex align-items-center">
|
||
<i class="fas fa-exclamation-triangle fa-2x me-3"></i>
|
||
<div>
|
||
<strong>Importante:</strong> Este mensaje se enviará a múltiples usuarios.
|
||
<br>Verifica cuidadosamente el contenido antes de enviar.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-grid">
|
||
<button type="submit" class="btn btn-warning btn-lg">
|
||
<i class="fas fa-broadcast-tower"></i> Enviar Mensaje Masivo
|
||
</button>
|
||
</div>
|
||
</form>
|
||
|
||
<div class="mt-4">
|
||
<div class="card bg-light">
|
||
<div class="card-body">
|
||
<h6 class="fw-bold"><i class="fas fa-lightbulb text-warning"></i> Consejos para envío masivo:</h6>
|
||
<ul class="mb-0 small">
|
||
<li>Personaliza tu mensaje para que sea relevante</li>
|
||
<li>Evita enviar mensajes muy frecuentes</li>
|
||
<li>Respeta la ventana de 24 horas de WhatsApp</li>
|
||
<li>Usa plantillas aprobadas para nuevos contactos</li>
|
||
<li>Prueba con un grupo pequeño primero</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Templates Tab -->
|
||
<div id="templates" class="tab-content">
|
||
<div class="card">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<h5><i class="fas fa-file-text"></i> Plantillas de Mensaje</h5>
|
||
<div>
|
||
<button class="btn btn-success" id="btn-sync-templates" onclick="syncTemplatesFromFacebook()">
|
||
<i class="fas fa-sync-alt"></i> Sincronizar desde Facebook
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="card-body">
|
||
<!-- Mensaje informativo sobre creación de plantillas -->
|
||
<div class="alert alert-info" role="alert">
|
||
<h6 class="alert-heading"><i class="fas fa-info-circle"></i> ¿Cómo crear nuevas plantillas?</h6>
|
||
<p class="mb-2">Las plantillas de WhatsApp deben crearse directamente en el Administrador de Meta Business.</p>
|
||
<p class="mb-3">Una vez creadas y aprobadas por Meta, puedes sincronizarlas aquí usando el botón "Sincronizar desde Facebook".</p>
|
||
<a href="https://business.facebook.com/latest/whatsapp_manager/message_templates?business_id=2144688622601603"
|
||
class="btn btn-primary btn-sm" target="_blank" rel="noopener noreferrer">
|
||
<i class="fas fa-external-link-alt"></i> Ir al Administrador de Plantillas de Meta
|
||
</a>
|
||
</div>
|
||
|
||
<div class="table-responsive">
|
||
<table class="table table-hover">
|
||
<thead>
|
||
<tr>
|
||
<th>Nombre</th>
|
||
<th>Plantilla WhatsApp</th>
|
||
<th>Idioma</th>
|
||
<th>Categoría</th>
|
||
<th>Estado</th>
|
||
<th>Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="templates-table">
|
||
<!-- Contenido dinámico -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Auto Responses Tab -->
|
||
<div id="autoresponses" class="tab-content">
|
||
<div class="card">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<h5><i class="fas fa-robot"></i> Respuestas Automáticas</h5>
|
||
<button class="btn btn-primary" onclick="showCreateAutoResponseModal()">
|
||
<i class="fas fa-plus"></i> Nueva Respuesta
|
||
</button>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="table-responsive">
|
||
<table class="table table-hover">
|
||
<thead>
|
||
<tr>
|
||
<th>Tipo</th>
|
||
<th>Palabra Clave</th>
|
||
<th>Respuesta</th>
|
||
<th>Estado</th>
|
||
<th>Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="autoresponses-table">
|
||
<!-- Contenido dinámico -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- system_config Tab -->
|
||
<div id="system_config" class="tab-content">
|
||
<div class="row">
|
||
<div class="col-lg-8">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5><i class="fas fa-cog"></i> Configuración del Sistema</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<form id="system_config-form">
|
||
<div class="mb-4">
|
||
<h6 class="text-muted">WhatsApp Business API</h6>
|
||
<div class="mb-3">
|
||
<label class="form-label">Token de Acceso</label>
|
||
<input type="password" class="form-control" id="whatsapp-token" placeholder="Token de WhatsApp Business API">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Phone Number ID</label>
|
||
<input type="text" class="form-control" id="phone-number-id" placeholder="ID del número de teléfono">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Business Account ID (WABA ID)</label>
|
||
<input type="text" class="form-control" id="business-account-id" placeholder="ID de la cuenta de WhatsApp Business">
|
||
<small class="form-text text-muted">Necesario para sincronizar plantillas desde Facebook</small>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Webhook Verify Token</label>
|
||
<input type="text" class="form-control" id="webhook-token" placeholder="Token de verificación del webhook">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">API URL</label>
|
||
<input type="text" class="form-control" id="api-url" placeholder="URL de la API" value="https://graph.facebook.com/v22.0/">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<h6 class="text-muted">Configuración General</h6>
|
||
<div class="mb-3">
|
||
<label class="form-label">Nombre de la Empresa</label>
|
||
<input type="text" class="form-control" id="business-name" placeholder="Nombre de tu empresa">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Mensaje de Bienvenida</label>
|
||
<textarea class="form-control" id="welcome-message" rows="3" placeholder="Mensaje que se envía a usuarios nuevos"></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-primary">
|
||
<i class="fas fa-save"></i> Guardar Configuración
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-4">
|
||
<!-- Panel de Diagnóstico -->
|
||
<div class="card mb-3">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<h5><i class="fas fa-stethoscope"></i> Diagnóstico</h5>
|
||
<button class="btn btn-sm btn-outline-primary" onclick="runDiagnostic()">
|
||
<i class="fas fa-play"></i> Ejecutar
|
||
</button>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="diagnostic-results">
|
||
<p class="text-muted">Haz clic en "Ejecutar" para verificar el estado del sistema</p>
|
||
</div>
|
||
<div class="mt-3" id="diagnostic-actions" style="display: none;">
|
||
<a href="approve_templates.html" class="btn btn-success btn-sm" target="_blank">
|
||
🔧 Solucionar Problema de Plantillas
|
||
</a>
|
||
<a href="GUIA_ERROR_PLANTILLAS.md" class="btn btn-info btn-sm" target="_blank">
|
||
📋 Ver Guía Completa
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Información del Sistema -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5><i class="fas fa-info-circle"></i> Información del Sistema</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<dl class="row">
|
||
<dt class="col-sm-6">Versión:</dt>
|
||
<dd class="col-sm-6">1.0.0</dd>
|
||
|
||
<dt class="col-sm-6">PHP:</dt>
|
||
<dd class="col-sm-6"><?php echo PHP_VERSION; ?></dd>
|
||
|
||
<dt class="col-sm-6">Base de Datos:</dt>
|
||
<dd class="col-sm-6"><?php echo $dbStatus; ?></dd>
|
||
|
||
<dt class="col-sm-6">Webhook URL:</dt>
|
||
<dd class="col-sm-6">
|
||
<code><?php echo APP_URL; ?>/api/webhook.php</code>
|
||
</dd>
|
||
</dl>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Logs Tab -->
|
||
<div id="logs" class="tab-content">
|
||
<div class="card">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<h5><i class="fas fa-file-alt"></i> Logs del Sistema</h5>
|
||
<div>
|
||
<select class="form-select form-select-sm d-inline-block w-auto me-2" id="log-level-filter" onchange="filterLogs()">
|
||
<option value="all">Todos los niveles</option>
|
||
<option value="ERROR">❌ Error</option>
|
||
<option value="WARNING">⚠️ Warning</option>
|
||
<option value="INFO">ℹ️ Info</option>
|
||
<option value="DEBUG">🔧 Debug</option>
|
||
<option value="SUCCESS">✅ Success</option>
|
||
</select>
|
||
<button class="btn btn-outline-primary btn-sm" onclick="refreshLogs()">
|
||
<i class="fas fa-sync-alt"></i> Actualizar
|
||
</button>
|
||
<button class="btn btn-outline-danger btn-sm" onclick="clearLogs()" title="Limpiar logs del sistema">
|
||
<i class="fas fa-trash"></i> Limpiar
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="mb-3">
|
||
<input type="text" class="form-control" id="log-search" placeholder="🔍 Buscar en logs..." onkeyup="filterLogs()">
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table table-sm table-hover">
|
||
<thead>
|
||
<tr>
|
||
<th style="width: 140px;">Fecha/Hora</th>
|
||
<th style="width: 80px;">Nivel</th>
|
||
<th>Mensaje</th>
|
||
<th style="width: 120px;">Origen</th>
|
||
<th style="width: 80px;">Acciones</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="logs-table">
|
||
<!-- Contenido dinámico -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- Modal - Crear Plantilla -->
|
||
<div class="modal fade" id="createTemplateModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">📝 Nueva Plantilla de WhatsApp</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="template-form">
|
||
<div class="mb-3">
|
||
<label class="form-label">Nombre descriptivo *</label>
|
||
<input type="text" class="form-control" id="template-name" required placeholder="ej: Bienvenida Cliente">
|
||
<small class="form-text text-muted">Nombre interno para identificar la plantilla</small>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Nombre en WhatsApp *</label>
|
||
<input type="text" class="form-control" id="template-whatsapp-name" required placeholder="ej: welcome_customer">
|
||
<small class="form-text text-muted">Debe coincidir exactamente con el nombre en WhatsApp Business Manager</small>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Idioma</label>
|
||
<input type="text" class="form-control" id="template-language" name="template_language" value="<?= defined('APP_LANG') ? constant('APP_LANG') : 'es_ES' ?>" placeholder="ej: es_ES, en_US, pt_BR">
|
||
<small class="form-text text-muted">Código de idioma según WhatsApp (ej: es_ES, en_US, pt_BR)</small>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Categoría</label>
|
||
<select class="form-control" id="template-category">
|
||
<option value="utility">Utilidad</option>
|
||
<option value="marketing">Marketing</option>
|
||
<option value="authentication">Autenticación</option>
|
||
</select>
|
||
</div>
|
||
<div class="alert alert-info">
|
||
<strong>⚠️ Importante:</strong> La plantilla debe ser creada y aprobada primero en WhatsApp Business Manager.
|
||
Este formulario solo registra plantillas ya existentes en WhatsApp.
|
||
</div>
|
||
</form>
|
||
</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" onclick="app.saveTemplate()">Guardar Plantilla</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal de Crear Menú -->
|
||
<div class="modal fade" id="createMenuModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">📋 Nuevo Menú</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="menu-form">
|
||
<div class="mb-3">
|
||
<label class="form-label">Nombre del Menú *</label>
|
||
<input type="text" class="form-control" id="menu-name" required placeholder="ej: Menú Principal">
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Descripción</label>
|
||
<textarea class="form-control" id="menu-description" rows="3" placeholder="Descripción del menú (opcional)"></textarea>
|
||
</div>
|
||
<div class="alert alert-info">
|
||
<strong>ℹ️ Información:</strong> Después de crear el menú, podrás agregar opciones y configurar las respuestas.
|
||
</div>
|
||
</form>
|
||
</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" onclick="saveNewMenu()">Crear Menú</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal para crear/editar usuario administrador -->
|
||
<div class="modal fade" id="adminUserModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="adminUserModalLabel">
|
||
<i class="fas fa-user-shield"></i> <span id="admin-user-modal-title">Nuevo Usuario</span>
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="admin-user-form">
|
||
<input type="hidden" id="admin-user-id">
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Nombre de Usuario *</label>
|
||
<input type="text" class="form-control" id="admin-username" required
|
||
placeholder="usuario123">
|
||
<small class="text-muted">Usado para iniciar sesión</small>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Nombre Completo</label>
|
||
<input type="text" class="form-control" id="admin-fullname"
|
||
placeholder="Juan Pérez">
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Email</label>
|
||
<input type="email" class="form-control" id="admin-email"
|
||
placeholder="usuario@ejemplo.com">
|
||
</div>
|
||
|
||
<div class="mb-3" id="password-group">
|
||
<label class="form-label">Contraseña *</label>
|
||
<input type="password" class="form-control" id="admin-password"
|
||
placeholder="Mínimo 6 caracteres">
|
||
<small class="text-muted">Mínimo 6 caracteres</small>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||
<i class="fas fa-times"></i> Cancelar
|
||
</button>
|
||
<button type="button" class="btn btn-primary" onclick="saveAdminUser()">
|
||
<i class="fas fa-save"></i> Guardar
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal para cambiar contraseña -->
|
||
<div class="modal fade" id="changePasswordModal" tabindex="-1">
|
||
<div class="modal-dialog modal-sm">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">
|
||
<i class="fas fa-key"></i> Cambiar Contraseña
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="hidden" id="change-password-user-id">
|
||
<p class="mb-3">Usuario: <strong id="change-password-username"></strong></p>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Nueva Contraseña *</label>
|
||
<input type="password" class="form-control" id="new-password"
|
||
placeholder="Mínimo 6 caracteres" required>
|
||
<small class="text-muted">Mínimo 6 caracteres</small>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Confirmar Contraseña *</label>
|
||
<input type="password" class="form-control" id="confirm-password"
|
||
placeholder="Repite la contraseña" required>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||
<i class="fas fa-times"></i> Cancelar
|
||
</button>
|
||
<button type="button" class="btn btn-primary" onclick="saveNewPassword()">
|
||
<i class="fas fa-key"></i> Cambiar Contraseña
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Scripts -->
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||
<script src="./assets/js/app_simple.js?v=18"></script>
|
||
<script>
|
||
// Función de diagnóstico mejorada
|
||
function runDiagnostic() {
|
||
const resultsDiv = document.getElementById('diagnostic-results');
|
||
const actionsDiv = document.getElementById('diagnostic-actions');
|
||
resultsDiv.innerHTML = '<p class="text-muted"><i class="fas fa-spinner fa-spin"></i> Ejecutando diagnóstico...</p>';
|
||
|
||
fetch('/api/diagnose_whatsapp.php')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
let html = '<div class="diagnostic-report">';
|
||
|
||
// Configuración
|
||
html += `<div class="mb-3">
|
||
<h6><i class="fas fa-cog"></i> Configuración WhatsApp</h6>
|
||
<div class="alert alert-${data.config.status === 'complete' ? 'success' : 'warning'}">
|
||
${data.config.message} (${data.config.completion}% completa)
|
||
</div>
|
||
</div>`;
|
||
|
||
// Plantillas
|
||
const templateIssues = data.templates.approved === 0 && data.templates.pending > 0;
|
||
html += `<div class="mb-3">
|
||
<h6><i class="fas fa-file-alt"></i> Estado de Plantillas</h6>
|
||
<div class="alert alert-${templateIssues ? 'danger' : 'success'}">
|
||
${data.templates.message}
|
||
<br><small>Aprobadas: ${data.templates.approved} | Pendientes: ${data.templates.pending}</small>
|
||
</div>
|
||
</div>`;
|
||
|
||
// API
|
||
let apiMessage = '';
|
||
if (typeof data.api.message === 'object' && data.api.message !== null) {
|
||
// Si el mensaje es un objeto de error
|
||
if (data.api.message.message) {
|
||
apiMessage = `<strong>Error:</strong> ${data.api.message.message.split(':')[0]}`;
|
||
apiMessage += `<br><small class="text-muted">Verifica tu token de acceso y Phone Number ID</small>`;
|
||
} else {
|
||
apiMessage = 'Error en la conectividad de la API';
|
||
}
|
||
} else {
|
||
// Si el mensaje es un string simple
|
||
apiMessage = data.api.message;
|
||
}
|
||
|
||
html += `<div class="mb-3">
|
||
<h6><i class="fas fa-plug"></i> Conectividad API</h6>
|
||
<div class="alert alert-${data.api.status === 'ready' ? 'success' : 'danger'}">
|
||
${apiMessage}
|
||
</div>
|
||
</div>`;
|
||
|
||
// Recomendaciones
|
||
if (data.recommendations && data.recommendations.length > 0) {
|
||
html += `<div class="mb-3">
|
||
<h6><i class="fas fa-lightbulb"></i> Recomendaciones</h6>
|
||
<ul class="list-unstyled">`;
|
||
|
||
data.recommendations.forEach(rec => {
|
||
html += `<li class="mb-1"><i class="fas fa-arrow-right text-primary"></i> ${rec}</li>`;
|
||
});
|
||
|
||
html += `</ul></div>`;
|
||
}
|
||
|
||
html += '</div>';
|
||
resultsDiv.innerHTML = html;
|
||
|
||
// Mostrar botones de acción si hay problemas con plantillas
|
||
if (templateIssues) {
|
||
actionsDiv.style.display = 'block';
|
||
} else {
|
||
actionsDiv.style.display = 'none';
|
||
}
|
||
|
||
} else {
|
||
resultsDiv.innerHTML = `<div class="alert alert-danger">Error: ${data.message}</div>`;
|
||
actionsDiv.style.display = 'none';
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
resultsDiv.innerHTML = '<div class="alert alert-danger">Error de conectividad</div>';
|
||
actionsDiv.style.display = 'none';
|
||
});
|
||
}
|
||
|
||
// Lightbox helper para imágenes (abre un modal Bootstrap con la imagen en grande)
|
||
function openImageLightbox(url, caption = '') {
|
||
let img = document.getElementById('imageLightboxImg');
|
||
let cap = document.getElementById('imageLightboxCaption');
|
||
if (!img || !cap) return; // seguridad
|
||
img.src = url;
|
||
cap.textContent = caption || '';
|
||
const modalEl = document.getElementById('imageLightboxModal');
|
||
const bsModal = new bootstrap.Modal(modalEl);
|
||
bsModal.show();
|
||
}
|
||
</script>
|
||
|
||
<script>
|
||
// Enhancer: pedir nombre para la plantilla `contacto_nuevo` antes de enviar
|
||
(function(){
|
||
const form = document.getElementById('send-message-form');
|
||
if (!form) return;
|
||
|
||
form.addEventListener('submit', function(e){
|
||
try {
|
||
const typeEl = document.getElementById('message-type');
|
||
const tplEl = document.getElementById('message-template');
|
||
const recipientTypeEl = document.getElementById('recipient-type');
|
||
if (!typeEl || !tplEl) return; // nothing to do
|
||
|
||
const type = typeEl.value;
|
||
const tpl = tplEl.value;
|
||
|
||
if (type === 'template' && tpl === 'contacto_nuevo') {
|
||
// obtener valor por defecto para el prompt
|
||
let defaultName = '';
|
||
if (recipientTypeEl && recipientTypeEl.value === 'existing') {
|
||
const sel = document.getElementById('message-recipient');
|
||
if (sel && sel.options && sel.selectedIndex >= 0) defaultName = sel.options[sel.selectedIndex].text || '';
|
||
} else {
|
||
const manual = document.getElementById('manual-recipient');
|
||
if (manual) defaultName = manual.value || '';
|
||
}
|
||
|
||
const name = prompt('Ingrese el nombre para la plantilla contacto_nuevo:', defaultName);
|
||
if (name === null) {
|
||
// usuario canceló
|
||
e.preventDefault();
|
||
return false;
|
||
}
|
||
|
||
const paramsInput = document.getElementById('template-params');
|
||
if (paramsInput) {
|
||
const clean = (typeof name === 'string') ? name.trim() : '';
|
||
paramsInput.value = JSON.stringify({ name: clean || defaultName });
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('template name prompt failed', err);
|
||
}
|
||
});
|
||
})();
|
||
</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>
|
||
<img id="imageLightboxImg" src="" class="img-fluid w-100" style="border-radius:10px;">
|
||
<div id="imageLightboxCaption" class="mt-2 text-center text-white small"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal para crear menú -->
|
||
<div class="modal fade" id="createMenuModal" tabindex="-1" aria-labelledby="createMenuModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="createMenuModalLabel">
|
||
<i class="fas fa-plus"></i> Crear Nuevo Menú
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="menu-form">
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<div class="mb-3">
|
||
<label class="form-label">Nombre del Menú *</label>
|
||
<input type="text" class="form-control" id="menu-name" placeholder="ej: Menú Principal" required>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="mb-3">
|
||
<label class="form-label">Clave del Menú *</label>
|
||
<input type="text" class="form-control" id="menu-key" placeholder="ej: main_menu" required>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Descripción</label>
|
||
<textarea class="form-control" id="menu-description" rows="2" placeholder="Descripción del menú"></textarea>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Mensaje de Bienvenida</label>
|
||
<textarea class="form-control" id="menu-welcome-message" rows="3" placeholder="¡Hola! Bienvenido. Por favor selecciona una opción:"></textarea>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Estado</label>
|
||
<select class="form-control" id="menu-status">
|
||
<option value="active">Activo</option>
|
||
<option value="inactive">Inactivo</option>
|
||
</select>
|
||
</div>
|
||
|
||
<hr>
|
||
<h6>📋 Opciones del Menú</h6>
|
||
<div id="menu-options-container">
|
||
<!-- Opciones se agregan dinámicamente -->
|
||
</div>
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="addMenuOption()">
|
||
<i class="fas fa-plus"></i> Agregar Opción
|
||
</button>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||
<i class="fas fa-times"></i> Cancelar
|
||
</button>
|
||
<button type="button" class="btn btn-primary" onclick="app.saveMenu()">
|
||
<i class="fas fa-save"></i> Guardar Menú
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal para crear/editar autorespuesta -->
|
||
<div class="modal fade" id="createAutoResponseModal" tabindex="-1" aria-labelledby="createAutoResponseModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="createAutoResponseModalLabel">
|
||
<i class="fas fa-robot"></i> Crear Nueva Respuesta Automática
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="autoresponse-form">
|
||
<input type="hidden" id="autoresponse-id">
|
||
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<div class="mb-3">
|
||
<label class="form-label">Tipo de Trigger *</label>
|
||
<select class="form-control" id="autoresponse-trigger-type" required onchange="updateTriggerFields(this.value)">
|
||
<option value="keyword">Palabra Clave</option>
|
||
<option value="contains">Contiene Texto</option>
|
||
<option value="exact">Mensaje Exacto</option>
|
||
<option value="welcome">Mensaje de Bienvenida</option>
|
||
<option value="default">Respuesta por Defecto</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="mb-3">
|
||
<label class="form-label">Prioridad</label>
|
||
<input type="number" class="form-control" id="autoresponse-priority" value="1" min="0" max="10">
|
||
<small class="text-muted">Mayor número = mayor prioridad (0-10)</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-3" id="trigger-value-group">
|
||
<label class="form-label" id="trigger-value-label">Palabras Clave *</label>
|
||
<input type="text" class="form-control" id="autoresponse-trigger-value" placeholder="palabra1,palabra2,palabra3">
|
||
<small class="text-muted" id="trigger-value-help">Separa múltiples palabras con comas</small>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Texto de Respuesta *</label>
|
||
<textarea class="form-control" id="autoresponse-text" rows="4" placeholder="Escribe aquí la respuesta que se enviará automáticamente..." required></textarea>
|
||
<small class="text-muted">Puedes usar emojis y saltos de línea</small>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<div class="mb-3">
|
||
<label class="form-label">Tipo de Respuesta</label>
|
||
<select class="form-control" id="autoresponse-type">
|
||
<option value="text">Mensaje de Texto</option>
|
||
<option value="template">Plantilla WhatsApp</option>
|
||
<option value="menu">Mostrar Menú</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="mb-3">
|
||
<label class="form-label">Estado</label>
|
||
<select class="form-control" id="autoresponse-active">
|
||
<option value="1">Activa</option>
|
||
<option value="0">Inactiva</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Campos adicionales para diferentes tipos de respuesta -->
|
||
<div id="template-fields" style="display: none;">
|
||
<div class="mb-3">
|
||
<label class="form-label">Nombre de la Plantilla</label>
|
||
<input type="text" class="form-control" id="autoresponse-template-name" placeholder="nombre_plantilla">
|
||
</div>
|
||
</div>
|
||
|
||
<div id="menu-fields" style="display: none;">
|
||
<div class="mb-3">
|
||
<label class="form-label">Menú a Mostrar</label>
|
||
<select class="form-control" id="autoresponse-menu-id">
|
||
<option value="">Seleccionar menú...</option>
|
||
<!-- Opciones cargadas dinámicamente -->
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||
<i class="fas fa-times"></i> Cancelar
|
||
</button>
|
||
<button type="button" class="btn btn-primary" onclick="app.saveAutoResponse()">
|
||
<i class="fas fa-save"></i> Guardar Respuesta
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
// Global Notification Manager: polls server for unread notifications and shows toasts + native notifications
|
||
const NotificationManager = {
|
||
interval: 7000,
|
||
// IDs de notificaciones ya mostradas para evitar re-sonar/repetir
|
||
_seenIds: new Set(),
|
||
init() {
|
||
this.bell = document.getElementById('notification-bell');
|
||
this.countEl = document.getElementById('notification-count');
|
||
this.toastsContainer = document.getElementById('global-notification-toasts');
|
||
|
||
if (this.bell) {
|
||
this.bell.addEventListener('click', () => {
|
||
// switch to conversations tab
|
||
const convLink = document.querySelector('.nav-link[data-tab="conversations"]');
|
||
if (convLink) convLink.click();
|
||
// Request notification permission on explicit interaction
|
||
if ("Notification" in window && Notification.permission === 'default') {
|
||
Notification.requestPermission().then(p => console.log('Notification permission:', p));
|
||
}
|
||
// Al abrir la campana se considera que se atendieron visualmente las notificaciones: limpiar indicador visual
|
||
this.bell.classList.remove('has-notifications');
|
||
});
|
||
}
|
||
|
||
this.loadNotifications();
|
||
this._timer = setInterval(() => this.loadNotifications(), this.interval);
|
||
},
|
||
async loadNotifications() {
|
||
try {
|
||
const resp = await fetch('api/get_notifications.php', { credentials: 'same-origin' });
|
||
if (resp.status === 401) {
|
||
console.warn('Notifications fetch: unauthorized (session may have expired)');
|
||
this.showSessionExpiredToast();
|
||
return;
|
||
}
|
||
const json = await resp.json();
|
||
console.debug('NotificationManager.loadNotifications response:', json);
|
||
if (json && json.success && Array.isArray(json.data)) {
|
||
const unreadCount = json.data.length;
|
||
if (unreadCount > 0) {
|
||
this.countEl.textContent = unreadCount;
|
||
this.countEl.style.display = 'inline-block';
|
||
this.bell.classList.add('has-notifications');
|
||
} else {
|
||
this.countEl.style.display = 'none';
|
||
this.bell.classList.remove('has-notifications');
|
||
}
|
||
|
||
// Mostrar sólo las nuevas notificaciones (no repetir sonido/visual)
|
||
json.data.forEach(n => {
|
||
if (!this._seenIds.has(n.id)) {
|
||
this._seenIds.add(n.id);
|
||
this.showToast(n);
|
||
}
|
||
});
|
||
} else if (json && json.success === false) {
|
||
console.warn('Notification API error:', json.error || json);
|
||
}
|
||
} catch (e) {
|
||
console.error('Error loading global notifications', e);
|
||
}
|
||
},
|
||
showSessionExpiredToast() {
|
||
const toast = document.createElement('div');
|
||
toast.className = 'notification-toast';
|
||
toast.style.background = '#fff7f7';
|
||
toast.style.border = '1px solid #f5c6cb';
|
||
toast.style.padding = '10px 14px';
|
||
toast.style.marginTop = '8px';
|
||
toast.style.borderRadius = '6px';
|
||
toast.style.minWidth = '220px';
|
||
const msg = document.createElement('div');
|
||
msg.textContent = 'La sesión ha expirado. Por favor, vuelve a iniciar sesión.';
|
||
toast.appendChild(msg);
|
||
const btn = document.createElement('button');
|
||
btn.className = 'btn btn-sm btn-primary';
|
||
btn.textContent = 'Ir a login';
|
||
btn.onclick = () => { window.location.href = 'login.php'; };
|
||
const actions = document.createElement('div');
|
||
actions.style.marginTop = '8px';
|
||
actions.appendChild(btn);
|
||
toast.appendChild(actions);
|
||
this.toastsContainer.appendChild(toast);
|
||
// auto-eliminar toast de sesión expirada después de 1s
|
||
setTimeout(() => { if (toast.parentNode) toast.remove(); }, 1000);
|
||
},
|
||
showToast(notification) {
|
||
const toast = document.createElement('div');
|
||
toast.className = 'notification-toast';
|
||
toast.style.background = '#fff';
|
||
toast.style.padding = '10px 14px';
|
||
toast.style.border = '1px solid #ddd';
|
||
toast.style.boxShadow = '0 2px 6px rgba(0,0,0,0.12)';
|
||
toast.style.marginTop = '8px';
|
||
toast.style.borderRadius = '6px';
|
||
toast.style.minWidth = '220px';
|
||
|
||
const msg = document.createElement('div');
|
||
msg.textContent = notification.message || 'Nuevo evento';
|
||
toast.appendChild(msg);
|
||
|
||
const actions = document.createElement('div');
|
||
actions.style.marginTop = '8px';
|
||
actions.style.display = 'flex';
|
||
actions.style.gap = '8px';
|
||
|
||
const openBtn = document.createElement('button');
|
||
openBtn.className = 'btn btn-sm btn-primary';
|
||
openBtn.textContent = 'Abrir';
|
||
openBtn.onclick = async () => {
|
||
await fetch('api/mark_notification_read.php', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({id: notification.id})});
|
||
let data = {};
|
||
try { data = notification.data ? JSON.parse(notification.data) : {}; } catch(e) {}
|
||
const userId = data.user_id || notification.user_id;
|
||
if (userId) {
|
||
// marcar conversaciones como leídas también
|
||
try { await fetch('api/mark_conversation_read.php', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({user_id: userId})}); } catch(e) { console.warn('mark_conversation_read failed', e); }
|
||
|
||
const convLink = document.querySelector('.nav-link[data-tab="conversations"]');
|
||
if (convLink) convLink.click();
|
||
const tryOpen = () => {
|
||
if (window.chatApp && typeof window.chatApp.openConversation === 'function') {
|
||
window.chatApp.openConversation(userId, 'Usuario', '');
|
||
} else {
|
||
setTimeout(tryOpen, 300);
|
||
}
|
||
};
|
||
tryOpen();
|
||
}
|
||
toast.remove();
|
||
};
|
||
|
||
const dismiss = document.createElement('button');
|
||
dismiss.className = 'btn btn-sm btn-secondary';
|
||
dismiss.textContent = 'Descartar';
|
||
dismiss.onclick = async () => {
|
||
await fetch('api/mark_notification_read.php', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({id: notification.id})});
|
||
toast.remove();
|
||
};
|
||
|
||
actions.appendChild(openBtn);
|
||
actions.appendChild(dismiss);
|
||
toast.appendChild(actions);
|
||
|
||
this.toastsContainer.appendChild(toast);
|
||
|
||
// native notification
|
||
if ("Notification" in window && Notification.permission === 'granted') {
|
||
try {
|
||
const data = notification.data ? JSON.parse(notification.data) : {};
|
||
const n = new Notification(notification.message || 'Nuevo evento', { body: (data.phone || '') });
|
||
n.onclick = function() {
|
||
window.focus();
|
||
const userId = data.user_id || notification.user_id;
|
||
if (userId) {
|
||
// marcar conversación leída cuando se abre desde la notification nativa
|
||
try { fetch('api/mark_conversation_read.php', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({user_id: userId})}); } catch(e) { console.warn('mark_conversation_read failed', e); }
|
||
|
||
const convLink = document.querySelector('.nav-link[data-tab="conversations"]');
|
||
if (convLink) convLink.click();
|
||
const tryOpen = () => {
|
||
if (window.chatApp && typeof window.chatApp.openConversation === 'function') {
|
||
window.chatApp.openConversation(userId, 'Usuario', '');
|
||
} else {
|
||
setTimeout(tryOpen, 300);
|
||
}
|
||
};
|
||
tryOpen();
|
||
}
|
||
n.close();
|
||
};
|
||
// cerrar la notificación nativa automáticamente después de 1s
|
||
setTimeout(() => { try { n.close(); } catch(e) {} }, 1000);
|
||
} catch (e) { console.warn('native notification failed', e); }
|
||
}
|
||
|
||
// play sound
|
||
try {
|
||
// usar Web Audio API para reproducir un sonido breve
|
||
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); }
|
||
|
||
setTimeout(() => { if (toast.parentNode) toast.remove(); }, 1000);
|
||
}
|
||
};
|
||
|
||
// ====== Funciones para Broadcast con Plantillas y Múltiples Usuarios ======
|
||
|
||
// Manejador del formulario de broadcast
|
||
async function handleBroadcastSubmit(event) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
|
||
console.log('Formulario de broadcast enviado');
|
||
|
||
// Validar que existe el manager
|
||
if (!window.whatsappManager || !window.whatsappManager.sendBroadcast) {
|
||
console.error('whatsappManager no disponible');
|
||
alert('Error: Sistema no inicializado. Recarga la página.');
|
||
return false;
|
||
}
|
||
|
||
// Validar variables de plantilla si se seleccionó plantilla
|
||
const messageType = document.getElementById('broadcast-message-type').value;
|
||
if (messageType === 'template') {
|
||
const templateId = document.getElementById('broadcast-template').value;
|
||
if (!templateId) {
|
||
alert('Por favor selecciona una plantilla');
|
||
return false;
|
||
}
|
||
|
||
// Validar que las variables requeridas estén llenas
|
||
const varInputs = document.querySelectorAll('.broadcast-template-var');
|
||
let hasEmpty = false;
|
||
varInputs.forEach(input => {
|
||
if (!input.value.trim()) {
|
||
input.classList.add('is-invalid');
|
||
hasEmpty = true;
|
||
} else {
|
||
input.classList.remove('is-invalid');
|
||
}
|
||
});
|
||
|
||
if (hasEmpty) {
|
||
alert('Por favor completa todos los campos de variables de la plantilla');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Llamar al método del manager
|
||
await window.whatsappManager.sendBroadcast();
|
||
return false;
|
||
}
|
||
|
||
function initializeBroadcastSelect2() {
|
||
// Inicializar Select2 para usuarios con transport personalizado
|
||
$('#broadcast-users').select2({
|
||
theme: 'bootstrap-5',
|
||
placeholder: 'Buscar y seleccionar usuarios...',
|
||
allowClear: true,
|
||
ajax: {
|
||
transport: function (params, success, failure) {
|
||
const searchTerm = params.data.term || '';
|
||
const page = params.data.page || 1;
|
||
|
||
fetch(`api/get_users.php?search=${encodeURIComponent(searchTerm)}&page=${page}&limit=50`, {
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
}
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
success(data);
|
||
} else {
|
||
failure();
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error cargando usuarios:', error);
|
||
failure();
|
||
});
|
||
},
|
||
delay: 250,
|
||
processResults: function (data, params) {
|
||
params.page = params.page || 1;
|
||
if (!data || !data.data) {
|
||
return { results: [] };
|
||
}
|
||
return {
|
||
results: data.data.map(user => ({
|
||
id: user.id,
|
||
text: `${user.name || 'Sin nombre'} (${user.phone_number})`
|
||
})),
|
||
pagination: {
|
||
more: data.pagination && data.pagination.hasNext
|
||
}
|
||
};
|
||
},
|
||
cache: true
|
||
},
|
||
minimumInputLength: 0
|
||
});
|
||
|
||
// Cargar plantillas aprobadas
|
||
loadBroadcastTemplates();
|
||
}
|
||
|
||
function toggleBroadcastSelectionType() {
|
||
const selectionType = document.getElementById('broadcast-selection-type').value;
|
||
const filterContainer = document.getElementById('broadcast-filter-container');
|
||
const usersContainer = document.getElementById('broadcast-users-container');
|
||
|
||
if (selectionType === 'filter') {
|
||
filterContainer.style.display = 'block';
|
||
usersContainer.style.display = 'none';
|
||
} else {
|
||
filterContainer.style.display = 'none';
|
||
usersContainer.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
function toggleBroadcastMessageType() {
|
||
const messageType = document.getElementById('broadcast-message-type').value;
|
||
const textContainer = document.getElementById('broadcast-text-container');
|
||
const templateContainer = document.getElementById('broadcast-template-container');
|
||
|
||
if (messageType === 'text') {
|
||
textContainer.style.display = 'block';
|
||
templateContainer.style.display = 'none';
|
||
document.getElementById('broadcast-template-variables').style.display = 'none';
|
||
document.getElementById('broadcast-template-preview').style.display = 'none';
|
||
} else {
|
||
textContainer.style.display = 'none';
|
||
templateContainer.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
async function loadBroadcastTemplates() {
|
||
try {
|
||
console.log('🔵 Cargando plantillas para broadcast...');
|
||
|
||
if (!window.whatsappManager) {
|
||
console.error('❌ whatsappManager no está disponible');
|
||
return;
|
||
}
|
||
|
||
const response = await window.whatsappManager.apiCall('get_templates.php');
|
||
console.log('Respuesta de get_templates:', response);
|
||
|
||
if (response && response.success && response.data) {
|
||
const select = document.getElementById('broadcast-template');
|
||
if (!select) {
|
||
console.error('Elemento broadcast-template no encontrado');
|
||
return;
|
||
}
|
||
|
||
select.innerHTML = '<option value="">-- Selecciona una plantilla --</option>';
|
||
|
||
const approvedTemplates = response.data.filter(t =>
|
||
t.status && (t.status.toUpperCase() === 'APPROVED' || t.status.toLowerCase() === 'approved')
|
||
);
|
||
console.log('Plantillas aprobadas encontradas:', approvedTemplates.length);
|
||
|
||
approvedTemplates.forEach(template => {
|
||
const option = document.createElement('option');
|
||
option.value = template.id;
|
||
option.textContent = `${template.name} (${template.language_code || template.language || 'es'})`;
|
||
option.dataset.templateId = template.id;
|
||
select.appendChild(option);
|
||
});
|
||
|
||
if (approvedTemplates.length === 0) {
|
||
console.warn('No hay plantillas aprobadas disponibles');
|
||
select.innerHTML = '<option value="">No hay plantillas aprobadas</option>';
|
||
}
|
||
} else {
|
||
console.error('Respuesta inválida del API:', response);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error cargando plantillas:', error);
|
||
}
|
||
}
|
||
|
||
async function loadBroadcastTemplateDetails() {
|
||
console.log('🔵 loadBroadcastTemplateDetails llamado');
|
||
const select = document.getElementById('broadcast-template');
|
||
const templateId = select.value;
|
||
console.log('Template ID seleccionado:', templateId);
|
||
|
||
if (!templateId) {
|
||
document.getElementById('broadcast-template-variables').style.display = 'none';
|
||
document.getElementById('broadcast-template-preview').style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await window.whatsappManager.apiCall(`get_template_details.php?id=${templateId}`);
|
||
console.log('✅ Detalles de plantilla:', response);
|
||
|
||
if (response && response.success) {
|
||
const template = response.template || response.data;
|
||
if (!template) {
|
||
console.error('❌ No se encontró template en la respuesta');
|
||
return;
|
||
}
|
||
|
||
console.log('📋 Template data:', template);
|
||
console.log('📋 Variables:', template.variables);
|
||
|
||
const variablesContainer = document.getElementById('broadcast-template-variables');
|
||
|
||
if (template.variables && template.variables.length > 0) {
|
||
console.log(`✅ Se encontraron ${template.variables.length} variables`);
|
||
let html = '<label class="form-label fw-bold"><i class="fas fa-edit"></i> Variables de la Plantilla</label>';
|
||
|
||
template.variables.forEach((variable, index) => {
|
||
console.log(`Variable ${index}:`, variable);
|
||
const placeholder = variable.example || `Valor para Variable ${variable.index}`;
|
||
const label = variable.label || `Variable ${variable.index}`;
|
||
|
||
html += `
|
||
<div class="mb-2">
|
||
<label class="form-label small">${label}</label>
|
||
<input
|
||
type="text"
|
||
class="form-control broadcast-template-var"
|
||
data-var="${variable.index}"
|
||
data-placeholder="${variable.placeholder}"
|
||
placeholder="${placeholder}"
|
||
onkeyup="updateBroadcastPreview()"
|
||
>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
console.log('📝 HTML generado para variables:', html);
|
||
variablesContainer.innerHTML = html;
|
||
variablesContainer.style.display = 'block';
|
||
console.log('✅ Variables container mostrado');
|
||
} else {
|
||
console.log('⚠️ Template sin variables');
|
||
variablesContainer.innerHTML = '<p class="text-muted small">Esta plantilla no tiene variables</p>';
|
||
variablesContainer.style.display = 'block';
|
||
}
|
||
|
||
updateBroadcastPreview();
|
||
} else {
|
||
console.error('❌ Respuesta sin success:', response);
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Error cargando detalles de plantilla:', error);
|
||
window.whatsappManager.showError('Error cargando plantilla');
|
||
}
|
||
}
|
||
|
||
async function updateBroadcastPreview() {
|
||
const templateId = document.getElementById('broadcast-template').value;
|
||
if (!templateId) return;
|
||
|
||
// Recolectar variables como array (por índice)
|
||
const variablesArray = [];
|
||
document.querySelectorAll('.broadcast-template-var').forEach(input => {
|
||
const index = parseInt(input.dataset.var) - 1; // Convertir a índice de array (0-based)
|
||
variablesArray[index] = input.value || `{{${input.dataset.var}}}`;
|
||
});
|
||
|
||
try {
|
||
const response = await window.whatsappManager.apiCall('preview_template.php', {
|
||
method: 'POST',
|
||
body: { template_id: templateId, parameters: variablesArray }
|
||
});
|
||
|
||
if (response && response.success && response.preview) {
|
||
const previewContainer = document.getElementById('broadcast-template-preview');
|
||
const previewContent = document.getElementById('broadcast-preview-content');
|
||
|
||
// Usar el HTML pre-formateado del servidor
|
||
previewContent.innerHTML = response.preview.html || response.preview.body.replace(/\n/g, '<br>');
|
||
previewContainer.style.display = 'block';
|
||
} else {
|
||
console.error('Error en preview:', response);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error generando vista previa:', error);
|
||
}
|
||
}
|
||
|
||
// ====== Fin de funciones de Broadcast ======
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
try { NotificationManager.init(); } catch(e) { console.error('NotificationManager init failed', e); }
|
||
|
||
// Inicializar Select2 para broadcast cuando el manager esté listo
|
||
const waitForManager = setInterval(() => {
|
||
if (window.whatsappManager) {
|
||
clearInterval(waitForManager);
|
||
console.log('Inicializando broadcast Select2...');
|
||
try {
|
||
initializeBroadcastSelect2();
|
||
} catch(e) {
|
||
console.error('Error inicializando broadcast Select2:', e);
|
||
}
|
||
}
|
||
}, 100);
|
||
|
||
// Timeout de seguridad
|
||
setTimeout(() => clearInterval(waitForManager), 5000);
|
||
|
||
// Sidebar toggle logic for responsive/mobile
|
||
const sidebar = document.querySelector('.sidebar');
|
||
const toggleBtn = document.getElementById('sidebar-toggle');
|
||
let overlayEl = null;
|
||
|
||
function ensureOverlay() {
|
||
if (!overlayEl) {
|
||
overlayEl = document.createElement('div');
|
||
overlayEl.className = 'sidebar-overlay';
|
||
document.body.appendChild(overlayEl);
|
||
overlayEl.addEventListener('click', closeSidebar);
|
||
}
|
||
}
|
||
|
||
function openSidebar() {
|
||
if (!sidebar) return;
|
||
sidebar.classList.add('open');
|
||
document.body.classList.add('sidebar-open');
|
||
ensureOverlay();
|
||
overlayEl.style.display = 'block';
|
||
}
|
||
|
||
function closeSidebar() {
|
||
if (!sidebar) return;
|
||
sidebar.classList.remove('open');
|
||
document.body.classList.remove('sidebar-open');
|
||
if (overlayEl) overlayEl.style.display = 'none';
|
||
}
|
||
|
||
if (toggleBtn) {
|
||
console.debug('[index] sidebar toggle found:', { toggleBtn: !!toggleBtn, sidebar: !!sidebar });
|
||
toggleBtn.addEventListener('click', (e) => {
|
||
console.debug('[index] sidebar-toggle clicked', { isOpen: sidebar && sidebar.classList.contains('open') });
|
||
e.stopPropagation();
|
||
if (sidebar && sidebar.classList.contains('open')) {
|
||
closeSidebar();
|
||
} else {
|
||
openSidebar();
|
||
}
|
||
});
|
||
}
|
||
|
||
// debug logs for open/close
|
||
const _openSidebar = openSidebar;
|
||
const _closeSidebar = closeSidebar;
|
||
openSidebar = function() { console.debug('[index] openSidebar()'); _openSidebar(); };
|
||
closeSidebar = function() { console.debug('[index] closeSidebar()'); _closeSidebar(); };
|
||
|
||
// Close sidebar when a nav item is selected (mobile UX improvement)
|
||
document.querySelectorAll('.nav-link[data-tab]').forEach(link => {
|
||
link.addEventListener('click', () => {
|
||
if (window.innerWidth < 992) {
|
||
closeSidebar();
|
||
}
|
||
});
|
||
});
|
||
|
||
// Close sidebar on Escape
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape') closeSidebar();
|
||
});
|
||
|
||
// Close sidebar when navigating in main content on small screens
|
||
document.querySelector('.main-content')?.addEventListener('click', () => {
|
||
if (window.innerWidth < 992) closeSidebar();
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
|
||
</html>
|