Files
kodeva/prisma/seed.example.ts
T
Amadea 61ce9d7926 feat: implementasi Kodeva - sistem manajemen kursus coding
Fitur lengkap:
- Auth JWT + RBAC (Admin/Teacher) + halaman login
- Shell 3 kolom (left nav, topbar, right rail dashboard)
- CRUD siswa + follow up kanban + status calon/student/ex
- Dashboard: siswa aktif, kehadiran, bar sumber lead, perlu follow up
- Pembayaran prorata per pertemuan (cash/transfer/qris)
- Kelas, enrollment, generate sesi, kehadiran bulk
- Jurnal aktivitas + upload multi-foto
- Raport + placement test (tampil di detail siswa)
- Pengaturan & seed data contoh

Stack: Next.js 16, TypeScript, Tailwind v4, Prisma (SQLite), Recharts, Zod
2026-09-18 19:24:59 +07:00

330 lines
24 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.example.ts — Contoh data Kodeva (Sistem Manajemen Kursus Coding)
* ---------------------------------------------------------------------
* Cara pakai:
* 1) Salin ke `prisma/seed.ts`
* 2) Tambahkan ke package.json → "prisma": { "seed": "tsx prisma/seed.ts" }
* 3) Jalankan: pnpm db:push && pnpm db:seed
*
* Login contoh:
* admin@kodeva.id / password123 (ADMIN)
* budi@kodeva.id / password123 (TEACHER)
* rina@kodeva.id / password123 (TEACHER)
*
* Catatan: cocok dengan skema di AGENTS.md (PostgreSQL).
*/
import { PrismaClient, AttendanceStatus, PaymentStatus, PlacementLevel } from "@prisma/client";
import bcrypt from "bcryptjs";
const prisma = new PrismaClient();
const BASE = new Date("2026-09-18T00:00:00.000Z");
const addDays = (n: number, from: Date = BASE) => new Date(from.getTime() + n * 86_400_000);
const addWeeks = (n: number, from: Date = BASE) => addDays(n * 7, from);
const PASS = bcrypt.hashSync("password123", 10);
const avatar = (seed: string) => `https://i.pravatar.cc/240?u=${encodeURIComponent(seed)}`;
const photo = (seed: string) => `https://picsum.photos/seed/${encodeURIComponent(seed)}/900/600`;
async function main() {
console.log("🌱 Seed Kodeva dimulai...");
// Bersihkan (urutan penting karena relasi)
await prisma.followUpActivity.deleteMany();
await prisma.followUp.deleteMany();
await prisma.placementScore.deleteMany();
await prisma.placementTest.deleteMany();
await prisma.reportScore.deleteMany();
await prisma.reportCard.deleteMany();
await prisma.journalStudent.deleteMany();
await prisma.journalPhoto.deleteMany();
await prisma.journal.deleteMany();
await prisma.payment.deleteMany();
await prisma.attendance.deleteMany();
await prisma.session.deleteMany();
await prisma.enrollment.deleteMany();
await prisma.student.deleteMany();
await prisma.class.deleteMany();
await prisma.user.deleteMany();
await prisma.setting.deleteMany();
// ─────────────────────────────────────────────────────────────
// 1. USERS (Admin + Teacher)
// ─────────────────────────────────────────────────────────────
const sari = await prisma.user.create({
data: {
name: "Sari Wulandari", email: "admin@kodeva.id", passwordHash: PASS,
role: "ADMIN", phone: "0811-2000-100", avatarUrl: avatar("sari"),
},
});
const budi = await prisma.user.create({
data: {
name: "Budi Santoso", email: "budi@kodeva.id", passwordHash: PASS,
role: "TEACHER", phone: "0812-3000-200", avatarUrl: avatar("budi"),
},
});
const rina = await prisma.user.create({
data: {
name: "Rina Kartika", email: "rina@kodeva.id", passwordHash: PASS,
role: "TEACHER", phone: "0813-4000-300", avatarUrl: avatar("rina"),
},
});
const ahmad = await prisma.user.create({
data: {
name: "Ahmad Fauzi", email: "ahmad@kodeva.id", passwordHash: PASS,
role: "TEACHER", phone: "0814-5000-400", avatarUrl: avatar("ahmad"),
},
});
// ─────────────────────────────────────────────────────────────
// 2. KELAS (konfigurasi kelas)
// pricePerMeeting = packagePrice / totalMeetings
// ─────────────────────────────────────────────────────────────
const classesData = [
{ code: "KOD-SCR-01", name: "Scratch Junior", level: "BEGINNER" as PlacementLevel, teacherId: rina.id, totalMeetings: 8, packagePrice: 1_200_000, pricePerMeeting: 150_000, capacity: 10, schedule: "Senin & Rabu 16:00-17:30", color: "#2AA9FF", description: "Belajar logika pemrograman lewat game visual Scratch." },
{ code: "KOD-PYT-01", name: "Python Dasar", level: "BEGINNER" as PlacementLevel, teacherId: budi.id, totalMeetings: 10, packagePrice: 2_000_000, pricePerMeeting: 200_000, capacity: 12, schedule: "Selasa & Kamis 17:00-18:30", color: "#7C5CFC", description: "Fondasi Python: variabel, logika, loop, dan fungsi." },
{ code: "KOD-WEB-01", name: "Web Dev: HTML & CSS", level: "BEGINNER" as PlacementLevel, teacherId: ahmad.id, totalMeetings: 12, packagePrice: 2_400_000, pricePerMeeting: 200_000, capacity: 14, schedule: "Sabtu 09:00-12:00", color: "#12B886", description: "Membangun website pertamamu dari nol." },
{ code: "KOD-JS-01", name: "JavaScript Intermediate", level: "INTERMEDIATE" as PlacementLevel, teacherId: budi.id, totalMeetings: 10, packagePrice: 2_500_000, pricePerMeeting: 250_000, capacity: 12, schedule: "Selasa & Kamis 18:45-20:15", color: "#FFB020", description: "DOM, event, async, dan mini project interaktif." },
{ code: "KOD-ARD-01", name: "Arduino & Robotics", level: "INTERMEDIATE" as PlacementLevel, teacherId: rina.id, totalMeetings: 8, packagePrice: 1_800_000, pricePerMeeting: 225_000, capacity: 8, schedule: "Jumat 15:30-17:30", color: "#F65FA7", description: "Rakit dan program robot pertamamu dengan Arduino." },
];
const klass = Object.fromEntries(
await Promise.all(
classesData.map(async (c) => [c.code, await prisma.class.create({ data: { ...c, startDate: addWeeks(-10), status: "ACTIVE" } })]),
),
) as Record<string, Awaited<ReturnType<typeof prisma.class.create>>>;
// ─────────────────────────────────────────────────────────────
// 3. SISWA (10 orang: 4 calon, 4 student, 2 ex student)
// Field: nama, telepon, email, foto, orang tua, sumber lead, status
// ─────────────────────────────────────────────────────────────
const studentsSeed = [
{ key: "andi", fullName: "Andi Pratama", phone: "0812-1111-0001", email: "andi@example.com", parentName: "Budi Pratama", parentPhone: "0812-1111-9001", leadSource: "REFERRAL" as const, status: "STUDENT" as const, joinedAt: addWeeks(-10) },
{ key: "citra", fullName: "Citra Lestari", phone: "0812-1111-0002", email: "citra@example.com", parentName: "Sari Lestari", parentPhone: "0812-1111-9002", leadSource: "WHATSAPP" as const, status: "CALON_STUDENT" as const, joinedAt: null },
{ key: "dimas", fullName: "Dimas Anggara", phone: "0812-1111-0003", email: "dimas@example.com", parentName: "Hendra Anggara", parentPhone: "0812-1111-9003", leadSource: "BANNER" as const, status: "STUDENT" as const, joinedAt: addWeeks(-9) },
{ key: "elsa", fullName: "Elsa Maharani", phone: "0812-1111-0004", email: "elsa@example.com", parentName: "Rina Maharani", parentPhone: "0812-1111-9004", leadSource: "SOCIAL_MEDIA" as const, status: "CALON_STUDENT" as const, joinedAt: null },
{ key: "fajar", fullName: "Fajar Nugroho", phone: "0812-1111-0005", email: "fajar@example.com", parentName: "Agus Nugroho", parentPhone: "0812-1111-9005", leadSource: "WHATSAPP" as const, status: "STUDENT" as const, joinedAt: addWeeks(-8) },
{ key: "gita", fullName: "Gita Permata", phone: "0812-1111-0006", email: "gita@example.com", parentName: "Dwi Permata", parentPhone: "0812-1111-9006", leadSource: "REFERRAL" as const, status: "EX_STUDENT" as const, joinedAt: addWeeks(-40) },
{ key: "hadi", fullName: "Hadi Susanto", phone: "0812-1111-0007", email: "hadi@example.com", parentName: "Tono Susanto", parentPhone: "0812-1111-9007", leadSource: "SOCIAL_MEDIA" as const, status: "STUDENT" as const, joinedAt: addWeeks(-6) },
{ key: "intan", fullName: "Intan Puspita", phone: "0812-1111-0008", email: "intan@example.com", parentName: "Maya Puspita", parentPhone: "0812-1111-9008", leadSource: "BANNER" as const, status: "CALON_STUDENT" as const, joinedAt: null },
{ key: "joko", fullName: "Joko Wijaya", phone: "0812-1111-0009", email: "joko@example.com", parentName: "Slamet Wijaya", parentPhone: "0812-1111-9009", leadSource: "WHATSAPP" as const, status: "STUDENT" as const, joinedAt: addWeeks(-4) },
{ key: "kirana", fullName: "Kirana Dewi", phone: "0812-1111-0010", email: "kirana@example.com", parentName: "Ratna Dewi", parentPhone: "0812-1111-9010", leadSource: "REFERRAL" as const, status: "EX_STUDENT" as const, joinedAt: addWeeks(-36) },
];
const student = {} as Record<string, Awaited<ReturnType<typeof prisma.student.create>>>;
for (const s of studentsSeed) {
student[s.key] = await prisma.student.create({
data: {
fullName: s.fullName, phone: s.phone, email: s.email,
photoUrl: avatar(s.key), parentName: s.parentName, parentPhone: s.parentPhone,
leadSource: s.leadSource, status: s.status, joinedAt: s.joinedAt,
birthDate: new Date("2012-05-10T00:00:00.000Z"),
gender: "MALE", address: "Jl. Merdeka No. 10, Bandung",
},
});
}
// ─────────────────────────────────────────────────────────────
// 4. ENROLLMENT ( siswa ↔ kelas )
// ─────────────────────────────────────────────────────────────
const enrollments = [
{ key: "andi", code: "KOD-PYT-01", status: "ACTIVE" as const },
{ key: "dimas", code: "KOD-SCR-01", status: "ACTIVE" as const },
{ key: "fajar", code: "KOD-WEB-01", status: "ACTIVE" as const },
{ key: "gita", code: "KOD-JS-01", status: "COMPLETED" as const },
{ key: "hadi", code: "KOD-ARD-01", status: "ACTIVE" as const },
{ key: "joko", code: "KOD-PYT-01", status: "ACTIVE" as const },
{ key: "kirana", code: "KOD-SCR-01", status: "COMPLETED" as const },
];
for (const e of enrollments) {
await prisma.enrollment.create({
data: { studentId: student[e.key].id, classId: klass[e.code].id, status: e.status, joinedAt: student[e.key].joinedAt ?? BASE, leftAt: e.status === "COMPLETED" ? addWeeks(-2) : null },
});
}
// ─────────────────────────────────────────────────────────────
// 5. SESSION (pertemuan per kelas) — dasar prorata
// ─────────────────────────────────────────────────────────────
const session: Record<string, { id: string; meetingNumber: number }[]> = {};
for (const c of Object.values(klass)) {
session[c.code] = [];
for (let i = 1; i <= c.totalMeetings; i++) {
const s = await prisma.session.create({
data: { classId: c.id, meetingNumber: i, date: addWeeks(-10 + i - 1), topic: `Pertemuan ${i}: ${c.name}`, teacherId: c.teacherId },
});
session[c.code].push({ id: s.id, meetingNumber: i });
}
}
// ─────────────────────────────────────────────────────────────
// 6. KEHADIRAN (PRESENT/LATE/EXCUSED/SICK/ABSENT)
// Terpakai = jumlah PRESENT + LATE → dasar hitung sisa kuota
// ─────────────────────────────────────────────────────────────
const plan: Record<string, { code: string; statuses: AttendanceStatus[] }> = {
andi: { code: "KOD-PYT-01", statuses: ["PRESENT", "PRESENT", "LATE", "PRESENT", "PRESENT", "PRESENT"] }, // 6 terpakai
dimas: { code: "KOD-SCR-01", statuses: ["PRESENT", "PRESENT", "ABSENT", "PRESENT"] }, // 3 hadir, 1 alpa
fajar: { code: "KOD-WEB-01", statuses: ["PRESENT", "LATE", "PRESENT", "PRESENT", "EXCUSED", "PRESENT"] }, // 5 hadir
gita: { code: "KOD-JS-01", statuses: ["PRESENT", "PRESENT", "PRESENT", "PRESENT", "PRESENT", "PRESENT", "PRESENT", "PRESENT", "PRESENT", "PRESENT"] },
hadi: { code: "KOD-ARD-01", statuses: ["PRESENT", "PRESENT", "PRESENT", "PRESENT"] },
joko: { code: "KOD-PYT-01", statuses: ["PRESENT", "SICK", "PRESENT", "PRESENT", "PRESENT"] },
kirana: { code: "KOD-SCR-01", statuses: ["PRESENT", "PRESENT", "LATE", "PRESENT", "PRESENT", "PRESENT"] },
};
let attendanceCount = 0;
for (const [key, p] of Object.entries(plan)) {
const sessions = session[p.code];
for (let i = 0; i < p.statuses.length; i++) {
await prisma.attendance.create({
data: { sessionId: sessions[i].id, studentId: student[key].id, status: p.statuses[i], recordedById: klass[p.code].teacherId, note: p.statuses[i] === "SICK" ? "Demam" : undefined },
});
attendanceCount++;
}
}
// ─────────────────────────────────────────────────────────────
// 7. PEMBAYARAN PRORATA
// amount = meetingsPaid × pricePerMeeting
// Contoh: Andi 10 × 200.000 = 2.000.000
// Hadir 6 → terpakai 1.200.000 → sisa 4 pertemuan = 800.000
// ─────────────────────────────────────────────────────────────
const paymentsSeed = [
{ key: "andi", code: "KOD-PYT-01", meetingsPaid: 10, method: "TRANSFER" as const, status: "PAID" as const, paidAt: addWeeks(-10) },
{ key: "dimas", code: "KOD-SCR-01", meetingsPaid: 8, method: "CASH" as const, status: "PAID" as const, paidAt: addWeeks(-9) },
{ key: "fajar", code: "KOD-WEB-01", meetingsPaid: 6, method: "QRIS" as const, status: "PAID" as const, paidAt: addWeeks(-8), note: "Bayar 6 pertemuan dulu" },
{ key: "gita", code: "KOD-JS-01", meetingsPaid: 10, method: "TRANSFER" as const, status: "PAID" as const, paidAt: addWeeks(-40) },
{ key: "hadi", code: "KOD-ARD-01", meetingsPaid: 8, method: "TRANSFER" as const, status: "PAID" as const, paidAt: addWeeks(-6) },
{ key: "joko", code: "KOD-PYT-01", meetingsPaid: 5, method: "QRIS" as const, status: "PARTIAL" as const, paidAt: addWeeks(-4), note: "Cicilan 1 dari 2" },
{ key: "kirana", code: "KOD-SCR-01", meetingsPaid: 8, method: "CASH" as const, status: "PAID" as const, paidAt: addWeeks(-36) },
];
let paySeq = 1;
for (const p of paymentsSeed) {
const c = klass[p.code];
const ppm = c.pricePerMeeting ?? Math.round(c.packagePrice / c.totalMeetings);
await prisma.payment.create({
data: {
invoiceNumber: `INV-2026-${String(paySeq++).padStart(4, "0")}`,
studentId: student[p.key].id, classId: c.id,
meetingsPaid: p.meetingsPaid, pricePerMeeting: ppm, amount: p.meetingsPaid * ppm,
method: p.method, status: p.status as PaymentStatus, paidAt: p.paidAt, note: p.note, createdById: sari.id,
},
});
}
// ─────────────────────────────────────────────────────────────
// 8. JURNAL AKTIVITAS + FOTO
// ─────────────────────────────────────────────────────────────
const journalsSeed = [
{ code: "KOD-SCR-01", title: "Membuat Game Ular dengan Scratch", description: "Anak-anak belajar event, loop, dan variabel skor sambil membuat game ular versi mereka sendiri.", tags: ["game", "scratch", "logika"], date: addWeeks(-2), students: ["dimas", "kirana"], photos: ["ular-1", "ular-2", "ular-3"] },
{ code: "KOD-PYT-01", title: "Dasar Variabel & Tipe Data Python", description: "Sesi seru bereksperimen dengan string, integer, dan input dari pengguna.", tags: ["python", "variable", "basic"], date: addWeeks(-3), students: ["andi", "joko"], photos: ["python-1", "python-2"] },
{ code: "KOD-WEB-01", title: "Landing Page Pertama", description: "Setiap siswa mempublikasikan halaman profil pribadinya menggunakan HTML & CSS.", tags: ["html", "css", "project"], date: addWeeks(-1), students: ["fajar"], photos: ["web-1", "web-2", "web-3", "web-4"] },
{ code: "KOD-ARD-01", title: "Sensor Cahaya dengan Arduino", description: "Merakit lampu otomatis yang menyala saat ruangan gelap.", tags: ["arduino", "sensor", "robotik"], date: addDays(-5), students: ["hadi"], photos: ["arduino-1", "arduino-2"] },
];
for (const j of journalsSeed) {
const c = klass[j.code];
await prisma.journal.create({
data: {
classId: c.id, sessionId: session[j.code][0].id, title: j.title, description: j.description,
activityDate: j.date, tags: j.tags, authorId: c.teacherId!,
photos: { create: j.photos.map((s, i) => ({ url: photo(s), caption: `Dokumentasi ${i + 1}`, sortOrder: i })) },
students: { create: j.students.map((k) => ({ studentId: student[k].id })) },
},
});
}
// ─────────────────────────────────────────────────────────────
// 9. RAPORT
// ─────────────────────────────────────────────────────────────
const reportsSeed = [
{ key: "andi", code: "KOD-PYT-01", period: "2026-Term1", scores: [["Logika", 88], ["Computational Thinking", 85], ["Problem Solving", 82], ["Kreativitas", 90], ["Kolaborasi", 86]], notes: "Andi sangat aktif bertanya dan cepat memahami konsep loop." },
{ key: "dimas", code: "KOD-SCR-01", period: "2026-Term1", scores: [["Logika", 80], ["Computational Thinking", 78], ["Problem Solving", 75], ["Kreativitas", 92], ["Kolaborasi", 88]], notes: "Kreativitas Dimas luar biasa, perlu latihan ketelitian." },
{ key: "fajar", code: "KOD-WEB-01", period: "2026-Term1", scores: [["Logika", 84], ["Computational Thinking", 82], ["Problem Solving", 86], ["Kreativitas", 85], ["Kolaborasi", 80]], notes: "Progres pesat di CSS layout." },
] as const;
for (const r of reportsSeed) {
const avg = Math.round(r.scores.reduce((a, [, v]) => a + v, 0) / r.scores.length);
const grade = avg >= 90 ? "A" : avg >= 80 ? "B" : avg >= 70 ? "C" : "D";
await prisma.reportCard.create({
data: {
studentId: student[r.key].id, classId: klass[r.code].id, period: r.period,
teacherId: klass[r.code].teacherId!, finalScore: avg, grade, teacherNotes: r.notes,
attendanceSummary: "Hadir 6 dari 6 pertemuan", publishedAt: addDays(-3),
scores: { create: r.scores.map(([aspect, score]) => ({ aspect, score })) },
},
});
}
// ─────────────────────────────────────────────────────────────
// 10. PLACEMENT TEST (hasil tes penempatan → tampil di detail siswa)
// ─────────────────────────────────────────────────────────────
const placementsSeed = [
{ key: "elsa", level: "INTERMEDIATE" as PlacementLevel, rec: "KOD-JS-01", scores: [["Logika", 82], ["Dasar Coding", 76], ["Problem Solving", 80], ["Kreativitas", 74]] },
{ key: "intan", level: "BEGINNER" as PlacementLevel, rec: "KOD-SCR-01", scores: [["Logika", 62], ["Dasar Coding", 48], ["Problem Solving", 55], ["Kreativitas", 70]] },
{ key: "citra", level: "BEGINNER" as PlacementLevel, rec: "KOD-PYT-01", scores: [["Logika", 50], ["Dasar Coding", 40], ["Problem Solving", 45], ["Kreativitas", 60]] },
{ key: "andi", level: "INTERMEDIATE" as PlacementLevel, rec: "KOD-JS-01", scores: [["Logika", 80], ["Dasar Coding", 78], ["Problem Solving", 72], ["Kreativitas", 68]] },
];
for (const p of placementsSeed) {
const total = Math.round(p.scores.reduce((a, [, v]) => a + v, 0) / p.scores.length);
await prisma.placementTest.create({
data: {
studentId: student[p.key].id, testDate: addDays(-12), levelResult: p.level,
recommendedClassId: klass[p.rec].id, totalScore: total, examinerId: rina.id,
notes: `Direkomendasikan masuk kelas ${klass[p.rec].name}.`,
scores: { create: p.scores.map(([category, score]) => ({ category, score })) },
},
});
}
// ─────────────────────────────────────────────────────────────
// 11. FOLLOW UP (berapa yang harus di-follow up)
// due = nextFollowUpAt <= hari ini && status ∉ (ENROLLED, LOST)
// ─────────────────────────────────────────────────────────────
const followUpsSeed = [
{ key: "citra", status: "CONTACTED" as const, picId: sari.id, next: addDays(-1), last: addDays(-3), channel: "WHATSAPP" as const, activities: [["wa", "Kirim brosur Python Dasar, ortu minta dihubungi lagi."]] },
{ key: "elsa", status: "NEW" as const, picId: budi.id, next: addDays(0), last: null, channel: "SOCIAL_MEDIA" as const, activities: [["wa", "Lead dari Instagram, tanya jadwal kelas."]] },
{ key: "intan", status: "TRIAL" as const, picId: rina.id, next: addDays(2), last: addDays(-1), channel: "BANNER" as const, activities: [["call", "Sudah trial Scratch, tinggal konfirmasi pembayaran."]] },
];
for (const f of followUpsSeed) {
await prisma.followUp.create({
data: {
studentId: student[f.key].id, status: f.status, picId: f.picId,
nextFollowUpAt: f.next, lastContactedAt: f.last, channel: f.channel,
activities: { create: f.activities.map(([type, note]) => ({ type, note, authorId: f.picId })) },
},
});
}
// ─────────────────────────────────────────────────────────────
// 12. SETTINGS
// ─────────────────────────────────────────────────────────────
await prisma.setting.createMany({
data: [
{ key: "academy", value: { name: "Kodeva Academy", address: "Jl. Coding Raya No. 1, Bandung", phone: "0821-1000-2000", email: "halo@kodeva.id" } },
{ key: "leadSources", value: [
{ value: "WHATSAPP", label: "WhatsApp", color: "#22C55E", active: true },
{ value: "REFERRAL", label: "Referral", color: "#7C5CFC", active: true },
{ value: "BANNER", label: "Banner", color: "#FFB020", active: true },
{ value: "SOCIAL_MEDIA", label: "Social Media", color: "#2AA9FF", active: true },
] },
{ key: "currency", value: { code: "IDR", locale: "id-ID", timezone: "Asia/Jakarta" } },
],
});
// ─────────────────────────────────────────────────────────────
// Ringkasan (untuk verifikasi dashboard)
// ─────────────────────────────────────────────────────────────
const [activeStudents, exStudents, followUpDue] = await Promise.all([
prisma.student.count({ where: { status: "STUDENT" } }),
prisma.student.count({ where: { status: "EX_STUDENT" } }),
prisma.followUp.count({ where: { status: { notIn: ["ENROLLED", "LOST"] }, nextFollowUpAt: { lte: addDays(1) } } }),
]);
console.log("✅ Seed selesai.");
console.log(` 👥 Siswa aktif : ${activeStudents}`);
console.log(` 🎓 Ex student : ${exStudents}`);
console.log(` 🔔 Perlu follow up: ${followUpDue}`);
console.log(` 📚 Kelas : ${Object.keys(klass).length}`);
console.log(` 🗓️ Kehadiran : ${attendanceCount} record`);
console.log("\n Login: admin@kodeva.id / password123");
}
main()
.catch((e) => { console.error("❌ Seed gagal:", e); process.exit(1); })
.finally(async () => { await prisma.$disconnect(); });