- Add posts.html: Post management with filtering by status/platform/type, stats display, pagination, edit modal, and actions (approve, reject, publish now, schedule, edit, delete) - Add calendar.html: Visual calendar with month/week views, drag-and-drop rescheduling, platform filtering with color-coded status - Add interactions.html: Interactions management with filtering, detail panel for responding, AI response suggestions, lead marking - Add settings.html: API connection status, DeepSeek config, Telegram notifications setup, system info, and quick actions - Update dashboard.py with settings route Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
423 lines
18 KiB
HTML
423 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Configuración - Social Media Automation</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
|
<style>
|
|
body { background-color: #1a1a2e; color: #eee; }
|
|
.card { background-color: #16213e; border-radius: 12px; }
|
|
.accent { color: #d4a574; }
|
|
.btn-primary { background-color: #d4a574; color: #1a1a2e; }
|
|
.btn-primary:hover { background-color: #c49564; }
|
|
.btn-secondary { background-color: #374151; }
|
|
.btn-secondary:hover { background-color: #4b5563; }
|
|
.status-ok { color: #10b981; }
|
|
.status-error { color: #ef4444; }
|
|
.status-warning { color: #f59e0b; }
|
|
</style>
|
|
</head>
|
|
<body class="min-h-screen">
|
|
<!-- Header -->
|
|
<header class="bg-gray-900 border-b border-gray-800 px-6 py-4">
|
|
<div class="flex justify-between items-center">
|
|
<h1 class="text-2xl font-bold">
|
|
<span class="accent">Consultoría AS</span> - Social Media
|
|
</h1>
|
|
<nav class="flex gap-4">
|
|
<a href="/dashboard" class="px-4 py-2 rounded hover:bg-gray-800">Home</a>
|
|
<a href="/dashboard/compose" class="px-4 py-2 rounded hover:bg-gray-800">+ Crear</a>
|
|
<a href="/dashboard/posts" class="px-4 py-2 rounded hover:bg-gray-800">Posts</a>
|
|
<a href="/dashboard/calendar" class="px-4 py-2 rounded hover:bg-gray-800">Calendario</a>
|
|
<a href="/dashboard/interactions" class="px-4 py-2 rounded hover:bg-gray-800">Interacciones</a>
|
|
<a href="/dashboard/settings" class="px-4 py-2 rounded bg-gray-800 accent">Config</a>
|
|
<a href="/logout" class="px-4 py-2 rounded hover:bg-gray-800 text-red-400">Salir</a>
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="container mx-auto px-6 py-8 max-w-4xl">
|
|
<h2 class="text-2xl font-bold mb-6">Configuración del Sistema</h2>
|
|
|
|
<!-- API Connections -->
|
|
<div class="card p-6 mb-6">
|
|
<h3 class="font-semibold text-lg mb-4">Conexiones de APIs</h3>
|
|
<div id="api-status" class="space-y-4">
|
|
<div class="text-gray-400">Verificando conexiones...</div>
|
|
</div>
|
|
<button onclick="testAllConnections()" class="btn-secondary px-4 py-2 rounded mt-4">
|
|
🔄 Verificar Todas
|
|
</button>
|
|
</div>
|
|
|
|
<!-- AI Configuration -->
|
|
<div class="card p-6 mb-6">
|
|
<h3 class="font-semibold text-lg mb-4">Configuración de IA (DeepSeek)</h3>
|
|
<div id="ai-status" class="mb-4">
|
|
<div class="text-gray-400">Verificando...</div>
|
|
</div>
|
|
<div class="bg-gray-800 rounded-lg p-4 text-sm">
|
|
<p class="text-gray-400 mb-2">Para configurar DeepSeek API:</p>
|
|
<ol class="list-decimal list-inside space-y-1 text-gray-300">
|
|
<li>Visita <a href="https://platform.deepseek.com/" target="_blank" class="text-blue-400 hover:underline">platform.deepseek.com</a></li>
|
|
<li>Crea una cuenta y genera una API Key</li>
|
|
<li>Agrega al archivo <code class="bg-gray-700 px-1 rounded">.env</code>: <code class="bg-gray-700 px-1 rounded">DEEPSEEK_API_KEY=tu_key</code></li>
|
|
<li>Reinicia la aplicación</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Telegram Notifications -->
|
|
<div class="card p-6 mb-6">
|
|
<h3 class="font-semibold text-lg mb-4">Notificaciones Telegram</h3>
|
|
<div id="telegram-status" class="mb-4">
|
|
<div class="text-gray-400">Verificando...</div>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<button onclick="testTelegram()" class="btn-secondary px-4 py-2 rounded">
|
|
📱 Enviar Prueba
|
|
</button>
|
|
<button onclick="showTelegramGuide()" class="btn-secondary px-4 py-2 rounded">
|
|
📖 Ver Guía
|
|
</button>
|
|
</div>
|
|
<div id="telegram-guide" class="hidden mt-4 bg-gray-800 rounded-lg p-4 text-sm">
|
|
<!-- Guide loaded dynamically -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Info -->
|
|
<div class="card p-6 mb-6">
|
|
<h3 class="font-semibold text-lg mb-4">Información del Sistema</h3>
|
|
<div class="grid grid-cols-2 gap-4 text-sm">
|
|
<div>
|
|
<span class="text-gray-400">Versión:</span>
|
|
<span class="ml-2">1.0.0</span>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-400">Entorno:</span>
|
|
<span class="ml-2" id="app-env">-</span>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-400">Base de datos:</span>
|
|
<span class="ml-2" id="db-status">-</span>
|
|
</div>
|
|
<div>
|
|
<span class="text-gray-400">Redis:</span>
|
|
<span class="ml-2" id="redis-status">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="card p-6 mb-6">
|
|
<h3 class="font-semibold text-lg mb-4">Acciones Rápidas</h3>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<button onclick="generateWeekContent()" class="btn-secondary p-4 rounded text-left">
|
|
<div class="font-semibold">📅 Generar Contenido Semanal</div>
|
|
<div class="text-sm text-gray-400">Crear posts para los próximos 7 días</div>
|
|
</button>
|
|
<button onclick="syncAllInteractions()" class="btn-secondary p-4 rounded text-left">
|
|
<div class="font-semibold">🔄 Sincronizar Interacciones</div>
|
|
<div class="text-sm text-gray-400">Obtener comentarios y menciones</div>
|
|
</button>
|
|
<button onclick="viewApiDocs()" class="btn-secondary p-4 rounded text-left">
|
|
<div class="font-semibold">📚 Documentación API</div>
|
|
<div class="text-sm text-gray-400">Ver endpoints disponibles</div>
|
|
</button>
|
|
<button onclick="downloadLogs()" class="btn-secondary p-4 rounded text-left">
|
|
<div class="font-semibold">📋 Ver Logs</div>
|
|
<div class="text-sm text-gray-400">Registro de actividad</div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Usage Stats -->
|
|
<div class="card p-6">
|
|
<h3 class="font-semibold text-lg mb-4">Estadísticas de Uso</h3>
|
|
<div id="usage-stats" class="grid grid-cols-4 gap-4">
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold accent" id="stat-posts-total">-</div>
|
|
<div class="text-sm text-gray-400">Posts Totales</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold text-green-400" id="stat-posts-month">-</div>
|
|
<div class="text-sm text-gray-400">Este Mes</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold text-blue-400" id="stat-interactions">-</div>
|
|
<div class="text-sm text-gray-400">Interacciones</div>
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="text-2xl font-bold text-purple-400" id="stat-leads">-</div>
|
|
<div class="text-sm text-gray-400">Leads</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Result Modal -->
|
|
<div id="result-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
|
|
<div class="card p-6 max-w-md w-full mx-4">
|
|
<div id="result-content"></div>
|
|
<button onclick="closeModal()" class="btn-primary w-full mt-4 py-2 rounded">Cerrar</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
window.addEventListener('load', () => {
|
|
loadApiStatus();
|
|
loadAiStatus();
|
|
loadTelegramStatus();
|
|
loadSystemInfo();
|
|
loadUsageStats();
|
|
});
|
|
|
|
async function loadApiStatus() {
|
|
const container = document.getElementById('api-status');
|
|
|
|
try {
|
|
const response = await fetch('/api/publish/test');
|
|
const data = await response.json();
|
|
|
|
const platforms = ['x', 'threads', 'facebook', 'instagram'];
|
|
let html = '';
|
|
|
|
for (const platform of platforms) {
|
|
const status = data.platforms[platform];
|
|
const icon = status.connected ? '✅' : (status.configured ? '⚠️' : '❌');
|
|
const statusClass = status.connected ? 'status-ok' : (status.configured ? 'status-warning' : 'status-error');
|
|
const statusText = status.connected ? 'Conectado' : (status.configured ? 'Configurado (error de conexión)' : 'No configurado');
|
|
|
|
html += `
|
|
<div class="flex items-center justify-between p-3 bg-gray-800 rounded-lg">
|
|
<div class="flex items-center gap-3">
|
|
<span class="text-xl">${icon}</span>
|
|
<div>
|
|
<div class="font-medium">${platform.charAt(0).toUpperCase() + platform.slice(1)}</div>
|
|
<div class="text-sm ${statusClass}">${statusText}</div>
|
|
</div>
|
|
</div>
|
|
${status.details ? `
|
|
<div class="text-sm text-gray-400">
|
|
${status.details.username ? '@' + status.details.username : ''}
|
|
${status.details.name ? status.details.name : ''}
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
container.innerHTML = html;
|
|
|
|
} catch (error) {
|
|
container.innerHTML = '<div class="text-red-400">Error al verificar APIs</div>';
|
|
}
|
|
}
|
|
|
|
async function loadAiStatus() {
|
|
const container = document.getElementById('ai-status');
|
|
|
|
try {
|
|
const response = await fetch('/api/generate/status');
|
|
const data = await response.json();
|
|
|
|
const icon = data.status === 'connected' ? '✅' : (data.configured ? '⚠️' : '❌');
|
|
const statusClass = data.status === 'connected' ? 'status-ok' : (data.configured ? 'status-warning' : 'status-error');
|
|
const statusText = data.status === 'connected' ? 'Conectado y funcionando' : (data.configured ? 'Error: ' + (data.error || 'desconocido') : 'No configurado');
|
|
|
|
container.innerHTML = `
|
|
<div class="flex items-center gap-3 p-3 bg-gray-800 rounded-lg">
|
|
<span class="text-xl">${icon}</span>
|
|
<div>
|
|
<div class="font-medium">DeepSeek API</div>
|
|
<div class="text-sm ${statusClass}">${statusText}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
} catch (error) {
|
|
container.innerHTML = '<div class="text-red-400">Error al verificar IA</div>';
|
|
}
|
|
}
|
|
|
|
async function loadTelegramStatus() {
|
|
const container = document.getElementById('telegram-status');
|
|
|
|
try {
|
|
const response = await fetch('/api/notifications/status');
|
|
const data = await response.json();
|
|
|
|
const icon = data.telegram_configured ? '✅' : '❌';
|
|
const statusClass = data.telegram_configured ? 'status-ok' : 'status-error';
|
|
const statusText = data.telegram_configured ? 'Configurado' : 'No configurado';
|
|
|
|
container.innerHTML = `
|
|
<div class="flex items-center gap-3 p-3 bg-gray-800 rounded-lg">
|
|
<span class="text-xl">${icon}</span>
|
|
<div>
|
|
<div class="font-medium">Telegram Bot</div>
|
|
<div class="text-sm ${statusClass}">${statusText}</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
} catch (error) {
|
|
container.innerHTML = '<div class="text-red-400">Error al verificar Telegram</div>';
|
|
}
|
|
}
|
|
|
|
async function loadSystemInfo() {
|
|
try {
|
|
const response = await fetch('/api/health');
|
|
const data = await response.json();
|
|
|
|
document.getElementById('app-env').textContent = 'Desarrollo';
|
|
document.getElementById('db-status').innerHTML = '<span class="status-ok">Conectada</span>';
|
|
document.getElementById('redis-status').innerHTML = '<span class="status-warning">No verificado</span>';
|
|
|
|
} catch (error) {
|
|
document.getElementById('db-status').innerHTML = '<span class="status-error">Error</span>';
|
|
}
|
|
}
|
|
|
|
async function loadUsageStats() {
|
|
try {
|
|
const response = await fetch('/api/stats');
|
|
const data = await response.json();
|
|
|
|
document.getElementById('stat-posts-total').textContent = data.posts_month || 0;
|
|
document.getElementById('stat-posts-month').textContent = data.posts_week || 0;
|
|
document.getElementById('stat-interactions').textContent = data.interactions_pending || 0;
|
|
document.getElementById('stat-leads').textContent = 0;
|
|
|
|
} catch (error) {
|
|
console.error('Error loading stats:', error);
|
|
}
|
|
}
|
|
|
|
async function testAllConnections() {
|
|
showModal('Verificando conexiones...', true);
|
|
await loadApiStatus();
|
|
await loadAiStatus();
|
|
await loadTelegramStatus();
|
|
closeModal();
|
|
}
|
|
|
|
async function testTelegram() {
|
|
showModal('Enviando mensaje de prueba...', true);
|
|
|
|
try {
|
|
const response = await fetch('/api/notifications/test', { method: 'POST' });
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showModal('✅ Mensaje enviado. Revisa tu Telegram.');
|
|
} else {
|
|
showModal('❌ Error: ' + (data.detail || 'No se pudo enviar'));
|
|
}
|
|
} catch (error) {
|
|
showModal('❌ Error de conexión');
|
|
}
|
|
}
|
|
|
|
async function showTelegramGuide() {
|
|
const container = document.getElementById('telegram-guide');
|
|
|
|
if (!container.classList.contains('hidden')) {
|
|
container.classList.add('hidden');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/notifications/setup-guide');
|
|
const data = await response.json();
|
|
|
|
let html = `<h4 class="font-semibold mb-3">${data.title}</h4>`;
|
|
|
|
data.steps.forEach(step => {
|
|
html += `
|
|
<div class="mb-3">
|
|
<div class="font-medium text-amber-400">Paso ${step.step}: ${step.title}</div>
|
|
<ul class="list-disc list-inside text-gray-300 ml-2">
|
|
${step.instructions.map(i => `<li>${i}</li>`).join('')}
|
|
</ul>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
container.classList.remove('hidden');
|
|
|
|
} catch (error) {
|
|
container.innerHTML = '<div class="text-red-400">Error al cargar guía</div>';
|
|
container.classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
async function generateWeekContent() {
|
|
showModal('Generando contenido para la semana...', true);
|
|
|
|
try {
|
|
const response = await fetch('/api/generate/batch', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
platforms: ['x', 'threads'],
|
|
days: 7
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showModal(`✅ Generados ${data.total_generated} posts para la semana`);
|
|
} else {
|
|
showModal('❌ Error: ' + (data.error || 'No se pudo generar'));
|
|
}
|
|
} catch (error) {
|
|
showModal('❌ Error: ' + error.message);
|
|
}
|
|
}
|
|
|
|
async function syncAllInteractions() {
|
|
showModal('Sincronizando interacciones...', true);
|
|
|
|
try {
|
|
const response = await fetch('/api/interactions/sync', { method: 'POST' });
|
|
const data = await response.json();
|
|
showModal(`✅ Sincronización completada`);
|
|
} catch (error) {
|
|
showModal('❌ Error: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function viewApiDocs() {
|
|
window.open('/api/docs', '_blank');
|
|
}
|
|
|
|
function downloadLogs() {
|
|
showModal('Función de logs próximamente disponible');
|
|
}
|
|
|
|
function showModal(content, loading = false) {
|
|
const modal = document.getElementById('result-modal');
|
|
const contentDiv = document.getElementById('result-content');
|
|
|
|
contentDiv.innerHTML = loading
|
|
? `<div class="text-center py-4"><div class="text-2xl mb-2">⏳</div>${content}</div>`
|
|
: `<div class="text-center py-4">${content}</div>`;
|
|
|
|
modal.classList.remove('hidden');
|
|
modal.classList.add('flex');
|
|
}
|
|
|
|
function closeModal() {
|
|
document.getElementById('result-modal').classList.add('hidden');
|
|
document.getElementById('result-modal').classList.remove('flex');
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|