{"id":930,"date":"2025-06-05T16:47:45","date_gmt":"2025-06-05T21:47:45","guid":{"rendered":"https:\/\/jovenesinvestigadores.org\/?p=930"},"modified":"2025-06-05T16:55:35","modified_gmt":"2025-06-05T21:55:35","slug":"agregador-de-noticias-en-el-peru","status":"publish","type":"post","link":"https:\/\/jovenesinvestigadores.org\/index.php\/2025\/06\/05\/agregador-de-noticias-en-el-peru\/","title":{"rendered":"Agregador de noticias en el Per\u00fa"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"es\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>\u00daltimas Noticias de Per\u00fa<\/title>\n    <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n    <style>\n        body {\n            font-family: 'Inter', sans-serif;\n            background-color: #f7fafc; \/* Tailwind gray-100 *\/\n        }\n        .news-item-card {\n            transition: transform 0.3s ease, box-shadow 0.3s ease;\n            border: 1px solid #e2e8f0; \/* Tailwind gray-300 *\/\n            display: flex;\n            flex-direction: column;\n        }\n        .news-item-card:hover {\n            transform: translateY(-5px);\n            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); \/* Tailwind shadow-lg *\/\n        }\n        .news-item-content {\n            flex-grow: 1; \/* Hace que el contenido ocupe el espacio disponible *\/\n        }\n        .loading-spinner-container {\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            min-height: 150px;\n        }\n        .loading-spinner {\n            border: 6px solid rgba(0, 0, 0, 0.1);\n            border-left-color: #2563eb; \/* Tailwind blue-600 *\/\n            border-radius: 50%;\n            width: 60px;\n            height: 60px;\n            animation: spin 1s linear infinite;\n        }\n        @keyframes spin {\n            to { transform: rotate(360deg); }\n        }\n        .source-tag {\n            display: inline-block;\n            padding: 0.25rem 0.75rem; \/* py-1 px-3 *\/\n            border-radius: 9999px; \/* rounded-full *\/\n            font-size: 0.75rem; \/* text-xs *\/\n            font-weight: 600; \/* font-semibold *\/\n            margin-right: 0.5rem; \/* mr-2 *\/\n            margin-bottom: 0.5rem; \/* mb-2 *\/\n            text-transform: uppercase;\n            letter-spacing: 0.05em;\n        }\n        \/* Clases de ejemplo para algunos medios peruanos *\/\n        .el-comercio { background-color: #FFD700; color: #000000; }\n        .la-republica { background-color: #D52B1E; color: #FFFFFF; }\n        .rpp { background-color: #0033A0; color: #FFFFFF; }\n        .gestion { background-color: #007A33; color: #FFFFFF; }\n        .andina { background-color: #1E90FF; color: #FFFFFF; }\n        .peru21 { background-color: #EF4A23; color: #FFFFFF; }\n        .exitosa { background-color: #00AEEF; color: #FFFFFF; }\n        .diario-correo { background-color: #F28F20; color: #FFFFFF; }\n        .ojo { background-color: #FFC107; color: #000000; }\n        .el-peruano { background-color: #AF1A2D; color: #FFFFFF; }\n        .expreso { background-color: #004B8D; color: #FFFFFF;}\n        .la-razon { background-color: #C00000; color: #FFFFFF;}\n        .peru-com { background-color: #FF6600; color: #FFFFFF;}\n        .atv-noticias { background-color: #F05A28; color: #FFFFFF;} \/* ATV *\/\n        \/* Medios de TV suelen ser los m\u00e1s dif\u00edciles para feeds RSS generales y estables *\/\n        .canal-n { background-color: #000000; color: #FFFFFF; }\n        .america-tv { background-color: #FF6C0C; color: #FFFFFF; }\n        .latina { background-color: #ED1C24; color: #FFFFFF; }\n        .panamericana { background-color: #0055A4; color: #FFFFFF; }\n        .default-source { background-color: #6B7280; color: #FFFFFF; } \/* Tailwind gray-500 *\/\n    <\/style>\n<\/head>\n<body class=\"p-4 md:p-8\">\n    <div class=\"container mx-auto max-w-7xl\">\n        <header class=\"mb-10 text-center\">\n            <h1 class=\"text-4xl md:text-5xl font-bold text-blue-700 mb-2\">Noticias de Per\u00fa<\/h1>\n            <p class=\"text-lg text-gray-600\">Las \u00faltimas actualizaciones de los principales medios.<\/p>\n        <\/header>\n\n        <div id=\"loading-indicator\" class=\"loading-spinner-container my-12\">\n            <div class=\"loading-spinner\"><\/div>\n            <p class=\"ml-4 text-xl text-gray-700 font-medium\">Cargando noticias&#8230;<\/p>\n        <\/div>\n\n        <div id=\"news-container\" class=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6\">\n            \n        <\/div>\n\n        <footer class=\"mt-16 py-8 text-center text-gray-500 text-sm border-t border-gray-300\">\n            <p>Agregador de noticias. El contenido y los derechos pertenecen a sus respectivos medios.<\/p>\n            <p>Las noticias se actualizan cada vez que se carga esta p\u00e1gina.<\/p>\n            <p>Nota: La carga de noticias depende de la disponibilidad y formato de los canales RSS y del funcionamiento del proxy CORS.<\/p>\n        <\/footer>\n    <\/div>\n\n    <script>\n        const newsSources = [\n            \/\/ Medios con feeds RSS que suelen ser m\u00e1s estables o confirmados:\n            \/\/ { name: 'El Comercio', url: 'https:\/\/elcomercio.pe\/arc\/outboundfeeds\/rss\/?outputType=xml', class: 'el-comercio' }, \/\/ RETIRADO\n            { name: 'La Rep\u00fablica', url: 'https:\/\/larepublica.pe\/feed\/latest\/', class: 'la-republica' }, \n            { name: 'RPP Noticias', url: 'https:\/\/rpp.pe\/feed\/portada', class: 'rpp' }, \n            \/\/ { name: 'Gesti\u00f3n', url: 'https:\/\/gestion.pe\/feed\/', class: 'gestion' }, \/\/ RETIRADO\n            { name: 'Agencia Andina', url: 'https:\/\/andina.pe\/agencia\/rss\/portada.xml', class: 'andina' }, \/\/ Mantenido como estaba, ya que funcionaba bien.\n            { name: 'Per\u00fa21', url: 'https:\/\/peru21.pe\/feed\/', class: 'peru21' },\n            { name: 'Exitosa Noticias', url: 'https:\/\/exitosanoticias.pe\/feed\/', class: 'exitosa' },\n            \/\/ { name: 'Diario Correo', url: 'https:\/\/diariocorreo.pe\/feed\/', class: 'diario-correo' }, \/\/ RETIRADO\n            \/\/ { name: 'Diario Ojo', url: 'https:\/\/ojo.pe\/feed\/', class: 'ojo' }, \/\/ RETIRADO\n            { name: 'El Peruano', url: 'https:\/\/elperuano.pe\/rss\/noticias', class: 'el-peruano' }, \n            \/\/ { name: 'Expreso', url: 'https:\/\/expreso.com.pe\/feed\/', class: 'expreso' }, \/\/ RETIRADO\n            { name: 'La Raz\u00f3n', url: 'https:\/\/larazon.pe\/feed\/', class: 'la-razon' }, \n            \/\/ { name: 'Per\u00fa.com', url: 'https:\/\/peru.com\/feed\/', class: 'peru-com' }, \/\/ RETIRADO\n\n            \/\/ Medios de TV - Sus feeds RSS generales pueden ser inestables o limitados.\n            { name: 'ATV Noticias', url: 'https:\/\/www.atv.pe\/feed', class: 'atv-noticias' }, \n            { name: 'Canal N', url: 'https:\/\/canaln.pe\/feed\/', class: 'canal-n' }, \n            \/\/ { name: 'Panamericana TV', url: 'https:\/\/panamericana.pe\/feed\/', class: 'panamericana' } \/\/ RETIRADO\n            \/\/ Se pueden a\u00f1adir otros medios aqu\u00ed si se encuentran feeds fiables.\n            \/\/ { name: 'Am\u00e9rica Noticias', url: 'https:\/\/www.americatv.com.pe\/noticias\/feed', class: 'america-tv' }, \n            \/\/ { name: 'Latina Noticias', url: 'https:\/\/www.latina.pe\/noticias\/feed\/', class: 'latina' }, \n        ];\n\n        const newsContainer = document.getElementById('news-container');\n        const loadingIndicator = document.getElementById('loading-indicator');\n        const MAX_ITEMS_PER_FEED = 4; \n\n        async function fetchNews(source) {\n            const proxyUrl = 'https:\/\/api.allorigins.win\/raw?url=';\n            let itemsToReturn = [];\n            try {\n                const controller = new AbortController();\n                const timeoutId = setTimeout(() => controller.abort(), 15000); \/\/ 15 segundos de timeout\n\n                const response = await fetch(proxyUrl + encodeURIComponent(source.url), { \n                    cache: \"no-store\",\n                    signal: controller.signal \n                });\n                clearTimeout(timeoutId);\n\n                if (!response.ok) {\n                    throw new Error(`Error HTTP: ${response.status} para ${source.name}`);\n                }\n                const text = await response.text();\n                const parser = new DOMParser();\n                const xmlDoc = parser.parseFromString(text, \"application\/xml\");\n                \n                const errorNode = xmlDoc.querySelector(\"parsererror\");\n                if (errorNode) {\n                    console.error(`Error de parseo XML para ${source.name} (URL: ${source.url}):`, errorNode.textContent);\n                    throw new Error(`Error de parseo XML para ${source.name}.`);\n                }\n                \n                let items = Array.from(xmlDoc.querySelectorAll(\"item\")); \n                if (items.length === 0) {\n                    items = Array.from(xmlDoc.querySelectorAll(\"entry\")); \n                }\n                \n                if (items.length === 0) {\n                    console.warn(`No se encontraron items <item> o <entry> en el feed de ${source.name} (URL: ${source.url})`);\n                }\n\n                items = items.slice(0, MAX_ITEMS_PER_FEED);\n                \n                itemsToReturn = items.map(item => {\n                    const title = item.querySelector(\"title\")?.textContent?.trim() || 'Sin t\u00edtulo';\n                    \n                    let link = item.querySelector(\"link\")?.textContent?.trim(); \n                    if (typeof link === 'object' || !link || link.trim() === \"\") { \n                        link = item.querySelector(\"link\")?.getAttribute('href')?.trim();\n                    }\n                    if (!link || link.trim() === \"\") { \n                        const atomLinkNode = Array.from(item.querySelectorAll(\"link[rel='alternate']\")).find(l => l.getAttribute('type') === 'text\/html');\n                        if (atomLinkNode) link = atomLinkNode.getAttribute('href')?.trim();\n                    }\n                    if (!link || link.trim() === \"\") { \n                         const guidNode = item.querySelector(\"guid\");\n                         if (guidNode) {\n                            const isPermaLink = guidNode.getAttribute('isPermaLink');\n                            const guidText = guidNode.textContent?.trim();\n                            if ( ((isPermaLink && isPermaLink.toLowerCase() !== 'false' && isPermaLink !== '0')) || (!guidNode.hasAttribute('isPermaLink') && guidText && guidText.startsWith('http')) ) { \n                                link = guidText;\n                            }\n                         }\n                    }\n                    if (!link || !link.startsWith('http')) {\n                        const linkTagAlone = item.getElementsByTagName('link')[0];\n                        if (linkTagAlone && linkTagAlone.nextSibling && linkTagAlone.nextSibling.nodeType === Node.TEXT_NODE) {\n                             link = linkTagAlone.nextSibling.nodeValue.trim();\n                        } else if (linkTagAlone && linkTagAlone.childNodes.length > 0 && linkTagAlone.childNodes[0].nodeType === Node.TEXT_NODE) {\n                             link = linkTagAlone.childNodes[0].nodeValue.trim();\n                        }\n                    }\n                    \n                    if (link && !link.startsWith('http') && !link.startsWith('#')) {\n                        try {\n                            const sourceUrlObj = new URL(source.url);\n                            const itemUrl = new URL(link, sourceUrlObj.origin);\n                            link = itemUrl.href;\n                        } catch (e) {\n                             console.warn(`No se pudo construir URL absoluta para link relativo \"${link}\" de ${source.name}`);\n                             link = null; \n                        }\n                    }\n\n                    if (!link || !link.startsWith(\"http\")) {\n                        console.warn(`Link no v\u00e1lido o no encontrado para \"${title}\" de ${source.name}. Usando fallback a #.`);\n                        link = \"#\"; \n                    }\n\n                    let descriptionContent = \n                        item.querySelector(\"description\")?.textContent ||\n                        item.querySelector(\"summary\")?.textContent ||\n                        item.querySelector(\"content\")?.textContent ||\n                        item.querySelector(\"content\\\\:encoded\")?.textContent || \n                        item.getElementsByTagNameNS('*', 'encoded')[0]?.textContent || \n                        ''; \n                    \n                    const tempDiv = document.createElement('div');\n                    tempDiv.innerHTML = descriptionContent; \n                    let description = (tempDiv.textContent || tempDiv.innerText || \"\").trim();\n                    description = description.replace(\/<[^>]+>\/g, '').replace(\/\\s\\s+\/g, ' ').trim(); \n                    description = description.substring(0, 120) + (description.length > 120 ? '...' : ''); \n                    if (description === '') description = 'No hay descripci\u00f3n disponible.';\n\n                    const dateString = \n                        item.querySelector(\"pubDate\")?.textContent || \n                        item.querySelector(\"published\")?.textContent ||\n                        item.querySelector(\"updated\")?.textContent ||\n                        item.querySelector(\"dc\\\\:date\")?.textContent; \n\n                    let formattedDate = \"Fecha no disponible\";\n                    let originalDateForSort = null; \n                    if (dateString) {\n                        originalDateForSort = dateString; \n                        try {\n                            const dateObj = new Date(dateString);\n                            if (!isNaN(dateObj.getTime())) {\n                                formattedDate = dateObj.toLocaleDateString('es-PE', { \n                                    day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' \n                                });\n                            } else {\n                                formattedDate = dateString.substring(0, 25) + (dateString.length > 25 ? '...' : ''); \n                            }\n                        } catch (e) {\n                            console.warn(`Error parseando fecha \"${dateString}\" para ${source.name}:`, e);\n                            formattedDate = dateString.substring(0, 25) + (dateString.length > 25 ? '...' : ''); \n                        }\n                    }\n\n                    return { \n                        title, \n                        link, \n                        description, \n                        pubDate: formattedDate, \n                        pubDateOriginal: originalDateForSort, \n                        sourceName: source.name, \n                        sourceClass: source.class || 'default-source' \n                    };\n                }).filter(item => item.title && item.title.toLowerCase() !== 'sin t\u00edtulo' && item.link && item.link !== \"#\");\n            } catch (error) {\n                console.error(`Error procesando el feed de ${source.name} (${source.url}):`, error);\n                const errorCardHtml = `\n                    <article class=\"news-item-card bg-white p-5 rounded-lg shadow-md border border-red-400\">\n                        <div class=\"news-item-content\">\n                            <span class=\"source-tag ${source.class || 'default-source'}\">${source.name}<\/span>\n                            <h2 class=\"text-lg font-semibold text-red-700 mb-1 leading-tight\">Error al Cargar Noticias<\/h2>\n                            <p class=\"text-gray-600 text-sm break-words\">${error.message}<\/p>\n                        <\/div>\n                        <p class=\"text-gray-500 text-xs mt-2\">URL: <a href=\"${source.url}\" target=\"_blank\" class=\"text-blue-500 hover:underline break-all\">${source.url}<\/a><\/p>\n                    <\/article>\n                `;\n                newsContainer.insertAdjacentHTML('beforeend', errorCardHtml);\n            }\n            return itemsToReturn;\n        }\n\n        async function loadAllNews() {\n            let allNewsItems = [];\n            const currentCards = newsContainer.querySelectorAll('.news-item-card, p.text-red-600'); \n            currentCards.forEach(card => card.remove());\n            \n            loadingIndicator.style.display = 'flex'; \n\n            const fetchPromises = newsSources.map(source => fetchNews(source));\n            const results = await Promise.allSettled(fetchPromises);\n\n            results.forEach(result => {\n                if (result.status === 'fulfilled' && result.value && Array.isArray(result.value)) {\n                    allNewsItems = allNewsItems.concat(result.value);\n                }\n            });\n            \n            loadingIndicator.style.display = 'none';\n\n            if (allNewsItems.length > 0) {\n                allNewsItems.sort((a, b) => {\n                    try {\n                        const dateA = a.pubDateOriginal ? new Date(a.pubDateOriginal) : null;\n                        const dateB = b.pubDateOriginal ? new Date(b.pubDateOriginal) : null;\n                        if (!dateA || isNaN(dateA.getTime())) return 1; \n                        if (!dateB || isNaN(dateB.getTime())) return -1;\n                        return dateB - dateA; \n                    } catch (e) { return 0; }\n                });\n\n                allNewsItems.forEach(item => {\n                    const newsItemHtml = `\n                        <article class=\"news-item-card bg-white p-5 rounded-lg shadow-md hover:shadow-xl\">\n                            <div class=\"news-item-content\">\n                                <span class=\"source-tag ${item.sourceClass}\">${item.sourceName}<\/span>\n                                <h2 class=\"text-lg font-semibold text-blue-700 mb-1 hover:text-blue-900 leading-tight\">\n                                    <a href=\"${item.link}\" target=\"_blank\" rel=\"noopener noreferrer\">${item.title}<\/a>\n                                <\/h2>\n                                <p class=\"text-gray-500 text-xs mb-2\">${item.pubDate}<\/p>\n                                <p class=\"text-gray-700 text-sm leading-relaxed\">${item.description}<\/p>\n                            <\/div>\n                        <\/article>\n                    `;\n                    newsContainer.insertAdjacentHTML('beforeend', newsItemHtml);\n                });\n            }\n            \n            const finalRenderedCards = newsContainer.querySelectorAll('.news-item-card'); \n            if (finalRenderedCards.length === 0) { \n                newsContainer.innerHTML = '<p class=\"text-center text-red-600 font-semibold col-span-1 sm:col-span-2 lg:col-span-3 xl:col-span-4\">No se pudieron cargar noticias de ninguna fuente activa. Verifique las URLs de los feeds RSS, la conexi\u00f3n a internet y la consola del navegador para m\u00e1s detalles.<\/p>';\n            }\n        }\n        document.addEventListener('DOMContentLoaded', loadAllNews);\n    <\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>\u00daltimas Noticias de Per\u00fa Noticias de Per\u00fa Las \u00faltimas actualizaciones de los principales medios. Cargando&#8230;<\/p>\n","protected":false},"author":1,"featured_media":931,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rop_custom_images_group":[],"rop_custom_messages_group":[],"rop_publish_now":"initial","rop_publish_now_accounts":{"facebook_9001523176567281_422463201840075":""},"rop_publish_now_history":[],"rop_publish_now_status":"pending","_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[288],"tags":[315,314,142],"class_list":["post-930","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-observatorio-de-la-desinformacion","tag-agregador-de-noticias","tag-noticias","tag-peru"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/jovenesinvestigadores.org\/wp-content\/uploads\/2025\/06\/breaking-news-7569437_1280.avif","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/jovenesinvestigadores.org\/index.php\/wp-json\/wp\/v2\/posts\/930","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jovenesinvestigadores.org\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jovenesinvestigadores.org\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jovenesinvestigadores.org\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/jovenesinvestigadores.org\/index.php\/wp-json\/wp\/v2\/comments?post=930"}],"version-history":[{"count":3,"href":"https:\/\/jovenesinvestigadores.org\/index.php\/wp-json\/wp\/v2\/posts\/930\/revisions"}],"predecessor-version":[{"id":935,"href":"https:\/\/jovenesinvestigadores.org\/index.php\/wp-json\/wp\/v2\/posts\/930\/revisions\/935"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/jovenesinvestigadores.org\/index.php\/wp-json\/wp\/v2\/media\/931"}],"wp:attachment":[{"href":"https:\/\/jovenesinvestigadores.org\/index.php\/wp-json\/wp\/v2\/media?parent=930"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jovenesinvestigadores.org\/index.php\/wp-json\/wp\/v2\/categories?post=930"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jovenesinvestigadores.org\/index.php\/wp-json\/wp\/v2\/tags?post=930"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}