Spaces:
Running
Running
| <html lang="es"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>PDF a Word - Conversor Offline</title> | |
| <!-- Bootstrap CSS --> | |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <!-- Font Awesome para iconos --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary-color: #4e73df; | |
| --secondary-color: #f8f9fc; | |
| --accent-color: #2e59d9; | |
| --text-color: #5a5c69; | |
| } | |
| body { | |
| font-family: 'Nunito', sans-serif; | |
| background-color: #f8f9fc; | |
| color: var(--text-color); | |
| } | |
| .card { | |
| border-radius: 15px; | |
| box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15); | |
| border: none; | |
| } | |
| .card-header { | |
| background-color: var(--primary-color); | |
| color: white; | |
| border-radius: 15px 15px 0 0 ; | |
| font-weight: 600; | |
| } | |
| .btn-primary { | |
| background-color: var(--primary-color); | |
| border-color: var(--primary-color); | |
| } | |
| .btn-primary:hover { | |
| background-color: var(--accent-color); | |
| border-color: var(--accent-color); | |
| } | |
| .dropzone { | |
| border: 2px dashed #d1d3e2; | |
| border-radius: 10px; | |
| padding: 2rem; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| background-color: white; | |
| } | |
| .dropzone:hover { | |
| border-color: var(--primary-color); | |
| background-color: var(--secondary-color); | |
| } | |
| .dropzone.active { | |
| border-color: var(--primary-color); | |
| background-color: var(--secondary-color); | |
| } | |
| .file-info { | |
| background-color: white; | |
| border-radius: 8px; | |
| padding: 15px; | |
| margin-top: 15px; | |
| display: none; | |
| } | |
| .progress { | |
| height: 10px; | |
| margin-top: 10px; | |
| } | |
| .conversion-result { | |
| display: none; | |
| } | |
| .file-icon { | |
| font-size: 3rem; | |
| color: var(--primary-color); | |
| } | |
| .file-size-warning { | |
| color: #e74a3b; | |
| font-weight: 600; | |
| display: none; | |
| } | |
| .conversion-time { | |
| font-size: 0.9rem; | |
| color: #858796; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container py-5"> | |
| <div class="row justify-content-center"> | |
| <div class="col-lg-8"> | |
| <div class="card shadow-lg"> | |
| <div class="card-header py-3 d-flex justify-content-between align-items-center"> | |
| <h4 class="m-0 font-weight-bold"><i class="fas fa-file-word me-2"></i> Conversor PDF a Word</h4> | |
| <span class="badge bg-light text-dark">Offline</span> | |
| </div> | |
| <div class="card-body"> | |
| <p class="text-muted mb-4">Convierte tus archivos PDF a documentos Word (.docx) directamente en tu navegador. Tus archivos nunca salen de tu computadora.</p> | |
| <div id="dropzone" class="dropzone"> | |
| <i class="fas fa-file-pdf file-icon mb-3"></i> | |
| <h5>Arrastra y suelta tu archivo PDF aqu铆</h5> | |
| <p class="text-muted">o haz clic para seleccionar</p> | |
| <input type="file" id="fileInput" class="d-none" accept=".pdf"> | |
| <button class="btn btn-primary mt-3"> | |
| <i class="fas fa-folder-open me-2"></i> Seleccionar archivo | |
| </button> | |
| <p class="file-size-warning mt-2"> | |
| <i class="fas fa-exclamation-triangle me-2"></i> | |
| El archivo no debe superar los 50MB | |
| </p> | |
| </div> | |
| <div id="fileInfo" class="file-info"> | |
| <div class="d-flex justify-content-between align-items-center"> | |
| <div> | |
| <h6 id="fileName" class="mb-0"></h6> | |
| <small class="text-muted" id="fileSize"></small> | |
| </div> | |
| <button id="removeFile" class="btn btn-sm btn-outline-danger"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mt-4"> | |
| <div class="d-flex justify-content-between mb-2"> | |
| <label for="quality" class="form-label">Opciones de conversi贸n:</label> | |
| <span class="badge bg-info">Texto editable</span> | |
| </div> | |
| <select class="form-select mb-3" id="conversionType"> | |
| <option value="text">Mantener texto editable</option> | |
| <option value="image">Convertir como imagen (mantiene formato)</option> | |
| </select> | |
| <button id="convertBtn" class="btn btn-primary w-100 py-2" disabled> | |
| <i class="fas fa-exchange-alt me-2"></i> Convertir a Word | |
| </button> | |
| <div class="progress mt-3 d-none" id="progressBar"> | |
| <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div id="conversionResult" class="conversion-result mt-4 text-center"> | |
| <div class="alert alert-success"> | |
| <i class="fas fa-check-circle me-2"></i> | |
| <span id="successMessage">Conversi贸n completada con 茅xito!</span> | |
| <p class="conversion-time mb-0" id="conversionTime"></p> | |
| </div> | |
| <button id="downloadBtn" class="btn btn-success mt-2"> | |
| <i class="fas fa-download me-2"></i> Descargar archivo Word | |
| </button> | |
| </div> | |
| </div> | |
| <div class="card-footer text-muted text-center"> | |
| <small>Procesamiento 100% en tu navegador 路 L铆mite de 50MB 路 No se suben archivos a servidores</small> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Bootstrap JS Bundle with Popper --> | |
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> | |
| <!-- PDF-lib para manejar PDFs --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.17.1/pdf-lib.min.js"></script> | |
| <!-- Docx para generar documentos Word --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/docx/7.1.0/docx.min.js"></script> | |
| <!-- FileSaver para descargar el archivo --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Elementos del DOM | |
| const dropzone = document.getElementById('dropzone'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const fileInfo = document.getElementById('fileInfo'); | |
| const fileName = document.getElementById('fileName'); | |
| const fileSize = document.getElementById('fileSize'); | |
| const removeFile = document.getElementById('removeFile'); | |
| const convertBtn = document.getElementById('convertBtn'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const conversionResult = document.getElementById('conversionResult'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| const successMessage = document.getElementById('successMessage'); | |
| const conversionTime = document.getElementById('conversionTime'); | |
| const fileSizeWarning = document.querySelector('.file-size-warning'); | |
| const conversionType = document.getElementById('conversionType'); | |
| let selectedFile = null; | |
| let convertedDoc = null; | |
| // Eventos para el dropzone | |
| dropzone.addEventListener('click', () => fileInput.click()); | |
| fileInput.addEventListener('change', handleFileSelect); | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| dropzone.addEventListener(eventName, preventDefaults, false); | |
| }); | |
| function preventDefaults(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| ['dragenter', 'dragover'].forEach(eventName => { | |
| dropzone.addEventListener(eventName, highlight, false); | |
| }); | |
| ['dragleave', 'drop'].forEach(eventName => { | |
| dropzone.addEventListener(eventName, unhighlight, false); | |
| }); | |
| function highlight() { | |
| dropzone.classList.add('active'); | |
| } | |
| function unhighlight() { | |
| dropzone.classList.remove('active'); | |
| } | |
| dropzone.addEventListener('drop', handleDrop, false); | |
| function handleDrop(e) { | |
| const dt = e.dataTransfer; | |
| const file = dt.files[0]; | |
| handleFile(file); | |
| } | |
| function handleFileSelect(e) { | |
| const file = e.target.files[0]; | |
| handleFile(file); | |
| } | |
| function handleFile(file) { | |
| if (!file) return; | |
| // Verificar tipo de archivo | |
| if (file.type !== 'application/pdf') { | |
| showAlert('Por favor, selecciona un archivo PDF v谩lido.', 'danger'); | |
| return; | |
| } | |
| // Verificar tama帽o del archivo (50MB m谩ximo) | |
| if (file.size > 50 * 1024 * 1024) { | |
| fileSizeWarning.style.display = 'block'; | |
| convertBtn.disabled = true; | |
| return; | |
| } else { | |
| fileSizeWarning.style.display = 'none'; | |
| } | |
| selectedFile = file; | |
| // Mostrar informaci贸n del archivo | |
| fileName.textContent = file.name; | |
| fileSize.textContent = formatFileSize(file.size); | |
| fileInfo.style.display = 'block'; | |
| convertBtn.disabled = false; | |
| // Ocultar resultado de conversi贸n previa | |
| conversionResult.style.display = 'none'; | |
| } | |
| removeFile.addEventListener('click', function() { | |
| selectedFile = null; | |
| fileInput.value = ''; | |
| fileInfo.style.display = 'none'; | |
| convertBtn.disabled = true; | |
| conversionResult.style.display = 'none'; | |
| }); | |
| // Formatear tama帽o de archivo | |
| function formatFileSize(bytes) { | |
| if (bytes === 0) return '0 Bytes'; | |
| const k = 1024; | |
| const sizes = ['Bytes', 'KB', 'MB', 'GB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| // Convertir PDF a Word | |
| convertBtn.addEventListener('click', async function() { | |
| if (!selectedFile) return; | |
| try { | |
| // Mostrar barra de progreso | |
| progressBar.classList.remove('d-none'); | |
| const progressBarInner = progressBar.querySelector('.progress-bar'); | |
| progressBarInner.style.width = '0%'; | |
| convertBtn.disabled = true; | |
| // Simular progreso (en una implementaci贸n real esto ser铆a el progreso real) | |
| let progress = 0; | |
| const progressInterval = setInterval(() => { | |
| progress += 5; | |
| progressBarInner.style.width = `${progress}%`; | |
| if (progress >= 100) { | |
| clearInterval(progressInterval); | |
| } | |
| }, 200); | |
| // Leer el archivo PDF | |
| const arrayBuffer = await readFileAsArrayBuffer(selectedFile); | |
| // Registrar tiempo de inicio | |
| const startTime = new Date(); | |
| // Convertir PDF a Word (simulaci贸n) | |
| // NOTA: En un entorno real usar铆as pdf-lib y docx para la conversi贸n | |
| // Esta es una simulaci贸n porque la conversi贸n real requiere m谩s c贸digo | |
| await simulateConversion(); | |
| // Registrar tiempo de finalizaci贸n | |
| const endTime = new Date(); | |
| const conversionDuration = ((endTime - startTime) / 1000).toFixed(2); | |
| // Mostrar resultado | |
| progressBarInner.style.width = '100%'; | |
| setTimeout(() => { | |
| progressBar.classList.add('d-none'); | |
| // Mostrar resultado exitoso | |
| successMessage.textContent = 'Conversi贸n completada con 茅xito!'; | |
| conversionTime.textContent = `Tiempo de conversi贸n: ${conversionDuration} segundos`; | |
| conversionResult.style.display = 'block'; | |
| convertBtn.disabled = false; | |
| }, 500); | |
| } catch (error) { | |
| console.error('Error durante la conversi贸n:', error); | |
| showAlert('Ocurri贸 un error durante la conversi贸n. Por favor, intenta nuevamente.', 'danger'); | |
| progressBar.classList.add('d-none'); | |
| convertBtn.disabled = false; | |
| } | |
| }); | |
| // Descargar archivo convertido | |
| downloadBtn.addEventListener('click', function() { | |
| if (!convertedDoc) return; | |
| // En una implementaci贸n real, usar铆as el docx generado | |
| // Aqu铆 simulamos la descarga del archivo | |
| const blob = new Blob(["Este ser铆a el contenido del archivo Word generado"], {type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}); | |
| const fileNameWithoutExt = selectedFile.name.replace('.pdf', ''); | |
| saveAs(blob, `${fileNameWithoutExt}.docx`); | |
| showAlert('Descarga iniciada!', 'success'); | |
| }); | |
| // Funciones auxiliares | |
| function readFileAsArrayBuffer(file) { | |
| return new Promise((resolve, reject) => { | |
| const reader = new FileReader(); | |
| reader.onload = () => resolve(reader.result); | |
| reader.onerror = reject; | |
| reader.readAsArrayBuffer(file); | |
| }); | |
| } | |
| function simulateConversion() { | |
| return new Promise(resolve => { | |
| setTimeout(resolve, 3000); // Simula una conversi贸n de 3 segundos | |
| }); | |
| } | |
| function showAlert(message, type) { | |
| const alertDiv = document.createElement('div'); | |
| alertDiv.className = `alert alert-${type} alert-dismissible fade show mt-3`; | |
| alertDiv.innerHTML = ` | |
| ${message} | |
| <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | |
| `; | |
| const container = document.querySelector('.card-body'); | |
| container.insertBefore(alertDiv, container.firstChild); | |
| // Eliminar la alerta despu茅s de 5 segundos | |
| setTimeout(() => { | |
| const bsAlert = new bootstrap.Alert(alertDiv); | |
| bsAlert.close(); | |
| }, 5000); | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 馃К <a href="https://enzostvs-deepsite.hf.space?remix=fernandox/pdf-to-word" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |