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
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:
@@ -71,6 +71,22 @@
|
|||||||
>
|
>
|
||||||
🌱 CARGAR DATOS DE EJEMPLO
|
🌱 CARGAR DATOS DE EJEMPLO
|
||||||
</UButton>
|
</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>
|
</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.
|
||||||
@@ -441,6 +457,8 @@ onMounted(() => {
|
|||||||
// NO ELIMINAR SIN CONSULTAR A DARIO/DRAGANEL/NUCLEO000
|
// NO ELIMINAR SIN CONSULTAR A DARIO/DRAGANEL/NUCLEO000
|
||||||
const resettingDB = ref(false)
|
const resettingDB = ref(false)
|
||||||
const seedingDB = ref(false)
|
const seedingDB = ref(false)
|
||||||
|
const clearingData = ref(false)
|
||||||
|
const exportingDB = ref(false)
|
||||||
|
|
||||||
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?')) {
|
||||||
@@ -491,6 +509,71 @@ const seedDatabase = async () => {
|
|||||||
seedingDB.value = false
|
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 ⚠️⚠️⚠️
|
// ⚠️⚠️⚠️ FIN FUNCIONES DE DEBUG ⚠️⚠️⚠️
|
||||||
|
|
||||||
// Funciones de prueba de API
|
// Funciones de prueba de API
|
||||||
|
|||||||
57
nuxt4/server/api/debug/clear-data.post.ts
Normal file
57
nuxt4/server/api/debug/clear-data.post.ts
Normal 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 },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
100
nuxt4/server/api/debug/export-database.post.ts
Normal file
100
nuxt4/server/api/debug/export-database.post.ts
Normal 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 },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user