feat(ADM-018): completed feature
This commit is contained in:
209
project/scripts/seed.cjs
Normal file
209
project/scripts/seed.cjs
Normal file
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* seed.cjs — Populate database with realistic dev/test data.
|
||||
* Usage: node scripts/seed.cjs
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { randomUUID } = require('crypto');
|
||||
const { Client } = require('pg');
|
||||
|
||||
// ── Read DATABASE_URL from .env ──────────────────────────────────────────────
|
||||
const envPath = path.join(__dirname, '..', '.env');
|
||||
if (fs.existsSync(envPath)) {
|
||||
fs.readFileSync(envPath, 'utf8')
|
||||
.split('\n')
|
||||
.filter((l) => l.trim() && !l.startsWith('#'))
|
||||
.forEach((l) => {
|
||||
const eq = l.indexOf('=');
|
||||
if (eq === -1) return;
|
||||
const k = l.slice(0, eq).trim();
|
||||
const v = l.slice(eq + 1).trim();
|
||||
if (k && !process.env[k]) process.env[k] = v;
|
||||
});
|
||||
}
|
||||
|
||||
const DATABASE_URL = process.env.DATABASE_URL;
|
||||
if (!DATABASE_URL) {
|
||||
console.error('DATABASE_URL not set');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────────
|
||||
async function sql(client, text, params, label) {
|
||||
try {
|
||||
const result = await client.query(text, params);
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error(`SQL ERROR [${label}]: ${err.message}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function seed(client) {
|
||||
await client.query('BEGIN');
|
||||
console.log('Seeding database...\n');
|
||||
|
||||
// ── Clear existing seed data ──────────────────────────────────────────
|
||||
await sql(client, 'DELETE FROM inventory_stock', [], 'clear-stock');
|
||||
await sql(client, 'DELETE FROM pricing_variant_prices', [], 'clear-prices');
|
||||
await sql(client, 'DELETE FROM catalog_product_variants', [], 'clear-variants');
|
||||
await sql(client, 'DELETE FROM catalog_product_categories', [], 'clear-prod-cats');
|
||||
await sql(client, 'DELETE FROM catalog_products', [], 'clear-products');
|
||||
await sql(client, "DELETE FROM categories_categories WHERE parent_id IS NOT NULL", [], 'clear-children');
|
||||
await sql(client, 'DELETE FROM categories_categories', [], 'clear-categories');
|
||||
await sql(client, 'DELETE FROM brands_brands', [], 'clear-brands');
|
||||
await sql(client, 'DELETE FROM shipping_methods', [], 'clear-methods');
|
||||
await sql(client, 'DELETE FROM shipping_zones', [], 'clear-zones');
|
||||
console.log('Cleared existing data\n');
|
||||
|
||||
const catBySlug = {};
|
||||
const brandBySlug = {};
|
||||
|
||||
// ── 1. Categories ─────────────────────────────────────────────────────
|
||||
const catData = [
|
||||
{ name: 'Alimentacion', slug: 'alimentacion', parentSlug: null },
|
||||
{ name: 'Suplementos', slug: 'suplementos', parentSlug: null },
|
||||
{ name: 'Cosmetica Natural', slug: 'cosmetica-natural', parentSlug: null },
|
||||
{ name: 'Limpieza Ecologica', slug: 'limpieza-ecologica', parentSlug: null },
|
||||
{ name: 'Frutos Secos', slug: 'frutos-secos', parentSlug: 'alimentacion' },
|
||||
{ name: 'Aceites', slug: 'aceites', parentSlug: 'alimentacion' },
|
||||
{ name: 'Hierbas e Infusiones', slug: 'hierbas-infusiones', parentSlug: 'alimentacion' },
|
||||
{ name: 'Vitaminas', slug: 'vitaminas', parentSlug: 'suplementos' },
|
||||
{ name: 'Proteinas', slug: 'proteinas', parentSlug: 'suplementos' },
|
||||
{ name: 'Cremas', slug: 'cremas', parentSlug: 'cosmetica-natural' },
|
||||
{ name: 'Jabones', slug: 'jabones', parentSlug: 'cosmetica-natural' },
|
||||
];
|
||||
|
||||
// Parents first (no FK dep), then children
|
||||
const parents = catData.filter((c) => !c.parentSlug);
|
||||
const children = catData.filter((c) => c.parentSlug);
|
||||
for (const cat of [...parents, ...children]) {
|
||||
const parentId = cat.parentSlug ? catBySlug[cat.parentSlug] : null;
|
||||
await sql(client,
|
||||
'INSERT INTO categories_categories (id, parent_id, name, slug, seo_title) VALUES ($1,$2,$3,$4,$5) ON CONFLICT (slug) DO UPDATE SET name=EXCLUDED.name, seo_title=EXCLUDED.seo_title',
|
||||
[randomUUID(), parentId, cat.name, cat.slug, cat.name],
|
||||
`cat-${cat.slug}`
|
||||
);
|
||||
const r = await client.query('SELECT id FROM categories_categories WHERE slug = $1', [cat.slug]);
|
||||
if (r.rows[0]) catBySlug[cat.slug] = r.rows[0].id;
|
||||
}
|
||||
console.log(`+ ${catData.length} categories`);
|
||||
|
||||
// ── 2. Brands ────────────────────────────────────────────────────────
|
||||
const brandData = [
|
||||
{ name: 'EcoVida', slug: 'ecovida' },
|
||||
{ name: 'NaturGreen', slug: 'naturgreen' },
|
||||
{ name: 'BioSana', slug: 'biosana' },
|
||||
{ name: 'VerdePura', slug: 'verdepura' },
|
||||
{ name: 'TierraNatural', slug: 'tierranatural' },
|
||||
{ name: 'SolNatural', slug: 'solnatural' },
|
||||
];
|
||||
|
||||
for (const brand of brandData) {
|
||||
await sql(client,
|
||||
'INSERT INTO brands_brands (id, name, slug, seo_title) VALUES ($1,$2,$3,$4) ON CONFLICT (slug) DO UPDATE SET name=EXCLUDED.name, seo_title=EXCLUDED.seo_title',
|
||||
[randomUUID(), brand.name, brand.slug, brand.name],
|
||||
`brand-${brand.slug}`
|
||||
);
|
||||
const r = await client.query('SELECT id FROM brands_brands WHERE slug = $1', [brand.slug]);
|
||||
if (r.rows[0]) brandBySlug[brand.slug] = r.rows[0].id;
|
||||
}
|
||||
console.log(`+ ${brandData.length} brands`);
|
||||
|
||||
// ── 3. Products + variants + prices + stock ───────────────────────────
|
||||
const productData = [
|
||||
{ name: 'Almendras Crudas Ecologicas', slug: 'almendras-crudas-ecologicas', brandSlug: 'ecovida', catSlug: 'frutos-secos', price: 895, vat: 'general' },
|
||||
{ name: 'Aceite de Oliva Virgen Extra Bio', slug: 'aceite-oliva-virgen-extra-bio', brandSlug: 'naturgreen', catSlug: 'aceites', price: 1290, vat: 'general' },
|
||||
{ name: 'Te Verde Matcha Premium', slug: 'te-verde-matcha-premium', brandSlug: 'biosana', catSlug: 'hierbas-infusiones', price: 1590, vat: 'general' },
|
||||
{ name: 'Vitamina D3 + K2', slug: 'vitamina-d3-k2', brandSlug: 'verdepura', catSlug: 'vitaminas', price: 1890, vat: 'reduced' },
|
||||
{ name: 'Proteina Guisante Ecologica', slug: 'proteina-guisante-ecologica', brandSlug: 'tierranatural', catSlug: 'proteinas', price: 2490, vat: 'reduced' },
|
||||
{ name: 'Complejo B Natural', slug: 'complejo-b-natural', brandSlug: 'ecovida', catSlug: 'vitaminas', price: 1190, vat: 'reduced' },
|
||||
{ name: 'Crema Facial Hidratante Aloe Vera', slug: 'crema-facial-aloe-vera', brandSlug: 'solnatural', catSlug: 'cremas', price: 2190, vat: 'general' },
|
||||
{ name: 'Jabon Negro Africano', slug: 'jabon-negro-africano', brandSlug: 'biosana', catSlug: 'jabones', price: 790, vat: 'general' },
|
||||
{ name: 'Serum Antioxidante Rosa Mosqueta', slug: 'serum-rosa-mosqueta', brandSlug: 'naturgreen', catSlug: 'cremas', price: 2690, vat: 'general' },
|
||||
{ name: 'Detergente Ecologico Ropa', slug: 'detergente-ecologico-ropa', brandSlug: 'verdepura', catSlug: 'limpieza-ecologica', price: 990, vat: 'general' },
|
||||
{ name: 'Limpiador Multiusos Bio', slug: 'limpiador-multiusos-bio', brandSlug: 'tierranatural', catSlug: 'limpieza-ecologica', price: 890, vat: 'general' },
|
||||
{ name: 'Desinfectante Natural Superficies', slug: 'desinfectante-natural-superficies', brandSlug: 'solnatural', catSlug: 'limpieza-ecologica', price: 1190, vat: 'general' },
|
||||
];
|
||||
|
||||
for (let pi = 0; pi < productData.length; pi++) {
|
||||
const p = productData[pi];
|
||||
const brandId = brandBySlug[p.brandSlug];
|
||||
const catId = catBySlug[p.catSlug];
|
||||
if (!brandId || !catId) { console.warn(` Skipping ${p.slug}: missing brand or cat`); continue; }
|
||||
|
||||
const productId = randomUUID();
|
||||
const variantId = randomUUID();
|
||||
const sku = `SKU-${p.slug.replace(/-/g, '').toUpperCase().slice(0, 8)}-001`;
|
||||
const ean = `590123412345${String(pi + 1).padStart(2, '0')}`;
|
||||
const desc = `${p.name} de primera calidad. Producto 100% natural y ecologico.`;
|
||||
|
||||
await sql(client,
|
||||
'INSERT INTO catalog_products (id, brand_id, name, slug, state, seo_title, description) VALUES ($1,$2,$3,$4,$5,$6,$7) ON CONFLICT (slug) DO UPDATE SET name=EXCLUDED.name, brand_id=EXCLUDED.brand_id, description=EXCLUDED.description',
|
||||
[productId, brandId, p.name, p.slug, 'active', p.name, desc],
|
||||
`product-${p.slug}`
|
||||
);
|
||||
await sql(client,
|
||||
'INSERT INTO catalog_product_categories (product_id, category_id) VALUES ($1,$2) ON CONFLICT DO NOTHING',
|
||||
[productId, catId],
|
||||
`prodcat-${p.slug}`
|
||||
);
|
||||
await sql(client,
|
||||
'INSERT INTO catalog_product_variants (id, product_id, sku, ean) VALUES ($1,$2,$3,$4) ON CONFLICT (sku) DO UPDATE SET ean=EXCLUDED.ean',
|
||||
[variantId, productId, sku, ean],
|
||||
`variant-${sku}`
|
||||
);
|
||||
await sql(client,
|
||||
'INSERT INTO pricing_variant_prices (variant_id, vat_rate, net_unit_amount_cents) VALUES ($1,$2,$3) ON CONFLICT (variant_id) DO UPDATE SET net_unit_amount_cents=EXCLUDED.net_unit_amount_cents, vat_rate=EXCLUDED.vat_rate',
|
||||
[variantId, p.vat, p.price],
|
||||
`price-${sku}`
|
||||
);
|
||||
await sql(client,
|
||||
'INSERT INTO inventory_stock (variant_id, available, reserved) VALUES ($1,$2,$3) ON CONFLICT (variant_id) DO UPDATE SET available=EXCLUDED.available, reserved=EXCLUDED.reserved',
|
||||
[variantId, 25, 0],
|
||||
`stock-${sku}`
|
||||
);
|
||||
}
|
||||
console.log(`+ ${productData.length} products (variants, prices, stock)`);
|
||||
|
||||
// ── 4. Shipping ───────────────────────────────────────────────────
|
||||
const zoneId = randomUUID();
|
||||
await sql(client,
|
||||
'INSERT INTO shipping_zones (id, name, country, postal_code_prefix) VALUES ($1,$2,$3,$4) ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name',
|
||||
[zoneId, 'Espania Peninsular', 'ES', null],
|
||||
'zone-es'
|
||||
);
|
||||
await sql(client,
|
||||
'INSERT INTO shipping_methods (id, zone_id, name, base_cost_cents, free_shipping_threshold_cents) VALUES ($1,$2,$3,$4,$5)',
|
||||
[randomUUID(), zoneId, 'Estandar', 499, 4900],
|
||||
'method-std'
|
||||
);
|
||||
await sql(client,
|
||||
'INSERT INTO shipping_methods (id, zone_id, name, base_cost_cents, free_shipping_threshold_cents) VALUES ($1,$2,$3,$4,$5)',
|
||||
[randomUUID(), zoneId, 'Express 24h', 899, null],
|
||||
'method-express'
|
||||
);
|
||||
console.log('+ 1 shipping zone + 2 methods');
|
||||
|
||||
await client.query('COMMIT');
|
||||
console.log('\nSeed complete!');
|
||||
}
|
||||
|
||||
// ── Run ──────────────────────────────────────────────────────────────────
|
||||
async function main() {
|
||||
const client = new Client({ connectionString: DATABASE_URL });
|
||||
try {
|
||||
await client.connect();
|
||||
await seed(client);
|
||||
} catch (err) {
|
||||
await client.query('ROLLBACK').catch(() => {});
|
||||
console.error('Seed failed:', err.message);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await client.end();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
58
project/scripts/start-dev.sh
Executable file
58
project/scripts/start-dev.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
# start-dev.sh — levanta infraestructura + migraciones + servidor dev
|
||||
# Uso: ./scripts/start-dev.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# ── 1. Verificar Docker ─────────────────────────────────────────────────────
|
||||
if ! command -v docker &>/dev/null; then
|
||||
echo "Docker no encontrado. Instalo o aseguro de que este en PATH."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── 2. Levantar postgres y redis ────────────────────────────────────────────
|
||||
echo "Levantando postgres y redis..."
|
||||
docker compose up -d postgres redis
|
||||
|
||||
# ── 3. Esperar a que postgres responda ──────────────────────────────────────
|
||||
echo "Esperando a postgres..."
|
||||
MAX_WAIT=30
|
||||
COUNT=0
|
||||
until docker compose exec -T postgres pg_isready -U mdv &>/dev/null; do
|
||||
sleep 1
|
||||
COUNT=$((COUNT + 1))
|
||||
if [ "$COUNT" -ge "$MAX_WAIT" ]; then
|
||||
echo "postgres no respondio en ${MAX_WAIT}s"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "postgres listo"
|
||||
|
||||
# ── 4. Instalar dependencias ─────────────────────────────────────────────────
|
||||
echo "npm install..."
|
||||
npm install
|
||||
|
||||
# ── 5. Asegurar .env con DATABASE_URL ───────────────────────────────────────
|
||||
if [ ! -f .env ]; then
|
||||
echo "Creando .env con DATABASE_URL..."
|
||||
cat > .env <<'EOF'
|
||||
DATABASE_URL=postgres://mdv:mdv_dev_only@localhost:5432/mercadodevida
|
||||
NODE_ENV=development
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ── 6. Exportar variables para que los procesos hijos las tengan ───────────────
|
||||
export DATABASE_URL="postgres://mdv:mdv_dev_only@localhost:5432/mercadodevida"
|
||||
export NODE_ENV="development"
|
||||
|
||||
# ── 7. Aplicar migraciones ──────────────────────────────────────────────────
|
||||
echo "Aplicando migraciones..."
|
||||
npx node-pg-migrate up
|
||||
|
||||
# ── 8. Arrancar servidor dev ─────────────────────────────────────────────────
|
||||
echo "Todo listo. Arrancando servidor dev..."
|
||||
npm run start
|
||||
Reference in New Issue
Block a user