const { PrismaClient } = require('@prisma/client'); const { PrismaPg } = require('@prisma/adapter-pg'); const { Pool } = require('pg'); require('dotenv').config(); const dbUrl = new URL(process.env.DATABASE_URL_WALKTHROUGH || "postgresql://special_hotel_user:SpecialHotel1235%2F%2A-%2B@pg.gaboggamer.online/special_hotel_walkthrough?schema=public"); const pool = new Pool({ host: dbUrl.hostname, port: dbUrl.port ? parseInt(dbUrl.port) : 5432, user: decodeURIComponent(dbUrl.username), password: decodeURIComponent(dbUrl.password), database: dbUrl.pathname.substring(1).split('?')[0], ssl: false }); const adapter = new PrismaPg(pool); const prisma = new PrismaClient({ adapter }); const PASSWORD_HASH = '$2b$10$lRn/GrwzaEGxWqqQGGz0f.44nBywObKkGWRajlxWeDGZ3o4f7ayjy'; // bcrypt of 'password123' async function main() { console.log("=== STARTING WALKTHROUGH SEED SCRIPT ==="); // 1. Clean existing records in the walkthrough database to ensure clean seeding console.log("Cleaning database..."); // Disable RLS temporarily for bulk seed operations await prisma.$executeRawUnsafe(`ALTER TABLE "regions" DISABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "hotels" DISABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "users" DISABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "goals" DISABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "sales_results" DISABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "settlements" DISABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "audit_logs" DISABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "compensation_plans" DISABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "calculation_rules" DISABLE ROW LEVEL SECURITY;`); await prisma.auditLog.deleteMany(); await prisma.notification.deleteMany(); await prisma.settlement.deleteMany(); await prisma.salesResult.deleteMany(); await prisma.goal.deleteMany(); await prisma.calculationRule.deleteMany(); await prisma.compensationPlan.deleteMany(); await prisma.user.deleteMany(); await prisma.hotel.deleteMany(); await prisma.region.deleteMany(); // 2. Seed Regions console.log("Seeding regions..."); const bogota = await prisma.region.create({ data: { name: 'Bogotá', code: 'BOG' } }); const antioquia = await prisma.region.create({ data: { name: 'Antioquia', code: 'ANT' } }); const caribe = await prisma.region.create({ data: { name: 'Caribe', code: 'CAR' } }); const valle = await prisma.region.create({ data: { name: 'Valle', code: 'VAL' } }); // 3. Seed Hotels console.log("Seeding hotels..."); const p93 = await prisma.hotel.create({ data: { name: 'Estelar Parque de la 93', code: 'EST-P93', regionId: bogota.id } }); const mde = await prisma.hotel.create({ data: { name: 'Estelar Medellin', code: 'EST-MDE', regionId: antioquia.id } }); const ctg = await prisma.hotel.create({ data: { name: 'Estelar Cartagena', code: 'EST-CTG', regionId: caribe.id } }); const cali = await prisma.hotel.create({ data: { name: 'Estelar Cali', code: 'EST-CALI', regionId: valle.id } }); // 4. Seed Users console.log("Seeding users..."); const usersToSeed = [ { username: 'admin', email: 'admin@estelar.com', role: 'admin', hotelId: p93.id, area: 'Sistemas' }, { username: 'director', email: 'director@estelar.com', role: 'director', hotelId: p93.id, area: 'Comercial' }, { username: 'analista', email: 'analista@estelar.com', role: 'analyst', hotelId: p93.id, area: 'Finanzas' }, { username: 'consulta', email: 'consulta@estelar.com', role: 'auditor', hotelId: p93.id, area: 'Auditoria' }, // Managers { username: 'gerente_bog', email: 'gerente.bog@estelar.com', role: 'hotel_manager', hotelId: p93.id, area: 'Administracion' }, { username: 'gerente_mde', email: 'gerente.mde@estelar.com', role: 'hotel_manager', hotelId: mde.id, area: 'Administracion' }, { username: 'gerente_ctg', email: 'gerente.ctg@estelar.com', role: 'hotel_manager', hotelId: ctg.id, area: 'Administracion' }, { username: 'gerente_cali', email: 'gerente.cali@estelar.com', role: 'hotel_manager', hotelId: cali.id, area: 'Administracion' }, // Leaders { username: 'lider_bog', email: 'lider.bog@estelar.com', role: 'commercial_leader', hotelId: p93.id, area: 'Ventas' }, { username: 'lider_mde', email: 'lider.mde@estelar.com', role: 'commercial_leader', hotelId: mde.id, area: 'Ventas' }, { username: 'lider_ctg', email: 'lider.ctg@estelar.com', role: 'commercial_leader', hotelId: ctg.id, area: 'Ventas' }, { username: 'lider_cali', email: 'lider.cali@estelar.com', role: 'commercial_leader', hotelId: cali.id, area: 'Ventas' }, // Collaborators { username: 'colab_bog_1', email: 'colab.bog1@estelar.com', role: 'collaborator', hotelId: p93.id, area: 'Ventas' }, { username: 'colab_bog_2', email: 'colab.bog2@estelar.com', role: 'collaborator', hotelId: p93.id, area: 'Ventas' }, { username: 'colab_mde_1', email: 'colab.mde1@estelar.com', role: 'collaborator', hotelId: mde.id, area: 'Ventas' }, { username: 'colab_mde_2', email: 'colab.mde2@estelar.com', role: 'collaborator', hotelId: mde.id, area: 'Ventas' }, { username: 'colaborador_mde', email: 'colaborador.mde@estelar.com', role: 'collaborator', hotelId: mde.id, area: 'Ventas' }, { username: 'colab_ctg_1', email: 'colab.ctg1@estelar.com', role: 'collaborator', hotelId: ctg.id, area: 'Ventas' }, { username: 'colab_ctg_2', email: 'colab.ctg2@estelar.com', role: 'collaborator', hotelId: ctg.id, area: 'Ventas' }, { username: 'colab_cali_1', email: 'colab.cali1@estelar.com', role: 'collaborator', hotelId: cali.id, area: 'Ventas' }, { username: 'colab_cali_2', email: 'colab.cali2@estelar.com', role: 'collaborator', hotelId: cali.id, area: 'Ventas' } ]; const seededUsers = {}; for (const u of usersToSeed) { const created = await prisma.user.create({ data: { username: u.username, email: u.email, passwordHash: PASSWORD_HASH, role: u.role, hotelId: u.hotelId, area: u.area, status: 'ACTIVE' } }); seededUsers[u.username] = created; } // 5. Seed Compensation Plans & Rules console.log("Seeding compensation plans..."); // Plan Q1 (Percentage, Inactive) const planQ1 = await prisma.compensationPlan.create({ data: { name: 'Plan Comisiones Q1 - Porcentaje Fijo', code: 'PLAN-2026-Q1', validityStart: new Date('2026-01-01'), validityEnd: new Date('2026-03-31'), type: 'PERCENTAGE', percentageRate: 0.025, // 2.5% maxCap: 15000.00, status: 'INACTIVE', version: 1, createdBy: seededUsers['admin'].id } }); // Plan Q2 (Scale, Active) const planQ2 = await prisma.compensationPlan.create({ data: { name: 'Plan Comisiones Q2 - Escala Escalonada', code: 'PLAN-2026-Q2', validityStart: new Date('2026-04-01'), validityEnd: new Date('2026-06-30'), type: 'SCALE', status: 'ACTIVE', version: 1, createdBy: seededUsers['admin'].id } }); // Calculation rules for Q2 Plan console.log("Seeding calculation rules..."); await prisma.calculationRule.createMany({ data: [ { planId: planQ2.id, type: 'TIER', minAchievement: 0.0, maxAchievement: 0.8, rate: 0.0, payoutAmount: 0.0 }, { planId: planQ2.id, type: 'TIER', minAchievement: 0.8, maxAchievement: 1.0, rate: 0.02, payoutAmount: 100.0 }, { planId: planQ2.id, type: 'TIER', minAchievement: 1.0, maxAchievement: 9.9, rate: 0.03, payoutAmount: 250.0 } ] }); // 6. Seed Goals, Sales Results and Settlements console.log("Seeding goals, sales, and settlements..."); const collaborators = [ 'colab_bog_1', 'colab_bog_2', 'colab_mde_1', 'colab_mde_2', 'colaborador_mde', 'colab_ctg_1', 'colab_ctg_2', 'colab_cali_1', 'colab_cali_2' ]; const periods = ['2026-01', '2026-02', '2026-03', '2026-04', '2026-05', '2026-06']; // Seed structured sales, goals and settlements month-by-month for (const period of periods) { const isQ2 = period >= '2026-04'; const plan = isQ2 ? planQ2 : planQ1; for (const username of collaborators) { const user = seededUsers[username]; // Goal Setup let goalAmount = 70000.00; if (username.endsWith('2')) goalAmount = 60000.00; if (username === 'colaborador_mde') goalAmount = 80000.00; // Random variance for each user and month const hashVal = (username.length + period.charCodeAt(6)) % 10; const achievementMultiplier = 0.75 + (hashVal * 0.04); // ranges from 0.75 to 1.15 const salesAmount = goalAmount * achievementMultiplier; // Create Goal const goal = await prisma.goal.create({ data: { targetType: 'INDIVIDUAL', targetId: user.id, period, amount: goalAmount } }); // Create Sales Entries const entryCount = 4; const amountPerEntry = salesAmount / entryCount; const salesCountPerEntry = Math.round(5 + hashVal / 2); for (let i = 0; i < entryCount; i++) { // Build unique idempotency key const idempotencyKey = `sale-${user.id}-${period}-${i}`; // Seed normal sale await prisma.salesResult.create({ data: { source: 'EXCEL', hotelId: user.hotelId, userId: user.id, period, amount: amountPerEntry, salesCount: salesCountPerEntry, status: 'PROCESSED', idempotencyKey, uploadedBy: seededUsers['gerente_mde'].id, // Seed uploaded by MDE manager for convenience isAnomaly: false } }); } // Add a flagged outlier anomaly for colab_bog_1 in March if (username === 'colab_bog_1' && period === '2026-03') { await prisma.salesResult.create({ data: { source: 'EXCEL', hotelId: user.hotelId, userId: user.id, period, amount: 85000.00, salesCount: 45, status: 'PROCESSED', idempotencyKey: `anomaly-outlier-bog1-mar`, uploadedBy: seededUsers['gerente_bog'].id, isAnomaly: true, flaggedReason: { en: "Outlier transaction: Sale amount exceeds standard region limits.", es: "Transacción atípica: El monto de venta supera los límites regionales estándar." } } }); } // Compute Commission & Bonus let commission = 0; let bonus = 0; const achievement = salesAmount / goalAmount; if (!isQ2) { // Percentage plan: 2.5% of total sales commission = salesAmount * 0.025; bonus = 0; } else { // Scale plan rules if (achievement >= 0.8 && achievement < 1.0) { commission = salesAmount * 0.02; bonus = 100.00; } else if (achievement >= 1.0) { commission = salesAmount * 0.03; bonus = 250.00; } } // Retroactive Clawback deduction for colab_mde_1 in May 2026 let adjustmentAmount = 0.00; let adjustmentNotes = null; let originalSettlementId = null; if (username === 'colab_mde_1' && period === '2026-05') { // Clawback applied for returned sale of $15000 in March 2026 (PLAN Q1 rate: 2.5%) adjustmentAmount = -375.00; adjustmentNotes = "Deducción de retroactivo: Devolución de reserva de ventas de marzo (PLAN-2026-Q1)."; // Find their March settlement to link as original const marchSettlement = await prisma.settlement.findFirst({ where: { userId: user.id, period: '2026-03' } }); if (marchSettlement) { originalSettlementId = marchSettlement.id; } } const totalPayout = commission + bonus + adjustmentAmount; // Settlement Status: APPROVED for Jan-May, PENDING for June const isJune = period === '2026-06'; const settlementStatus = isJune ? 'PENDING' : 'APPROVED'; const managerMap = { [p93.id]: seededUsers['gerente_bog'].id, [mde.id]: seededUsers['gerente_mde'].id, [ctg.id]: seededUsers['gerente_ctg'].id, [cali.id]: seededUsers['gerente_cali'].id }; await prisma.settlement.create({ data: { period, planId: plan.id, userId: user.id, salesAmount, goalAmount, achievementPercentage: achievement, calculatedCommission: commission, calculatedBonus: bonus, adjustmentAmount, totalPayout, status: settlementStatus, approvedBy: isJune ? null : managerMap[user.hotelId], approvedAt: isJune ? null : new Date(`${period}-05T14:30:00Z`), originalSettlementId, adjustmentNotes, aiAudited: true, aiAuditNotes: { en: isJune ? "Calculated under PLAN-2026-Q2. Validation checks successfully completed." : "Compliant commission settlement. Payout successfully audited.", es: isJune ? "Calculado bajo el PLAN-2026-Q2. Revisiones de validación completadas exitosamente." : "Liquidación de comisiones conforme. Pago auditado correctamente." } } }); } } // 7. Seed Audit Logs Trail console.log("Seeding audit logs..."); const logs = [ { userId: seededUsers['admin'].id, action: 'LOGIN', targetTable: 'users', targetId: seededUsers['admin'].id, prev: null, next: null, ip: '::1', date: '2026-01-02T09:00:00Z' }, { userId: seededUsers['admin'].id, action: 'CREATE', targetTable: 'compensation_plans', targetId: planQ1.id, prev: null, next: { id: planQ1.id, code: planQ1.code, status: 'DRAFT' }, ip: '127.0.0.1', date: '2026-01-02T09:15:00Z' }, { userId: seededUsers['admin'].id, action: 'UPDATE', targetTable: 'compensation_plans', targetId: planQ1.id, prev: { status: 'DRAFT' }, next: { status: 'ACTIVE' }, ip: '127.0.0.1', date: '2026-01-05T10:00:00Z' }, { userId: seededUsers['gerente_mde'].id, action: 'LOGIN', targetTable: 'users', targetId: seededUsers['gerente_mde'].id, prev: null, next: null, ip: '10.8.0.5', date: '2026-02-05T08:30:00Z' }, { userId: seededUsers['gerente_mde'].id, action: 'APPROVE', targetTable: 'settlements', targetId: 1, prev: { status: 'PENDING' }, next: { status: 'APPROVED' }, ip: '10.8.0.5', date: '2026-02-05T08:45:00Z' }, { userId: seededUsers['gerente_bog'].id, action: 'LOGIN', targetTable: 'users', targetId: seededUsers['gerente_bog'].id, prev: null, next: null, ip: '10.8.0.6', date: '2026-02-05T09:12:00Z' }, { userId: seededUsers['gerente_bog'].id, action: 'APPROVE', targetTable: 'settlements', targetId: 2, prev: { status: 'PENDING' }, next: { status: 'APPROVED' }, ip: '10.8.0.6', date: '2026-02-05T09:18:00Z' } ]; for (const log of logs) { await prisma.auditLog.create({ data: { userId: log.userId, action: log.action, targetTable: log.targetTable, targetId: log.targetId, previousValue: log.prev, newValue: log.next, ipAddress: log.ip, createdAt: new Date(log.date) } }); } // 8. Re-enable Row-Level Security Policies via SQL console.log("Configuring database RLS boundaries..."); await prisma.$executeRawUnsafe(`ALTER TABLE "regions" ENABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "hotels" ENABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "goals" ENABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "sales_results" ENABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "settlements" ENABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "audit_logs" ENABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "compensation_plans" ENABLE ROW LEVEL SECURITY;`); await prisma.$executeRawUnsafe(`ALTER TABLE "calculation_rules" ENABLE ROW LEVEL SECURITY;`); // Disconnect temporary prisma connection before running manual execute await prisma.$disconnect(); console.log("Walkthrough seeding completed successfully!"); } main() .catch(async (e) => { console.error("Error during walkthrough seeding:", e); process.exit(1); });