/** * Seed de desarrollo — Moraworld Imports * Crea datos de prueba para todos los roles documentados en §06 de documentacion.html * * Usuarios de prueba: * super@moraworld.test → SUPER_ADMIN * admin@moraworld.test → ADMIN_EMPRESA * bodega@moraworld.test → OPERADOR_BODEGA * aduanero@moraworld.test → AGENTE_ADUANERO * cliente@moraworld.test → CLIENTE (con Suite EC-00001) * soporte@moraworld.test → SOPORTE * * NOTA: Las contraseñas son placeholders (bcrypt se implementa en Fase 1 — auth). */ import { PrismaClient, UserRole, PackageStatus, SenaeCategory } from "@prisma/client"; const prisma = new PrismaClient(); // Placeholder hash (en Fase 1 se reemplaza por bcrypt.hash("Test1234!", 10)) const PLACEHOLDER_HASH = "$2b$10$PLACEHOLDER_CHANGE_IN_PHASE_1_AUTH_MODULE"; async function main() { // ─── Tenant ───────────────────────────────────────────────── const tenant = await prisma.tenant.upsert({ where: { slug: "moraworld" }, update: { name: "Moraworld Imports", isActive: true }, create: { slug: "moraworld", name: "Moraworld Imports", isActive: true, }, }); console.log(`✓ Tenant: ${tenant.slug} (id: ${tenant.id})`); // ─── Tarifa base (doc §15) ──────────────────────────────── await prisma.tariff.upsert({ where: { tenantId: tenant.id }, update: {}, create: { tenantId: tenant.id, pricePerLb: 3.50, insurancePct: 0.02, fodinfaPct: 0.005, ivaPct: 0.15, max4x4Value: 400, max4x4WeightKg: 4, max4x4PerYear: 4, }, }); console.log("✓ Tarifa base configurada ($3.50/lb, 2% seguro, 0.5% FODINFA, 15% IVA)"); // ─── Bodega predeterminada (doc §07 + §12) ──────────────── const warehouseCount = await prisma.warehouse.count({ where: { tenantId: tenant.id } }); if (warehouseCount === 0) { await prisma.warehouse.create({ data: { tenantId: tenant.id, name: "Bodega NJ — City of Orange", street: process.env.WAREHOUSE_ADDRESS_STREET ?? "150 N Day St", city: process.env.WAREHOUSE_ADDRESS_CITY ?? "City of Orange", state: process.env.WAREHOUSE_ADDRESS_STATE ?? "NJ", zip: process.env.WAREHOUSE_ADDRESS_ZIP ?? "07050", country: process.env.WAREHOUSE_ADDRESS_COUNTRY ?? "US", phone: "+1-555-0100", contactName: "Operaciones Moraworld NJ", schedule: "Lun–Vie 8:00–17:00 ET", isDefault: true, isActive: true, }, }); console.log("✓ Bodega predeterminada creada: 150 N Day St, City of Orange, NJ 07050"); } else { console.log("→ Bodega predeterminada ya existe"); } // ─── Usuarios de prueba (uno por cada rol del §06) ──────── const usersToSeed: Array<{ email: string; role: UserRole; firstName: string; lastName: string; phone?: string; }> = [ { email: "super@moraworld.test", role: UserRole.SUPER_ADMIN, firstName: "System", lastName: "Admin" }, { email: "admin@moraworld.test", role: UserRole.ADMIN_EMPRESA, firstName: "Carlos", lastName: "Mora" }, { email: "bodega@moraworld.test", role: UserRole.OPERADOR_BODEGA, firstName: "James", lastName: "Wilson", phone: "+1-555-0100" }, { email: "aduanero@moraworld.test", role: UserRole.AGENTE_ADUANERO, firstName: "Sofía", lastName: "Estrella" }, { email: "cliente@moraworld.test", role: UserRole.CLIENTE, firstName: "Andrés", lastName: "Gutiérrez", phone: "+593-99-999-0001" }, { email: "soporte@moraworld.test", role: UserRole.SOPORTE, firstName: "Laura", lastName: "Vásquez" }, ]; const createdUsers: Record = {}; for (const u of usersToSeed) { const existing = await prisma.user.findUnique({ where: { tenantId_email: { tenantId: tenant.id, email: u.email } }, }); if (!existing) { const user = await prisma.user.create({ data: { tenantId: tenant.id, email: u.email, passwordHash: PLACEHOLDER_HASH, firstName: u.firstName, lastName: u.lastName, phone: u.phone ?? null, role: u.role, mfaEnabled: false, isActive: true, }, }); createdUsers[u.role] = user.id; console.log(`✓ Usuario creado: ${u.email} (${u.role})`); } else { createdUsers[u.role] = existing.id; console.log(`→ Usuario ya existe: ${u.email} (${u.role})`); } } // ─── Suite para el cliente demo ─────────────────────────── // Formato: EC-00001 (doc §02 y §09) const clientId = createdUsers[UserRole.CLIENTE]; if (clientId) { const existingSuite = await prisma.suite.findUnique({ where: { userId: clientId } }); if (!existingSuite) { await prisma.suite.create({ data: { tenantId: tenant.id, userId: clientId, code: "EC-00001", }, }); console.log("✓ Suite EC-00001 asignada a cliente demo"); } else { console.log(`→ Suite ya existe: ${existingSuite.code}`); } } // ─── Paquete demo con historial de estados ──────────────── const existingPkg = await prisma.package.findUnique({ where: { trackingId: "EC-20260506-000001" }, }); if (!existingPkg && clientId) { const pkg = await prisma.package.create({ data: { tenantId: tenant.id, userId: clientId, trackingId: "EC-20260506-000001", status: PackageStatus.VERIFICADO, description: "Tenis Nike Air Max 270 (demo seed)", store: "Amazon", declaredValue: 150.00, declaredWeight: 2.5, actualWeight: 2.6, lengthCm: 32, widthCm: 22, heightCm: 14, hasDiscrepancy: false, vendorTracking: "1Z999AA10123456784", productUrl: "https://www.amazon.com/dp/DEMO", photos: [], senaeCategory: SenaeCategory.REGIMEN_4X4, paidAt: new Date("2026-05-06T10:00:00.000Z"), }, }); // Historial de estados hasta VERIFICADO const historialEstados: Array<{ status: PackageStatus; note: string; date: string }> = [ { status: PackageStatus.REGISTRADO, note: "Paquete registrado por el cliente", date: "2026-05-06T10:00:00Z" }, { status: PackageStatus.EN_TRANSITO_BODEGA, note: "Amazon confirmó despacho", date: "2026-05-07T09:00:00Z" }, { status: PackageStatus.RECIBIDO_BODEGA, note: "Recibido en 150 N Day St, NJ", date: "2026-05-09T14:30:00Z" }, { status: PackageStatus.EN_VERIFICACION, note: "Operador revisando paquete", date: "2026-05-09T15:00:00Z" }, { status: PackageStatus.VERIFICADO, note: "Peso real: 2.6 lbs. Sin discrepancia.", date: "2026-05-09T15:45:00Z" }, ]; for (const h of historialEstados) { await prisma.packageStatusHistory.create({ data: { packageId: pkg.id, status: h.status, note: h.note, createdBy: clientId, createdAt: new Date(h.date), }, }); } console.log(`✓ Paquete demo creado: ${pkg.trackingId} (estado: ${pkg.status})`); } else { console.log("→ Paquete demo ya existe"); } // ─── Pre-alerta demo ────────────────────────────────────── if (clientId) { const existingPreAlert = await prisma.preAlert.findFirst({ where: { tenantId: tenant.id, userId: clientId }, }); if (!existingPreAlert) { await prisma.preAlert.create({ data: { tenantId: tenant.id, userId: clientId, store: "eBay", vendorTracking: "9400111899223397992682", description: "Audífonos Sony WH-1000XM5 (demo pre-alerta)", declaredValue: 280.00, estimatedArrival: new Date("2026-05-20T00:00:00.000Z"), status: "PENDIENTE", }, }); console.log("✓ Pre-alerta demo creada"); } else { console.log("→ Pre-alerta demo ya existe"); } } // ─── Solicitud B2B demo (doc §13) ───────────────────────── const existingB2B = await prisma.b2BRequest.findUnique({ where: { trackingId: "B2B-20260506-000001" }, }); if (!existingB2B) { await prisma.b2BRequest.create({ data: { tenantId: tenant.id, trackingId: "B2B-20260506-000001", contactName: "Roberto Andrade", contactEmail: "roberto@importadora.ec", contactPhone: "+593-98-765-4321", companyName: "Importadora Andrade S.A.", merchandiseType: "Calzado deportivo", description: "1,000 pares de zapatos deportivos Nike (demo B2B)", estimatedWeightKg: 1500, pallets: 4, commercialValue: 18000.00, originCity: "Miami, FL", requiresInen: true, status: "PENDIENTE", }, }); console.log("✓ Solicitud B2B demo creada: B2B-20260506-000001"); } else { console.log("→ Solicitud B2B demo ya existe"); } console.log("\n✅ Seed completado correctamente."); console.log("\n📋 Usuarios de prueba:"); console.log(" super@moraworld.test → SUPER_ADMIN"); console.log(" admin@moraworld.test → ADMIN_EMPRESA"); console.log(" bodega@moraworld.test → OPERADOR_BODEGA"); console.log(" aduanero@moraworld.test → AGENTE_ADUANERO"); console.log(" cliente@moraworld.test → CLIENTE (Suite EC-00001)"); console.log(" soporte@moraworld.test → SOPORTE"); console.log("\n ⚠️ Contraseñas: placeholder — implementar bcrypt en Fase 1 (auth)."); } main() .catch((e) => { console.error(e); process.exit(1); }) .finally(() => prisma.$disconnect());