Initial commit - Last War messaging system

This commit is contained in:
2026-02-19 01:33:28 -06:00
commit 38a8447a64
2162 changed files with 216183 additions and 0 deletions

161
admin/comandos.php Executable file
View File

@@ -0,0 +1,161 @@
<?php
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/../includes/session_check.php';
requireAdmin();
$pageTitle = 'Gestión de Comandos';
$templates = [];
try {
$pdo = getDbConnection();
$stmt = $pdo->query("SELECT id, name, telegram_command FROM recurrent_messages ORDER BY name");
$templates = $stmt->fetchAll();
} catch (Exception $e) {
$error = $e->getMessage();
}
require_once __DIR__ . '/../templates/header.php';
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-terminal"></i> Gestión de Comandos</h2>
</div>
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> Los comandos se usan en Discord y Telegram anteponiendo <code>#</code> al nombre del comando.
</div>
<div class="row">
<div class="col-md-12">
<div class="card border-0 shadow-sm">
<div class="card-body">
<?php if (empty($templates)): ?>
<p class="text-muted text-center py-4">No hay plantillas con comandos</p>
<?php else: ?>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Nombre</th>
<th>Comando</th>
<th>Uso</th>
</tr>
</thead>
<tbody>
<?php foreach ($templates as $template): ?>
<tr>
<td><?= $template['id'] ?></td>
<td><?= htmlspecialchars($template['name']) ?></td>
<td>
<?php if ($template['telegram_command']): ?>
<code>#<?= htmlspecialchars($template['telegram_command']) ?></code>
<?php else: ?>
<span class="text-muted">Sin comando</span>
<?php endif; ?>
</td>
<td>
<span class="badge bg-primary">Discord</span>
<span class="badge bg-info">Telegram</span>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0">
<h5 class="mb-0"><i class="bi bi-discord"></i> Comandos de Discord</h5>
</div>
<div class="card-body">
<table class="table table-sm">
<thead>
<tr>
<th>Comando</th>
<th>Descripción</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>#comando</code></td>
<td>Envía la plantilla asociada</td>
</tr>
<tr>
<td><code>/comandos</code></td>
<td>Lista de comandos disponibles</td>
</tr>
<tr>
<td><code>/setlang [código]</code></td>
<td>Establece el idioma del usuario</td>
</tr>
<tr>
<td><code>/bienvenida</code></td>
<td>Envía mensaje de bienvenida</td>
</tr>
<tr>
<td><code>/agente</code></td>
<td>Cambia a modo IA</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0">
<h5 class="mb-0"><i class="bi bi-telegram"></i> Comandos de Telegram</h5>
</div>
<div class="card-body">
<table class="table table-sm">
<thead>
<tr>
<th>Comando</th>
<th>Descripción</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>#comando</code></td>
<td>Envía la plantilla asociada</td>
</tr>
<tr>
<td><code>/start</code></td>
<td>Inicia el bot</td>
</tr>
<tr>
<td><code>/comandos</code></td>
<td>Lista de comandos disponibles</td>
</tr>
<tr>
<td><code>/setlang [código]</code></td>
<td>Establece el idioma del usuario</td>
</tr>
<tr>
<td><code>/bienvenida</code></td>
<td>Envía mensaje de bienvenida</td>
</tr>
<tr>
<td><code>/agente</code></td>
<td>Cambia a modo IA</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/../templates/footer.php'; ?>

253
admin/ia_agent.php Executable file
View File

@@ -0,0 +1,253 @@
<?php
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/../includes/session_check.php';
requireAdmin();
$pageTitle = 'Configuración del Agente IA';
require_once __DIR__ . '/../templates/header.php';
require_once __DIR__ . '/../src/IA/Agent.php';
$agent = new \IA\Agent();
$message = '';
$messageType = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['update_kb_config'])) {
$kbConfigKeys = [
'kb_db_host' => 'kb_host',
'kb_db_port' => 'kb_port',
'kb_db_name' => 'kb_dbname',
'kb_db_user' => 'kb_user',
];
$saved = true;
foreach ($kbConfigKeys as $dbKey => $postKey) {
$value = $_POST[$postKey] ?? '';
if (!$agent->updateConfig($dbKey, $value)) {
$saved = false;
}
}
if (!empty($_POST['kb_pass'])) {
if (!$agent->updateConfig('kb_db_pass', $_POST['kb_pass'])) {
$saved = false;
}
}
if ($saved) {
$message = 'Configuración de base de datos actualizada correctamente.';
$messageType = 'success';
$agent = new \IA\Agent();
$config = $agent->getAllConfig();
} else {
$message = 'Error al guardar la configuración.';
$messageType = 'danger';
}
} elseif (isset($_POST['update_config'])) {
$key = $_POST['config_key'] ?? '';
$value = $_POST['config_value'] ?? '';
if (!empty($key) && $agent->updateConfig($key, $value)) {
$message = 'Configuración actualizada correctamente.';
$messageType = 'success';
$agent = new \IA\Agent();
} else {
$message = 'Error al actualizar la configuración.';
$messageType = 'danger';
}
} elseif (isset($_POST['update_config_model']) && isset($_POST['config_value_model'])) {
$key = 'ai_model';
$value = $_POST['config_value_model'] ?? '';
if (!empty($value) && $agent->updateConfig($key, $value)) {
$message = 'Modelo de IA actualizado correctamente.';
$messageType = 'success';
$agent = new \IA\Agent();
} else {
$message = 'Error al actualizar el modelo.';
$messageType = 'danger';
}
} elseif (isset($_POST['test_connection'])) {
$result = $agent->testKbConnection();
$message = $result['message'];
$messageType = $result['success'] ? 'success' : 'danger';
} elseif (isset($_POST['test_question'])) {
$question = $_POST['test_question'] ?? '';
if (!empty($question)) {
$response = $agent->generateResponse($question);
$testResponse = $response;
}
}
}
$config = $agent->getAllConfig();
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-robot"></i> Configuración del Agente IA</h2>
</div>
<?php if (!empty($message)): ?>
<div class="alert alert-<?= $messageType ?> alert-dismissible fade show" role="alert">
<?= htmlspecialchars($message) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="row">
<div class="col-md-6">
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0">
<h5 class="mb-0"><i class="bi bi-database"></i> Conexión a Knowledge Base</h5>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label class="form-label">Host</label>
<input type="text" name="kb_host" class="form-control" value="<?= htmlspecialchars($config['kb_db_host'] ?? $_ENV['KB_DB_HOST'] ?? '') ?>">
</div>
<div class="mb-3">
<label class="form-label">Puerto</label>
<input type="text" name="kb_port" class="form-control" value="<?= htmlspecialchars($config['kb_db_port'] ?? $_ENV['KB_DB_PORT'] ?? '') ?>">
</div>
<div class="mb-3">
<label class="form-label">Base de Datos</label>
<input type="text" name="kb_dbname" class="form-control" value="<?= htmlspecialchars($config['kb_db_name'] ?? $_ENV['KB_DB_NAME'] ?? '') ?>">
</div>
<div class="mb-3">
<label class="form-label">Usuario</label>
<input type="text" name="kb_user" class="form-control" value="<?= htmlspecialchars($config['kb_db_user'] ?? $_ENV['KB_DB_USER'] ?? '') ?>">
</div>
<div class="mb-3">
<label class="form-label">Contraseña</label>
<input type="password" name="kb_pass" class="form-control" value="<?= htmlspecialchars($config['kb_db_pass'] ?? '') ?>" placeholder="Dejar vacío para mantener actual">
</div>
<div class="d-grid gap-2 d-md-flex">
<button type="submit" name="update_kb_config" class="btn btn-primary">
<i class="bi bi-save"></i> Guardar
</button>
<button type="submit" name="test_connection" class="btn btn-outline-primary">
<i class="bi bi-plug"></i> Probar Conexión
</button>
</div>
</form>
</div>
</div>
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0">
<h5 class="mb-0"><i class="bi bi-cpu"></i> Configuración de IA</h5>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label class="form-label">Habilitar Knowledge Base</label>
<select name="config_value" class="form-select">
<option value="1" <?= ($config['kb_enabled'] ?? '1') === '1' ? 'selected' : '' ?>>Habilitado</option>
<option value="0" <?= ($config['kb_enabled'] ?? '1') === '0' ? 'selected' : '' ?>>Deshabilitado</option>
</select>
<input type="hidden" name="config_key" value="kb_enabled">
</div>
<div class="mb-3">
<label class="form-label">Modelo de IA (Groq)</label>
<select name="config_value_model" class="form-select">
<option value="llama-3.1-8b-instant" <?= ($config['ai_model'] ?? 'llama-3.1-8b-instant') === 'llama-3.1-8b-instant' ? 'selected' : '' ?>>Llama 3.1 8B (Rápido - Recomendado)</option>
<option value="mixtral-8x7b-32768" <?= ($config['ai_model'] ?? '') === 'mixtral-8x7b-32768' ? 'selected' : '' ?>>Mixtral 8x7B</option>
<option value="llama3-70b-8192" <?= ($config['ai_model'] ?? '') === 'llama3-70b-8192' ? 'selected' : '' ?>>Llama 3 70B</option>
</select>
<small class="text-muted">Solo modelos gratuitos. Si se agotan los tokens, cambiará automáticamente al siguiente.</small>
</div>
<div class="d-grid gap-2">
<button type="submit" name="update_config" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Guardar Configuración
</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0">
<h5 class="mb-0"><i class="bi bi-chat-left-text"></i> Prompt del Sistema</h5>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label class="form-label">Instrucciones para el agente</label>
<textarea name="config_value" class="form-control" rows="6"><?= htmlspecialchars($config['system_prompt'] ?? '') ?></textarea>
<small class="text-muted">Instrucciones que seguirá el agente al responder.</small>
<input type="hidden" name="config_key" value="system_prompt">
</div>
<div class="d-grid">
<button type="submit" name="update_config" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Guardar Prompt
</button>
</div>
</form>
</div>
</div>
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0">
<h5 class="mb-0"><i class="bi bi-sliders"></i> Parámetros Adicionales</h5>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label class="form-label">Máximo de resultados de KB</label>
<input type="number" name="config_value" class="form-control" value="<?= htmlspecialchars($config['kb_max_results'] ?? '5') ?>" min="1" max="20">
<small class="text-muted">Cantidad de artículos a buscar en la base de conocimientos.</small>
<input type="hidden" name="config_key" value="kb_max_results">
</div>
<div class="mb-3">
<label class="form-label">Máximo de caracteres en respuesta</label>
<input type="number" name="config_value2" class="form-control" value="<?= htmlspecialchars($config['response_max_length'] ?? '1500') ?>" min="100" max="4000">
<small class="text-muted">Límite de caracteres en las respuestas del agente.</small>
</div>
<div class="d-grid">
<button type="submit" name="update_config" class="btn btn-primary">
<i class="bi bi-check-circle"></i> Guardar Parámetros
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0">
<h5 class="mb-0"><i class="bi bi-chat-dots"></i> Prueba del Agente</h5>
</div>
<div class="card-body">
<form method="POST" class="mb-3">
<div class="input-group">
<span class="input-group-text"><i class="bi bi-question-circle"></i></span>
<input type="text" name="test_question" class="form-control" placeholder="Escribe una pregunta para probar el agente..." value="<?= htmlspecialchars($_POST['test_question'] ?? '') ?>">
<button type="submit" class="btn btn-success">
<i class="bi bi-send"></i> Enviar
</button>
</div>
</form>
<?php if (isset($testResponse)): ?>
<div class="mt-3">
<label class="form-label text-muted">Respuesta del agente:</label>
<div class="p-3 bg-light rounded border">
<pre class="mb-0 white-space: pre-wrap;"><?= htmlspecialchars($testResponse) ?></pre>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php require_once __DIR__ . '/../templates/footer.php'; ?>

465
admin/languages.php Executable file
View File

@@ -0,0 +1,465 @@
<?php
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/../includes/session_check.php';
require_once __DIR__ . '/../includes/activity_logger.php';
require_once __DIR__ . '/../includes/env_loader.php';
require_once __DIR__ . '/../src/Translate.php';
requireAdmin();
$pageTitle = 'Gestión de Idiomas';
$languages = [];
try {
$pdo = getDbConnection();
$stmt = $pdo->query("SELECT * FROM supported_languages ORDER BY language_name");
$languages = $stmt->fetchAll();
} catch (Exception $e) {
$error = $e->getMessage();
}
$syncMessage = '';
$syncError = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$action = $_POST['action'];
if ($action === 'toggle_status') {
$id = (int) $_POST['id'];
$stmt = $pdo->prepare("UPDATE supported_languages SET is_active = NOT is_active WHERE id = ?");
$stmt->execute([$id]);
logActivity(getCurrentUserId(), 'toggle_language', "Idioma ID: $id");
header('Location: languages.php');
exit;
} elseif ($action === 'update_flag') {
$id = (int) $_POST['id'];
$flag = $_POST['flag_emoji'];
$stmt = $pdo->prepare("UPDATE supported_languages SET flag_emoji = ? WHERE id = ?");
$stmt->execute([$flag, $id]);
header('Location: languages.php');
exit;
} elseif ($action === 'add') {
$code = $_POST['language_code'];
$name = $_POST['language_name'];
$flag = $_POST['flag_emoji'];
$stmt = $pdo->prepare("INSERT INTO supported_languages (language_code, language_name, flag_emoji, is_active) VALUES (?, ?, ?, FALSE)");
$stmt->execute([$code, $name, $flag]);
logActivity(getCurrentUserId(), 'add_language', "Idioma agregado: $name");
header('Location: languages.php');
exit;
} elseif ($action === 'sync_libretranslate') {
try {
$translator = new src\Translate();
$ltLanguages = $translator->getSupportedLanguages();
if (empty($ltLanguages)) {
$syncError = "No se pudieron obtener los idiomas de LibreTranslate. Verifica que el servicio esté corriendo en: " . ($_ENV['LIBRETRANSLATE_URL'] ?? 'http://localhost:5000');
} else {
$added = 0;
foreach ($ltLanguages as $ltLang) {
$code = $ltLang['code'];
$name = $ltLang['name'];
$stmt = $pdo->prepare("SELECT id FROM supported_languages WHERE language_code = ?");
$stmt->execute([$code]);
if (!$stmt->fetch()) {
$flag = getFlagForLanguage($code);
$stmt = $pdo->prepare("INSERT INTO supported_languages (language_code, language_name, flag_emoji, is_active) VALUES (?, ?, ?, FALSE)");
$stmt->execute([$code, $name, $flag]);
$added++;
}
}
$syncMessage = "Se sincronizaron $added idiomas desde LibreTranslate";
logActivity(getCurrentUserId(), 'sync_languages', "Sincronizados $added idiomas desde LibreTranslate");
}
} catch (Exception $e) {
$syncError = "Error al conectar con LibreTranslate: " . $e->getMessage() . ". Verifica que el servicio esté configurado correctamente en el archivo .env";
}
}
}
// Array completo de banderas con variantes regionales
$availableFlags = [
// Español - Variantes
['code' => 'es-MX', 'flag' => '🇲🇽', 'name' => 'Español (México)'],
['code' => 'es-ES', 'flag' => '🇪🇸', 'name' => 'Español (España)'],
['code' => 'es-AR', 'flag' => '🇦🇷', 'name' => 'Español (Argentina)'],
['code' => 'es-CO', 'flag' => '🇨🇴', 'name' => 'Español (Colombia)'],
['code' => 'es-CL', 'flag' => '🇨🇱', 'name' => 'Español (Chile)'],
['code' => 'es-PE', 'flag' => '🇵🇪', 'name' => 'Español (Perú)'],
['code' => 'es-VE', 'flag' => '🇻🇪', 'name' => 'Español (Venezuela)'],
// Inglés - Variantes
['code' => 'en-US', 'flag' => '🇺🇸', 'name' => 'English (USA)'],
['code' => 'en-GB', 'flag' => '🇬🇧', 'name' => 'English (UK)'],
['code' => 'en-CA', 'flag' => '🇨🇦', 'name' => 'English (Canada)'],
['code' => 'en-AU', 'flag' => '🇦🇺', 'name' => 'English (Australia)'],
// Portugués - Variantes
['code' => 'pt-BR', 'flag' => '🇧🇷', 'name' => 'Português (Brasil)'],
['code' => 'pt-PT', 'flag' => '🇵🇹', 'name' => 'Português (Portugal)'],
// Francés - Variantes
['code' => 'fr-FR', 'flag' => '🇫🇷', 'name' => 'Français (France)'],
['code' => 'fr-CA', 'flag' => '🇨🇦', 'name' => 'Français (Canada)'],
// Alemán
['code' => 'de', 'flag' => '🇩🇪', 'name' => 'Deutsch'],
// Italiano
['code' => 'it', 'flag' => '🇮🇹', 'name' => 'Italiano'],
// Ruso
['code' => 'ru', 'flag' => '🇷🇺', 'name' => 'Русский'],
// Chino - Variantes
['code' => 'zh-CN', 'flag' => '🇨🇳', 'name' => '中文 (简体)'],
['code' => 'zh-TW', 'flag' => '🇹🇼', 'name' => '中文 (繁體)'],
// Japonés
['code' => 'ja', 'flag' => '🇯🇵', 'name' => '日本語'],
// Coreano
['code' => 'ko', 'flag' => '🇰🇷', 'name' => '한국어'],
// Árabe
['code' => 'ar', 'flag' => '🇸🇦', 'name' => 'العربية'],
// Hindi
['code' => 'hi', 'flag' => '🇮🇳', 'name' => 'हिन्दी'],
// Holandés
['code' => 'nl', 'flag' => '🇳🇱', 'name' => 'Nederlands'],
// Polaco
['code' => 'pl', 'flag' => '🇵🇱', 'name' => 'Polski'],
// Turco
['code' => 'tr', 'flag' => '🇹🇷', 'name' => 'Türkçe'],
// Sueco
['code' => 'sv', 'flag' => '🇸🇪', 'name' => 'Svenska'],
// Danés
['code' => 'da', 'flag' => '🇩🇰', 'name' => 'Dansk'],
// Finés
['code' => 'fi', 'flag' => '🇫🇮', 'name' => 'Suomi'],
// Noruego
['code' => 'no', 'flag' => '🇳🇴', 'name' => 'Norsk'],
// Checo
['code' => 'cs', 'flag' => '🇨🇿', 'name' => 'Čeština'],
// Griego
['code' => 'el', 'flag' => '🇬🇷', 'name' => 'Ελληνικά'],
// Hebreo
['code' => 'he', 'flag' => '🇮🇱', 'name' => 'עברית'],
// Tailandés
['code' => 'th', 'flag' => '🇹🇭', 'name' => 'ไทย'],
// Vietnamita
['code' => 'vi', 'flag' => '🇻🇳', 'name' => 'Tiếng Việt'],
// Indonesio
['code' => 'id', 'flag' => '🇮🇩', 'name' => 'Bahasa Indonesia'],
// Malayo
['code' => 'ms', 'flag' => '🇲🇾', 'name' => 'Bahasa Melayu'],
// Ucraniano
['code' => 'uk', 'flag' => '🇺🇦', 'name' => 'Українська'],
// Catalán
['code' => 'ca', 'flag' => '🇪🇸', 'name' => 'Català'],
// Gallego
['code' => 'gl', 'flag' => '🇪🇸', 'name' => 'Galego'],
// Rumano
['code' => 'ro', 'flag' => '🇷🇴', 'name' => 'Română'],
// Húngaro
['code' => 'hu', 'flag' => '🇭🇺', 'name' => 'Magyar'],
// Búlgaro
['code' => 'bg', 'flag' => '🇧🇬', 'name' => 'Български'],
// Otros países importantes
['code' => 'other', 'flag' => '🇦🇹', 'name' => 'Austria'],
['code' => 'other', 'flag' => '🇧🇪', 'name' => 'Bélgica'],
['code' => 'other', 'flag' => '🇨🇭', 'name' => 'Suiza'],
['code' => 'other', 'flag' => '🇮🇪', 'name' => 'Irlanda'],
['code' => 'other', 'flag' => '🇳🇿', 'name' => 'Nueva Zelanda'],
['code' => 'other', 'flag' => '🇿🇦', 'name' => 'Sudáfrica'],
['code' => 'other', 'flag' => '🇪🇬', 'name' => 'Egipto'],
['code' => 'other', 'flag' => '🇮🇷', 'name' => 'Irán'],
['code' => 'other', 'flag' => '🇵🇰', 'name' => 'Pakistán'],
['code' => 'other', 'flag' => '🇧🇩', 'name' => 'Bangladesh'],
['code' => 'other', 'flag' => '🇵🇭', 'name' => 'Filipinas'],
['code' => 'other', 'flag' => '🇸🇬', 'name' => 'Singapur'],
['code' => 'other', 'flag' => '🇭🇰', 'name' => 'Hong Kong'],
['code' => 'other', 'flag' => '🇲🇴', 'name' => 'Macao'],
];
function getFlagForLanguage(string $code): string {
// Por defecto usar México para español
$flags = [
'en' => '🇺🇸', // USA para inglés general
'es' => '🇲🇽', // México para español (como pidió el usuario)
'pt' => '🇧🇷', // Brasil para portugués
'fr' => '🇫🇷', // Francia
'de' => '🇩🇪', // Alemania
'it' => '🇮🇹', // Italia
'ru' => '🇷🇺', // Rusia
'zh' => '🇨🇳', // China
'ja' => '🇯🇵', // Japón
'ko' => '🇰🇷', // Corea del Sur
'ar' => '🇸🇦', // Arabia Saudita
'hi' => '🇮🇳', // India
'nl' => '🇳🇱', // Países Bajos
'pl' => '🇵🇱', // Polonia
'tr' => '🇹🇷', // Turquía
'sv' => '🇸🇪', // Suecia
'da' => '🇩🇰', // Dinamarca
'fi' => '🇫🇮', // Finlandia
'no' => '🇳🇴', // Noruega
'cs' => '🇨🇿', // República Checa
'el' => '🇬🇷', // Grecia
'he' => '🇮🇱', // Israel
'th' => '🇹🇭', // Tailandia
'vi' => '🇻🇳', // Vietnam
'id' => '🇮🇩', // Indonesia
'ms' => '🇲🇾', // Malasia
'uk' => '🇺🇦', // Ucrania
'ca' => '🇪🇸', // España (Cataluña)
'gl' => '🇪🇸', // España (Galicia)
'ro' => '🇷🇴', // Rumania
'hu' => '🇭🇺', // Hungría
'bg' => '🇧🇬', // Bulgaria
];
return $flags[$code] ?? '🌐';
}
require_once __DIR__ . '/../templates/header.php';
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-translate"></i> Gestión de Idiomas</h2>
<div>
<form method="POST" class="d-inline">
<input type="hidden" name="action" value="sync_libretranslate">
<button type="submit" class="btn btn-outline-primary">
<i class="bi bi-cloud-download"></i> Sincronizar con LibreTranslate
</button>
</form>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#languageModal">
<i class="bi bi-plus-circle"></i> Nuevo Idioma
</button>
</div>
</div>
<?php if ($syncMessage): ?>
<div class="alert alert-success"><?= htmlspecialchars($syncMessage) ?></div>
<?php endif; ?>
<?php if ($syncError): ?>
<div class="alert alert-danger"><?= htmlspecialchars($syncError) ?></div>
<?php endif; ?>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<div class="row">
<div class="col-md-12">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Bandera</th>
<th>Código</th>
<th>Nombre</th>
<th>Estado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($languages as $lang): ?>
<tr>
<td style="font-size: 1.5rem;"><?= htmlspecialchars($lang['flag_emoji']) ?></td>
<td><code><?= htmlspecialchars($lang['language_code']) ?></code></td>
<td><?= htmlspecialchars($lang['language_name']) ?></td>
<td>
<?php if ($lang['is_active']): ?>
<span class="badge bg-success">Activo</span>
<?php else: ?>
<span class="badge bg-secondary">Inactivo</span>
<?php endif; ?>
</td>
<td>
<form method="POST" class="d-inline">
<input type="hidden" name="action" value="toggle_status">
<input type="hidden" name="id" value="<?= $lang['id'] ?>">
<button type="submit" class="btn btn-sm btn-outline-<?= $lang['is_active'] ? 'warning' : 'success' ?>">
<i class="bi bi-<?= $lang['is_active'] ? 'pause' : 'play' ?>-fill"></i>
</button>
</form>
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#flagModal<?= $lang['id'] ?>">
<i class="bi bi-flag"></i> Cambiar
</button>
</td>
</tr>
<!-- Modal Selector de Banderas -->
<div class="modal fade" id="flagModal<?= $lang['id'] ?>" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form method="POST">
<input type="hidden" name="action" value="update_flag">
<input type="hidden" name="id" value="<?= $lang['id'] ?>">
<div class="modal-header">
<h5 class="modal-title">Seleccionar Bandera - <?= htmlspecialchars($lang['language_name']) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Bandera actual</label>
<div class="display-4"><?= htmlspecialchars($lang['flag_emoji']) ?></div>
</div>
<div class="mb-3">
<label class="form-label">Seleccionar nueva bandera</label>
<div class="flag-selector" style="max-height: 400px; overflow-y: auto;">
<div class="row g-2">
<?php foreach ($availableFlags as $flag): ?>
<div class="col-6 col-md-4 col-lg-3">
<label class="d-block p-2 border rounded cursor-pointer text-center" style="cursor: pointer; <?= $flag['flag'] === $lang['flag_emoji'] ? 'background-color: #e3f2fd; border-color: #2196f3;' : '' ?>" onclick="selectFlag<?= $lang['id'] ?>('<?= $flag['flag'] ?>')">
<input type="radio" name="flag_emoji" value="<?= $flag['flag'] ?>" class="d-none" <?= $flag['flag'] === $lang['flag_emoji'] ? 'checked' : '' ?>>
<span style="font-size: 2rem;"><?= $flag['flag'] ?></span>
<div class="small text-muted mt-1"><?= htmlspecialchars($flag['name']) ?></div>
</label>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="mt-3">
<label class="form-label">O escribir emoji manualmente</label>
<input type="text" name="flag_emoji_custom" id="customFlag<?= $lang['id'] ?>" class="form-control" value="<?= htmlspecialchars($lang['flag_emoji']) ?>" maxlength="10" placeholder="🇲🇽">
<small class="text-muted">Puedes copiar y pegar cualquier emoji de bandera aquí</small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary">Guardar</button>
</div>
</form>
</div>
</div>
</div>
<script>
function selectFlag<?= $lang['id'] ?>(flag) {
document.getElementById('customFlag<?= $lang['id'] ?>').value = flag;
}
</script>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Modal Nuevo Idioma -->
<div class="modal fade" id="languageModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST">
<input type="hidden" name="action" value="add">
<div class="modal-header">
<h5 class="modal-title">Nuevo Idioma</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Código de idioma (ej: ca, gl)</label>
<input type="text" name="language_code" class="form-control" required maxlength="10">
</div>
<div class="mb-3">
<label class="form-label">Nombre del idioma</label>
<input type="text" name="language_name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Seleccionar bandera</label>
<div class="flag-selector-new" style="max-height: 300px; overflow-y: auto;">
<div class="row g-2">
<?php foreach ($availableFlags as $flag): ?>
<div class="col-4">
<label class="d-block p-2 border rounded cursor-pointer text-center" style="cursor: pointer;" onclick="selectNewFlag('<?= $flag['flag'] ?>')">
<input type="radio" name="flag_emoji" value="<?= $flag['flag'] ?>" class="d-none">
<span style="font-size: 1.5rem;"><?= $flag['flag'] ?></span>
<div class="small text-muted" style="font-size: 0.75rem;"><?= htmlspecialchars($flag['name']) ?></div>
</label>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="mt-3">
<label class="form-label">O escribir emoji manualmente</label>
<input type="text" id="newFlagInput" name="flag_emoji" class="form-control" maxlength="10" placeholder="🇲🇽">
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Agregar</button>
</div>
</form>
</div>
</div>
</div>
<script>
function selectNewFlag(flag) {
document.getElementById('newFlagInput').value = flag;
// Marcar visualmente la selección
document.querySelectorAll('.flag-selector-new label').forEach(function(label) {
label.style.backgroundColor = '';
label.style.borderColor = '';
});
event.currentTarget.style.backgroundColor = '#e3f2fd';
event.currentTarget.style.borderColor = '#2196f3';
}
// Estilo hover para las banderas
document.querySelectorAll('.flag-selector label, .flag-selector-new label').forEach(function(label) {
label.addEventListener('mouseenter', function() {
if (!this.querySelector('input:checked')) {
this.style.backgroundColor = '#f5f5f5';
}
});
label.addEventListener('mouseleave', function() {
if (!this.querySelector('input:checked')) {
this.style.backgroundColor = '';
}
});
});
</script>
<?php require_once __DIR__ . '/../templates/footer.php'; ?>

273
admin/recipients.php Executable file
View File

@@ -0,0 +1,273 @@
<?php
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/../includes/session_check.php';
require_once __DIR__ . '/../includes/activity_logger.php';
requireAdmin();
$pageTitle = 'Gestión de Destinatarios';
$recipients = [];
$languages = [];
try {
$pdo = getDbConnection();
$stmt = $pdo->query("SELECT * FROM recipients ORDER BY platform, name");
$recipients = $stmt->fetchAll();
$stmt = $pdo->query("SELECT * FROM supported_languages ORDER BY language_name");
$languages = $stmt->fetchAll();
} catch (Exception $e) {
$error = $e->getMessage();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$action = $_POST['action'];
if ($action === 'add') {
$platformId = $_POST['platform_id'];
$name = $_POST['name'];
$type = $_POST['type'];
$platform = $_POST['platform'];
$languageCode = $_POST['language_code'];
$stmt = $pdo->prepare("
INSERT INTO recipients (platform_id, name, type, platform, language_code, created_at)
VALUES (?, ?, ?, ?, ?, NOW())
");
$stmt->execute([$platformId, $name, $type, $platform, $languageCode]);
logActivity(getCurrentUserId(), 'add_recipient', "Destinatario agregado: $name ($platform)");
header('Location: recipients.php');
exit;
} elseif ($action === 'update_language') {
$recipientId = (int) $_POST['recipient_id'];
$languageCode = $_POST['language_code'];
$stmt = $pdo->prepare("UPDATE recipients SET language_code = ? WHERE id = ?");
$stmt->execute([$languageCode, $recipientId]);
header('Location: recipients.php');
exit;
} elseif ($action === 'delete') {
$recipientId = (int) $_POST['recipient_id'];
$stmt = $pdo->prepare("DELETE FROM recipients WHERE id = ?");
$stmt->execute([$recipientId]);
logActivity(getCurrentUserId(), 'delete_recipient', "Destinatario eliminado ID: $recipientId");
header('Location: recipients.php');
exit;
}
}
require_once __DIR__ . '/../templates/header.php';
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-person-check"></i> Gestión de Destinatarios</h2>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addRecipientModal">
<i class="bi bi-plus-circle"></i> Nuevo Destinatario
</button>
</div>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<ul class="nav nav-tabs mb-3">
<li class="nav-item">
<a class="nav-link active" href="#discord" data-bs-toggle="tab">Discord</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#telegram" data-bs-toggle="tab">Telegram</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="discord">
<div class="card border-0 shadow-sm">
<div class="card-body">
<?php $discordRecipients = array_filter($recipients, fn($r) => $r['platform'] === 'discord'); ?>
<?php if (empty($discordRecipients)): ?>
<p class="text-muted">No hay destinatarios de Discord</p>
<?php else: ?>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Platform ID</th>
<th>Nombre</th>
<th>Tipo</th>
<th>Idioma</th>
<th>Creado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($discordRecipients as $recipient): ?>
<tr>
<td><?= $recipient['id'] ?></td>
<td><code><?= $recipient['platform_id'] ?></code></td>
<td><?= htmlspecialchars($recipient['name']) ?></td>
<td><?= $recipient['type'] ?></td>
<td>
<form method="POST" class="d-inline">
<input type="hidden" name="action" value="update_language">
<input type="hidden" name="recipient_id" value="<?= $recipient['id'] ?>">
<select name="language_code" class="form-select form-select-sm" style="width: auto;" onchange="this.form.submit()">
<?php foreach ($languages as $lang): ?>
<option value="<?= $lang['language_code'] ?>" <?= $recipient['language_code'] === $lang['language_code'] ? 'selected' : '' ?>>
<?= $lang['flag_emoji'] ?> <?= $lang['language_name'] ?>
</option>
<?php endforeach; ?>
</select>
</form>
</td>
<td><?= date('d/m/Y', strtotime($recipient['created_at'])) ?></td>
<td>
<form method="POST" onsubmit="return confirm('¿Eliminar?');" class="d-inline">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="recipient_id" value="<?= $recipient['id'] ?>">
<button type="submit" class="btn btn-outline-danger btn-sm">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="tab-pane fade" id="telegram">
<div class="card border-0 shadow-sm">
<div class="card-body">
<?php $telegramRecipients = array_filter($recipients, fn($r) => $r['platform'] === 'telegram'); ?>
<?php if (empty($telegramRecipients)): ?>
<p class="text-muted">No hay destinatarios de Telegram</p>
<?php else: ?>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Platform ID</th>
<th>Nombre</th>
<th>Tipo</th>
<th>Idioma</th>
<th>Creado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($telegramRecipients as $recipient): ?>
<tr>
<td><?= $recipient['id'] ?></td>
<td><code><?= $recipient['platform_id'] ?></code></td>
<td><?= htmlspecialchars($recipient['name']) ?></td>
<td><?= $recipient['type'] ?></td>
<td>
<form method="POST" class="d-inline">
<input type="hidden" name="action" value="update_language">
<input type="hidden" name="recipient_id" value="<?= $recipient['id'] ?>">
<select name="language_code" class="form-select form-select-sm" style="width: auto;" onchange="this.form.submit()">
<?php foreach ($languages as $lang): ?>
<option value="<?= $lang['language_code'] ?>" <?= $recipient['language_code'] === $lang['language_code'] ? 'selected' : '' ?>>
<?= $lang['flag_emoji'] ?> <?= $lang['language_name'] ?>
</option>
<?php endforeach; ?>
</select>
</form>
</td>
<td><?= date('d/m/Y', strtotime($recipient['created_at'])) ?></td>
<td>
<form method="POST" onsubmit="return confirm('¿Eliminar?');" class="d-inline">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="recipient_id" value="<?= $recipient['id'] ?>">
<button type="submit" class="btn btn-outline-danger btn-sm">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Modal para agregar destinatario -->
<div class="modal fade" id="addRecipientModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST">
<input type="hidden" name="action" value="add">
<div class="modal-header">
<h5 class="modal-title">Nuevo Destinatario</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Plataforma</label>
<select name="platform" class="form-select" required>
<option value="discord">Discord</option>
<option value="telegram">Telegram</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">ID en la plataforma</label>
<input type="text" name="platform_id" class="form-control" required placeholder="Ej: 123456789">
<small class="text-muted">ID del canal/usuario en Discord o Telegram</small>
</div>
<div class="mb-3">
<label class="form-label">Nombre</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Tipo</label>
<select name="type" class="form-select" required>
<option value="channel">Canal</option>
<option value="user">Usuario</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Idioma</label>
<select name="language_code" class="form-select">
<?php foreach ($languages as $lang): ?>
<option value="<?= $lang['language_code'] ?>">
<?= $lang['flag_emoji'] ?> <?= $lang['language_name'] ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary">Agregar</button>
</div>
</form>
</div>
</div>
</div>
<?php require_once __DIR__ . '/../templates/footer.php'; ?>

178
admin/test_discord_connection.php Executable file
View File

@@ -0,0 +1,178 @@
<?php
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/../includes/session_check.php';
require_once __DIR__ . '/../includes/env_loader.php';
requireAdmin();
$pageTitle = 'Test de Conexión Discord';
$results = [];
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'test') {
$token = $_ENV['DISCORD_BOT_TOKEN'] ?? getenv('DISCORD_BOT_TOKEN');
if (empty($token)) {
$error = 'Token de bot no configurado';
} else {
$ch = curl_init('https://discord.com/api/v10/users/@me');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bot ' . $token]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$userData = json_decode($response, true);
$results['user'] = $userData;
$results['http_code'] = $httpCode;
if ($httpCode === 200) {
$guildId = $_ENV['DISCORD_GUILD_ID'] ?? getenv('DISCORD_GUILD_ID');
if ($guildId) {
$ch = curl_init("https://discord.com/api/v10/guilds/{$guildId}");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bot ' . $token]);
$guildResponse = curl_exec($ch);
$guildCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$results['guild'] = json_decode($guildResponse, true);
$results['guild_code'] = $guildCode;
}
$ch = curl_init('https://discord.com/api/v10/channels');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bot ' . $token]);
$channelsResponse = curl_exec($ch);
curl_close($ch);
$results['channels'] = json_decode($channelsResponse, true);
}
if ($_POST['test_message'] ?? false) {
$channelId = $_POST['test_channel_id'] ?? '';
if ($channelId) {
$testMessage = $_POST['test_message_text'] ?? '✅ Prueba de conexión desde el sistema de mensajería';
$ch = curl_init("https://discord.com/api/v10/channels/{$channelId}/messages");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bot ' . $token,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['content' => $testMessage]));
$msgResponse = curl_exec($ch);
$msgCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$results['test_message'] = json_decode($msgResponse, true);
$results['test_message_code'] = $msgCode;
}
}
}
}
require_once __DIR__ . '/../templates/header.php';
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-discord"></i> Test de Conexión Discord</h2>
</div>
<?php if ($error): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<?php if (!empty($results)): ?>
<?php if ($results['http_code'] === 200): ?>
<div class="alert alert-success">
<i class="bi bi-check-circle"></i> <strong>Conexión exitosa!</strong> El bot está conectado como <strong><?= htmlspecialchars($results['user']['username']) ?></strong>
</div>
<?php else: ?>
<div class="alert alert-danger">
<i class="bi bi-x-circle"></i> <strong>Error de conexión:</strong> Código HTTP <?= $results['http_code'] ?>
<?php if (isset($results['user']['message'])): ?>
<br><?= htmlspecialchars($results['user']['message']) ?>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (isset($results['guild'])): ?>
<div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-white border-0">
<h5 class="mb-0">Servidor</h5>
</div>
<div class="card-body">
<?php if ($results['guild_code'] === 200): ?>
<p><strong>Nombre:</strong> <?= htmlspecialchars($results['guild']['name']) ?></p>
<p><strong>ID:</strong> <?= htmlspecialchars($results['guild']['id']) ?></p>
<p><strong>Miembros:</strong> <?= $results['guild']['approximate_member_count'] ?? 'N/A' ?></p>
<?php else: ?>
<div class="alert alert-warning">Error al obtener servidor: Código <?= $results['guild_code'] ?></div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php if (isset($results['test_message'])): ?>
<div class="card border-0 shadow-sm mb-3">
<div class="card-header bg-white border-0">
<h5 class="mb-0">Prueba de Envío</h5>
</div>
<div class="card-body">
<?php if ($results['test_message_code'] === 200): ?>
<div class="alert alert-success"><i class="bi bi-check-circle"></i> Mensaje enviado correctamente</div>
<?php else: ?>
<div class="alert alert-danger">Error al enviar mensaje: Código <?= $results['test_message_code'] ?></div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white border-0">
<h5 class="mb-0">Probar Conexión</h5>
</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="action" value="test">
<button type="submit" class="btn btn-primary">
<i class="bi bi-plug"></i> Verificar Conexión
</button>
</form>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0">
<h5 class="mb-0">Enviar Mensaje de Prueba</h5>
</div>
<div class="card-body">
<form method="POST">
<input type="hidden" name="action" value="test">
<input type="hidden" name="test_message" value="1">
<div class="mb-3">
<label class="form-label">ID del Canal</label>
<input type="text" name="test_channel_id" class="form-control" placeholder="123456789012345678">
</div>
<div class="mb-3">
<label class="form-label">Mensaje</label>
<textarea name="test_message_text" class="form-control" rows="3">✅ Prueba de conexión desde el sistema de mensajería</textarea>
</div>
<button type="submit" class="btn btn-success">
<i class="bi bi-send"></i> Enviar Mensaje
</button>
</form>
</div>
</div>
<?php require_once __DIR__ . '/../templates/footer.php'; ?>

137
admin/users.php Executable file
View File

@@ -0,0 +1,137 @@
<?php
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/../includes/session_check.php';
require_once __DIR__ . '/../includes/auth.php';
requireAdmin();
$pageTitle = 'Gestión de Usuarios';
$users = getAllUsers();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'create') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$role = $_POST['role'] ?? 'user';
if ($username && $password) {
$userId = registerUser($username, $password, $role);
if ($userId) {
logActivity(getCurrentUserId(), 'create_user', "Usuario creado: $username");
header('Location: users.php');
exit;
} else {
$error = 'El usuario ya existe';
}
}
} elseif ($action === 'delete') {
$userId = (int) $_POST['user_id'];
if ($userId !== getCurrentUserId()) {
deleteUser($userId);
logActivity(getCurrentUserId(), 'delete_user', "Usuario eliminado ID: $userId");
header('Location: users.php');
exit;
} else {
$error = 'No puedes eliminarte a ti mismo';
}
}
}
require_once __DIR__ . '/../templates/header.php';
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="bi bi-people"></i> Gestión de Usuarios</h2>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#userModal">
<i class="bi bi-plus-circle"></i> Nuevo Usuario
</button>
</div>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Usuario</th>
<th>Rol</th>
<th>Telegram</th>
<th>Creado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?= $user['id'] ?></td>
<td><?= htmlspecialchars($user['username']) ?></td>
<td>
<span class="badge bg-<?= $user['role'] === 'admin' ? 'danger' : 'primary' ?>">
<?= $user['role'] ?>
</span>
</td>
<td><?= $user['telegram_chat_id'] ? htmlspecialchars($user['telegram_chat_id']) : '-' ?></td>
<td><?= date('d/m/Y', strtotime($user['created_at'])) ?></td>
<td>
<?php if ($user['id'] !== getCurrentUserId()): ?>
<form method="POST" onsubmit="return confirm('¿Eliminar este usuario?');" class="d-inline">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="user_id" value="<?= $user['id'] ?>">
<button type="submit" class="btn btn-outline-danger btn-sm">
<i class="bi bi-trash"></i>
</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="modal fade" id="userModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="POST">
<input type="hidden" name="action" value="create">
<div class="modal-header">
<h5 class="modal-title">Nuevo Usuario</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Usuario</label>
<input type="text" name="username" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Contraseña</label>
<input type="password" name="password" class="form-control" required minlength="6">
</div>
<div class="mb-3">
<label class="form-label">Rol</label>
<select name="role" class="form-select">
<option value="user">Usuario</option>
<option value="admin">Administrador</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Crear</button>
</div>
</form>
</div>
</div>
</div>
<?php require_once __DIR__ . '/../templates/footer.php'; ?>