From fe1b49c108e452da52c813162af7f1e0574f4da4 Mon Sep 17 00:00:00 2001 From: josedario87 Date: Sat, 22 Nov 2025 04:22:50 -0600 Subject: [PATCH] =?UTF-8?q?Agregar=20funcionalidad=20de=20importaci=C3=B3n?= =?UTF-8?q?=20de=20backups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- nuxt4/app/app.vue | 72 ++++++++++++++ .../server/api/debug/import-database.post.ts | 97 +++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 nuxt4/server/api/debug/import-database.post.ts diff --git a/nuxt4/app/app.vue b/nuxt4/app/app.vue index e031da5..32e8b34 100644 --- a/nuxt4/app/app.vue +++ b/nuxt4/app/app.vue @@ -87,6 +87,21 @@ > 💾 EXPORTAR BACKUP + + 📥 IMPORTAR BACKUP + +

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 clearingData = ref(false) const exportingDB = ref(false) +const importingDB = ref(false) +const fileInputRef = ref(null) const resetDatabase = async () => { 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 } } + +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 ⚠️⚠️⚠️ // Funciones de prueba de API diff --git a/nuxt4/server/api/debug/import-database.post.ts b/nuxt4/server/api/debug/import-database.post.ts new file mode 100644 index 0000000..fc35c86 --- /dev/null +++ b/nuxt4/server/api/debug/import-database.post.ts @@ -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 }, + }) + } +})