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

@@ -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 },
})
}
})