From 2eb0cfa45983aee0a575353ff13f879f14c2e658 Mon Sep 17 00:00:00 2001 From: josedario87 Date: Sat, 22 Nov 2025 04:10:50 -0600 Subject: [PATCH] Feat: agregar botones de debug para limpiar datos y exportar backup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Agregar botón 'LIMPIAR DATOS' que hace TRUNCATE de tablas sin borrar estructura - Agregar botón 'EXPORTAR BACKUP' que descarga pg_dump completo de la BD - Crear endpoint POST /api/debug/clear-data para TRUNCATE CASCADE - Crear endpoint POST /api/debug/export-database para pg_dump con descarga - Mantener estructura de botones temporales de debug existentes - Incluir confirmaciones y manejo de errores apropiados --- nuxt4/app/app.vue | 83 +++++++++++++++ nuxt4/server/api/debug/clear-data.post.ts | 57 ++++++++++ .../server/api/debug/export-database.post.ts | 100 ++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 nuxt4/server/api/debug/clear-data.post.ts create mode 100644 nuxt4/server/api/debug/export-database.post.ts diff --git a/nuxt4/app/app.vue b/nuxt4/app/app.vue index 68c39d2..e031da5 100644 --- a/nuxt4/app/app.vue +++ b/nuxt4/app/app.vue @@ -71,6 +71,22 @@ > 🌱 CARGAR DATOS DE EJEMPLO + + 🧹 LIMPIAR DATOS (solo datos) + + + 💾 EXPORTAR BACKUP +

Resultados en consola (F12). Recarga la página después de usar estos botones. @@ -441,6 +457,8 @@ onMounted(() => { // NO ELIMINAR SIN CONSULTAR A DARIO/DRAGANEL/NUCLEO000 const resettingDB = ref(false) const seedingDB = ref(false) +const clearingData = ref(false) +const exportingDB = ref(false) const resetDatabase = async () => { if (!confirm('⚠️ ADVERTENCIA: Esto BORRARÁ TODOS LOS DATOS de la base de datos.\n\n¿Estás seguro de continuar?')) { @@ -491,6 +509,71 @@ const seedDatabase = async () => { seedingDB.value = false } } + +const clearData = async () => { + if (!confirm('⚠️ ADVERTENCIA: Esto ELIMINARÁ TODOS LOS DATOS de las tablas (TRUNCATE) pero mantendrá la estructura.\n\n¿Estás seguro de continuar?')) { + return + } + + console.log('🧹 === LIMPIANDO DATOS DE TABLAS ===') + clearingData.value = true + + try { + const response = await fetch('/api/debug/clear-data', { + method: 'POST', + }) + const data = await response.json() + console.log('Status:', response.status) + console.log('Respuesta:', data) + + if (data.success) { + alert('✅ Datos eliminados exitosamente. Las tablas están vacías.\n\nRecarga la página para ver los cambios.') + } + } catch (error) { + console.error('❌ Error:', error) + alert('❌ Error limpiando datos. Ver consola.') + } finally { + clearingData.value = false + } +} + +const exportDatabase = async () => { + console.log('💾 === EXPORTANDO BACKUP DE BASE DE DATOS ===') + exportingDB.value = true + + try { + const response = await fetch('/api/debug/export-database', { + method: 'POST', + }) + + if (!response.ok) { + throw new Error('Error en la exportación') + } + + // Descargar el archivo SQL + const blob = await response.blob() + const url = window.URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + + // Nombre del archivo con timestamp + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5) + a.download = `backup-seguidordelotes-${timestamp}.sql` + + document.body.appendChild(a) + a.click() + window.URL.revokeObjectURL(url) + document.body.removeChild(a) + + console.log('✅ Backup descargado exitosamente') + alert('✅ Backup de base de datos descargado exitosamente.') + } catch (error) { + console.error('❌ Error:', error) + alert('❌ Error exportando backup. Ver consola.') + } finally { + exportingDB.value = false + } +} // ⚠️⚠️⚠️ FIN FUNCIONES DE DEBUG ⚠️⚠️⚠️ // Funciones de prueba de API diff --git a/nuxt4/server/api/debug/clear-data.post.ts b/nuxt4/server/api/debug/clear-data.post.ts new file mode 100644 index 0000000..813411f --- /dev/null +++ b/nuxt4/server/api/debug/clear-data.post.ts @@ -0,0 +1,57 @@ +/** + * ⚠️ ⚠️ ⚠️ ENDPOINT DE DEBUG - TEMPORAL ⚠️ ⚠️ ⚠️ + * + * POST /api/debug/clear-data + * + * ELIMINA TODOS LOS DATOS DE LAS TABLAS (TRUNCATE) SIN BORRAR LA ESTRUCTURA + * + * ⚠️ 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('🧹 CLEAR DATA - Eliminando todos los datos de las tablas...') + + const client = await getClient() + + try { + await client.query('BEGIN') + + // Eliminar datos de todas las tablas en orden correcto (respetar foreign keys) + console.log(' - Truncando operacion_lotes...') + await client.query('TRUNCATE TABLE operacion_lotes CASCADE') + + console.log(' - Truncando operaciones...') + await client.query('TRUNCATE TABLE operaciones CASCADE') + + console.log(' - Truncando lotes...') + await client.query('TRUNCATE TABLE lotes CASCADE') + + await client.query('COMMIT') + + console.log('✅ Datos eliminados exitosamente. Las tablas están vacías pero la estructura se mantiene.') + + return { + success: true, + message: 'Datos eliminados exitosamente. Las tablas están vacías.', + } + } catch (error) { + await client.query('ROLLBACK') + throw error + } finally { + client.release() + } + } catch (error: any) { + console.error('❌ Error limpiando datos:', error) + throw createError({ + statusCode: 500, + statusMessage: 'Error limpiando datos', + data: { message: error.message }, + }) + } +}) diff --git a/nuxt4/server/api/debug/export-database.post.ts b/nuxt4/server/api/debug/export-database.post.ts new file mode 100644 index 0000000..eb4d29b --- /dev/null +++ b/nuxt4/server/api/debug/export-database.post.ts @@ -0,0 +1,100 @@ +/** + * ⚠️ ⚠️ ⚠️ ENDPOINT DE DEBUG - TEMPORAL ⚠️ ⚠️ ⚠️ + * + * POST /api/debug/export-database + * + * EXPORTA TODA LA BASE DE DATOS A UN ARCHIVO SQL (pg_dump) + * + * ⚠️ 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 { exec } from 'child_process' +import { promisify } from 'util' + +const execAsync = promisify(exec) + +export default defineEventHandler(async (event) => { + try { + console.log('💾 EXPORT DATABASE - Generando backup con pg_dump...') + + // Obtener credenciales de la base de datos + const config = useRuntimeConfig() + let host: string + let port: string + let database: string + let user: string + let password: string + + if (config.postgresUrl) { + // Parsear la URL de conexión + const url = new URL(config.postgresUrl) + host = url.hostname + port = url.port || '5432' + database = url.pathname.slice(1) + user = url.username + password = url.password + } else { + // Fallback a variables de entorno + const defaultHost = process.env.APP_NAME ? `${process.env.APP_NAME}-postgres` : 'postgres' + host = process.env.POSTGRES_HOST || defaultHost + port = process.env.POSTGRES_PORT || '5432' + database = process.env.POSTGRES_DB || 'seguidor_lotes' + user = process.env.POSTGRES_USER || 'seguidor' + password = process.env.POSTGRES_PASSWORD || 'seguidor_password' + } + + console.log(` - Host: ${host}`) + console.log(` - Puerto: ${port}`) + console.log(` - Base de datos: ${database}`) + console.log(` - Usuario: ${user}`) + + // Generar timestamp para el nombre del archivo + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5) + const filename = `backup-seguidordelotes-${timestamp}.sql` + + // Ejecutar pg_dump + // Usar PGPASSWORD en la variable de entorno para evitar prompt de contraseña + const command = `PGPASSWORD="${password}" pg_dump -h ${host} -p ${port} -U ${user} -d ${database} --clean --if-exists --no-owner --no-acl` + + console.log(' - Ejecutando pg_dump...') + const { stdout, stderr } = await execAsync(command, { + maxBuffer: 50 * 1024 * 1024, // 50MB buffer para backups grandes + }) + + if (stderr && !stderr.includes('NOTICE')) { + console.warn('⚠️ Advertencias de pg_dump:', stderr) + } + + console.log('✅ Backup generado exitosamente') + console.log(` - Tamaño: ${(stdout.length / 1024).toFixed(2)} KB`) + + // Establecer headers para descarga + setResponseHeader(event, 'Content-Type', 'application/sql') + setResponseHeader(event, 'Content-Disposition', `attachment; filename="${filename}"`) + setResponseHeader(event, 'Content-Length', stdout.length.toString()) + + return stdout + } catch (error: any) { + console.error('❌ Error exportando base de datos:', error) + + // Si el error es porque pg_dump no está disponible + if (error.message?.includes('pg_dump') && error.message?.includes('not found')) { + throw createError({ + statusCode: 500, + statusMessage: 'pg_dump no está disponible', + data: { + message: 'El comando pg_dump no está disponible en el servidor. Asegúrate de que PostgreSQL client tools estén instalados.', + }, + }) + } + + throw createError({ + statusCode: 500, + statusMessage: 'Error exportando base de datos', + data: { message: error.message }, + }) + } +})