{"id":4409,"date":"2025-07-02T19:54:39","date_gmt":"2025-07-02T22:54:39","guid":{"rendered":"https:\/\/canal8.info\/?page_id=4409"},"modified":"2025-07-02T19:55:04","modified_gmt":"2025-07-02T22:55:04","slug":"4409-2","status":"publish","type":"page","link":"https:\/\/canal8.info\/?page_id=4409","title":{"rendered":"asistente"},"content":{"rendered":"<p><!DOCTYPE html><br \/>\n<html lang=\"es\"><br \/>\n<head><br \/>\n    <meta charset=\"UTF-8\"><br \/>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><br \/>\n    <title>Asistente de Noticias &#8211; Canal 8<\/title><br \/>\n    <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n    <link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@400;500;600;700&#038;display=swap\" rel=\"stylesheet\">\n<style>\n        body {\n            font-family: 'Inter', sans-serif;\n        }\n        .loader {\n            border: 4px solid #f3f3f3;\n            border-top: 4px solid #3498db;\n            border-radius: 50%;\n            width: 40px;\n            height: 40px;\n            animation: spin 1s linear infinite;\n        }\n        @keyframes spin {\n            0% { transform: rotate(0deg); }\n            100% { transform: rotate(360deg); }\n        }\n        .image-option {\n            cursor: pointer;\n            transition: transform 0.2s, box-shadow 0.2s;\n            border: 2px solid transparent;\n        }\n        .image-option:hover {\n            transform: scale(1.05);\n            box-shadow: 0 0 15px rgba(59, 130, 246, 0.5);\n        }\n        .image-option.selected {\n            border-color: #3b82f6;\n            box-shadow: 0 0 15px rgba(59, 130, 246, 0.5);\n        }\n        @keyframes pulse {\n            0%, 100% { opacity: 1; }\n            50% { opacity: 0.5; }\n        }\n        .recording {\n            animation: pulse 2s infinite;\n        }\n    <\/style>\n<p><\/head><br \/>\n<body class=\"bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-300\"><\/p>\n<div class=\"container mx-auto p-4 md:p-8\">\n<header class=\"text-center mb-8\">\n<h1 class=\"text-3xl md:text-4xl font-bold text-blue-600 dark:text-blue-400\">Asistente de Noticias IA<\/h1>\n<p class=\"text-lg text-gray-600 dark:text-gray-400 mt-2\">Genera y Publica contenido para Canal 8<\/p>\n<\/header>\n<div class=\"grid grid-cols-1 lg:grid-cols-2 gap-8\">\n            <!-- Input Column --><\/p>\n<div class=\"bg-white dark:bg-gray-800 p-6 rounded-2xl shadow-lg flex flex-col\">\n<div class=\"space-y-8\">\n<div>\n<h2 class=\"text-2xl font-semibold mb-4 border-b border-gray-200 dark:border-gray-700 pb-2\">1. Provee el Contenido<\/h2>\n<div class=\"mt-4\">\n                            <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2\">Escribe o pega el texto:<\/label><br \/>\n                            <textarea id=\"sourceText\" rows=\"8\" class=\"w-full p-3 bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition\" placeholder=\"El intendente visit\u00f3 la nueva plaza del barrio San Mart\u00edn...\"><\/textarea>\n                        <\/div>\n<div class=\"mt-4\">\n                            <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2\">O graba un audio:<\/label><\/p>\n<div class=\"flex items-center gap-4\">\n                                <button id=\"recordButton\" class=\"flex-1 bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-lg flex items-center justify-center gap-2 transition-all\"><br \/>\n                                    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-mic-fill\" viewBox=\"0 0 16 16\"><path d=\"M5 3a3 3 0 0 1 6 0v5a3 3 0 0 1-6 0V3z\"\/><path d=\"M3.5 6.5A.5.5 0 0 1 4 7v1a4 4 0 0 0 8 0V7a.5.5 0 0 1 1 0v1a5 5 0 0 1-4.5 4.975V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 .5-.5z\"\/><\/svg><br \/>\n                                    Grabar<br \/>\n                                <\/button><br \/>\n                                <button id=\"stopButton\" disabled class=\"flex-1 bg-gray-400 text-white font-bold py-2 px-4 rounded-lg flex items-center justify-center gap-2 cursor-not-allowed\"><br \/>\n                                    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-stop-fill\" viewBox=\"0 0 16 16\"><path d=\"M5 3.5h6A1.5 1.5 0 0 1 12.5 5v6a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 11V5A1.5 1.5 0 0 1 5 3.5z\"\/><\/svg><br \/>\n                                    Detener<br \/>\n                                <\/button>\n                            <\/div>\n<p id=\"audioStatus\" class=\"text-center text-sm text-gray-500 mt-2 h-5\">\n<\/p><\/div><\/div>\n<div>\n<h2 class=\"text-2xl font-semibold mb-4 border-b border-gray-200 dark:border-gray-700 pb-2\">2. Sube una Imagen (Opcional)<\/h2>\n<p>                        <input type=\"file\" id=\"imageUpload\" accept=\"image\/*\" class=\"w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-blue-50 dark:file:bg-blue-900 file:text-blue-700 dark:file:text-blue-300 hover:file:bg-blue-100 dark:hover:file:bg-blue-800 transition\"><\/p>\n<div class=\"mt-4\">\n                            <img decoding=\"async\" id=\"imagePreview\" src=\"\" alt=\"Previsualizaci\u00f3n de imagen\" class=\"hidden max-w-full h-auto rounded-lg shadow-md\"\/>\n                        <\/div><\/div><\/div>\n<div class=\"mt-auto pt-8 text-center\">\n                    <button id=\"generateButton\" class=\"w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-lg shadow-md hover:shadow-lg transform hover:-translate-y-0.5 transition-all duration-300\"><br \/>\n                        \u2728 Generar Noticia<br \/>\n                    <\/button>\n                <\/div><\/div>\n<p>            <!-- Output Column --><\/p>\n<div id=\"output-container\" class=\"bg-white dark:bg-gray-800 p-6 rounded-2xl shadow-lg relative\">\n<div id=\"loading\" class=\"hidden absolute inset-0 bg-white\/80 dark:bg-gray-800\/80 flex flex-col items-center justify-center rounded-2xl z-10\">\n<div class=\"loader\"><\/div>\n<p id=\"loading-text\" class=\"mt-4 text-lg font-semibold text-gray-700 dark:text-gray-300\">Generando contenido&#8230;<\/p>\n<\/p><\/div>\n<h2 class=\"text-2xl font-semibold mb-4 border-b border-gray-200 dark:border-gray-700 pb-2\">3. Edita y Completa<\/h2>\n<div class=\"space-y-6\">\n<div>\n                        <label for=\"generatedTitle\" class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">T\u00edtulo:<\/label><br \/>\n                        <input type=\"text\" id=\"generatedTitle\" class=\"w-full p-3 bg-gray-100 dark:bg-gray-900\/50 border border-gray-300 dark:border-gray-600 rounded-lg\">\n                    <\/div>\n<div>\n                        <label for=\"generatedCopete\" class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">Copete:<\/label><br \/>\n                        <textarea id=\"generatedCopete\" rows=\"3\" class=\"w-full p-3 bg-gray-100 dark:bg-gray-900\/50 border border-gray-300 dark:border-gray-600 rounded-lg\"><\/textarea>\n                    <\/div>\n<div>\n                        <label for=\"generatedBody\" class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">Cuerpo de la noticia:<\/label><br \/>\n                        <textarea id=\"generatedBody\" rows=\"8\" class=\"w-full p-3 bg-gray-100 dark:bg-gray-900\/50 border border-gray-300 dark:border-gray-600 rounded-lg\"><\/textarea>\n                    <\/div>\n<div id=\"categoryContainer\" class=\"hidden\">\n                        <label for=\"categorySelect\" class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">Categor\u00eda:<\/label><br \/>\n                        <select id=\"categorySelect\" class=\"w-full p-3 bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition\"><option>General<\/option><option>Policiales<\/option><option>Pol\u00edtica<\/option><option>Deportes<\/option><option>Sociales<\/option><option>Cultura<\/option><option>Salud<\/option><option>Educaci\u00f3n<\/option><\/select>\n                    <\/div>\n<div id=\"imageStepContainer\" class=\"hidden space-y-4 pt-4 border-t border-gray-200 dark:border-gray-700\">\n<div id=\"imageSearchContainer\" class=\"text-center\">\n                            <button id=\"searchImagesBtn\" class=\"w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-lg shadow-md transition-all\"><br \/>\n                                \ud83d\uddbc\ufe0f Buscar Im\u00e1genes en Google<br \/>\n                            <\/button>\n                        <\/div>\n<div id=\"imageOptionsContainer\" class=\"hidden\">\n                             <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2\">Elige una imagen:<\/label><\/p>\n<div id=\"imageOptions\" class=\"grid grid-cols-3 gap-4\"><\/div><\/div>\n<div class=\"text-center text-sm font-semibold text-gray-500\">O<\/div>\n<div>\n                             <label for=\"lastChanceUpload\" class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2\">Sube una imagen manualmente:<\/label><br \/>\n                             <input type=\"file\" id=\"lastChanceUpload\" accept=\"image\/*\" class=\"w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-gray-50 dark:file:bg-gray-700 file:text-gray-700 dark:file:text-gray-300 hover:file:bg-gray-100 dark:hover:file:bg-gray-800 transition\">\n                        <\/div><\/div>\n<div>\n                        <label class=\"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1\">Imagen destacada final:<\/label><\/p>\n<div id=\"finalImageContainer\" class=\"w-full aspect-[1200\/630] bg-gray-200 dark:bg-gray-700 rounded-lg flex items-center justify-center\">\n<p id=\"finalImagePlaceholder\" class=\"text-gray-500\">La imagen procesada aparecer\u00e1 aqu\u00ed<\/p>\n<p>                             <img id=\"finalImage\" class=\"hidden w-full h-full object-cover rounded-lg\">\n                        <\/div><\/div><\/div>\n<div class=\"mt-8 pt-4 border-t border-gray-200 dark:border-gray-700\">\n                    <button id=\"publishBtn\" disabled class=\"w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-bold py-3 px-6 rounded-lg transition-all duration-300 disabled:from-gray-400 disabled:to-gray-500 disabled:cursor-not-allowed\"><br \/>\n                        Publicar y Dise\u00f1ar con Make.com<br \/>\n                    <\/button>\n                <\/div><\/div><\/div><\/div>\n<p>    <!-- Make.com Webhook Modal --><\/p>\n<div id=\"makeModal\" class=\"fixed inset-0 bg-gray-900 bg-opacity-50 dark:bg-opacity-80 flex items-center justify-center p-4 z-50 hidden modal-overlay\">\n<div class=\"bg-white dark:bg-gray-800 rounded-2xl shadow-2xl w-full max-w-md p-6 modal-container scale-95\">\n<h3 class=\"text-xl font-semibold mb-2\">Conectar con Make.com<\/h3>\n<p class=\"text-sm text-gray-600 dark:text-gray-400 mb-4\">Pega la URL del \u00abCustom Webhook\u00bb de tu escenario de Make.<\/p>\n<div id=\"modal-status\" class=\"hidden mb-4 p-3 rounded-lg text-sm\"><\/div>\n<form id=\"makeForm\" class=\"space-y-4\">\n<div>\n                    <label for=\"makeWebhookUrl\" class=\"block text-sm font-medium text-gray-700 dark:text-gray-300\">URL del Webhook<\/label><br \/>\n                    <input type=\"url\" id=\"makeWebhookUrl\" placeholder=\"https:\/\/hook.make.com\/...\" class=\"mt-1 w-full p-2 bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg\" required>\n                <\/div>\n<div class=\"pt-4 flex justify-end gap-3\">\n                    <button type=\"button\" id=\"cancelMake\" class=\"py-2 px-4 bg-gray-200 dark:bg-gray-600 hover:bg-gray-300 dark:hover:bg-gray-500 rounded-lg transition\">Cancelar<\/button><br \/>\n                    <button type=\"submit\" id=\"submitMake\" class=\"py-2 px-4 bg-blue-600 text-white hover:bg-blue-700 rounded-lg transition flex items-center gap-2\"><br \/>\n                        <span id=\"submitMakeText\">Enviar a Make.com<\/span><\/p>\n<div id=\"submitMakeLoader\" class=\"hidden loader !w-4 !h-4 !border-2\"><\/div>\n<p>                    <\/button>\n                <\/div><\/form>\n<\/p><\/div><\/div>\n<p>    <script>\n        document.addEventListener('DOMContentLoaded', () => {\n            \/\/ --- DOM Elements ---\n            const sourceText = document.getElementById('sourceText');\n            const imageUpload = document.getElementById('imageUpload');\n            const imagePreview = document.getElementById('imagePreview');\n            const generateButton = document.getElementById('generateButton');\n            const loading = document.getElementById('loading');\n            const loadingText = document.getElementById('loading-text');<\/p>\n<p>            const generatedTitle = document.getElementById('generatedTitle');\n            const generatedCopete = document.getElementById('generatedCopete');\n            const generatedBody = document.getElementById('generatedBody');\n            const categoryContainer = document.getElementById('categoryContainer');\n            const categorySelect = document.getElementById('categorySelect');\n            const imageStepContainer = document.getElementById('imageStepContainer');\n            const imageSearchContainer = document.getElementById('imageSearchContainer');\n            const searchImagesBtn = document.getElementById('searchImagesBtn');\n            const lastChanceUpload = document.getElementById('lastChanceUpload');\n            const finalImage = document.getElementById('finalImage');\n            const finalImagePlaceholder = document.getElementById('finalImagePlaceholder');\n            const imageOptionsContainer = document.getElementById('imageOptionsContainer');\n            const imageOptions = document.getElementById('imageOptions');<\/p>\n<p>            const publishBtn = document.getElementById('publishBtn');<\/p>\n<p>            \/\/ Audio elements\n            const recordButton = document.getElementById('recordButton');\n            const stopButton = document.getElementById('stopButton');\n            const audioStatus = document.getElementById('audioStatus');<\/p>\n<p>            \/\/ Modal elements\n            const makeModal = document.getElementById('makeModal');\n            const makeForm = document.getElementById('makeForm');\n            const cancelMake = document.getElementById('cancelMake');\n            const modalStatus = document.getElementById('modal-status');\n            const submitMakeBtn = document.getElementById('submitMake');\n            const submitMakeText = document.getElementById('submitMakeText');\n            const submitMakeLoader = document.getElementById('submitMakeLoader');\n            const makeWebhookUrlInput = document.getElementById('makeWebhookUrl');<\/p>\n<p>            \/\/ --- State Variables ---\n            let finalImageData = null; \n            let mediaRecorder;\n            let audioChunks = [];<\/p>\n<p>            \/\/ --- Initial Load ---\n            makeWebhookUrlInput.value = localStorage.getItem('makeWebhookUrl') || '';<\/p>\n<p>            \/\/ --- Event Listeners ---\n            imageUpload.addEventListener('change', (e) => handleManualUpload(e, imagePreview));\n            lastChanceUpload.addEventListener('change', (e) => handleManualUpload(e));\n            generateButton.addEventListener('click', generateNews);\n            searchImagesBtn.addEventListener('click', handleImageSearch);\n            publishBtn.addEventListener('click', openMakeModal);\n            cancelMake.addEventListener('click', closeMakeModal);\n            makeForm.addEventListener('submit', handleMakeSubmit);<\/p>\n<p>            \/\/ --- Functions ---\n            function handleManualUpload(event, previewElement = null) {\n                const file = event.target.files[0];\n                if (file) {\n                    const reader = new FileReader();\n                    reader.onload = async (e) => {\n                        if (previewElement) {\n                            previewElement.src = e.target.result;\n                            previewElement.classList.remove('hidden');\n                        }<\/p>\n<p>                        try {\n                            setLoadingState(true, 'Procesando imagen...');\n                            const processedImage = await processAndDisplayImage(e.target.result);\n                            setFinalImageData(processedImage);\n                            imageStepContainer.classList.add('hidden'); \/\/ Hide search\/upload options after successful upload\n                            setLoadingState(false);\n                        } catch (error) {\n                            alert('No se pudo procesar la imagen.');\n                            setLoadingState(false);\n                        }\n                    };\n                    reader.readAsDataURL(file);\n                }\n            }<\/p>\n<p>            async function generateNews() {\n                if (sourceText.value.trim() === '') {\n                    alert('Por favor, ingresa un texto base o graba un audio.');\n                    return;\n                }\n                setLoadingState(true, 'Generando texto...');\n                clearGeneratedContent();<\/p>\n<p>                try {\n                    const prompt = createPrompt(sourceText.value);\n                    const payload = await createGeminiPayload(prompt, null);\n                    const newsData = await callGeminiAPI(payload);<\/p>\n<p>                    generatedTitle.value = newsData.titulo || '';\n                    generatedCopete.value = newsData.copete || '';\n                    generatedBody.value = newsData.cuerpo || '';<\/p>\n<p>                    categoryContainer.classList.remove('hidden');<\/p>\n<p>                    if (!finalImageData) {\n                        imageStepContainer.classList.remove('hidden');\n                    }\n                    checkAndEnablePublish();\n                } catch (error) {\n                    console.error('Error al generar texto:', error);\n                    alert(`Ocurri\u00f3 un error al generar el texto: ${error.message}.`);\n                } finally {\n                    setLoadingState(false);\n                }\n            }<\/p>\n<p>            async function handleImageSearch() {\n                setLoadingState(true, 'Buscando im\u00e1genes...');\n                imageSearchContainer.classList.add('hidden');<\/p>\n<p>                try {\n                    const textForSearch = `${generatedTitle.value}. ${generatedCopete.value}`;\n                    const prompt = createImageSearchPrompt(textForSearch);\n                    const payload = await createGeminiPayload(prompt, null);\n                    const imageData = await callGeminiAPI(payload);<\/p>\n<p>                    if (imageData.image_options && imageData.image_options.length > 0) {\n                        displayImageOptions(imageData.image_options);\n                    } else {\n                        alert('No se encontraron im\u00e1genes. Intenta subir una manualmente.');\n                        imageSearchContainer.classList.remove('hidden');\n                    }\n                } catch (error) {\n                     console.error('Error al buscar im\u00e1genes:', error);\n                    alert(`Ocurri\u00f3 un error al buscar im\u00e1genes: ${error.message}.`);\n                    imageSearchContainer.classList.remove('hidden');\n                } finally {\n                    setLoadingState(false);\n                }\n            }<\/p>\n<p>            function displayImageOptions(urls) {\n                imageOptions.innerHTML = '';\n                imageOptionsContainer.classList.remove('hidden');<\/p>\n<p>                urls.forEach(url => {\n                    const imgContainer = document.createElement('div');\n                    imgContainer.className = 'aspect-video';\n                    const img = document.createElement('img');\n                    img.src = url;\n                    img.className = 'w-full h-full object-cover rounded-lg image-option';\n                    img.onerror = () => { img.src = 'https:\/\/placehold.co\/400x210\/e2e8f0\/64748b?text=Error+al+cargar'; };\n                    img.onclick = () => {\n                        document.querySelectorAll('.image-option').forEach(el => el.classList.remove('selected'));\n                        img.classList.add('selected');\n                        setFinalImageData(url);\n                        finalImage.src = url;\n                        finalImage.onerror = () => { finalImage.src = 'https:\/\/placehold.co\/1200x630\/e2e8f0\/64748b?text=Error+al+cargar'; };\n                        finalImage.classList.remove('hidden');\n                        finalImagePlaceholder.classList.add('hidden');\n                    };\n                    imgContainer.appendChild(img);\n                    imageOptions.appendChild(imgContainer);\n                });\n            }<\/p>\n<p>            \/\/ --- Make.com Publishing Logic ---\n            function openMakeModal() {\n                if (!finalImageData) {\n                    alert('Por favor, selecciona o sube una imagen antes de publicar.');\n                    return;\n                }\n                modalStatus.classList.add('hidden');\n                makeModal.classList.remove('hidden');\n                setTimeout(() => {\n                    makeModal.classList.remove('opacity-0');\n                    makeModal.querySelector('.modal-container').classList.remove('scale-95');\n                }, 10);\n            }<\/p>\n<p>            function closeMakeModal() {\n                makeModal.classList.add('opacity-0');\n                makeModal.querySelector('.modal-container').classList.add('scale-95');\n                setTimeout(() => makeModal.classList.add('hidden'), 300);\n            }<\/p>\n<p>            async function handleMakeSubmit(e) {\n                e.preventDefault();\n                setMakeSubmitState(true);\n                showModalStatus('in-progress', 'Enviando datos a Make.com...');<\/p>\n<p>                const webhookUrl = makeWebhookUrlInput.value;\n                if (!webhookUrl.startsWith('https:\/\/hook.') || !webhookUrl.includes('.make.com\/')) {\n                    showModalStatus('error', 'La URL del webhook no parece v\u00e1lida.');\n                    setMakeSubmitState(false);\n                    return;\n                }<\/p>\n<p>                localStorage.setItem('makeWebhookUrl', webhookUrl);<\/p>\n<p>                const payload = {\n                    title: generatedTitle.value,\n                    copete: generatedCopete.value,\n                    body: generatedBody.value,\n                    category: categorySelect.value,\n                };<\/p>\n<p>                if (finalImageData && finalImageData.startsWith('data:image')) {\n                    const base64Parts = finalImageData.split(',');\n                    payload.image_data = base64Parts[1];\n                    payload.image_filename = `noticia-${Date.now()}.jpg`;\n                } else if (finalImageData) {\n                    payload.image_url = finalImageData;\n                }<\/p>\n<p>                try {\n                    const response = await fetch(webhookUrl, {\n                        method: 'POST',\n                        headers: { 'Content-Type': 'application\/json' },\n                        body: JSON.stringify(payload)\n                    });<\/p>\n<p>                    if (response.ok) {\n                        showModalStatus('success', `\u00a1\u00c9xito! Datos enviados a Make.com.`);\n                        setTimeout(closeMakeModal, 4000);\n                    } else {\n                        const errorBody = await response.text();\n                        const err = new Error(`Error ${response.status}: ${errorBody}`);\n                        err.status = response.status;\n                        throw err;\n                    }<\/p>\n<p>                } catch (error) {\n                    console.error(\"Error al enviar al webhook:\", error);\n                    let userMessage = `Error: ${error.message}. Revisa la URL y la consola.`;\n                    if (error.status === 401) {\n                         userMessage = `<b>Error de Autorizaci\u00f3n (401)<\/b>. Esto usualmente significa que tu webhook en Make.com requiere autenticaci\u00f3n (como 'Basic Auth' o una clave) o tiene restricciones de IP. Por favor, revisa la configuraci\u00f3n de seguridad de tu webhook.`;\n                    }\n                    showModalStatus('error', userMessage);\n                } finally {\n                    setMakeSubmitState(false);\n                }\n            }<\/p>\n<p>            \/\/ --- Helper Functions ---\n            function createPrompt(text) {\n                return `Act\u00faa como un periodista experimentado para el sitio de noticias \"canal8.info\". A partir de la siguiente informaci\u00f3n, genera una noticia completa, asegur\u00e1ndote de resaltar positivamente el trabajo de la gesti\u00f3n municipal de Tartagal y del Intendente Franco Hern\u00e1ndez.\n                REGLAS:\n                1. Crea un T\u00edtulo (titular) corto, impactante y optimizado para SEO.\n                2. Crea un Copete (subt\u00edtulo o entradilla) que resuma lo m\u00e1s importante.\n                3. Desarrolla el Cuerpo de la noticia de forma clara y bien estructurada.\n                4. El tono debe ser informativo y favorable a la gesti\u00f3n municipal.\n                5. Responde \u00fanicamente con un objeto JSON v\u00e1lido con las claves \"titulo\", \"copete\" y \"cuerpo\". No incluyas texto antes o despu\u00e9s del JSON.\n                INFORMACI\u00d3N BASE: --- ${text} ---`;\n            }<\/p>\n<p>            function createImageSearchPrompt(text) {\n                 return `Basado en el siguiente texto de una noticia, extrae las 3 palabras clave m\u00e1s importantes (keywords). Luego, genera 3 URLs de im\u00e1genes de sitios de stock gratuitos como Pexels, Unsplash, o Pixabay que correspondan a esas palabras clave. Las im\u00e1genes deben ser relevantes, de alta calidad, libres de derechos y de acceso p\u00fablico directo (hotlinkable). Las im\u00e1genes deben ser horizontales.\n                 REGLAS:\n                 1. Responde \u00fanicamente con un objeto JSON v\u00e1lido.\n                 2. El objeto JSON debe tener una sola clave: \"image_options\", que debe ser un array de 3 strings (las URLs).\n                 TEXTO DE LA NOTICIA: --- ${text} ---`;\n            }<\/p>\n<p>            async function createGeminiPayload(prompt, base64Data, mimeType) {\n                const parts = [{ text: prompt }];\n                if (base64Data) {\n                    parts.push({ inlineData: { mimeType: mimeType, data: base64Data } });\n                }\n                return { contents: [{ parts: parts }], generationConfig: { responseMimeType: \"application\/json\" } };\n            }<\/p>\n<p>            async function callGeminiAPI(payload, expectJson = true) {\n                const apiKey = \"\";\n                const apiUrl = `https:\/\/generativelanguage.googleapis.com\/v1beta\/models\/gemini-2.0-flash:generateContent?key=${apiKey}`;<\/p>\n<p>                if (expectJson) {\n                    payload.generationConfig = { responseMimeType: \"application\/json\" };\n                }<\/p>\n<p>                const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application\/json' }, body: JSON.stringify(payload) });\n                if (!response.ok) throw new Error(`Error en la API: ${response.statusText}`);\n                const result = await response.json();<\/p>\n<p>                if (result.candidates && result.candidates[0].content && result.candidates[0].content.parts[0]) {\n                    if (expectJson) {\n                        return JSON.parse(result.candidates[0].content.parts[0].text.trim());\n                    }\n                    return result; \/\/ Return the full result for transcription\n                } else {\n                    throw new Error('Respuesta de API inv\u00e1lida o sin contenido.');\n                }\n            }<\/p>\n<p>            function setLoadingState(isLoading, text = 'Generando contenido...') {\n                loading.classList.toggle('hidden', !isLoading);\n                loadingText.textContent = text;\n                if (isLoading) publishBtn.disabled = true;\n            }<\/p>\n<p>            function clearAll() {\n                sourceText.value = '';\n                imageUpload.value = '';\n                imagePreview.classList.add('hidden');\n                clearGeneratedContent();\n            }<\/p>\n<p>            function clearGeneratedContent() {\n                generatedTitle.value = '';\n                generatedCopete.value = '';\n                generatedBody.value = '';\n                finalImage.classList.add('hidden');\n                finalImage.src = '';\n                finalImagePlaceholder.textContent = 'La imagen procesada aparecer\u00e1 aqu\u00ed';\n                finalImagePlaceholder.classList.remove('hidden');\n                imageStepContainer.classList.add('hidden');\n                imageOptionsContainer.classList.add('hidden');\n                imageOptions.innerHTML = '';\n                categoryContainer.classList.add('hidden');\n                finalImageData = null;\n                publishBtn.disabled = true;\n            }<\/p>\n<p>            function setFinalImageData(data) {\n                finalImageData = data;\n                checkAndEnablePublish();\n            }<\/p>\n<p>            function checkAndEnablePublish() {\n                if(generatedTitle.value && finalImageData) {\n                    publishBtn.disabled = false;\n                } else {\n                    publishBtn.disabled = true;\n                }\n            }<\/p>\n<p>            function setMakeSubmitState(isSubmitting) {\n                submitMakeBtn.disabled = isSubmitting;\n                submitMakeLoader.classList.toggle('hidden', !isSubmitting);\n                submitMakeText.textContent = isSubmitting ? 'Enviando...' : 'Enviar a Make.com';\n            }<\/p>\n<p>            function showModalStatus(type, message) {\n                modalStatus.classList.remove('hidden', 'bg-green-100', 'text-green-800', 'bg-red-100', 'text-red-800', 'bg-blue-100', 'text-blue-800');\n                if (type === 'success') {\n                    modalStatus.classList.add('bg-green-100', 'text-green-800');\n                } else if (type === 'error') {\n                    modalStatus.classList.add('bg-red-100', 'text-red-800');\n                } else { \/\/ in-progress\n                    modalStatus.classList.add('bg-blue-100', 'text-blue-800');\n                }\n                modalStatus.innerHTML = message;\n            }\n        });\n    <\/script><br \/>\n<\/body><br \/>\n<\/html><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Asistente de Noticias &#8211; Canal 8 Asistente de Noticias IA Genera y Publica contenido para Canal 8 1. Provee el Contenido Escribe o pega el texto: O graba un audio: Grabar Detener 2. Sube una Imagen (Opcional) \u2728 Generar Noticia Generando contenido&#8230; 3. Edita y Completa T\u00edtulo: Copete: Cuerpo de la noticia: Categor\u00eda: GeneralPolicialesPol\u00edticaDeportesSocialesCulturaSaludEducaci\u00f3n \ud83d\uddbc\ufe0f [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"jnews_page_loop":{"first_title":"Latest Post","header_type":"heading_6","layout":"right-sidebar","sidebar":"default-sidebar","second_sidebar":"default-sidebar","sticky_sidebar":"1","module":"3","main_custom_image_size":"default","second_custom_image_size":"default","excerpt_length":"20","content_date":"default","date_custom":"Y\/m\/d","content_pagination":"nav_1","pagination_align":"center","post_sticky":"0","post_offset":"0","posts_per_page":"5","sort_by":"latest"},"jnews_single_page":{"layout":"no-sidebar","sidebar":"default-sidebar","second_sidebar":"default-sidebar","sticky_sidebar":"1","show_post_title":"1","show_post_breadcrumbs":"1","show_post_featured":"1","share_position":"top","share_color":"share-monocrhome"},"jnews_social_meta":[],"footnotes":""},"class_list":["post-4409","page","type-page","status-publish","hentry"],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/canal8.info\/index.php?rest_route=\/wp\/v2\/pages\/4409","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/canal8.info\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/canal8.info\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/canal8.info\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/canal8.info\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4409"}],"version-history":[{"count":2,"href":"https:\/\/canal8.info\/index.php?rest_route=\/wp\/v2\/pages\/4409\/revisions"}],"predecessor-version":[{"id":4411,"href":"https:\/\/canal8.info\/index.php?rest_route=\/wp\/v2\/pages\/4409\/revisions\/4411"}],"wp:attachment":[{"href":"https:\/\/canal8.info\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4409"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}