{"id":1438,"date":"2026-02-26T23:04:23","date_gmt":"2026-02-27T02:04:23","guid":{"rendered":"https:\/\/chilemosaico.cl\/acusgrafia\/?page_id=1438"},"modified":"2026-03-14T00:32:21","modified_gmt":"2026-03-14T03:32:21","slug":"qr","status":"publish","type":"page","link":"https:\/\/chilemosaico.cl\/acusgrafia\/qr\/","title":{"rendered":"Generador QR libre &#8211; Chilemosaico"},"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>Generador QR lubre &#8211; Chilemosaico<\/title>\n    <!-- Librer\u00edas necesarias -->\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/qrcode@1.5.1\/build\/qrcode.min.js\"><\/script>\n    <style>\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\n            background: #f5f5f7;\n            min-height: 100vh;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            padding: 20px;\n        }\n\n        .app {\n            max-width: 480px;\n            width: 100%;\n            background: white;\n            border-radius: 24px;\n            box-shadow: 0 10px 40px rgba(0,0,0,0.08);\n            padding: 24px;\n        }\n\n        h1 {\n            font-size: 20px;\n            font-weight: 500;\n            color: #1d1d1f;\n            margin-bottom: 4px;\n        }\n\n        .subtitle {\n            font-size: 14px;\n            color: #86868b;\n            margin-bottom: 24px;\n        }\n\n        \/* Input group *\/\n        .input-group {\n            margin-bottom: 20px;\n        }\n\n        label {\n            display: block;\n            font-size: 13px;\n            font-weight: 500;\n            color: #1d1d1f;\n            margin-bottom: 6px;\n        }\n\n        input[type=\"text\"] {\n            width: 100%;\n            padding: 10px 14px;\n            font-size: 15px;\n            border: 1px solid #d2d2d7;\n            border-radius: 12px;\n            outline: none;\n            transition: border 0.2s, box-shadow 0.2s;\n            background: #ffffff;\n        }\n\n        input[type=\"text\"]:focus {\n            border-color: #007aff;\n            box-shadow: 0 0 0 3px rgba(0,122,255,0.1);\n        }\n\n        \/* Upload area *\/\n        .upload-area {\n            border: 1px dashed #d2d2d7;\n            border-radius: 16px;\n            padding: 20px;\n            text-align: center;\n            margin-bottom: 20px;\n            cursor: pointer;\n            transition: background 0.2s;\n            background: #fafafc;\n        }\n\n        .upload-area:hover {\n            background: #f5f5f7;\n        }\n\n        .upload-area.dragover {\n            border-color: #007aff;\n            background: rgba(0,122,255,0.05);\n        }\n\n        .upload-icon {\n            font-size: 24px;\n            margin-bottom: 8px;\n            opacity: 0.5;\n        }\n\n        .upload-text {\n            font-size: 14px;\n            color: #86868b;\n        }\n\n        .upload-text strong {\n            color: #007aff;\n            font-weight: 500;\n        }\n\n        \/* Logo preview *\/\n        .logo-preview {\n            display: flex;\n            align-items: center;\n            gap: 12px;\n            padding: 12px;\n            background: #f5f5f7;\n            border-radius: 16px;\n            margin-bottom: 20px;\n        }\n\n        .logo-preview img {\n            width: 48px;\n            height: 48px;\n            object-fit: contain;\n            border-radius: 8px;\n            background: white;\n            padding: 4px;\n            border: 1px solid #e0e0e5;\n        }\n\n        .logo-info {\n            flex: 1;\n        }\n\n        .logo-name {\n            font-size: 14px;\n            font-weight: 500;\n            color: #1d1d1f;\n            margin-bottom: 2px;\n        }\n\n        .logo-size {\n            font-size: 12px;\n            color: #86868b;\n        }\n\n        .remove-logo {\n            background: none;\n            border: none;\n            color: #ff3b30;\n            font-size: 20px;\n            cursor: pointer;\n            padding: 4px 8px;\n            border-radius: 8px;\n        }\n\n        .remove-logo:hover {\n            background: rgba(255,59,48,0.1);\n        }\n\n        \/* Controls row *\/\n        .controls-row {\n            display: flex;\n            gap: 12px;\n            margin-bottom: 20px;\n        }\n\n        .control-item {\n            flex: 1;\n        }\n\n        .control-item label {\n            font-size: 12px;\n            margin-bottom: 4px;\n        }\n\n        .control-item input[type=\"range\"] {\n            width: 100%;\n        }\n\n        .value-display {\n            font-size: 12px;\n            color: #86868b;\n            text-align: right;\n        }\n\n        \/* QR Container *\/\n        .qr-container {\n            background: #ffffff;\n            border-radius: 24px;\n            padding: 20px;\n            margin: 20px 0;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            border: 1px solid #f0f0f3;\n            box-shadow: 0 2px 10px rgba(0,0,0,0.02);\n        }\n\n        .qr-wrapper {\n            position: relative;\n            display: inline-block;\n        }\n\n        #qr-canvas {\n            display: block;\n            border-radius: 16px;\n            width: 100%;\n            height: auto;\n            background: white;\n        }\n\n        .qr-logo {\n            position: absolute;\n            top: 50%;\n            left: 50%;\n            transform: translate(-50%, -50%);\n            width: 20%;\n            height: auto;\n            max-width: 60px;\n            pointer-events: none;\n            background: white;\n            border-radius: 8px;\n            padding: 4px;\n            box-shadow: 0 2px 8px rgba(0,0,0,0.1);\n        }\n\n        \/* Buttons *\/\n        .button-group {\n            display: flex;\n            gap: 12px;\n            margin-top: 16px;\n        }\n\n        .btn {\n            flex: 1;\n            padding: 12px 16px;\n            border-radius: 40px;\n            font-size: 14px;\n            font-weight: 500;\n            border: none;\n            cursor: pointer;\n            transition: all 0.2s;\n            background: white;\n            border: 1px solid #d2d2d7;\n            color: #1d1d1f;\n        }\n\n        .btn-primary {\n            background: #007aff;\n            border-color: #007aff;\n            color: white;\n        }\n\n        .btn-primary:hover {\n            background: #005fc9;\n        }\n\n        .btn:hover {\n            background: #f5f5f7;\n        }\n\n        .btn-primary:hover {\n            background: #005fc9;\n        }\n\n        \/* Hidden file input *\/\n        #file-input {\n            display: none;\n        }\n\n        .footer-note {\n            font-size: 12px;\n            color: #86868b;\n            text-align: center;\n            margin-top: 20px;\n        }\n    <\/style>\n<\/head>\n<body>\n    <div class=\"app\">\n        <h1>Generador QR Libre &#8211; Chilemosaico<\/h1>\n        <div class=\"subtitle\">Escribe la direcci\u00f3n web, sube tu logo\/imagen y descarga el QR.<\/div>\n\n        <!-- URL Input -->\n        <div class=\"input-group\">\n            <label>URL o texto<\/label>\n            <input type=\"text\" id=\"qr-text\" value=\"chilemosaico.cl\" placeholder=\"https:\/\/...\" autocomplete=\"off\">\n        <\/div>\n\n        <!-- Logo Upload Area -->\n        <div class=\"upload-area\" id=\"drop-zone\">\n            <div class=\"upload-icon\">\ud83d\uddbc\ufe0f<\/div>\n            <div class=\"upload-text\">\n                <strong>Selecciona archivo<\/strong> o arrastra aqu\u00ed\n            <\/div>\n            <div style=\"font-size: 11px; color: #a0a0a7; margin-top: 6px;\">\n                PNG, JPG, SVG \u00b7 m\u00e1x 1MB\n            <\/div>\n        <\/div>\n        <input type=\"file\" id=\"file-input\" accept=\"image\/png,image\/jpeg,image\/svg+xml\">\n\n        <!-- Logo Preview (hidden by default) -->\n        <div id=\"logo-preview-container\" style=\"display: none;\">\n            <div class=\"logo-preview\">\n                <img decoding=\"async\" id=\"preview-img\" src=\"\" alt=\"logo\">\n                <div class=\"logo-info\">\n                    <div class=\"logo-name\" id=\"logo-name\">logo.png<\/div>\n                    <div class=\"logo-size\" id=\"logo-size\">0 KB<\/div>\n                <\/div>\n                <button class=\"remove-logo\" id=\"remove-logo\">\u2715<\/button>\n            <\/div>\n        <\/div>\n\n        <!-- Size Control -->\n        <div class=\"controls-row\">\n            <div class=\"control-item\">\n                <label>Tama\u00f1o QR<\/label>\n                <input type=\"range\" id=\"qr-size\" min=\"200\" max=\"400\" value=\"280\" step=\"10\">\n                <div class=\"value-display\" id=\"size-display\">280px<\/div>\n            <\/div>\n            <div class=\"control-item\">\n                <label>Logo size<\/label>\n                <input type=\"range\" id=\"logo-size-range\" min=\"15\" max=\"35\" value=\"22\" step=\"1\">\n                <div class=\"value-display\" id=\"logo-size-display\">22%<\/div>\n            <\/div>\n        <\/div>\n\n        <!-- QR Display -->\n        <div class=\"qr-container\">\n            <div class=\"qr-wrapper\" id=\"qr-wrapper\">\n                <canvas id=\"qr-canvas\"><\/canvas>\n                <img decoding=\"async\" id=\"overlay-logo\" class=\"qr-logo\" src=\"\" style=\"display: none;\">\n            <\/div>\n        <\/div>\n\n        <!-- Actions -->\n        <div class=\"button-group\">\n            <button class=\"btn\" id=\"download-btn\">\u2b07\ufe0f Descargar<\/button>\n            <button class=\"btn btn-primary\" id=\"generate-btn\">Generar QR<\/button>\n        <\/div>\n\n        <div class=\"footer-note\">\n            El QR se regenera autom\u00e1ticamente al cambiar la direcci\u00f3n web.\n        <\/div>\n    <\/div>\n\n    <script>\n        \/\/ Elementos DOM\n        const qrText = document.getElementById('qr-text');\n        const qrCanvas = document.getElementById('qr-canvas');\n        const qrSize = document.getElementById('qr-size');\n        const sizeDisplay = document.getElementById('size-display');\n        const logoSizeRange = document.getElementById('logo-size-range');\n        const logoSizeDisplay = document.getElementById('logo-size-display');\n        const overlayLogo = document.getElementById('overlay-logo');\n        const fileInput = document.getElementById('file-input');\n        const dropZone = document.getElementById('drop-zone');\n        const logoPreviewContainer = document.getElementById('logo-preview-container');\n        const previewImg = document.getElementById('preview-img');\n        const logoName = document.getElementById('logo-name');\n        const logoSize = document.getElementById('logo-size');\n        const removeLogoBtn = document.getElementById('remove-logo');\n        const generateBtn = document.getElementById('generate-btn');\n        const downloadBtn = document.getElementById('download-btn');\n        const qrWrapper = document.getElementById('qr-wrapper');\n\n        \/\/ Estado\n        let currentLogo = null; \/\/ Almacena el archivo de logo como DataURL\n        let qrCodeInstance = null;\n\n        \/\/ Inicializar: generar QR por defecto\n        window.addEventListener('load', () => {\n            generateQR();\n        });\n\n        \/\/ Actualizar displays de rangos\n        qrSize.addEventListener('input', () => {\n            sizeDisplay.textContent = qrSize.value + 'px';\n        });\n\n        logoSizeRange.addEventListener('input', () => {\n            logoSizeDisplay.textContent = logoSizeRange.value + '%';\n            if (currentLogo) {\n                updateLogoSize();\n            }\n        });\n\n        \/\/ Funci\u00f3n para actualizar tama\u00f1o del logo en pantalla\n        function updateLogoSize() {\n            if (currentLogo) {\n                overlayLogo.style.width = logoSizeRange.value + '%';\n            }\n        }\n\n        \/\/ Generar QR con QRCode library\n        async function generateQR() {\n            const text = qrText.value.trim();\n            if (!text) {\n                alert('Ingresa una URL o texto');\n                return;\n            }\n\n            const size = parseInt(qrSize.value);\n\n            \/\/ Configuraci\u00f3n mejorada para QR m\u00e1s denso (versi\u00f3n m\u00e1s alta autom\u00e1tica)\n            try {\n                await QRCode.toCanvas(qrCanvas, text, {\n                    width: size,\n                    margin: 2,\n                    color: {\n                        dark: '#000000',\n                        light: '#ffffff'\n                    },\n                    errorCorrectionLevel: currentLogo ? 'H' : 'M' \/\/ ALTA correcci\u00f3n si hay logo\n                });\n                \n                \/\/ Asegurar que el canvas tenga el tama\u00f1o correcto\n                qrCanvas.style.width = '100%';\n                qrCanvas.style.height = 'auto';\n                \n                \/\/ Mostrar logo si existe\n                if (currentLogo) {\n                    overlayLogo.src = currentLogo;\n                    overlayLogo.style.display = 'block';\n                    overlayLogo.style.width = logoSizeRange.value + '%';\n                } else {\n                    overlayLogo.style.display = 'none';\n                }\n            } catch (err) {\n                console.error('Error generando QR:', err);\n                alert('Error al generar el c\u00f3digo QR');\n            }\n        }\n\n        \/\/ ===== MANEJO DE LOGO =====\n        \/\/ Click en dropzone\n        dropZone.addEventListener('click', () => {\n            fileInput.click();\n        });\n\n        \/\/ Drag & drop\n        dropZone.addEventListener('dragover', (e) => {\n            e.preventDefault();\n            dropZone.classList.add('dragover');\n        });\n\n        dropZone.addEventListener('dragleave', () => {\n            dropZone.classList.remove('dragover');\n        });\n\n        dropZone.addEventListener('drop', (e) => {\n            e.preventDefault();\n            dropZone.classList.remove('dragover');\n            const files = e.dataTransfer.files;\n            if (files.length > 0) {\n                handleLogoFile(files[0]);\n            }\n        });\n\n        \/\/ Input file change\n        fileInput.addEventListener('change', (e) => {\n            if (e.target.files.length > 0) {\n                handleLogoFile(e.target.files[0]);\n            }\n        });\n\n        \/\/ Procesar archivo de logo\n        function handleLogoFile(file) {\n            \/\/ Validar tipo\n            if (!file.type.match('image.*')) {\n                alert('Por favor selecciona una imagen (PNG, JPG, SVG)');\n                return;\n            }\n\n            \/\/ Validar tama\u00f1o (max 1MB)\n            if (file.size > 1 * 1024 * 1024) {\n                alert('La imagen no debe superar 1MB');\n                return;\n            }\n\n            const reader = new FileReader();\n            reader.onload = (e) => {\n                currentLogo = e.target.result;\n                \n                \/\/ Mostrar preview\n                previewImg.src = currentLogo;\n                logoName.textContent = file.name;\n                const sizeKB = Math.round(file.size \/ 1024);\n                logoSize.textContent = sizeKB + ' KB';\n                logoPreviewContainer.style.display = 'block';\n                \n                \/\/ Ocultar dropzone (opcional, pero lo dejamos visible para reemplazar)\n                \/\/ En este dise\u00f1o lo dejamos visible arriba\n                \n                \/\/ Si ya hay un QR generado, actualizar con logo\n                if (qrText.value.trim()) {\n                    generateQR();\n                }\n            };\n            reader.readAsDataURL(file);\n        }\n\n        \/\/ Remover logo\n        removeLogoBtn.addEventListener('click', () => {\n            currentLogo = null;\n            overlayLogo.style.display = 'none';\n            logoPreviewContainer.style.display = 'none';\n            fileInput.value = ''; \/\/ reset file input\n            \/\/ Regenerar sin logo (con correcci\u00f3n M)\n            if (qrText.value.trim()) {\n                generateQR();\n            }\n        });\n\n        \/\/ Bot\u00f3n generar manual\n        generateBtn.addEventListener('click', generateQR);\n\n        \/\/ Bot\u00f3n descargar\n        downloadBtn.addEventListener('click', () => {\n            if (!qrCanvas) return;\n            \n            \/\/ Crear un canvas temporal para combinar QR + logo si es necesario\n            const canvas = document.createElement('canvas');\n            const ctx = canvas.getContext('2d');\n            const qrSizePx = parseInt(qrSize.value);\n            \n            canvas.width = qrSizePx;\n            canvas.height = qrSizePx;\n            \n            \/\/ Dibujar el QR (que ya est\u00e1 en qrCanvas)\n            ctx.drawImage(qrCanvas, 0, 0, qrSizePx, qrSizePx);\n            \n            \/\/ Si hay logo, dibujarlo encima\n            if (currentLogo && overlayLogo.src) {\n                const logoImg = new Image();\n                logoImg.crossOrigin = 'anonymous';\n                logoImg.src = currentLogo;\n                \n                \/\/ Para descargar, necesitamos esperar a que cargue si no est\u00e1 en DOM\n                \/\/ Pero overlayLogo ya est\u00e1 cargado, podemos usar ese\n                const tempLogo = new Image();\n                tempLogo.onload = () => {\n                    const logoWidth = qrSizePx * (parseInt(logoSizeRange.value) \/ 100);\n                    const logoHeight = (tempLogo.height \/ tempLogo.width) * logoWidth;\n                    const x = (qrSizePx - logoWidth) \/ 2;\n                    const y = (qrSizePx - logoHeight) \/ 2;\n                    \n                    \/\/ Fondo blanco detr\u00e1s del logo\n                    ctx.fillStyle = '#ffffff';\n                    ctx.fillRect(x - 4, y - 4, logoWidth + 8, logoHeight + 8);\n                    \n                    ctx.drawImage(tempLogo, x, y, logoWidth, logoHeight);\n                    \n                    \/\/ Descargar\n                    downloadCanvas(canvas);\n                };\n                tempLogo.src = currentLogo;\n            } else {\n                downloadCanvas(canvas);\n            }\n            \n            function downloadCanvas(canvas) {\n                const link = document.createElement('a');\n                link.download = 'qr-con-logo-' + Date.now() + '.png';\n                link.href = canvas.toDataURL('image\/png');\n                link.click();\n            }\n        });\n\n        \/\/ Generar inicial al cambiar texto (debounce simple)\n        let timeout;\n        qrText.addEventListener('input', () => {\n            clearTimeout(timeout);\n            timeout = setTimeout(generateQR, 300);\n        });\n\n        \/\/ Al cambiar tama\u00f1o, regenerar\n        qrSize.addEventListener('change', generateQR);\n\n        \/\/ Ajustar tama\u00f1o del wrapper seg\u00fan canvas\n        function resizeWrapper() {\n            \/\/ No necesario por ahora\n        }\n    <\/script>\n<\/body>\n<\/html>\n\n\n\n<p class=\"has-text-align-center\">Made in Iquique<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Generador QR lubre &#8211; Chilemosaico Generador QR Libre &#8211; Chilemosaico Escribe la direcci\u00f3n web, sube tu logo\/imagen y descarga el QR. URL o texto \ud83d\uddbc\ufe0f Selecciona archivo o arrastra aqu\u00ed PNG, JPG, SVG \u00b7 m\u00e1x 1MB logo.png 0 KB \u2715 Tama\u00f1o QR 280px Logo size 22% \u2b07\ufe0f Descargar Generar QR El QR se regenera autom\u00e1ticamente&#8230;<\/p>\n","protected":false},"author":1,"featured_media":1475,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_kad_post_transparent":"","_kad_post_title":"hide","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"class_list":["post-1438","page","type-page","status-publish","has-post-thumbnail","hentry"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/chilemosaico.cl\/acusgrafia\/wp-json\/wp\/v2\/pages\/1438","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/chilemosaico.cl\/acusgrafia\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/chilemosaico.cl\/acusgrafia\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/chilemosaico.cl\/acusgrafia\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/chilemosaico.cl\/acusgrafia\/wp-json\/wp\/v2\/comments?post=1438"}],"version-history":[{"count":31,"href":"https:\/\/chilemosaico.cl\/acusgrafia\/wp-json\/wp\/v2\/pages\/1438\/revisions"}],"predecessor-version":[{"id":1480,"href":"https:\/\/chilemosaico.cl\/acusgrafia\/wp-json\/wp\/v2\/pages\/1438\/revisions\/1480"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/chilemosaico.cl\/acusgrafia\/wp-json\/wp\/v2\/media\/1475"}],"wp:attachment":[{"href":"https:\/\/chilemosaico.cl\/acusgrafia\/wp-json\/wp\/v2\/media?parent=1438"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}