Agregar campo username a User entity y DTO

- Columna username (unique, nullable) en auth.users
- Campo username en CreateUserDto y UpdateUserDto

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 11:47:37 -04:00
parent 6e0ad420ab
commit 8b6483aa7d
971 changed files with 16339 additions and 752 deletions

175
scripts/fetch-images-wikimedia.js Executable file
View File

@@ -0,0 +1,175 @@
#!/usr/bin/env node
/**
* Script para obtener imágenes de Wikimedia Commons para monumentos
* Usa la API de Wikimedia Commons (gratuita, sin API key)
*/
const { Client } = require('pg');
const https = require('https');
// Configuración
const config = {
db: {
host: 'localhost',
port: 5432,
user: 'karibeo',
password: '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',
database: 'karibeo_db',
},
};
// Buscar imágenes en Wikimedia Commons
async function searchWikimediaImages(query, limit = 3) {
return new Promise((resolve, reject) => {
// Primero buscar en Wikipedia para obtener la página del lugar
const searchUrl = `https://commons.wikimedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&srnamespace=6&srlimit=${limit}&format=json`;
https.get(searchUrl, { headers: { 'User-Agent': 'KaribeoAI/1.0 (contact@karibeo.ai)' } }, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const json = JSON.parse(data);
if (json.query && json.query.search && json.query.search.length > 0) {
// Obtener URLs de las imágenes
const titles = json.query.search.map(s => s.title).join('|');
getImageUrls(titles).then(resolve).catch(reject);
} else {
resolve([]);
}
} catch (e) {
reject(e);
}
});
}).on('error', reject);
});
}
// Obtener URLs directas de las imágenes
async function getImageUrls(titles) {
return new Promise((resolve, reject) => {
const url = `https://commons.wikimedia.org/w/api.php?action=query&titles=${encodeURIComponent(titles)}&prop=imageinfo&iiprop=url|size&format=json`;
https.get(url, { headers: { 'User-Agent': 'KaribeoAI/1.0 (contact@karibeo.ai)' } }, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const json = JSON.parse(data);
const urls = [];
if (json.query && json.query.pages) {
for (const pageId in json.query.pages) {
const page = json.query.pages[pageId];
if (page.imageinfo && page.imageinfo[0]) {
const info = page.imageinfo[0];
// Solo incluir imágenes de tamaño razonable (> 100KB y < 10MB)
if (info.size > 100000 && info.size < 10000000) {
urls.push(info.url);
}
}
}
}
resolve(urls);
} catch (e) {
reject(e);
}
});
}).on('error', reject);
});
}
// Buscar con términos alternativos
async function searchWithAlternatives(name, country) {
const countryName = country === 'DO' ? 'Dominican Republic' : 'Puerto Rico';
const countryNameEs = country === 'DO' ? 'República Dominicana' : 'Puerto Rico';
// Lista de búsquedas a intentar
const searches = [
`${name} ${countryNameEs}`,
`${name} ${countryName}`,
name,
`${name} Caribbean`,
];
for (const query of searches) {
console.log(` Buscando: "${query}"`);
const images = await searchWikimediaImages(query, 5);
if (images.length > 0) {
return images.slice(0, 3); // Máximo 3 imágenes
}
await delay(500); // Rate limit
}
return [];
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function main() {
console.log('🖼️ Buscador de Imágenes - Wikimedia Commons');
console.log('============================================\n');
// Conectar a la DB
const client = new Client(config.db);
await client.connect();
console.log('✅ Conectado a la base de datos\n');
// Obtener monumentos sin imágenes válidas
const query = `
SELECT id, name, country, slug, images
FROM tourism.places_of_interest
WHERE active = true
AND (images IS NULL OR images::text = '[]' OR images::text LIKE '%example.com%')
ORDER BY name
`;
const result = await client.query(query);
const places = result.rows;
console.log(`📍 Encontrados ${places.length} lugares sin imágenes válidas\n`);
let found = 0;
let notFound = 0;
for (let i = 0; i < places.length; i++) {
const place = places[i];
console.log(`[${i + 1}/${places.length}] ${place.name} (${place.country})`);
try {
const images = await searchWithAlternatives(place.name, place.country);
if (images.length > 0) {
// Guardar en la DB
await client.query(
`UPDATE tourism.places_of_interest SET images = $1 WHERE id = $2`,
[JSON.stringify(images), place.id]
);
console.log(`${images.length} imágenes encontradas`);
found++;
} else {
console.log(` ⚠️ No se encontraron imágenes`);
notFound++;
}
} catch (error) {
console.log(` ❌ Error: ${error.message}`);
notFound++;
}
// Rate limit para la API de Wikimedia
await delay(1000);
}
await client.end();
console.log('\n============================================');
console.log('📊 RESUMEN');
console.log(` ✅ Con imágenes: ${found}`);
console.log(` ⚠️ Sin imágenes: ${notFound}`);
console.log('============================================\n');
}
main().catch(err => {
console.error('Error fatal:', err);
process.exit(1);
});

213
scripts/generate-audios.js Executable file
View File

@@ -0,0 +1,213 @@
#!/usr/bin/env node
/**
* Script standalone para generar audios TTS para todos los monumentos
* Usa Piper TTS directamente sin pasar por el API
*/
const { Client } = require('pg');
const { exec } = require('child_process');
const { promisify } = require('util');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const execAsync = promisify(exec);
// Configuración
const config = {
db: {
host: 'localhost',
port: 5432,
user: 'karibeo',
password: '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',
database: 'karibeo_db',
},
tts: {
piperPath: '/home/karibeo-api/karibeo-api/tts/piper/piper',
voicesPath: '/home/karibeo-api/karibeo-api/tts/voices',
cachePath: '/home/karibeo-api/karibeo-api/tts/cache',
baseUrl: 'https://api.karibeo.ai:8443',
},
voiceMap: {
es: 'es_ES-davefx-medium',
en: 'en_US-amy-medium',
fr: 'fr_FR-siwis-medium',
it: 'it_IT-riccardo-x_low',
de: 'de_DE-thorsten-medium',
},
languages: ['es', 'en', 'fr', 'it', 'de'],
};
// Limpiar texto para TTS
function cleanTextForSpeech(text) {
return text
.replace(/[\u4E00-\u9FFF]/g, '')
.replace(/[\u3400-\u4DBF]/g, '')
.replace(/[\u3040-\u309F]/g, '')
.replace(/[\u30A0-\u30FF]/g, '')
.replace(/[\uAC00-\uD7AF]/g, '')
.replace(/[\u{1F300}-\u{1F9FF}]/gu, '')
.replace(/[\u{2600}-\u{26FF}]/gu, '')
.replace(/[\u{2700}-\u{27BF}]/gu, '')
.replace(/[\u{1F600}-\u{1F64F}]/gu, '')
.replace(/[\u{1F680}-\u{1F6FF}]/gu, '')
.replace(/[\u{1F1E0}-\u{1F1FF}]/gu, '')
.replace(/[★☆✓✗✔✘●○◆◇▪▫►◄→←↑↓⇒⇐⇑⇓♠♣♥♦]/g, '')
.replace(/\*\*([^*]+)\*\*/g, '$1')
.replace(/\*([^*]+)\*/g, '$1')
.replace(/__([^_]+)__/g, '$1')
.replace(/_([^_]+)_/g, '$1')
.replace(/\s+/g, ' ')
.trim();
}
function getAudioHash(text, language) {
const content = `${language}:${text}`;
return crypto.createHash('md5').update(content).digest('hex');
}
async function generateAudio(text, language) {
const voice = config.voiceMap[language] || config.voiceMap['en'];
const voiceModel = path.join(config.tts.voicesPath, `${voice}.onnx`);
if (!fs.existsSync(voiceModel)) {
console.log(` ⚠️ Modelo de voz no encontrado: ${voiceModel}`);
return null;
}
const cleanText = cleanTextForSpeech(text)
.replace(/[\n\r]/g, ' ')
.replace(/"/g, "'")
.substring(0, 3000);
const hash = getAudioHash(cleanText, language);
const fileName = `${hash}.wav`;
const filePath = path.join(config.tts.cachePath, fileName);
// Si ya existe, retornar URL
if (fs.existsSync(filePath)) {
console.log(` 📦 Cache hit: ${fileName}`);
return `${config.tts.baseUrl}/api/v1/tts/audio/${fileName}`;
}
// Crear directorio si no existe
if (!fs.existsSync(config.tts.cachePath)) {
fs.mkdirSync(config.tts.cachePath, { recursive: true });
}
// Guardar texto en archivo temporal
const tempTextFile = path.join(config.tts.cachePath, `${hash}.txt`);
fs.writeFileSync(tempTextFile, cleanText, 'utf-8');
try {
const command = `cat "${tempTextFile}" | "${config.tts.piperPath}" --model "${voiceModel}" --output_file "${filePath}" 2>/dev/null`;
await execAsync(command, { timeout: 120000 });
// Limpiar archivo temporal
if (fs.existsSync(tempTextFile)) {
fs.unlinkSync(tempTextFile);
}
if (fs.existsSync(filePath)) {
const stats = fs.statSync(filePath);
console.log(` ✅ Audio generado: ${fileName} (${Math.round(stats.size/1024)}KB)`);
return `${config.tts.baseUrl}/api/v1/tts/audio/${fileName}`;
}
return null;
} catch (error) {
console.log(` ❌ Error TTS: ${error.message}`);
if (fs.existsSync(tempTextFile)) {
fs.unlinkSync(tempTextFile);
}
return null;
}
}
async function main() {
console.log('🎙️ Generador de Audios TTS para Monumentos');
console.log('==========================================\n');
// Verificar Piper
if (!fs.existsSync(config.tts.piperPath)) {
console.error(`❌ Piper no encontrado en: ${config.tts.piperPath}`);
process.exit(1);
}
console.log('✅ Piper TTS encontrado\n');
// Conectar a la DB
const client = new Client(config.db);
await client.connect();
console.log('✅ Conectado a la base de datos\n');
// Obtener lugares con descripción
const query = `
SELECT id, name, slug,
description_es, description_en, description_fr, description_it, description_de,
audio_url_es, audio_url_en, audio_url_fr, audio_url_it, audio_url_de
FROM tourism.places_of_interest
WHERE active = true
ORDER BY id
`;
const result = await client.query(query);
const places = result.rows;
console.log(`📍 Encontrados ${places.length} lugares\n`);
let totalGenerated = 0;
let totalSkipped = 0;
let totalFailed = 0;
for (let i = 0; i < places.length; i++) {
const place = places[i];
console.log(`\n[${i + 1}/${places.length}] ${place.name}`);
for (const lang of config.languages) {
const descCol = `description_${lang}`;
const audioCol = `audio_url_${lang}`;
const description = place[descCol];
const existingAudio = place[audioCol];
if (!description) {
continue;
}
if (existingAudio) {
totalSkipped++;
continue;
}
console.log(` 🔊 Generando audio (${lang})...`);
const audioUrl = await generateAudio(description, lang);
if (audioUrl) {
// Actualizar en DB
await client.query(
`UPDATE tourism.places_of_interest SET ${audioCol} = $1 WHERE id = $2`,
[audioUrl, place.id]
);
totalGenerated++;
} else {
totalFailed++;
}
// Pequeña pausa
await new Promise(r => setTimeout(r, 100));
}
}
await client.end();
console.log('\n==========================================');
console.log('📊 RESUMEN');
console.log(` ✅ Generados: ${totalGenerated}`);
console.log(` ⏭️ Omitidos (ya existían): ${totalSkipped}`);
console.log(` ❌ Fallidos: ${totalFailed}`);
console.log('==========================================\n');
}
main().catch(err => {
console.error('Error fatal:', err);
process.exit(1);
});

332
scripts/generate-missing.js Executable file
View File

@@ -0,0 +1,332 @@
#!/usr/bin/env node
/**
* Script para generar descripciones y audios FALTANTES
*/
const { Client } = require('pg');
const { exec } = require('child_process');
const { promisify } = require('util');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const https = require('https');
const execAsync = promisify(exec);
// Configuración
const config = {
db: {
host: 'localhost',
port: 5432,
user: 'karibeo',
password: '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',
database: 'karibeo_db',
},
kimi: {
apiKey: process.env.KIMI_API_KEY || '',
baseUrl: 'https://api.moonshot.ai/v1',
model: 'moonshot-v1-128k',
},
tts: {
piperPath: '/home/karibeo-api/karibeo-api/tts/piper/piper',
voicesPath: '/home/karibeo-api/karibeo-api/tts/voices',
cachePath: '/home/karibeo-api/karibeo-api/tts/cache',
baseUrl: 'https://api.karibeo.ai:8443',
},
voiceMap: {
es: 'es_ES-davefx-medium',
en: 'en_US-amy-medium',
fr: 'fr_FR-siwis-medium',
it: 'it_IT-riccardo-x_low',
de: 'de_DE-thorsten-medium',
},
languages: ['es', 'en', 'fr', 'it', 'de'],
};
// Prompts por idioma
function getPrompt(placeName, country, language) {
const countryName = country === 'DO' ? 'República Dominicana' : 'Puerto Rico';
const countryNameEn = country === 'DO' ? 'Dominican Republic' : 'Puerto Rico';
const prompts = {
es: `Actúa como un guía turístico ${country === 'DO' ? 'dominicano' : 'puertorriqueño'} experto. Dame una descripción completa e interesante de ${placeName} en ${countryName}. Incluye su historia, importancia cultural, arquitectura, datos curiosos, horarios de visita si aplica. Habla en segunda persona directamente al turista usando "tú" (NO uses "vosotros" ni expresiones de España). Usa español latinoamericano natural. Máximo 250 palabras.`,
en: `Act as an expert ${country === 'DO' ? 'Dominican' : 'Puerto Rican'} tour guide. Give me a complete and interesting description of ${placeName} in ${countryNameEn}. Include its history, cultural importance, architecture, fun facts, and visiting hours if applicable. Speak in second person as if you were giving a tour. Maximum 250 words.`,
fr: `Agis comme un guide touristique ${country === 'DO' ? 'dominicain' : 'portoricain'} expert. Donne-moi une description complète et intéressante de ${placeName} en ${countryName}. Inclus son histoire, son importance culturelle, son architecture, des anecdotes et les horaires de visite si applicable. Parle à la deuxième personne comme si tu donnais une visite guidée. Maximum 250 mots.`,
it: `Agisci come una guida turistica ${country === 'DO' ? 'dominicana' : 'portoricana'} esperta. Dammi una descrizione completa e interessante di ${placeName} in ${countryName}. Includi la sua storia, importanza culturale, architettura, curiosità e orari di visita se applicabile. Parla in seconda persona come se stessi facendo un tour. Massimo 250 parole.`,
de: `Handle als erfahrener ${country === 'DO' ? 'dominikanischer' : 'puertoricanischer'} Reiseführer. Gib mir eine vollständige und interessante Beschreibung von ${placeName} in ${countryName}. Füge die Geschichte, kulturelle Bedeutung, Architektur, interessante Fakten und Besuchszeiten falls zutreffend hinzu. Sprich in der zweiten Person, als würdest du eine Tour geben. Maximal 250 Wörter.`,
};
return prompts[language] || prompts['en'];
}
function getLanguageName(code) {
const names = {
es: 'español latinoamericano',
en: 'English',
fr: 'français',
it: 'italiano',
de: 'Deutsch',
};
return names[code] || 'English';
}
// Llamar a Kimi API
async function callKimiAPI(placeName, country, language) {
const prompt = getPrompt(placeName, country, language);
const data = JSON.stringify({
model: config.kimi.model,
messages: [
{
role: 'system',
content: `Eres un guía turístico experto del Caribe. Responde siempre en ${getLanguageName(language)}. Sé informativo, amigable y entusiasta. No uses emojis.`,
},
{
role: 'user',
content: prompt,
},
],
temperature: 0.7,
max_tokens: 1000,
});
return new Promise((resolve, reject) => {
const url = new URL(`${config.kimi.baseUrl}/chat/completions`);
const options = {
hostname: url.hostname,
port: 443,
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.kimi.apiKey}`,
'Content-Length': Buffer.byteLength(data),
},
};
const req = https.request(options, (res) => {
let body = '';
res.on('data', (chunk) => body += chunk);
res.on('end', () => {
try {
const json = JSON.parse(body);
if (json.choices && json.choices[0] && json.choices[0].message) {
resolve(json.choices[0].message.content);
} else {
reject(new Error('Invalid response: ' + body));
}
} catch (e) {
reject(e);
}
});
});
req.on('error', reject);
req.setTimeout(60000, () => {
req.destroy();
reject(new Error('Request timeout'));
});
req.write(data);
req.end();
});
}
// Limpiar texto para TTS
function cleanTextForSpeech(text) {
return text
.replace(/[\u4E00-\u9FFF]/g, '')
.replace(/[\u3400-\u4DBF]/g, '')
.replace(/[\u3040-\u309F]/g, '')
.replace(/[\u30A0-\u30FF]/g, '')
.replace(/[\uAC00-\uD7AF]/g, '')
.replace(/[\u{1F300}-\u{1F9FF}]/gu, '')
.replace(/[\u{2600}-\u{26FF}]/gu, '')
.replace(/[\u{2700}-\u{27BF}]/gu, '')
.replace(/[\u{1F600}-\u{1F64F}]/gu, '')
.replace(/[\u{1F680}-\u{1F6FF}]/gu, '')
.replace(/[\u{1F1E0}-\u{1F1FF}]/gu, '')
.replace(/[★☆✓✗✔✘●○◆◇▪▫►◄→←↑↓⇒⇐⇑⇓♠♣♥♦]/g, '')
.replace(/\*\*([^*]+)\*\*/g, '$1')
.replace(/\*([^*]+)\*/g, '$1')
.replace(/__([^_]+)__/g, '$1')
.replace(/_([^_]+)_/g, '$1')
.replace(/\s+/g, ' ')
.trim();
}
function getAudioHash(text, language) {
const content = `${language}:${text}`;
return crypto.createHash('md5').update(content).digest('hex');
}
async function generateAudio(text, language) {
const voice = config.voiceMap[language] || config.voiceMap['en'];
const voiceModel = path.join(config.tts.voicesPath, `${voice}.onnx`);
if (!fs.existsSync(voiceModel)) {
console.log(` ⚠️ Modelo de voz no encontrado: ${voiceModel}`);
return null;
}
const cleanText = cleanTextForSpeech(text)
.replace(/[\n\r]/g, ' ')
.replace(/"/g, "'")
.substring(0, 3000);
const hash = getAudioHash(cleanText, language);
const fileName = `${hash}.wav`;
const filePath = path.join(config.tts.cachePath, fileName);
if (fs.existsSync(filePath)) {
console.log(` 📦 Cache hit: ${fileName}`);
return `${config.tts.baseUrl}/api/v1/tts/audio/${fileName}`;
}
if (!fs.existsSync(config.tts.cachePath)) {
fs.mkdirSync(config.tts.cachePath, { recursive: true });
}
const tempTextFile = path.join(config.tts.cachePath, `${hash}.txt`);
fs.writeFileSync(tempTextFile, cleanText, 'utf-8');
try {
const command = `cat "${tempTextFile}" | "${config.tts.piperPath}" --model "${voiceModel}" --output_file "${filePath}" 2>/dev/null`;
await execAsync(command, { timeout: 120000 });
if (fs.existsSync(tempTextFile)) {
fs.unlinkSync(tempTextFile);
}
if (fs.existsSync(filePath)) {
const stats = fs.statSync(filePath);
console.log(` ✅ Audio generado: ${fileName} (${Math.round(stats.size/1024)}KB)`);
return `${config.tts.baseUrl}/api/v1/tts/audio/${fileName}`;
}
return null;
} catch (error) {
console.log(` ❌ Error TTS: ${error.message}`);
if (fs.existsSync(tempTextFile)) {
fs.unlinkSync(tempTextFile);
}
return null;
}
}
async function main() {
console.log('🔧 Generador de Contenido Faltante');
console.log('===================================\n');
// Verificar API key
if (!config.kimi.apiKey) {
// Leer del .env
const envPath = '/home/karibeo-api/karibeo-api/.env';
if (fs.existsSync(envPath)) {
const envContent = fs.readFileSync(envPath, 'utf-8');
const match = envContent.match(/KIMI_API_KEY=(.+)/);
if (match) {
config.kimi.apiKey = match[1].trim();
}
}
}
if (!config.kimi.apiKey) {
console.error('❌ KIMI_API_KEY no encontrada');
process.exit(1);
}
console.log('✅ Kimi API Key encontrada\n');
// Conectar a la DB
const client = new Client(config.db);
await client.connect();
console.log('✅ Conectado a la base de datos\n');
// Obtener lugares con contenido faltante
const query = `
SELECT id, name, country,
description_es, description_en, description_fr, description_it, description_de,
audio_url_es, audio_url_en, audio_url_fr, audio_url_it, audio_url_de
FROM tourism.places_of_interest
WHERE active = true
AND (
description_es IS NULL OR description_en IS NULL OR description_fr IS NULL OR description_it IS NULL OR description_de IS NULL
OR audio_url_es IS NULL OR audio_url_en IS NULL OR audio_url_fr IS NULL OR audio_url_it IS NULL OR audio_url_de IS NULL
)
ORDER BY name
`;
const result = await client.query(query);
const places = result.rows;
console.log(`📍 Encontrados ${places.length} lugares con contenido faltante\n`);
let descGenerated = 0;
let audioGenerated = 0;
let errors = 0;
for (let i = 0; i < places.length; i++) {
const place = places[i];
console.log(`\n[${i + 1}/${places.length}] ${place.name}`);
for (const lang of config.languages) {
const descCol = `description_${lang}`;
const audioCol = `audio_url_${lang}`;
let description = place[descCol];
let audioUrl = place[audioCol];
// Generar descripción si falta
if (!description) {
console.log(` 📝 Generando descripción (${lang})...`);
try {
description = await callKimiAPI(place.name, place.country || 'DO', lang);
if (description) {
await client.query(
`UPDATE tourism.places_of_interest SET ${descCol} = $1 WHERE id = $2`,
[description, place.id]
);
console.log(` ✅ Descripción generada (${lang})`);
descGenerated++;
}
} catch (error) {
console.log(` ❌ Error descripción (${lang}): ${error.message}`);
errors++;
}
await new Promise(r => setTimeout(r, 1000)); // Rate limit
}
// Generar audio si falta y tiene descripción
if (description && !audioUrl) {
console.log(` 🔊 Generando audio (${lang})...`);
audioUrl = await generateAudio(description, lang);
if (audioUrl) {
await client.query(
`UPDATE tourism.places_of_interest SET ${audioCol} = $1 WHERE id = $2`,
[audioUrl, place.id]
);
audioGenerated++;
} else {
errors++;
}
await new Promise(r => setTimeout(r, 100));
}
}
}
await client.end();
console.log('\n===================================');
console.log('📊 RESUMEN');
console.log(` 📝 Descripciones generadas: ${descGenerated}`);
console.log(` 🔊 Audios generados: ${audioGenerated}`);
console.log(` ❌ Errores: ${errors}`);
console.log('===================================\n');
}
main().catch(err => {
console.error('Error fatal:', err);
process.exit(1);
});

View File

@@ -0,0 +1,226 @@
-- Script para poblar monumentos adicionales de RD y Puerto Rico (Parte 2)
-- Coordenadas en formato PostgreSQL point: (longitude, latitude)
-- ==========================================
-- REPÚBLICA DOMINICANA - ZONA COLONIAL (Adicionales)
-- ==========================================
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Casa de la Moneda', 'casa-de-la-moneda', 'museum', point(-69.88282, 18.47353), 'DO', 'Calle Las Damas, Zona Colonial, Santo Domingo', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Reloj de Sol', 'reloj-de-sol', 'monument', point(-69.88235, 18.47481), 'DO', 'Calle Las Damas, Zona Colonial, Santo Domingo', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Casa del Cordón', 'casa-del-cordon', 'monument', point(-69.88312, 18.47594), 'DO', 'Calle Isabel la Católica, Zona Colonial, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Casa de Tostado', 'casa-de-tostado', 'museum', point(-69.88264, 18.47167), 'DO', 'Calle Padre Billini, Zona Colonial, Santo Domingo', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Iglesia de la Merced', 'iglesia-de-la-merced', 'church', point(-69.88724, 18.47398), 'DO', 'Calle Las Mercedes, Zona Colonial, Santo Domingo', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Iglesia de Regina Angelorum', 'iglesia-regina-angelorum', 'church', point(-69.88602, 18.47055), 'DO', 'Calle Padre Billini, Zona Colonial, Santo Domingo', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Ruinas del Monasterio de San Francisco', 'ruinas-monasterio-san-francisco', 'ruins', point(-69.88478, 18.47648), 'DO', 'Calle Hostos, Zona Colonial, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Casa de las Gárgolas', 'casa-de-las-gargolas', 'monument', point(-69.88272, 18.47504), 'DO', 'Calle Las Damas, Zona Colonial, Santo Domingo', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Museo del Ámbar', 'museo-del-ambar', 'museum', point(-69.88651, 18.47402), 'DO', 'Calle Arzobispo Meriño, Zona Colonial, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Museo de Larimar', 'museo-de-larimar', 'museum', point(-69.88295, 18.47185), 'DO', 'Calle Isabel la Católica, Zona Colonial, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- ==========================================
-- SANTO DOMINGO (Fuera de Zona Colonial)
-- ==========================================
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Malecón de Santo Domingo', 'malecon-santo-domingo', 'promenade', point(-69.89381, 18.46351), 'DO', 'Av. George Washington, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Plaza de la Cultura', 'plaza-de-la-cultura', 'plaza', point(-69.91102, 18.47141), 'DO', 'Av. Máximo Gómez, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Palacio de Bellas Artes', 'palacio-bellas-artes', 'museum', point(-69.90956, 18.46654), 'DO', 'Av. Máximo Gómez, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Teatro Nacional Eduardo Brito', 'teatro-nacional', 'theater', point(-69.91008, 18.47172), 'DO', 'Plaza de la Cultura, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Centro Olímpico Juan Pablo Duarte', 'centro-olimpico', 'sports', point(-69.91605, 18.48002), 'DO', 'Av. 27 de Febrero, Santo Domingo', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- ==========================================
-- RD - NORTE (Puerto Plata)
-- ==========================================
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('27 Charcos de Damajagua', '27-charcos-damajagua', 'natural', point(-70.82471, 19.72885), 'DO', 'Imbert, Puerto Plata', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Ocean World Adventure Park', 'ocean-world', 'attraction', point(-70.73155, 19.83152), 'DO', 'Cofresí, Puerto Plata', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Playa Dorada', 'playa-dorada', 'beach', point(-70.64402, 19.76801), 'DO', 'Puerto Plata', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- ==========================================
-- RD - ESTE (Punta Cana, Hato Mayor)
-- ==========================================
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Cueva Fun Fun', 'cueva-fun-fun', 'natural', point(-69.44405, 19.04802), 'DO', 'Hato Mayor', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Hoyo Azul', 'hoyo-azul', 'natural', point(-68.45502, 18.44855), 'DO', 'Scape Park, Cap Cana, Punta Cana', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Indigenous Eyes Ecological Park', 'indigenous-eyes', 'natural', point(-68.37504, 18.51301), 'DO', 'Puntacana Resort & Club', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Playa Bávaro', 'playa-bavaro', 'beach', point(-68.41805, 18.68002), 'DO', 'Bávaro, Punta Cana', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Parque Nacional Los Haitises', 'parque-los-haitises', 'natural', point(-69.51672, 19.06671), 'DO', 'Sabana de la Mar, Hato Mayor', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- ==========================================
-- RD - SUROESTE (Barahona, Pedernales)
-- ==========================================
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Lago Enriquillo', 'lago-enriquillo', 'natural', point(-71.65002, 18.48405), 'DO', 'Independencia / Bahoruco', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Isla Cabritos', 'isla-cabritos', 'natural', point(-71.68504, 18.48802), 'DO', 'Lago Enriquillo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Bahía de las Águilas', 'bahia-de-las-aguilas', 'beach', point(-71.64205, 17.86402), 'DO', 'Parque Nacional Jaragua, Pedernales', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Parque Nacional Jaragua', 'parque-jaragua', 'natural', point(-71.50004, 17.85002), 'DO', 'Oviedo, Pedernales', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- ==========================================
-- RD - MONTAÑAS (Constanza, Jarabacoa)
-- ==========================================
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Aguas Blancas', 'aguas-blancas', 'natural', point(-70.67505, 18.85002), 'DO', 'Constanza, La Vega', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Piedra Letrada', 'piedra-letrada', 'natural', point(-70.76204, 18.78852), 'DO', 'Constanza, La Vega', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- ==========================================
-- PUERTO RICO - SAN JUAN (Adicionales)
-- ==========================================
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Museo de Arte de Puerto Rico', 'museo-arte-puerto-rico', 'museum', point(-66.06652, 18.44825), 'PR', 'Av. De Diego 299, Santurce, San Juan', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Fuerte San Juan de la Cruz (El Cañuelo)', 'fuerte-el-canuelo', 'fortress', point(-66.13601, 18.47352), 'PR', 'Isla de Cabras, Toa Baja', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Plaza de la Rogativa', 'plaza-de-la-rogativa', 'plaza', point(-66.11972, 18.46685), 'PR', 'Caleta de las Monjas, Viejo San Juan', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Fuente Raíces', 'fuente-raices', 'monument', point(-66.11865, 18.46382), 'PR', 'Paseo de la Princesa, Viejo San Juan', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- ==========================================
-- PUERTO RICO - COSTA NORTE
-- ==========================================
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Observatorio de Arecibo', 'observatorio-arecibo', 'museum', point(-66.75282, 18.34421), 'PR', 'Carretera 625, Arecibo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Playa Crash Boat', 'playa-crash-boat', 'beach', point(-67.16301, 18.45802), 'PR', 'Aguadilla', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Parque de las Cavernas del Río Camuy', 'cavernas-rio-camuy', 'natural', point(-66.82405, 18.34702), 'PR', 'Carretera 129, Camuy', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- ==========================================
-- PUERTO RICO - COSTA SUR
-- ==========================================
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Porta Coeli', 'porta-coeli', 'church', point(-67.04021, 18.08182), 'PR', 'San Germán', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('La Parguera', 'la-parguera', 'natural', point(-67.04505, 17.97202), 'PR', 'Lajas', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Bosque Seco de Guánica', 'bosque-seco-guanica', 'natural', point(-66.86554, 17.97152), 'PR', 'Guánica', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- ==========================================
-- PUERTO RICO - CENTRO / MONTAÑAS
-- ==========================================
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Toro Verde Adventure Park', 'toro-verde', 'attraction', point(-66.39101, 18.25202), 'PR', 'Orocovis', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Hacienda Buena Vista', 'hacienda-buena-vista', 'museum', point(-66.65481, 18.08442), 'PR', 'Ponce', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Bosque Estatal de Toro Negro', 'bosque-toro-negro', 'natural', point(-66.58784, 18.17252), 'PR', 'Jayuya/Ciales', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- ==========================================
-- PUERTO RICO - ISLAS
-- ==========================================
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Faro de Culebrita', 'faro-culebrita', 'monument', point(-65.23004, 18.31502), 'PR', 'Isla Culebrita, Culebra', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Sun Bay Beach', 'sun-bay-beach', 'beach', point(-65.46205, 18.10002), 'PR', 'Vieques', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- Mostrar resultado
SELECT 'Parte 2 completada!' as status, COUNT(*) as total_places FROM tourism.places_of_interest WHERE active = true;
SELECT country, COUNT(*) as cantidad FROM tourism.places_of_interest WHERE active = true GROUP BY country ORDER BY country;

226
scripts/seed-monuments.sql Normal file
View File

@@ -0,0 +1,226 @@
-- Script para poblar monumentos de RD y Puerto Rico
-- Coordenadas en formato PostgreSQL point: (longitude, latitude)
-- Agregar unique constraint en slug si no existe
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'places_of_interest_slug_key') THEN
ALTER TABLE tourism.places_of_interest ADD CONSTRAINT places_of_interest_slug_key UNIQUE (slug);
END IF;
EXCEPTION
WHEN duplicate_object THEN NULL;
END $$;
-- REPÚBLICA DOMINICANA - ZONA COLONIAL
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Catedral Primada de América', 'catedral-primada-de-america', 'monument', point(-69.884250, 18.472988), 'DO', 'Calle Arzobispo Meriño, Zona Colonial, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Fortaleza Ozama', 'fortaleza-ozama', 'fortress', point(-69.8817, 18.4732), 'DO', 'Calle Las Damas, Zona Colonial, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Alcázar de Colón', 'alcazar-de-colon', 'palace', point(-69.8832, 18.4775), 'DO', 'Plaza de España, Zona Colonial, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Faro a Colón', 'faro-a-colon', 'monument', point(-69.8682, 18.4786), 'DO', 'Av. España, Santo Domingo Este', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Panteón Nacional', 'panteon-nacional', 'monument', point(-69.8845, 18.4735), 'DO', 'Calle Las Damas, Zona Colonial, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Parque Colón', 'parque-colon', 'park', point(-69.8842, 18.4730), 'DO', 'Calle El Conde, Zona Colonial, Santo Domingo', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Calle Las Damas', 'calle-las-damas', 'street', point(-69.8820, 18.4738), 'DO', 'Zona Colonial, Santo Domingo', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Museo de las Casas Reales', 'museo-casas-reales', 'museum', point(-69.8825, 18.4742), 'DO', 'Calle Las Damas, Zona Colonial, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Puerta del Conde', 'puerta-del-conde', 'monument', point(-69.8955, 18.4692), 'DO', 'Parque Independencia, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Parque Independencia', 'parque-independencia', 'park', point(-69.8960, 18.4688), 'DO', 'Av. Bolívar, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Los Tres Ojos', 'los-tres-ojos', 'natural', point(-69.8545, 18.4695), 'DO', 'Parque Mirador del Este, Santo Domingo Este', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Jardín Botánico Nacional', 'jardin-botanico-nacional', 'park', point(-69.9467, 18.4950), 'DO', 'Av. República de Colombia, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Palacio Nacional', 'palacio-nacional', 'palace', point(-69.9140, 18.4750), 'DO', 'Av. México, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Ruinas del Hospital San Nicolás de Bari', 'ruinas-hospital-san-nicolas', 'ruins', point(-69.8852, 18.4722), 'DO', 'Calle Hostos, Zona Colonial, Santo Domingo', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Iglesia de Santa Bárbara', 'iglesia-santa-barbara', 'church', point(-69.8812, 18.4755), 'DO', 'Calle Isabel la Católica, Zona Colonial, Santo Domingo', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Convento de los Dominicos', 'convento-dominicos', 'monument', point(-69.8868, 18.4725), 'DO', 'Calle Padre Billini, Zona Colonial, Santo Domingo', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Plaza de España', 'plaza-de-espana', 'plaza', point(-69.8828, 18.4770), 'DO', 'Zona Colonial, Santo Domingo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- RD - SANTIAGO
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Monumento a los Héroes de la Restauración', 'monumento-heroes-restauracion', 'monument', point(-70.6931, 19.4792), 'DO', 'Centro de Santiago de los Caballeros', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- RD - PUERTO PLATA
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Fortaleza San Felipe', 'fortaleza-san-felipe', 'fortress', point(-70.6940, 19.7982), 'DO', 'Puerto Plata', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Teleférico de Puerto Plata', 'teleferico-puerto-plata', 'attraction', point(-70.6875, 19.7842), 'DO', 'Monte Isabel de Torres, Puerto Plata', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- RD - LA ROMANA
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Altos de Chavón', 'altos-de-chavon', 'village', point(-68.9620, 18.4270), 'DO', 'Casa de Campo, La Romana', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- RD - SAMANÁ
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Salto El Limón', 'salto-el-limon', 'natural', point(-69.4420, 19.2850), 'DO', 'El Limón, Samaná', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Playa Rincón', 'playa-rincon', 'beach', point(-69.2385, 19.2120), 'DO', 'Samaná', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Cayo Levantado', 'cayo-levantado', 'island', point(-69.3840, 19.1750), 'DO', 'Bahía de Samaná', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- RD - HIGÜEY
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Basílica Nuestra Señora de la Altagracia', 'basilica-altagracia', 'church', point(-68.7108, 18.6155), 'DO', 'Higüey, La Altagracia', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- RD - CONSTANZA
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Valle Nuevo', 'valle-nuevo', 'natural', point(-70.6020, 18.7880), 'DO', 'Constanza, La Vega', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- RD - JARABACOA
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Salto de Jimenoa', 'salto-jimenoa', 'natural', point(-70.6210, 19.1320), 'DO', 'Jarabacoa, La Vega', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Pico Duarte', 'pico-duarte', 'natural', point(-70.9893, 19.0291), 'DO', 'Cordillera Central', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- RD - BAYAHIBE
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Isla Saona', 'isla-saona', 'island', point(-68.7280, 18.1550), 'DO', 'Parque Nacional del Este', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- ==========================================
-- PUERTO RICO - VIEJO SAN JUAN
-- ==========================================
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Castillo San Felipe del Morro', 'castillo-san-felipe-del-morro', 'fortress', point(-66.1212, 18.4693), 'PR', '501 Calle Norzagaray, San Juan', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Castillo San Cristóbal', 'castillo-san-cristobal', 'fortress', point(-66.1067, 18.4670), 'PR', 'Calle Norzagaray, San Juan', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('La Fortaleza', 'la-fortaleza', 'palace', point(-66.1190, 18.4641), 'PR', '63 Calle de la Fortaleza, San Juan', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Catedral de San Juan Bautista', 'catedral-san-juan-bautista', 'church', point(-66.1170, 18.4660), 'PR', '153 Calle del Cristo, San Juan', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Capilla del Cristo', 'capilla-del-cristo', 'church', point(-66.1195, 18.4636), 'PR', 'Calle del Cristo, San Juan', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Casa Blanca', 'casa-blanca-pr', 'museum', point(-66.1185, 18.4685), 'PR', '1 Calle San Sebastián, San Juan', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Paseo de la Princesa', 'paseo-de-la-princesa', 'promenade', point(-66.1175, 18.4620), 'PR', 'Paseo de la Princesa, San Juan', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Puerta de San Juan', 'puerta-de-san-juan', 'monument', point(-66.1200, 18.4645), 'PR', 'Recinto Sur, San Juan', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Cementerio Santa María Magdalena de Pazzis', 'cementerio-santa-maria-pazzis', 'cemetery', point(-66.1225, 18.4705), 'PR', 'Calle Norzagaray, San Juan', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Plaza de Armas', 'plaza-de-armas-pr', 'plaza', point(-66.1155, 18.4655), 'PR', 'Calle San Francisco, San Juan', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Parque de las Palomas', 'parque-de-las-palomas', 'park', point(-66.1190, 18.4635), 'PR', 'Final Calle del Cristo, San Juan', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- PUERTO RICO - PONCE
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Parque de Bombas', 'parque-de-bombas', 'museum', point(-66.6142, 18.0125), 'PR', 'Plaza Las Delicias, Ponce', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Catedral Nuestra Señora de Guadalupe', 'catedral-ponce', 'church', point(-66.6138, 18.0128), 'PR', 'Plaza Las Delicias, Ponce', false, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Castillo Serrallés', 'castillo-serralles', 'palace', point(-66.6285, 18.0205), 'PR', 'El Vigía, Ponce', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- PUERTO RICO - NATURALEZA
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('El Yunque National Forest', 'el-yunque', 'natural', point(-65.7847, 18.3154), 'PR', 'Río Grande', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Bahía Bioluminiscente de Vieques', 'bahia-bioluminiscente-vieques', 'natural', point(-65.4775, 18.0965), 'PR', 'Mosquito Bay, Vieques', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Cueva del Indio', 'cueva-del-indio', 'natural', point(-66.6375, 18.4835), 'PR', 'Arecibo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Flamenco Beach', 'flamenco-beach', 'beach', point(-65.3180, 18.3290), 'PR', 'Culebra', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Bahía Bioluminiscente de Fajardo', 'laguna-grande-fajardo', 'natural', point(-65.6350, 18.3640), 'PR', 'Las Croabas, Fajardo', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
INSERT INTO tourism.places_of_interest (name, slug, category, coordinates, country, address, featured, active, created_at, updated_at)
VALUES ('Cañón San Cristóbal', 'canon-san-cristobal', 'natural', point(-66.3510, 18.1890), 'PR', 'Barranquitas/Aibonito', true, true, NOW(), NOW())
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, coordinates = EXCLUDED.coordinates, address = EXCLUDED.address, updated_at = NOW();
-- Mostrar resultado
SELECT 'Seeding completado!' as status, COUNT(*) as total_places FROM tourism.places_of_interest WHERE active = true;
SELECT country, COUNT(*) as cantidad FROM tourism.places_of_interest WHERE active = true GROUP BY country;