Feat: agregar botones de debug para limpiar datos y exportar backup
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m6s

- 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
This commit is contained in:
2025-11-22 04:10:50 -06:00
parent f3a170c882
commit 2eb0cfa459
3 changed files with 240 additions and 0 deletions

View File

@@ -71,6 +71,22 @@
>
🌱 CARGAR DATOS DE EJEMPLO
</UButton>
<UButton
@click="clearData"
color="yellow"
variant="solid"
:loading="clearingData"
>
🧹 LIMPIAR DATOS (solo datos)
</UButton>
<UButton
@click="exportDatabase"
color="blue"
variant="solid"
:loading="exportingDB"
>
💾 EXPORTAR BACKUP
</UButton>
</div>
<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.
@@ -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

View File

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

View File

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