diff --git a/managers/mongodb_manager.py b/managers/mongodb_manager.py index bb793c6..2fe84dd 100644 --- a/managers/mongodb_manager.py +++ b/managers/mongodb_manager.py @@ -110,8 +110,8 @@ class MongoDBArticleCache: article_id = str(article.get_id()) key = (platform, article_id) - # Preparar datos del artículo (sin user_info) - article_data = { + # Campos base del artículo (sin user_info, sin createdAt/updatedAt) + base_fields = { 'platform': platform, 'id': article_id, 'title': article.get_title(), @@ -124,7 +124,6 @@ class MongoDBArticleCache: 'images': article.get_images(), 'modified_at': article.get_modified_at(), 'expiresAt': expires_at, - 'updatedAt': now, } # Preparar user_info @@ -175,35 +174,37 @@ class MongoDBArticleCache: ) ) else: - # Precio diferente, actualizar artículo completo - # Mantener updatedAt para saber cuándo cambió el precio - # Eliminar notifiedAt/notified_at a nivel de artículo (solo debe estar en user_info) - article_data['user_info'] = existing_user_info + # Precio diferente, actualizar campos del artículo + user_info + # Mantener createdAt/updatedAt originales (no se incluyen en el $set) + update_data = dict(base_fields) + update_data['user_info'] = existing_user_info operations.append( UpdateOne( {'platform': platform, 'id': article_id}, { - '$set': article_data, + '$set': update_data, '$unset': {'notifiedAt': '', 'notified_at': ''} } ) ) else: # Artículo nuevo - article_data['user_info'] = [{ + new_article_data = dict(base_fields) + new_article_data['user_info'] = [{ 'username': username, 'worker_name': worker_name, 'notified': True, 'notified_at': now, 'is_favorite': False, }] - article_data['createdAt'] = now + new_article_data['createdAt'] = now + new_article_data['updatedAt'] = now operations.append( UpdateOne( {'platform': platform, 'id': article_id}, { - '$set': article_data, + '$set': new_article_data, }, upsert=True ) diff --git a/web/backend/routes/articles.js b/web/backend/routes/articles.js index ad71c4c..5862f92 100644 --- a/web/backend/routes/articles.js +++ b/web/backend/routes/articles.js @@ -39,13 +39,7 @@ router.get('/', basicAuthMiddleware, async (req, res) => { const limit = parseInt(req.query.limit) || 100; const offset = parseInt(req.query.offset) || 0; - // Los artículos ya vienen ordenados de MongoDB, pero asegurémonos - const sorted = articles.sort((a, b) => { - const aTime = typeof a.notifiedAt === 'number' ? a.notifiedAt : new Date(a.notifiedAt).getTime(); - const bTime = typeof b.notifiedAt === 'number' ? b.notifiedAt : new Date(b.notifiedAt).getTime(); - return bTime - aTime; - }); - const paginated = sorted.slice(offset, offset + limit); + const paginated = articles.slice(offset, offset + limit); res.json({ articles: paginated, @@ -124,16 +118,9 @@ router.get('/search', basicAuthMiddleware, async (req, res) => { return false; }); - // Ordenar por fecha de notificación (más recientes primero) - const sorted = filtered.sort((a, b) => { - const aTime = typeof a.notifiedAt === 'number' ? a.notifiedAt : new Date(a.notifiedAt).getTime(); - const bTime = typeof b.notifiedAt === 'number' ? b.notifiedAt : new Date(b.notifiedAt).getTime(); - return bTime - aTime; - }); - res.json({ - articles: sorted, - total: sorted.length, + articles: filtered, + total: filtered.length, query: query, }); } catch (error) { diff --git a/web/backend/services/mongodb.js b/web/backend/services/mongodb.js index c492744..4f26469 100644 --- a/web/backend/services/mongodb.js +++ b/web/backend/services/mongodb.js @@ -281,11 +281,21 @@ export async function getNotifiedArticles(filter = {}) { }; // Si hay un user_info específico, usar sus datos + const fallbackTime = + article.createdAt?.getTime?.() || + (typeof article.createdAt === 'number' ? article.createdAt : null) || + article.modified_at?.getTime?.() || + null; + if (relevantUserInfo) { result.username = relevantUserInfo.username; result.worker_name = relevantUserInfo.worker_name; result.is_favorite = relevantUserInfo.is_favorite || false; - result.notifiedAt = relevantUserInfo.notified_at?.getTime() || Date.now(); + // NO usar Date.now() como fallback, para no mover artículos antiguos + result.notifiedAt = + relevantUserInfo.notified_at?.getTime?.() || + (typeof relevantUserInfo.notified_at === 'number' ? relevantUserInfo.notified_at : null) || + fallbackTime; } else { // Sin filtro específico, mostrar el primer user_info o datos generales const firstUserInfo = (article.user_info || [])[0]; @@ -293,13 +303,19 @@ export async function getNotifiedArticles(filter = {}) { result.username = firstUserInfo.username; result.worker_name = firstUserInfo.worker_name; result.is_favorite = firstUserInfo.is_favorite || false; - result.notifiedAt = firstUserInfo.notified_at?.getTime() || Date.now(); + result.notifiedAt = + firstUserInfo.notified_at?.getTime?.() || + (typeof firstUserInfo.notified_at === 'number' ? firstUserInfo.notified_at : null) || + fallbackTime; } else { // Compatibilidad con estructura antigua result.username = article.username; result.worker_name = article.worker_name; result.is_favorite = article.is_favorite || false; - result.notifiedAt = article.notifiedAt?.getTime() || Date.now(); + result.notifiedAt = + article.notifiedAt?.getTime?.() || + (typeof article.notifiedAt === 'number' ? article.notifiedAt : null) || + fallbackTime; } } @@ -331,7 +347,7 @@ export async function getFavorites(username = null) { const articles = await articlesCollection .find(query) - .sort({ 'user_info.notified_at': -1, createdAt: -1 }) + .sort({ createdAt: -1, modified_at: -1 }) .toArray(); // Filtrar y transformar para devolver solo los favoritos relevantes @@ -351,7 +367,14 @@ export async function getFavorites(username = null) { username: userInfo.username, worker_name: userInfo.worker_name, is_favorite: true, - notifiedAt: userInfo.notified_at?.getTime() || Date.now(), + // NOTA: no usar Date.now() como fallback, para no mover favoritos antiguos + notifiedAt: + userInfo.notified_at?.getTime?.() || + (typeof userInfo.notified_at === 'number' ? userInfo.notified_at : null) || + article.createdAt?.getTime?.() || + (typeof article.createdAt === 'number' ? article.createdAt : null) || + article.modified_at?.getTime?.() || + null, expiresAt: article.expiresAt?.getTime() || null, }); } @@ -365,7 +388,14 @@ export async function getFavorites(username = null) { username: userInfo.username, worker_name: userInfo.worker_name, is_favorite: true, - notifiedAt: userInfo.notified_at?.getTime() || Date.now(), + // NOTA: no usar Date.now() como fallback, para no mover favoritos antiguos + notifiedAt: + userInfo.notified_at?.getTime?.() || + (typeof userInfo.notified_at === 'number' ? userInfo.notified_at : null) || + article.createdAt?.getTime?.() || + (typeof article.createdAt === 'number' ? article.createdAt : null) || + article.modified_at?.getTime?.() || + null, expiresAt: article.expiresAt?.getTime() || null, }); }