1113 lines
58 KiB
PHP
1113 lines
58 KiB
PHP
<?php
|
||
|
||
/**
|
||
* Página principal del sistema WhatsApp Bot
|
||
* Fecha: 13 de noviembre de 2025
|
||
*/
|
||
|
||
// Verificar si la instalación está completada
|
||
$installationLockFile = '.installation_completed';
|
||
if (!file_exists($installationLockFile)) {
|
||
header('Location: instalacion.html');
|
||
exit();
|
||
}
|
||
|
||
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="./assets/css/styles.css" rel="stylesheet">
|
||
<link href="./assets/css/diagnostic.css" 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</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="#send_message" class="nav-link" data-tab="send_message"><i class="fas fa-paper-plane"></i> Enviar Mensaje</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(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">
|
||
<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"><i class="fas fa-comments"></i> Conversaciones</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">
|
||
<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>
|
||
</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>
|
||
|
||
<!-- 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">
|
||
<option value="">Seleccionar plantilla...</option>
|
||
</select>
|
||
</div>
|
||
<button type="submit" class="btn btn-primary">
|
||
<i class="fas fa-paper-plane"></i> Enviar Mensaje
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-6" id="broadcast-section" style="display:none;">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5><i class="fas fa-broadcast-tower"></i> Mensaje Masivo (oculto)</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<form id="broadcast-form">
|
||
<div class="mb-3">
|
||
<label class="form-label">Filtro de Usuarios</label>
|
||
<select class="form-control" id="broadcast-filter">
|
||
<option value="all">Todos los usuarios</option>
|
||
<option value="active">Solo usuarios activos</option>
|
||
<option value="recent">Usuarios recientes (30 días)</option>
|
||
</select>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Mensaje</label>
|
||
<textarea class="form-control" id="broadcast-message" rows="4" placeholder="Mensaje para envío masivo..."></textarea>
|
||
</div>
|
||
<div class="alert alert-warning">
|
||
<i class="fas fa-exclamation-triangle"></i>
|
||
<strong>Cuidado:</strong> Este mensaje se enviará a múltiples usuarios.
|
||
</div>
|
||
<button type="submit" class="btn btn-warning">
|
||
<i class="fas fa-broadcast-tower"></i> Envío Masivo
|
||
</button>
|
||
</form>
|
||
</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>
|
||
<button class="btn btn-primary" id="btn-new-template" onclick="showCreateTemplateModal()">
|
||
<i class="fas fa-plus"></i> Nueva Plantilla
|
||
</button>
|
||
</div>
|
||
<div class="card-body">
|
||
<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">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>
|
||
<button class="btn btn-outline-primary" onclick="refreshLogs()">
|
||
<i class="fas fa-sync-alt"></i> Actualizar
|
||
</button>
|
||
<button class="btn btn-outline-danger" onclick="clearLogs()">
|
||
<i class="fas fa-trash"></i> Limpiar
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="table-responsive">
|
||
<table class="table table-sm">
|
||
<thead>
|
||
<tr>
|
||
<th>Fecha</th>
|
||
<th>IP</th>
|
||
<th>Estado</th>
|
||
<th>Request</th>
|
||
<th>Response</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>
|
||
<select class="form-control" id="template-language" name="template_language">
|
||
<option value="es_ES" <?= (defined('APP_LANG') && constant('APP_LANG') === 'es_ES') ? 'selected' : '' ?>>Español</option>
|
||
<option value="en_US" <?= (defined('APP_LANG') && constant('APP_LANG') === 'en_US') ? 'selected' : '' ?>>English</option>
|
||
</select>
|
||
</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>
|
||
|
||
<!-- 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/chart.js"></script>
|
||
<script src="./assets/js/app_simple.js?v=5"></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>
|
||
|
||
<!-- 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,
|
||
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));
|
||
}
|
||
});
|
||
}
|
||
|
||
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';
|
||
} else {
|
||
this.countEl.style.display = 'none';
|
||
}
|
||
json.data.forEach(n => 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);
|
||
}
|
||
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) {
|
||
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) {
|
||
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();
|
||
};
|
||
} catch (e) { console.warn('native notification failed', e); }
|
||
}
|
||
|
||
// play sound
|
||
try {
|
||
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(); }, 18000);
|
||
}
|
||
};
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
try { NotificationManager.init(); } catch(e) { console.error('NotificationManager init failed', e); }
|
||
});
|
||
</script>
|
||
</body>
|
||
|
||
</html>
|