fix: Fix YAML syntax errors and validator prompt formatting

- 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>
This commit is contained in:
2026-01-28 21:13:58 +00:00
parent 11b0ba46fa
commit e32885afc5
19 changed files with 3440 additions and 4186 deletions

View File

@@ -1,422 +1,343 @@
<!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">
{% extends "base.html" %}
{% block title %}Configuración{% endblock %}
{% block content %}
<div class="animate-fade-in">
<!-- 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>
<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>
<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 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 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 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-2">
<button onclick="testTelegram()" class="btn-secondary px-4 py-2 rounded">
📱 Enviar Prueba
<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="showTelegramGuide()" class="btn-secondary px-4 py-2 rounded">
📖 Ver Guía
<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-4 bg-gray-800 rounded-lg p-4 text-sm">
<!-- Guide loaded dynamically -->
<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 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 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>
<span class="text-gray-400">Entorno:</span>
<span class="ml-2" id="app-env">-</span>
<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>
<span class="text-gray-400">Base de datos:</span>
<span class="ml-2" id="db-status">-</span>
<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>
<span class="text-gray-400">Redis:</span>
<span class="ml-2" id="redis-status">-</span>
<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>
<!-- 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>
</div>
{% endblock %}
<script>
window.addEventListener('load', () => {
loadApiStatus();
loadAiStatus();
loadTelegramStatus();
loadSystemInfo();
loadUsageStats();
});
{% block extra_scripts %}
<script>
// Load all statuses on page load
document.addEventListener('DOMContentLoaded', function() {
loadPlatformStatus();
loadTelegramStatus();
loadOdooStatus();
loadAIStatus();
loadSystemStatus();
});
async function loadApiStatus() {
const container = document.getElementById('api-status');
async function loadPlatformStatus() {
const container = document.getElementById('platforms-container');
try {
const response = await fetch('/api/publish/test');
const data = await response.json();
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>
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>
${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>
<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-red-400">Error al verificar IA</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');
async function loadTelegramStatus() {
const container = document.getElementById('telegram-status');
try {
const response = await fetch('/api/notifications/status');
const data = await response.json();
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>
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>
<div class="font-medium">Telegram Bot</div>
<div class="text-sm ${statusClass}">${statusText}</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>
`;
} catch (error) {
container.innerHTML = '<div class="text-red-400">Error al verificar Telegram</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 loadSystemInfo() {
try {
const response = await fetch('/api/health');
const data = await response.json();
async function loadOdooStatus() {
const container = document.getElementById('odoo-status');
try {
const response = await fetch('/api/odoo/status');
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>
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>';
}
}
container.innerHTML = html;
container.classList.remove('hidden');
async function loadAIStatus() {
const container = document.getElementById('ai-status');
try {
const response = await fetch('/api/generate/status');
const data = await response.json();
} catch (error) {
container.innerHTML = '<div class="text-red-400">Error al cargar guía</div>';
container.classList.remove('hidden');
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>');
}
}
async function generateWeekContent() {
showModal('Generando contenido para la semana...', true);
function toggleTelegramGuide() {
const guide = document.getElementById('telegram-guide');
guide.classList.toggle('hidden');
}
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 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 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);
}
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>');
}
}
function viewApiDocs() {
window.open('/api/docs', '_blank');
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 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>
function getPlatformIcon(platform) {
const icons = { x: '𝕏', facebook: '📘', instagram: '📸', threads: '🧵' };
return icons[platform] || '📱';
}
</script>
{% endblock %}