feat: initial API Ubigeo Peru - INEI 2025 + países del mundo
This commit is contained in:
BIN
prisma/.DS_Store
vendored
Normal file
BIN
prisma/.DS_Store
vendored
Normal file
Binary file not shown.
121
prisma/migrations/20260310035352_init/migration.sql
Normal file
121
prisma/migrations/20260310035352_init/migration.sql
Normal file
@@ -0,0 +1,121 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "paises" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"codigo" VARCHAR(3) NOT NULL,
|
||||
"codigo_alpha2" VARCHAR(2) NOT NULL,
|
||||
"nombre" VARCHAR(100) NOT NULL,
|
||||
"nombre_en" VARCHAR(100) NOT NULL,
|
||||
"capital" VARCHAR(100) NOT NULL,
|
||||
"region" VARCHAR(50) NOT NULL,
|
||||
"subregion" VARCHAR(50) NOT NULL,
|
||||
"emoji" VARCHAR(10) NOT NULL,
|
||||
"latitud" DECIMAL(10,8),
|
||||
"longitud" DECIMAL(11,8),
|
||||
"activo" BOOLEAN NOT NULL DEFAULT true,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "paises_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "departamentos" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"codigo" VARCHAR(2) NOT NULL,
|
||||
"nombre" VARCHAR(100) NOT NULL,
|
||||
"latitud" DECIMAL(10,8),
|
||||
"longitud" DECIMAL(11,8),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "departamentos_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "provincias" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"codigo" VARCHAR(4) NOT NULL,
|
||||
"codigo_dep" VARCHAR(2) NOT NULL,
|
||||
"nombre" VARCHAR(100) NOT NULL,
|
||||
"latitud" DECIMAL(10,8),
|
||||
"longitud" DECIMAL(11,8),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "provincias_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "distritos" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"codigo" VARCHAR(6) NOT NULL,
|
||||
"codigo_prov" VARCHAR(4) NOT NULL,
|
||||
"codigo_dep" VARCHAR(2) NOT NULL,
|
||||
"nombre" VARCHAR(100) NOT NULL,
|
||||
"capital" VARCHAR(100),
|
||||
"categoria" VARCHAR(30),
|
||||
"altitud" DECIMAL(10,2),
|
||||
"poblacion" INTEGER,
|
||||
"latitud" DECIMAL(10,8),
|
||||
"longitud" DECIMAL(11,8),
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "distritos_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "paises_codigo_key" ON "paises"("codigo");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "paises_codigo_alpha2_key" ON "paises"("codigo_alpha2");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "paises_codigo_idx" ON "paises"("codigo");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "paises_codigo_alpha2_idx" ON "paises"("codigo_alpha2");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "paises_nombre_idx" ON "paises"("nombre");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "paises_region_idx" ON "paises"("region");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "departamentos_codigo_key" ON "departamentos"("codigo");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "departamentos_codigo_idx" ON "departamentos"("codigo");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "departamentos_nombre_idx" ON "departamentos"("nombre");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "provincias_codigo_key" ON "provincias"("codigo");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "provincias_codigo_idx" ON "provincias"("codigo");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "provincias_codigo_dep_idx" ON "provincias"("codigo_dep");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "provincias_nombre_idx" ON "provincias"("nombre");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "distritos_codigo_key" ON "distritos"("codigo");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "distritos_codigo_idx" ON "distritos"("codigo");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "distritos_codigo_prov_idx" ON "distritos"("codigo_prov");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "distritos_codigo_dep_idx" ON "distritos"("codigo_dep");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "distritos_nombre_idx" ON "distritos"("nombre");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "provincias" ADD CONSTRAINT "provincias_codigo_dep_fkey" FOREIGN KEY ("codigo_dep") REFERENCES "departamentos"("codigo") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "distritos" ADD CONSTRAINT "distritos_codigo_prov_fkey" FOREIGN KEY ("codigo_prov") REFERENCES "provincias"("codigo") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
85
prisma/schema.prisma
Normal file
85
prisma/schema.prisma
Normal file
@@ -0,0 +1,85 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
}
|
||||
|
||||
model Pais {
|
||||
id Int @id @default(autoincrement())
|
||||
codigo String @unique @db.VarChar(3)
|
||||
codigoAlpha2 String @unique @map("codigo_alpha2") @db.VarChar(2)
|
||||
nombre String @db.VarChar(100)
|
||||
nombreEn String @map("nombre_en") @db.VarChar(100)
|
||||
capital String @db.VarChar(100)
|
||||
region String @db.VarChar(50)
|
||||
subregion String @db.VarChar(50)
|
||||
emoji String @db.VarChar(10)
|
||||
latitud Decimal? @db.Decimal(10, 8)
|
||||
longitud Decimal? @db.Decimal(11, 8)
|
||||
activo Boolean @default(true)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@index([codigo])
|
||||
@@index([codigoAlpha2])
|
||||
@@index([nombre])
|
||||
@@index([region])
|
||||
@@map("paises")
|
||||
}
|
||||
|
||||
model Departamento {
|
||||
id Int @id @default(autoincrement())
|
||||
codigo String @unique @db.VarChar(2)
|
||||
nombre String @db.VarChar(100)
|
||||
latitud Decimal? @db.Decimal(10, 8)
|
||||
longitud Decimal? @db.Decimal(11, 8)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
provincias Provincia[]
|
||||
|
||||
@@index([codigo])
|
||||
@@index([nombre])
|
||||
@@map("departamentos")
|
||||
}
|
||||
|
||||
model Provincia {
|
||||
id Int @id @default(autoincrement())
|
||||
codigo String @unique @db.VarChar(4)
|
||||
codigoDep String @map("codigo_dep") @db.VarChar(2)
|
||||
nombre String @db.VarChar(100)
|
||||
latitud Decimal? @db.Decimal(10, 8)
|
||||
longitud Decimal? @db.Decimal(11, 8)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
departamento Departamento @relation(fields: [codigoDep], references: [codigo])
|
||||
distritos Distrito[]
|
||||
|
||||
@@index([codigo])
|
||||
@@index([codigoDep])
|
||||
@@index([nombre])
|
||||
@@map("provincias")
|
||||
}
|
||||
|
||||
model Distrito {
|
||||
id Int @id @default(autoincrement())
|
||||
codigo String @unique @db.VarChar(6)
|
||||
codigoProv String @map("codigo_prov") @db.VarChar(4)
|
||||
codigoDep String @map("codigo_dep") @db.VarChar(2)
|
||||
nombre String @db.VarChar(100)
|
||||
capital String? @db.VarChar(100)
|
||||
categoria String? @db.VarChar(30)
|
||||
altitud Decimal? @db.Decimal(10, 2)
|
||||
poblacion Int?
|
||||
latitud Decimal? @db.Decimal(10, 8)
|
||||
longitud Decimal? @db.Decimal(11, 8)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
provincia Provincia @relation(fields: [codigoProv], references: [codigo])
|
||||
|
||||
@@index([codigo])
|
||||
@@index([codigoProv])
|
||||
@@index([codigoDep])
|
||||
@@index([nombre])
|
||||
@@map("distritos")
|
||||
}
|
||||
164
prisma/seed/seed.ts
Normal file
164
prisma/seed/seed.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { PrismaPg } from '@prisma/adapter-pg';
|
||||
import * as https from 'https';
|
||||
import * as http from 'http';
|
||||
|
||||
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
// ─── HELPERS ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function fetch(url: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = url.startsWith('https') ? https : http;
|
||||
let data = '';
|
||||
client.get(url, (res) => {
|
||||
if (res.statusCode === 301 || res.statusCode === 302) {
|
||||
return fetch(res.headers.location!).then(resolve).catch(reject);
|
||||
}
|
||||
res.on('data', (chunk) => (data += chunk));
|
||||
res.on('end', () => resolve(data));
|
||||
}).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
// ─── SEED UBIGEO INEI 2025 ────────────────────────────────────────────────────
|
||||
|
||||
async function seedUbigeo() {
|
||||
console.log('📍 Descargando ubigeos INEI 2025...');
|
||||
const csv = await fetch('https://raw.githubusercontent.com/MichaelSuarez0/ubigeos_peru/main/databases/ubigeo_inei_2025.csv');
|
||||
|
||||
const lines = csv.trim().split('\n').slice(1); // skip header
|
||||
console.log(` ${lines.length} registros encontrados`);
|
||||
|
||||
const depMap = new Map<string, { nombre: string; lat?: number; lon?: number }>();
|
||||
const provMap = new Map<string, { nombre: string; depCod: string; lat?: number; lon?: number }>();
|
||||
const distritos: any[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
const parts = line.split(';');
|
||||
if (parts.length < 10) continue;
|
||||
|
||||
const [depNombre, provNombre, distNombre, ubigeo, capital, categoria, altitud, poblacion, lat, lon] = parts;
|
||||
|
||||
if (!ubigeo || ubigeo.length !== 6) continue;
|
||||
|
||||
const codDep = ubigeo.substring(0, 2);
|
||||
const codProv = ubigeo.substring(0, 4);
|
||||
const latNum = parseFloat(lat) || undefined;
|
||||
const lonNum = parseFloat(lon) || undefined;
|
||||
|
||||
if (!depMap.has(codDep)) {
|
||||
depMap.set(codDep, { nombre: depNombre.trim(), lat: latNum, lon: lonNum });
|
||||
}
|
||||
|
||||
if (!provMap.has(codProv)) {
|
||||
provMap.set(codProv, { nombre: provNombre.trim(), depCod: codDep, lat: latNum, lon: lonNum });
|
||||
}
|
||||
|
||||
distritos.push({
|
||||
codigo: ubigeo.trim(),
|
||||
codigoProv: codProv,
|
||||
codigoDep: codDep,
|
||||
nombre: distNombre.trim(),
|
||||
capital: capital?.trim() || null,
|
||||
categoria: categoria?.trim() || null,
|
||||
altitud: parseFloat(altitud) || null,
|
||||
poblacion: parseInt(poblacion) || null,
|
||||
latitud: latNum,
|
||||
longitud: lonNum,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(` Departamentos: ${depMap.size} | Provincias: ${provMap.size} | Distritos: ${distritos.length}`);
|
||||
|
||||
// Insertar departamentos
|
||||
console.log(' Insertando departamentos...');
|
||||
await prisma.departamento.deleteMany();
|
||||
for (const [codigo, { nombre, lat, lon }] of depMap) {
|
||||
await prisma.departamento.create({
|
||||
data: { codigo, nombre, latitud: lat, longitud: lon },
|
||||
});
|
||||
}
|
||||
|
||||
// Insertar provincias
|
||||
console.log(' Insertando provincias...');
|
||||
await prisma.provincia.deleteMany();
|
||||
for (const [codigo, { nombre, depCod, lat, lon }] of provMap) {
|
||||
await prisma.provincia.create({
|
||||
data: { codigo, codigoDep: depCod, nombre, latitud: lat, longitud: lon },
|
||||
});
|
||||
}
|
||||
|
||||
// Insertar distritos en batches
|
||||
console.log(' Insertando distritos...');
|
||||
await prisma.distrito.deleteMany();
|
||||
const batchSize = 100;
|
||||
for (let i = 0; i < distritos.length; i += batchSize) {
|
||||
const batch = distritos.slice(i, i + batchSize);
|
||||
await prisma.distrito.createMany({ data: batch, skipDuplicates: true });
|
||||
process.stdout.write(`\r Progreso: ${Math.min(i + batchSize, distritos.length)}/${distritos.length}`);
|
||||
}
|
||||
console.log('\n ✅ Ubigeos insertados');
|
||||
}
|
||||
|
||||
// ─── SEED PAÍSES ──────────────────────────────────────────────────────────────
|
||||
|
||||
async function seedPaises() {
|
||||
console.log('\n🌍 Descargando países del mundo...');
|
||||
const raw = await fetch('https://restcountries.com/v3.1/all?fields=name,cca2,cca3,capital,region,subregion,latlng,flag');
|
||||
const countries = JSON.parse(raw);
|
||||
|
||||
console.log(` ${countries.length} países encontrados`);
|
||||
|
||||
await prisma.pais.deleteMany();
|
||||
|
||||
const data = countries
|
||||
.filter((c: any) => c.cca3 && c.cca2)
|
||||
.map((c: any) => ({
|
||||
codigo: c.cca3,
|
||||
codigoAlpha2: c.cca2,
|
||||
nombre: c.name?.translations?.spa?.official || c.name?.translations?.spa?.common || c.name?.common || '',
|
||||
nombreEn: c.name?.common || '',
|
||||
capital: Array.isArray(c.capital) ? c.capital[0] || '' : '',
|
||||
region: c.region || '',
|
||||
subregion: c.subregion || '',
|
||||
emoji: c.flag || '',
|
||||
latitud: c.latlng?.[0] || null,
|
||||
longitud: c.latlng?.[1] || null,
|
||||
}))
|
||||
.filter((c: any) => c.nombre);
|
||||
|
||||
await prisma.pais.createMany({ data, skipDuplicates: true });
|
||||
console.log(` ✅ ${data.length} países insertados`);
|
||||
}
|
||||
|
||||
// ─── MAIN ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
async function main() {
|
||||
console.log('🚀 Iniciando seed...\n');
|
||||
try {
|
||||
await seedUbigeo();
|
||||
await seedPaises();
|
||||
|
||||
const stats = {
|
||||
departamentos: await prisma.departamento.count(),
|
||||
provincias: await prisma.provincia.count(),
|
||||
distritos: await prisma.distrito.count(),
|
||||
paises: await prisma.pais.count(),
|
||||
};
|
||||
|
||||
console.log('\n✅ Seed completado:');
|
||||
console.log(` Departamentos: ${stats.departamentos}`);
|
||||
console.log(` Provincias: ${stats.provincias}`);
|
||||
console.log(` Distritos: ${stats.distritos}`);
|
||||
console.log(` Países: ${stats.paises}`);
|
||||
} catch (e) {
|
||||
console.error('❌ Error en seed:', e);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user