diff --git a/web/backend/server.js b/web/backend/server.js
index 178d1dd..539c836 100644
--- a/web/backend/server.js
+++ b/web/backend/server.js
@@ -63,6 +63,15 @@ async function initRedis() {
redisClient.on('error', (err) => console.error('Redis Client Error', err));
await redisClient.connect();
console.log('✅ Conectado a Redis');
+
+ // Inicializar claves conocidas para evitar notificar artículos existentes
+ try {
+ const initialKeys = await redisClient.keys('notified:*');
+ notifiedArticleKeys = new Set(initialKeys);
+ console.log(`📋 ${notifiedArticleKeys.size} artículos ya notificados detectados`);
+ } catch (error) {
+ console.error('Error inicializando claves de artículos:', error.message);
+ }
} else {
console.log('ℹ️ Redis no configurado, usando modo memoria');
}
@@ -446,7 +455,7 @@ app.get('/api/articles/search', async (req, res) => {
}
});
-// Obtener logs (últimas líneas)
+// Obtener logs (últimas líneas o nuevas líneas desde un número de línea)
app.get('/api/logs', (req, res) => {
try {
// Intentar múltiples ubicaciones posibles
@@ -457,7 +466,7 @@ app.get('/api/logs', (req, res) => {
if (existsSync(altPath)) {
logFile = altPath;
} else {
- return res.json({ logs: [] });
+ return res.json({ logs: [], totalLines: 0, lastLineNumber: 0 });
}
}
@@ -465,19 +474,37 @@ app.get('/api/logs', (req, res) => {
try {
const stats = statSync(logFile);
if (stats.isDirectory()) {
- return res.json({ logs: ['Error: monitor.log es un directorio. Por favor, elimínalo y reinicia.'] });
+ return res.json({ logs: ['Error: monitor.log es un directorio. Por favor, elimínalo y reinicia.'], totalLines: 0, lastLineNumber: 0 });
}
} catch (e) {
- return res.json({ logs: [] });
+ return res.json({ logs: [], totalLines: 0, lastLineNumber: 0 });
}
- const logs = readFileSync(logFile, 'utf8');
- const lines = logs.split('\n').filter(l => l.trim());
- const limit = parseInt(req.query.limit) || 100;
- const lastLines = lines.slice(-limit);
- // Mantener orden natural: más antiguo a más reciente
+ const logsContent = readFileSync(logFile, 'utf8');
+ const allLines = logsContent.split('\n').filter(l => l.trim());
+ const totalLines = allLines.length;
- res.json({ logs: lastLines });
+ // Si se proporciona since (número de línea desde el que empezar), devolver solo las nuevas
+ const sinceLine = parseInt(req.query.since) || 0;
+
+ if (sinceLine > 0 && sinceLine < totalLines) {
+ // Devolver solo las líneas nuevas después de sinceLine
+ const newLines = allLines.slice(sinceLine);
+ return res.json({
+ logs: newLines,
+ totalLines: totalLines,
+ lastLineNumber: totalLines - 1 // Índice de la última línea
+ });
+ } else {
+ // Carga inicial: devolver las últimas líneas
+ const limit = parseInt(req.query.limit) || 500;
+ const lastLines = allLines.slice(-limit);
+ return res.json({
+ logs: lastLines,
+ totalLines: totalLines,
+ lastLineNumber: totalLines - 1 // Índice de la última línea
+ });
+ }
} catch (error) {
res.status(500).json({ error: error.message });
}
@@ -580,8 +607,8 @@ if (!existsSync(watchLogPath)) {
}
}
-// Watch files for changes (ya no vigilamos FAVORITES_PATH porque usa Redis)
-const watcher = watch([WORKERS_PATH, watchLogPath].filter(p => existsSync(p)), {
+// Watch files for changes (ya no vigilamos logs porque usa polling)
+const watcher = watch([WORKERS_PATH].filter(p => existsSync(p)), {
persistent: true,
ignoreInitial: true,
});
@@ -591,17 +618,100 @@ watcher.on('change', async (path) => {
if (path === WORKERS_PATH) {
const workers = readJSON(WORKERS_PATH);
broadcast({ type: 'workers_updated', data: workers });
- } else if (path === LOG_PATH) {
- broadcast({ type: 'logs_updated' });
}
});
+// Rastrear artículos ya notificados para detectar nuevos
+let notifiedArticleKeys = new Set();
+let articlesCheckInterval = null;
+
+// Función para detectar y enviar artículos nuevos
+async function checkForNewArticles() {
+ if (!redisClient) {
+ return;
+ }
+
+ try {
+ const currentKeys = await redisClient.keys('notified:*');
+ const currentKeysSet = new Set(currentKeys);
+
+ // Encontrar claves nuevas
+ const newKeys = currentKeys.filter(key => !notifiedArticleKeys.has(key));
+
+ if (newKeys.length > 0) {
+ // Obtener los artículos nuevos
+ const newArticles = [];
+ for (const key of newKeys) {
+ try {
+ const value = await redisClient.get(key);
+ if (value) {
+ // Intentar parsear como JSON
+ let articleData = {};
+ try {
+ articleData = JSON.parse(value);
+ } catch (e) {
+ // Si no es JSON válido, extraer información de la key
+ const parts = key.split(':');
+ if (parts.length >= 3) {
+ articleData = {
+ platform: parts[1],
+ id: parts.slice(2).join(':'),
+ };
+ }
+ }
+
+ // Añadir información adicional si está disponible
+ if (articleData.platform && articleData.id) {
+ newArticles.push({
+ platform: articleData.platform || 'unknown',
+ id: articleData.id || 'unknown',
+ title: articleData.title || null,
+ price: articleData.price || null,
+ currency: articleData.currency || '€',
+ url: articleData.url || null,
+ images: articleData.images || [],
+ });
+ }
+ }
+ } catch (error) {
+ console.error(`Error obteniendo artículo de Redis (${key}):`, error.message);
+ }
+ }
+
+ // Enviar artículos nuevos por WebSocket
+ if (newArticles.length > 0) {
+ broadcast({
+ type: 'new_articles',
+ data: newArticles
+ });
+ }
+
+ // Actualizar el set de claves notificadas
+ notifiedArticleKeys = currentKeysSet;
+ }
+ } catch (error) {
+ console.error('Error verificando artículos nuevos:', error.message);
+ }
+}
+
+// Inicializar el check de artículos cuando Redis esté listo
+async function startArticleMonitoring() {
+ if (redisClient) {
+ // Iniciar intervalo para verificar nuevos artículos cada 3 segundos
+ articlesCheckInterval = setInterval(checkForNewArticles, 3000);
+ console.log('✅ Monitoreo de artículos nuevos iniciado');
+ }
+}
+
// Inicializar servidor
const PORT = process.env.PORT || 3001;
async function startServer() {
await initRedis();
+ // Iniciar monitoreo de artículos nuevos
+ await startArticleMonitoring();
+
server.listen(PORT, () => {
console.log(`🚀 Servidor backend ejecutándose en http://localhost:${PORT}`);
console.log(`📡 WebSocket disponible en ws://localhost:${PORT}`);
diff --git a/web/frontend/src/App.vue b/web/frontend/src/App.vue
index 02a6221..c24c8be 100644
--- a/web/frontend/src/App.vue
+++ b/web/frontend/src/App.vue
@@ -83,6 +83,65 @@