Files
moraworld/packages/database/prisma/seed.ts
T
Lizandro Guarnizo b2b292c50a feat: add Warehouses + Integrations modules, /admin/configuracion page
- Schema v0.3: Warehouse + Integration models (db push applied)
- WarehousesModule: CRUD, set-default, multi-warehouse support
- IntegrationsModule: 26 keys across 7 groups (payment, notifications,
  customs, courier, marketplace, compliance, warehouse)
- AuthService: suite address pulled from default Warehouse in DB (env fallback)
- AuthModule: imports WarehousesModule
- Seed: creates default warehouse from WAREHOUSE_ADDRESS_* env vars
- Web: /admin/configuracion page (3 tabs: Bodegas, Integraciones, Estado APIs)
- Web: admin layout adds Configuracion nav link
- api.ts: warehouses + integrations client methods
2026-06-01 16:49:19 -05:00

265 lines
9.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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: "LunVie 8:0017: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<string, string> = {};
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());