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/[email protected]/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/[email protected]/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> |