- Fix YAML files with unquoted strings containing quotes - Export singleton instances in ai/__init__.py - Fix validator scoring prompt to use replace() instead of format() to avoid conflicts with JSON curly braces Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
344 lines
17 KiB
HTML
344 lines
17 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Configuración{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="animate-fade-in">
|
||
<!-- Header -->
|
||
<div class="mb-8">
|
||
<h1 class="text-3xl font-bold">Configuración</h1>
|
||
<p class="text-gray-400 mt-1">Administra las conexiones y preferencias del sistema</p>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
<!-- Platform Connections -->
|
||
<div class="card rounded-2xl p-6">
|
||
<h2 class="text-lg font-semibold mb-6 flex items-center gap-2">
|
||
<span>🔗</span>
|
||
<span>Conexiones de Plataformas</span>
|
||
</h2>
|
||
<div class="space-y-4" id="platforms-container">
|
||
<div class="text-center py-4 text-gray-500">
|
||
<div class="animate-spin w-6 h-6 border-2 border-primary border-t-transparent rounded-full mx-auto"></div>
|
||
<p class="mt-2">Verificando conexiones...</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Telegram Notifications -->
|
||
<div class="card rounded-2xl p-6">
|
||
<h2 class="text-lg font-semibold mb-6 flex items-center gap-2">
|
||
<span>📱</span>
|
||
<span>Notificaciones Telegram</span>
|
||
</h2>
|
||
<div id="telegram-status" class="mb-6">
|
||
<div class="text-center py-4 text-gray-500">
|
||
<div class="animate-spin w-6 h-6 border-2 border-primary border-t-transparent rounded-full mx-auto"></div>
|
||
<p class="mt-2">Verificando...</p>
|
||
</div>
|
||
</div>
|
||
<div class="flex gap-3">
|
||
<button onclick="testTelegram()" class="btn-primary px-4 py-2 rounded-lg flex items-center gap-2 transition-all">
|
||
<span>📤</span>
|
||
<span>Enviar Prueba</span>
|
||
</button>
|
||
<button onclick="toggleTelegramGuide()" class="btn-secondary px-4 py-2 rounded-lg flex items-center gap-2">
|
||
<span>📖</span>
|
||
<span>Ver Guía</span>
|
||
</button>
|
||
</div>
|
||
<div id="telegram-guide" class="hidden mt-6 bg-dark-800 rounded-xl p-4 text-sm space-y-3">
|
||
<h4 class="font-medium text-primary">Configurar Telegram Bot</h4>
|
||
<ol class="list-decimal list-inside space-y-2 text-gray-400">
|
||
<li>Busca <strong>@BotFather</strong> en Telegram</li>
|
||
<li>Envía <code class="bg-dark-700 px-1 rounded">/newbot</code> y sigue las instrucciones</li>
|
||
<li>Copia el <strong>Bot Token</strong> que te proporciona</li>
|
||
<li>Inicia chat con tu nuevo bot</li>
|
||
<li>Visita <code class="bg-dark-700 px-1 rounded">api.telegram.org/bot{TOKEN}/getUpdates</code></li>
|
||
<li>Copia tu <strong>Chat ID</strong> del resultado</li>
|
||
<li>Configura ambos valores en el archivo .env</li>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Odoo Integration -->
|
||
<div class="card rounded-2xl p-6">
|
||
<h2 class="text-lg font-semibold mb-6 flex items-center gap-2">
|
||
<span>🏢</span>
|
||
<span>Integración Odoo</span>
|
||
</h2>
|
||
<div id="odoo-status" class="mb-6">
|
||
<div class="text-center py-4 text-gray-500">
|
||
<div class="animate-spin w-6 h-6 border-2 border-primary border-t-transparent rounded-full mx-auto"></div>
|
||
<p class="mt-2">Verificando...</p>
|
||
</div>
|
||
</div>
|
||
<div class="flex flex-wrap gap-3">
|
||
<button onclick="syncOdooProducts()" class="btn-secondary px-4 py-2 rounded-lg flex items-center gap-2">
|
||
<span>📦</span>
|
||
<span>Sync Productos</span>
|
||
</button>
|
||
<button onclick="syncOdooServices()" class="btn-secondary px-4 py-2 rounded-lg flex items-center gap-2">
|
||
<span>🛠️</span>
|
||
<span>Sync Servicios</span>
|
||
</button>
|
||
<button onclick="syncOdooLeads()" class="btn-secondary px-4 py-2 rounded-lg flex items-center gap-2">
|
||
<span>🎯</span>
|
||
<span>Exportar Leads</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- AI Generation -->
|
||
<div class="card rounded-2xl p-6">
|
||
<h2 class="text-lg font-semibold mb-6 flex items-center gap-2">
|
||
<span>🤖</span>
|
||
<span>Generación con IA</span>
|
||
</h2>
|
||
<div id="ai-status" class="mb-6">
|
||
<div class="text-center py-4 text-gray-500">
|
||
<div class="animate-spin w-6 h-6 border-2 border-primary border-t-transparent rounded-full mx-auto"></div>
|
||
<p class="mt-2">Verificando...</p>
|
||
</div>
|
||
</div>
|
||
<div class="bg-dark-800/50 rounded-xl p-4">
|
||
<h4 class="font-medium mb-2">Información del Negocio</h4>
|
||
<div class="space-y-2 text-sm text-gray-400">
|
||
<p><strong>Nombre:</strong> <span id="business-name">-</span></p>
|
||
<p><strong>Ubicación:</strong> <span id="business-location">-</span></p>
|
||
<p><strong>Tono:</strong> <span id="content-tone">-</span></p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- System Info -->
|
||
<div class="card rounded-2xl p-6 lg:col-span-2">
|
||
<h2 class="text-lg font-semibold mb-6 flex items-center gap-2">
|
||
<span>ℹ️</span>
|
||
<span>Información del Sistema</span>
|
||
</h2>
|
||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<div class="bg-dark-800/50 rounded-xl p-4 text-center">
|
||
<p class="text-2xl font-bold text-primary">1.0.0</p>
|
||
<p class="text-sm text-gray-500">Versión</p>
|
||
</div>
|
||
<div class="bg-dark-800/50 rounded-xl p-4 text-center">
|
||
<p class="text-2xl font-bold text-green-400" id="system-status">●</p>
|
||
<p class="text-sm text-gray-500">Estado</p>
|
||
</div>
|
||
<div class="bg-dark-800/50 rounded-xl p-4 text-center">
|
||
<p class="text-2xl font-bold" id="db-status">-</p>
|
||
<p class="text-sm text-gray-500">Base de Datos</p>
|
||
</div>
|
||
<div class="bg-dark-800/50 rounded-xl p-4 text-center">
|
||
<p class="text-2xl font-bold" id="worker-status">-</p>
|
||
<p class="text-sm text-gray-500">Workers</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block extra_scripts %}
|
||
<script>
|
||
// Load all statuses on page load
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
loadPlatformStatus();
|
||
loadTelegramStatus();
|
||
loadOdooStatus();
|
||
loadAIStatus();
|
||
loadSystemStatus();
|
||
});
|
||
|
||
async function loadPlatformStatus() {
|
||
const container = document.getElementById('platforms-container');
|
||
try {
|
||
const response = await fetch('/api/publish/test');
|
||
const data = await response.json();
|
||
|
||
container.innerHTML = '';
|
||
for (const [platform, status] of Object.entries(data)) {
|
||
const isConnected = status.configured && status.connected;
|
||
container.innerHTML += `
|
||
<div class="flex items-center justify-between bg-dark-800/50 rounded-xl p-4">
|
||
<div class="flex items-center gap-3">
|
||
<span class="text-2xl">${getPlatformIcon(platform)}</span>
|
||
<div>
|
||
<p class="font-medium capitalize">${platform}</p>
|
||
${status.details?.username ? `<p class="text-xs text-gray-500">@${status.details.username}</p>` : ''}
|
||
</div>
|
||
</div>
|
||
<span class="px-3 py-1 rounded-full text-sm ${isConnected ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'}">
|
||
${isConnected ? '✓ Conectado' : '✗ Desconectado'}
|
||
</span>
|
||
</div>
|
||
`;
|
||
}
|
||
} catch (error) {
|
||
container.innerHTML = '<div class="text-center text-red-400 py-4">Error cargando estado</div>';
|
||
}
|
||
}
|
||
|
||
async function loadTelegramStatus() {
|
||
const container = document.getElementById('telegram-status');
|
||
try {
|
||
const response = await fetch('/api/notifications/status');
|
||
const data = await response.json();
|
||
|
||
const isConfigured = data.telegram_configured;
|
||
container.innerHTML = `
|
||
<div class="flex items-center justify-between bg-dark-800/50 rounded-xl p-4">
|
||
<div class="flex items-center gap-3">
|
||
<span class="text-2xl">📱</span>
|
||
<div>
|
||
<p class="font-medium">Telegram Bot</p>
|
||
<p class="text-xs text-gray-500">${data.bot_token_set ? 'Token configurado' : 'Token no configurado'}</p>
|
||
</div>
|
||
</div>
|
||
<span class="px-3 py-1 rounded-full text-sm ${isConfigured ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'}">
|
||
${isConfigured ? '✓ Activo' : '✗ Inactivo'}
|
||
</span>
|
||
</div>
|
||
`;
|
||
} catch (error) {
|
||
container.innerHTML = '<div class="text-center text-red-400 py-4">Error verificando Telegram</div>';
|
||
}
|
||
}
|
||
|
||
async function loadOdooStatus() {
|
||
const container = document.getElementById('odoo-status');
|
||
try {
|
||
const response = await fetch('/api/odoo/status');
|
||
const data = await response.json();
|
||
|
||
container.innerHTML = `
|
||
<div class="flex items-center justify-between bg-dark-800/50 rounded-xl p-4">
|
||
<div class="flex items-center gap-3">
|
||
<span class="text-2xl">🏢</span>
|
||
<div>
|
||
<p class="font-medium">Odoo ERP</p>
|
||
<p class="text-xs text-gray-500">${data.version ? 'v' + data.version : (data.error || 'No configurado')}</p>
|
||
</div>
|
||
</div>
|
||
<span class="px-3 py-1 rounded-full text-sm ${data.connected ? 'bg-green-500/20 text-green-400' : 'bg-yellow-500/20 text-yellow-400'}">
|
||
${data.connected ? '✓ Conectado' : '⚠ ' + (data.configured ? 'Error' : 'No configurado')}
|
||
</span>
|
||
</div>
|
||
`;
|
||
} catch (error) {
|
||
container.innerHTML = '<div class="text-center text-red-400 py-4">Error verificando Odoo</div>';
|
||
}
|
||
}
|
||
|
||
async function loadAIStatus() {
|
||
const container = document.getElementById('ai-status');
|
||
try {
|
||
const response = await fetch('/api/generate/status');
|
||
const data = await response.json();
|
||
|
||
document.getElementById('business-name').textContent = data.business_name || '-';
|
||
document.getElementById('business-location').textContent = data.business_location || '-';
|
||
document.getElementById('content-tone').textContent = data.content_tone || '-';
|
||
|
||
container.innerHTML = `
|
||
<div class="flex items-center justify-between bg-dark-800/50 rounded-xl p-4">
|
||
<div class="flex items-center gap-3">
|
||
<span class="text-2xl">🤖</span>
|
||
<div>
|
||
<p class="font-medium">DeepSeek API</p>
|
||
<p class="text-xs text-gray-500">${data.provider || 'DeepSeek'}</p>
|
||
</div>
|
||
</div>
|
||
<span class="px-3 py-1 rounded-full text-sm ${data.configured ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'}">
|
||
${data.configured ? '✓ Configurado' : '✗ No configurado'}
|
||
</span>
|
||
</div>
|
||
`;
|
||
} catch (error) {
|
||
container.innerHTML = '<div class="text-center text-red-400 py-4">Error verificando IA</div>';
|
||
}
|
||
}
|
||
|
||
async function loadSystemStatus() {
|
||
try {
|
||
const response = await fetch('/api/health');
|
||
const data = await response.json();
|
||
|
||
document.getElementById('system-status').textContent = data.status === 'healthy' ? '● Online' : '● Offline';
|
||
document.getElementById('system-status').className = data.status === 'healthy' ? 'text-2xl font-bold text-green-400' : 'text-2xl font-bold text-red-400';
|
||
document.getElementById('db-status').textContent = '✓ OK';
|
||
document.getElementById('db-status').className = 'text-2xl font-bold text-green-400';
|
||
document.getElementById('worker-status').textContent = '✓ OK';
|
||
document.getElementById('worker-status').className = 'text-2xl font-bold text-green-400';
|
||
} catch (error) {
|
||
document.getElementById('system-status').textContent = '● Error';
|
||
document.getElementById('system-status').className = 'text-2xl font-bold text-red-400';
|
||
}
|
||
}
|
||
|
||
async function testTelegram() {
|
||
showModal('Enviando mensaje de prueba...', true);
|
||
try {
|
||
const response = await fetch('/api/notifications/test', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({})
|
||
});
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
showModal('<div class="text-center"><span class="text-4xl mb-4 block">✅</span><p class="text-lg">¡Mensaje enviado!</p><p class="text-gray-400 mt-2">Revisa tu Telegram</p></div>');
|
||
} else {
|
||
showModal('<div class="text-center"><span class="text-4xl mb-4 block">❌</span><p class="text-lg">Error al enviar</p><p class="text-gray-400 mt-2">' + (data.detail || 'Verifica la configuración') + '</p></div>');
|
||
}
|
||
} catch (error) {
|
||
showModal('<div class="text-center"><span class="text-4xl mb-4 block">❌</span><p class="text-lg">Error de conexión</p></div>');
|
||
}
|
||
}
|
||
|
||
function toggleTelegramGuide() {
|
||
const guide = document.getElementById('telegram-guide');
|
||
guide.classList.toggle('hidden');
|
||
}
|
||
|
||
async function syncOdooProducts() {
|
||
showModal('Sincronizando productos...', true);
|
||
try {
|
||
const response = await fetch('/api/odoo/sync/products', { method: 'POST' });
|
||
const data = await response.json();
|
||
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">📦</span><p class="text-lg">Sincronización completada</p><p class="text-gray-400 mt-2">Creados: ${data.created} | Actualizados: ${data.updated}</p></div>`);
|
||
} catch (error) {
|
||
showModal('<div class="text-center"><span class="text-4xl mb-4 block">❌</span><p>Error sincronizando</p></div>');
|
||
}
|
||
}
|
||
|
||
async function syncOdooServices() {
|
||
showModal('Sincronizando servicios...', true);
|
||
try {
|
||
const response = await fetch('/api/odoo/sync/services', { method: 'POST' });
|
||
const data = await response.json();
|
||
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">🛠️</span><p class="text-lg">Sincronización completada</p><p class="text-gray-400 mt-2">Creados: ${data.created} | Actualizados: ${data.updated}</p></div>`);
|
||
} catch (error) {
|
||
showModal('<div class="text-center"><span class="text-4xl mb-4 block">❌</span><p>Error sincronizando</p></div>');
|
||
}
|
||
}
|
||
|
||
async function syncOdooLeads() {
|
||
showModal('Exportando leads...', true);
|
||
try {
|
||
const response = await fetch('/api/odoo/sync/leads', { method: 'POST' });
|
||
const data = await response.json();
|
||
showModal(`<div class="text-center"><span class="text-4xl mb-4 block">🎯</span><p class="text-lg">Exportación completada</p><p class="text-gray-400 mt-2">Exportados: ${data.created}</p></div>`);
|
||
} catch (error) {
|
||
showModal('<div class="text-center"><span class="text-4xl mb-4 block">❌</span><p>Error exportando</p></div>');
|
||
}
|
||
}
|
||
|
||
function getPlatformIcon(platform) {
|
||
const icons = { x: '𝕏', facebook: '📘', instagram: '📸', threads: '🧵' };
|
||
return icons[platform] || '📱';
|
||
}
|
||
</script>
|
||
{% endblock %}
|