Agregar funcionalidad de importación de backups
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m6s

Implementa la capacidad de restaurar la base de datos desde
un archivo SQL de backup exportado previamente.

Cambios:

Backend:
- Nuevo endpoint POST /api/debug/import-database.post.ts
- Recibe archivo .sql via multipart/form-data
- Ejecuta el SQL del backup (que incluye DROP y CREATE)
- Reemplaza completamente la base de datos existente

Frontend:
- Nuevo botón verde "📥 IMPORTAR BACKUP"
- Input file oculto con accept=".sql"
- Validación de extensión de archivo
- Confirmación con advertencia clara
- Estado de carga durante importación

Flujo de uso:
1. Usuario hace click en "📥 IMPORTAR BACKUP"
2. Selecciona archivo .sql previamente exportado
3. Confirma que desea reemplazar toda la BD
4. Sistema ejecuta el SQL completo
5. Muestra mensaje de éxito/error
6. Usuario recarga la página

Esto completa el ciclo backup/restore permitiendo
recuperación completa del estado de la base de datos.
This commit is contained in:
2025-11-22 04:22:50 -06:00
parent 9a8d5391c5
commit fe1b49c108
2 changed files with 169 additions and 0 deletions

View File

@@ -87,6 +87,21 @@
> >
💾 EXPORTAR BACKUP 💾 EXPORTAR BACKUP
</UButton> </UButton>
<UButton
@click="triggerFileInput"
color="green"
variant="solid"
:loading="importingDB"
>
📥 IMPORTAR BACKUP
</UButton>
<input
ref="fileInputRef"
type="file"
accept=".sql"
@change="importDatabase"
style="display: none"
/>
</div> </div>
<p class="text-xs text-red-600 dark:text-red-400 mt-2"> <p class="text-xs text-red-600 dark:text-red-400 mt-2">
Resultados en consola (F12). Recarga la página después de usar estos botones. Resultados en consola (F12). Recarga la página después de usar estos botones.
@@ -459,6 +474,8 @@ const resettingDB = ref(false)
const seedingDB = ref(false) const seedingDB = ref(false)
const clearingData = ref(false) const clearingData = ref(false)
const exportingDB = ref(false) const exportingDB = ref(false)
const importingDB = ref(false)
const fileInputRef = ref<HTMLInputElement | null>(null)
const resetDatabase = async () => { const resetDatabase = async () => {
if (!confirm('⚠️ ADVERTENCIA: Esto BORRARÁ TODOS LOS DATOS de la base de datos.\n\n¿Estás seguro de continuar?')) { if (!confirm('⚠️ ADVERTENCIA: Esto BORRARÁ TODOS LOS DATOS de la base de datos.\n\n¿Estás seguro de continuar?')) {
@@ -574,6 +591,61 @@ const exportDatabase = async () => {
exportingDB.value = false exportingDB.value = false
} }
} }
const triggerFileInput = () => {
fileInputRef.value?.click()
}
const importDatabase = async (event: Event) => {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
if (!file) {
return
}
if (!file.name.endsWith('.sql')) {
alert('⚠️ Por favor selecciona un archivo .sql')
input.value = ''
return
}
if (!confirm('⚠️ ADVERTENCIA: Esto REEMPLAZARÁ TODA LA BASE DE DATOS con el contenido del backup.\n\nSe eliminará todo lo existente y se cargará el backup.\n\n¿Estás seguro de continuar?')) {
input.value = ''
return
}
console.log('📥 === IMPORTANDO BACKUP DE BASE DE DATOS ===')
console.log(` - Archivo: ${file.name}`)
console.log(` - Tamaño: ${(file.size / 1024).toFixed(2)} KB`)
importingDB.value = true
try {
const formData = new FormData()
formData.append('file', file)
const response = await fetch('/api/debug/import-database', {
method: 'POST',
body: formData,
})
const data = await response.json()
console.log('Status:', response.status)
console.log('Respuesta:', data)
if (data.success) {
alert('✅ Backup importado exitosamente.\n\nLa base de datos ha sido restaurada.\n\nRecarga la página para ver los cambios.')
} else {
throw new Error(data.message || 'Error desconocido')
}
} catch (error: any) {
console.error('❌ Error:', error)
alert(`❌ Error importando backup: ${error.message || 'Ver consola para más detalles'}`)
} finally {
importingDB.value = false
input.value = '' // Limpiar el input
}
}
// ⚠️⚠️⚠️ FIN FUNCIONES DE DEBUG ⚠️⚠️⚠️ // ⚠️⚠️⚠️ FIN FUNCIONES DE DEBUG ⚠️⚠️⚠️
// Funciones de prueba de API // Funciones de prueba de API

View File

@@ -0,0 +1,97 @@
/**
* ⚠️ ⚠️ ⚠️ ENDPOINT DE DEBUG - TEMPORAL ⚠️ ⚠️ ⚠️
*
* POST /api/debug/import-database
*
* IMPORTA UN BACKUP SQL COMPLETO (reemplaza todo lo existente)
*
* ⚠️ NO ELIMINAR SIN CONSULTAR A DARIO/DRAGANEL/NUCLEO000 ⚠️
*
* Este endpoint fue creado para desarrollo y debugging.
* Antes de eliminarlo, preguntar si todavía es necesario.
*/
import { getClient } from '../../utils/db'
export default defineEventHandler(async (event) => {
try {
console.log('📥 IMPORT DATABASE - Importando backup SQL...')
// Leer el archivo del request
const formData = await readMultipartFormData(event)
if (!formData || formData.length === 0) {
throw createError({
statusCode: 400,
statusMessage: 'No se recibió ningún archivo',
data: { message: 'Debes enviar un archivo .sql en el campo "file"' },
})
}
const fileData = formData.find(item => item.name === 'file')
if (!fileData) {
throw createError({
statusCode: 400,
statusMessage: 'Campo "file" no encontrado',
data: { message: 'El archivo debe enviarse en el campo "file"' },
})
}
const sqlContent = fileData.data.toString('utf-8')
const fileSize = (sqlContent.length / 1024).toFixed(2)
console.log(` - Archivo recibido: ${fileData.filename || 'sin nombre'}`)
console.log(` - Tamaño: ${fileSize} KB`)
if (!sqlContent.trim()) {
throw createError({
statusCode: 400,
statusMessage: 'Archivo vacío',
data: { message: 'El archivo SQL está vacío' },
})
}
const client = await getClient()
try {
await client.query('BEGIN')
console.log(' - Ejecutando SQL del backup...')
console.log(' - ADVERTENCIA: Esto eliminará y recreará todas las tablas')
// Ejecutar el SQL del backup
// Los backups de pg_dump con --clean --if-exists ya incluyen los DROP necesarios
await client.query(sqlContent)
await client.query('COMMIT')
console.log('✅ Backup importado exitosamente')
return {
success: true,
message: 'Backup importado exitosamente. La base de datos ha sido restaurada.',
file_size: fileSize,
filename: fileData.filename || 'sin nombre',
}
} catch (error) {
await client.query('ROLLBACK')
throw error
} finally {
client.release()
}
} catch (error: any) {
console.error('❌ Error importando backup:', error)
// Si el error ya es un createError, reusarlo
if (error.statusCode) {
throw error
}
throw createError({
statusCode: 500,
statusMessage: 'Error importando backup',
data: { message: error.message },
})
}
})