#!/usr/bin/env tsx import { randomUUID } from "node:crypto"; import { config } from "dotenv"; import { and, eq, ne, sql } from "drizzle-orm"; import { budgets, cards, categories, financialAccounts, inboxItems, invoices, notes, payers, transactions, user, } from "@/db/schema"; import { loadAvatarOptions } from "@/features/payers/lib/avatar-options"; import type { TransactionInsert } from "@/features/transactions/actions/core"; import type { PAYMENT_METHODS, TRANSACTION_CONDITIONS, TRANSACTION_TYPES, } from "@/features/transactions/constants"; import { buildInvoicePaymentNote, INITIAL_BALANCE_CATEGORY_NAME, INITIAL_BALANCE_CONDITION, INITIAL_BALANCE_NOTE, INITIAL_BALANCE_PAYMENT_METHOD, INITIAL_BALANCE_TRANSACTION_TYPE, } from "@/shared/lib/accounts/constants"; import { DEFAULT_CARD_BRANDS, DEFAULT_CARD_STATUS, } from "@/shared/lib/cards/constants"; import { DEFAULT_CATEGORIES } from "@/shared/lib/categories/defaults"; import { db } from "@/shared/lib/db"; import { INVOICE_PAYMENT_STATUS } from "@/shared/lib/invoices"; import { loadLogoOptions } from "@/shared/lib/logo/options"; import { DEFAULT_PAYER_AVATAR, PAYER_ROLE_ADMIN, PAYER_ROLE_THIRD_PARTY, PAYER_STATUS_OPTIONS, } from "@/shared/lib/payers/constants"; import { generateShareCode } from "@/shared/lib/payers/share-code"; import { normalizeNameFromEmail } from "@/shared/lib/payers/utils"; import { TRANSFER_CATEGORY_NAME, TRANSFER_CONDITION, TRANSFER_ESTABLISHMENT_ENTRADA, TRANSFER_ESTABLISHMENT_SAIDA, TRANSFER_PAYMENT_METHOD, } from "@/shared/lib/transfers/constants"; import { addMonthsToDate, buildDateOnlyStringFromPeriodDay, compareDateOnly, getBusinessTodayInfo, parseLocalDateString, } from "@/shared/utils/date"; import { addMonthsToPeriod, comparePeriods, derivePeriodFromDate, getNextPeriod, parsePeriod, } from "@/shared/utils/period"; config(); const DEFAULT_MONTHS = 6; const MIN_MONTHS = 3; const MAX_MONTHS = 24; const ACCOUNT_TYPES = { CHECKING: "Conta Corrente", DIGITAL_WALLET: "Carteira Digital", } as const; const ACCOUNT_STATUS = { ACTIVE: "Ativa", } as const; const CARD_STATUS = DEFAULT_CARD_STATUS[0]; const CARD_BRANDS = { MASTERCARD: DEFAULT_CARD_BRANDS[1] ?? "Mastercard", VISA: DEFAULT_CARD_BRANDS[0] ?? "Visa", } as const; const PAYER_STATUS = PAYER_STATUS_OPTIONS[0]; type CliOptions = { userId: string; startPeriod: string; months: number; }; type SeedTransactionInput = { name: string; amount: number; purchaseDate: string; transactionType: (typeof TRANSACTION_TYPES)[number]; condition: (typeof TRANSACTION_CONDITIONS)[number]; paymentMethod: (typeof PAYMENT_METHODS)[number]; accountId?: string | null; cardId?: string | null; categoryId?: string | null; payerId?: string | null; secondaryPayerId?: string | null; isSplit?: boolean; primarySplitAmount?: number; secondarySplitAmount?: number; installmentCount?: number; recurrenceCount?: number; dueDate?: string | null; note?: string | null; settlementBehavior?: "auto" | "open" | "settled"; cardMeta?: { closingDay: string; dueDay: string; }; }; type Share = { payerId: string | null; amountCents: number; }; type SeedSummary = { payers: number; accounts: number; cards: number; notes: number; budgets: number; transactions: number; invoices: number; invoicePayments: number; inboxItems: number; }; function printUsage() { console.log(` Uso: pnpm mockup -- --userId= --startPeriod=YYYY-MM [--months=${DEFAULT_MONTHS}] Exemplos: pnpm mockup -- --userId=user_123 --startPeriod=2026-01 pnpm mockup -- --userId=user_123 --startPeriod=2025-10 --months=8 `); } function parseArgs(argv: string[]): CliOptions { let userId = ""; let startPeriod = ""; let months = DEFAULT_MONTHS; for (const arg of argv) { if (arg === "--help" || arg === "-h") { printUsage(); process.exit(0); } if (arg.startsWith("--userId=") || arg.startsWith("--user-id=")) { userId = arg.split("=")[1] ?? ""; continue; } if (arg.startsWith("--startPeriod=") || arg.startsWith("--start-period=")) { startPeriod = arg.split("=")[1] ?? ""; continue; } if (arg.startsWith("--months=")) { const rawValue = arg.split("=")[1] ?? ""; months = Number.parseInt(rawValue, 10); } } if (!userId.trim()) { throw new Error("Informe o `--userId` do usuário que receberá os dados."); } if (!startPeriod.trim()) { throw new Error("Informe o `--startPeriod` no formato `YYYY-MM`."); } parsePeriod(startPeriod); if (Number.isNaN(months) || months < MIN_MONTHS || months > MAX_MONTHS) { throw new Error( `O parâmetro \`--months\` deve ficar entre ${MIN_MONTHS} e ${MAX_MONTHS}.`, ); } return { userId: userId.trim(), startPeriod: startPeriod.trim(), months, }; } function centsToDecimalString(value: number) { const decimal = value / 100; const formatted = decimal.toFixed(2); return Object.is(decimal, -0) ? "0.00" : formatted; } function splitAmount(totalCents: number, parts: number) { if (parts <= 0) { return []; } const base = Math.trunc(totalCents / parts); const remainder = totalCents % parts; return Array.from( { length: parts }, (_, index) => base + (index < remainder ? 1 : 0), ); } function buildShares(input: SeedTransactionInput): Share[] { const totalCents = Math.round(Math.abs(input.amount) * 100); if (!input.isSplit) { return [{ payerId: input.payerId ?? null, amountCents: totalCents }]; } if (!input.payerId || !input.secondaryPayerId) { throw new Error(`Divisão inválida para o lançamento "${input.name}".`); } if ( input.primarySplitAmount !== undefined && input.secondarySplitAmount !== undefined ) { return [ { payerId: input.payerId, amountCents: Math.round(input.primarySplitAmount * 100), }, { payerId: input.secondaryPayerId, amountCents: Math.round(input.secondarySplitAmount * 100), }, ]; } const [primaryAmount, secondaryAmount] = splitAmount(totalCents, 2); return [ { payerId: input.payerId, amountCents: primaryAmount ?? 0 }, { payerId: input.secondaryPayerId, amountCents: secondaryAmount ?? 0, }, ]; } function deriveCreditCardPeriod( purchaseDate: string, closingDay: string | null | undefined, dueDay?: string | null | undefined, ) { const basePeriod = derivePeriodFromDate(purchaseDate); if (!closingDay) return basePeriod; const closingDayNum = Number.parseInt(closingDay, 10); if (Number.isNaN(closingDayNum)) return basePeriod; const dayPart = purchaseDate.split("-")[2]; const purchaseDayNum = Number.parseInt(dayPart ?? "1", 10); let period = basePeriod; if (purchaseDayNum >= closingDayNum) { period = getNextPeriod(period); } const dueDayNum = Number.parseInt(dueDay ?? "", 10); if (!Number.isNaN(dueDayNum) && dueDayNum < closingDayNum) { period = getNextPeriod(period); } return period; } function pickOption(options: string[], candidates: string[], fallback: string) { const normalizedOptions = new Set(options); for (const candidate of candidates) { if (normalizedOptions.has(candidate)) { return candidate; } } return options[0] ?? fallback; } function dateForPeriodDay(period: string, day: number) { const value = buildDateOnlyStringFromPeriodDay(period, day); if (!value) { throw new Error(`Não foi possível montar a data ${period}/${day}.`); } return value; } function resolveSeedSettlement( input: SeedTransactionInput, referenceDate: Date, todayDate: Date, ) { if (input.paymentMethod === "Cartão de crédito") { return null; } if (input.settlementBehavior === "settled") { return true; } if (input.settlementBehavior === "open") { return false; } return compareDateOnly(referenceDate, todayDate) <= 0; } function createTransactionRecords( input: SeedTransactionInput, userId: string, todayDate: Date, ): TransactionInsert[] { const purchaseDate = parseLocalDateString(input.purchaseDate); if (Number.isNaN(purchaseDate.getTime())) { throw new Error(`Data inválida no lançamento "${input.name}".`); } const dueDate = input.dueDate ? parseLocalDateString(input.dueDate) : null; const shares = buildShares(input); const amountSign: 1 | -1 = input.transactionType === "Despesa" ? -1 : 1; const isSeries = input.condition === "Parcelado" || input.condition === "Recorrente"; const seriesId = isSeries ? randomUUID() : null; const initialPeriod = input.cardId && input.cardMeta ? deriveCreditCardPeriod( input.purchaseDate, input.cardMeta.closingDay, input.cardMeta.dueDay, ) : derivePeriodFromDate(input.purchaseDate); const basePayload = { name: input.name, transactionType: input.transactionType, condition: input.condition, paymentMethod: input.paymentMethod, note: input.note ?? null, accountId: input.accountId ?? null, cardId: input.cardId ?? null, categoryId: input.categoryId ?? null, isDivided: input.isSplit ?? false, userId, seriesId, }; const records: TransactionInsert[] = []; if (input.condition === "Parcelado") { const installmentTotal = input.installmentCount ?? 0; const amountsByShare = shares.map((share) => splitAmount(share.amountCents, installmentTotal), ); for ( let installmentIndex = 0; installmentIndex < installmentTotal; installmentIndex += 1 ) { const period = addMonthsToPeriod(initialPeriod, installmentIndex); const installmentDueDate = dueDate ? addMonthsToDate(dueDate, installmentIndex) : null; const settlementReferenceDate = installmentDueDate ?? addMonthsToDate(purchaseDate, installmentIndex); shares.forEach((share, shareIndex) => { const amountCents = amountsByShare[shareIndex]?.[installmentIndex] ?? 0; const isSettled = resolveSeedSettlement( input, settlementReferenceDate, todayDate, ); records.push({ ...basePayload, amount: centsToDecimalString(amountCents * amountSign), payerId: share.payerId, purchaseDate, period, isSettled, installmentCount: installmentTotal, currentInstallment: installmentIndex + 1, recurrenceCount: null, dueDate: installmentDueDate, boletoPaymentDate: input.paymentMethod === "Boleto" && isSettled ? (installmentDueDate ?? settlementReferenceDate) : null, }); }); } return records; } if (input.condition === "Recorrente") { const recurrenceTotal = input.recurrenceCount ?? 0; for (let index = 0; index < recurrenceTotal; index += 1) { const period = addMonthsToPeriod(initialPeriod, index); const recurrencePurchaseDate = addMonthsToDate(purchaseDate, index); const recurrenceDueDate = dueDate ? addMonthsToDate(dueDate, index) : null; const settlementReferenceDate = recurrenceDueDate ?? recurrencePurchaseDate; shares.forEach((share) => { const isSettled = resolveSeedSettlement( input, settlementReferenceDate, todayDate, ); records.push({ ...basePayload, amount: centsToDecimalString(share.amountCents * amountSign), payerId: share.payerId, purchaseDate: recurrencePurchaseDate, period, isSettled, installmentCount: null, currentInstallment: null, recurrenceCount: recurrenceTotal, dueDate: recurrenceDueDate, boletoPaymentDate: input.paymentMethod === "Boleto" && isSettled ? (recurrenceDueDate ?? recurrencePurchaseDate) : null, }); }); } return records; } shares.forEach((share) => { const settlementReferenceDate = dueDate ?? purchaseDate; const isSettled = resolveSeedSettlement( input, settlementReferenceDate, todayDate, ); records.push({ ...basePayload, amount: centsToDecimalString(share.amountCents * amountSign), payerId: share.payerId, purchaseDate, period: initialPeriod, isSettled, installmentCount: null, currentInstallment: null, recurrenceCount: null, dueDate, boletoPaymentDate: input.paymentMethod === "Boleto" && isSettled ? (dueDate ?? purchaseDate) : null, }); }); return records; } async function ensureCategories(userId: string) { const existing = await db.query.categories.findMany({ columns: { id: true, name: true }, where: eq(categories.userId, userId), }); const existingNames = new Set(existing.map((item) => item.name)); const missingDefaults = DEFAULT_CATEGORIES.filter( (category) => !existingNames.has(category.name), ); if (missingDefaults.length > 0) { await db.insert(categories).values( missingDefaults.map((category) => ({ name: category.name, type: category.type, icon: category.icon, userId, })), ); } const refreshed = await db.query.categories.findMany({ columns: { id: true, name: true }, where: eq(categories.userId, userId), }); return new Map(refreshed.map((category) => [category.name, category.id])); } async function ensureAdminPayer(targetUser: typeof user.$inferSelect) { const existingAdmin = await db.query.payers.findFirst({ columns: { id: true, name: true }, where: and( eq(payers.userId, targetUser.id), eq(payers.role, PAYER_ROLE_ADMIN), ), }); if (existingAdmin) { return existingAdmin; } const name = targetUser.name?.trim() || normalizeNameFromEmail(targetUser.email) || "Admin"; const [created] = await db .insert(payers) .values({ name, email: targetUser.email ?? null, avatarUrl: targetUser.image ?? DEFAULT_PAYER_AVATAR, status: PAYER_STATUS, note: null, role: PAYER_ROLE_ADMIN, isAutoSend: false, shareCode: generateShareCode(), userId: targetUser.id, }) .returning({ id: payers.id, name: payers.name }); if (!created) { throw new Error("Não foi possível criar o pagador admin do usuário."); } return created; } async function countByUserId( table: | typeof financialAccounts | typeof cards | typeof budgets | typeof notes | typeof invoices | typeof transactions | typeof inboxItems, userColumn: | typeof financialAccounts.userId | typeof cards.userId | typeof budgets.userId | typeof notes.userId | typeof invoices.userId | typeof transactions.userId | typeof inboxItems.userId, userId: string, ) { const [result] = await db .select({ count: sql`count(*)` }) .from(table) .where(eq(userColumn, userId)); return Number(result?.count ?? 0); } async function assertFinancialSpaceIsEmpty(userId: string) { const [ accountCount, cardCount, budgetCount, noteCount, invoiceCount, transactionCount, inboxCount, extraPayerCount, ] = await Promise.all([ countByUserId(financialAccounts, financialAccounts.userId, userId), countByUserId(cards, cards.userId, userId), countByUserId(budgets, budgets.userId, userId), countByUserId(notes, notes.userId, userId), countByUserId(invoices, invoices.userId, userId), countByUserId(transactions, transactions.userId, userId), countByUserId(inboxItems, inboxItems.userId, userId), db .select({ count: sql`count(*)` }) .from(payers) .where(and(eq(payers.userId, userId), ne(payers.role, PAYER_ROLE_ADMIN))) .then((rows) => Number(rows[0]?.count ?? 0)), ]); const blockers = [ accountCount > 0 ? `${accountCount} conta(s)` : null, cardCount > 0 ? `${cardCount} cartao(oes)` : null, transactionCount > 0 ? `${transactionCount} lancamento(s)` : null, invoiceCount > 0 ? `${invoiceCount} fatura(s)` : null, budgetCount > 0 ? `${budgetCount} orcamento(s)` : null, noteCount > 0 ? `${noteCount} anotacao(oes)` : null, extraPayerCount > 0 ? `${extraPayerCount} pagador(es) extra(s)` : null, inboxCount > 0 ? `${inboxCount} pre-lancamento(s)` : null, ].filter(Boolean); if (blockers.length > 0) { throw new Error( `O usuário ${userId} não está com a conta zerada. Itens encontrados: ${blockers.join(", ")}.`, ); } } async function seedInvoicesForCards(params: { userId: string; adminPayerId: string; cardsByKey: Record< string, { id: string; name: string; accountId: string; dueDay: string; closingDay: string; } >; paymentCategoryId: string | undefined; insertedTransactionRecords: TransactionInsert[]; }) { const { userId, adminPayerId, cardsByKey, paymentCategoryId } = params; const todayInfo = getBusinessTodayInfo(); const cardEntries = Object.entries(cardsByKey); let createdInvoices = 0; let createdInvoicePayments = 0; await db.transaction(async (tx) => { for (const [cardKey, card] of cardEntries) { const cardPeriods = Array.from( new Set( params.insertedTransactionRecords .filter((record) => record.cardId === card.id) .map((record) => record.period ?? "") .filter(Boolean), ), ).sort(comparePeriods); const historicalPeriods = cardPeriods.filter( (period) => comparePeriods(period, todayInfo.period) <= 0, ); if (historicalPeriods.length === 0) { continue; } const latestHistoricalPeriod = historicalPeriods[historicalPeriods.length - 1]; for (const period of historicalPeriods) { const shouldLeavePending = cardKey === "ultravioleta" && period === latestHistoricalPeriod; const status = shouldLeavePending ? INVOICE_PAYMENT_STATUS.PENDING : INVOICE_PAYMENT_STATUS.PAID; const existingInvoice = await tx.query.invoices.findFirst({ columns: { id: true }, where: and( eq(invoices.userId, userId), eq(invoices.cardId, card.id), eq(invoices.period, period), ), }); if (existingInvoice) { await tx .update(invoices) .set({ paymentStatus: status, }) .where(eq(invoices.id, existingInvoice.id)); } else { await tx.insert(invoices).values({ cardId: card.id, userId, period, paymentStatus: status, }); } await tx .update(transactions) .set({ isSettled: status === INVOICE_PAYMENT_STATUS.PAID, }) .where( and( eq(transactions.userId, userId), eq(transactions.cardId, card.id), eq(transactions.period, period), ), ); createdInvoices += 1; if (status !== INVOICE_PAYMENT_STATUS.PAID) { continue; } const [adminShareRow] = await tx .select({ total: sql`coalesce(sum(${transactions.amount}), 0)`, }) .from(transactions) .where( and( eq(transactions.userId, userId), eq(transactions.cardId, card.id), eq(transactions.period, period), eq(transactions.payerId, adminPayerId), ), ); const adminShare = Number(adminShareRow?.total ?? 0); const adminPayableAmount = Math.abs(Math.min(adminShare, 0)); if (adminPayableAmount <= 0) { continue; } const paymentDate = dateForPeriodDay(period, Number(card.dueDay)); const paymentNote = buildInvoicePaymentNote(card.id, period); await tx.insert(transactions).values({ condition: "À vista", name: `Pagamento fatura - ${card.name}`, paymentMethod: "Pix", note: paymentNote, amount: `-${adminPayableAmount.toFixed(2)}`, purchaseDate: parseLocalDateString(paymentDate), transactionType: "Despesa", period, isSettled: true, userId, accountId: card.accountId, categoryId: paymentCategoryId ?? null, payerId: adminPayerId, }); createdInvoicePayments += 1; } } }); return { createdInvoices, createdInvoicePayments, }; } async function main() { const options = parseArgs(process.argv.slice(2)); if (!process.env.DATABASE_URL) { throw new Error("DATABASE_URL não está configurada no ambiente."); } const logoOptions = await loadLogoOptions(); const avatarOptions = await loadAvatarOptions(); const businessToday = getBusinessTodayInfo(); const summary: SeedSummary = { payers: 0, accounts: 0, cards: 0, notes: 0, budgets: 0, transactions: 0, invoices: 0, invoicePayments: 0, inboxItems: 0, }; const targetUser = await db.query.user.findFirst({ where: eq(user.id, options.userId), }); if (!targetUser) { throw new Error(`Usuário ${options.userId} não foi encontrado.`); } await assertFinancialSpaceIsEmpty(targetUser.id); const adminPayer = await ensureAdminPayer(targetUser); const categoriesByName = await ensureCategories(targetUser.id); const getCategoryId = (name: string) => { const categoryId = categoriesByName.get(name); if (!categoryId) { throw new Error(`Categoria obrigatória não encontrada: ${name}.`); } return categoryId; }; const periods = Array.from({ length: options.months }, (_, index) => addMonthsToPeriod(options.startPeriod, index), ); const firstPeriod = periods[0]; const secondPeriod = periods[1] ?? periods[0]; const thirdPeriod = periods[2] ?? periods[periods.length - 1] ?? periods[0]; const middlePeriod = periods[Math.floor(periods.length / 2)] ?? periods[0]; const lastPeriod = periods[periods.length - 1] ?? periods[0]; const firstDayOfSeed = dateForPeriodDay(firstPeriod, 1); const preferredAvatar = (candidates: string[]) => pickOption(avatarOptions, candidates, DEFAULT_PAYER_AVATAR); const preferredLogo = (candidates: string[], fallback: string) => pickOption(logoOptions, candidates, fallback); const createdPayers: Record = { admin: adminPayer.id, }; const createdAccounts: Record = {}; const createdCards: Record< string, { id: string; name: string; accountId: string; closingDay: string; dueDay: string; } > = {}; await db.transaction(async (tx) => { const extraPayerDefinitions = [ { key: "mario", name: "Mario Oliveira", email: "mario.oliveira@exemplo.com", avatarUrl: preferredAvatar(["4825038.png", "4825051.png"]), note: "Divide as despesas da casa e do mercado.", isAutoSend: true, }, { key: "eduardo", name: "Eduardo Lima", email: "eduardo.lima@exemplo.com", avatarUrl: preferredAvatar(["4825096.png", "4825123.png"]), note: "Costuma rachar viagens e presentes em familia.", isAutoSend: false, }, ] as const; for (const definition of extraPayerDefinitions) { const [created] = await tx .insert(payers) .values({ name: definition.name, email: definition.email, avatarUrl: definition.avatarUrl, status: PAYER_STATUS, note: definition.note, role: PAYER_ROLE_THIRD_PARTY, isAutoSend: definition.isAutoSend, shareCode: generateShareCode(), userId: targetUser.id, }) .returning({ id: payers.id }); if (!created) { throw new Error(`Falha ao criar o pagador ${definition.name}.`); } createdPayers[definition.key] = created.id; summary.payers += 1; } const accountDefinitions = [ { key: "itau", name: "Itaú Personnalité", accountType: ACCOUNT_TYPES.CHECKING, status: ACCOUNT_STATUS.ACTIVE, logo: preferredLogo(["itaupersonnalite.png", "itau.png"], "itau.png"), note: "Conta principal onde cai o salário e saem boletos e investimentos.", initialBalance: "8200.00", }, { key: "nubank", name: "Nubank", accountType: ACCOUNT_TYPES.DIGITAL_WALLET, status: ACCOUNT_STATUS.ACTIVE, logo: preferredLogo(["nubank.png"], "nubank.png"), note: "Conta do dia a dia para mercado, transporte e despesas variáveis.", initialBalance: "5500.00", }, { key: "inter", name: "Banco Inter", accountType: ACCOUNT_TYPES.CHECKING, status: ACCOUNT_STATUS.ACTIVE, logo: preferredLogo(["intermedium.png"], "intermedium.png"), note: "Conta usada para reserva de emergência e investimentos curtos.", initialBalance: "2300.00", }, { key: "mercado-pago", name: "Mercado Pago", accountType: ACCOUNT_TYPES.DIGITAL_WALLET, status: ACCOUNT_STATUS.ACTIVE, logo: preferredLogo(["mercadopago.png"], "mercadopago.png"), note: "Carteira para corridas por aplicativo e pequenos pagamentos.", initialBalance: "480.00", }, ] as const; for (const definition of accountDefinitions) { const [created] = await tx .insert(financialAccounts) .values({ name: definition.name, accountType: definition.accountType, status: definition.status, note: definition.note, logo: definition.logo, initialBalance: definition.initialBalance, excludeFromBalance: false, excludeInitialBalanceFromIncome: true, userId: targetUser.id, }) .returning({ id: financialAccounts.id }); if (!created) { throw new Error(`Falha ao criar a conta ${definition.name}.`); } createdAccounts[definition.key] = created.id; summary.accounts += 1; await tx.insert(transactions).values({ condition: INITIAL_BALANCE_CONDITION, name: `Saldo inicial - ${definition.name}`, paymentMethod: INITIAL_BALANCE_PAYMENT_METHOD, note: INITIAL_BALANCE_NOTE, amount: definition.initialBalance, purchaseDate: parseLocalDateString(firstDayOfSeed), transactionType: INITIAL_BALANCE_TRANSACTION_TYPE, period: firstPeriod, isSettled: true, userId: targetUser.id, accountId: created.id, categoryId: getCategoryId(INITIAL_BALANCE_CATEGORY_NAME), payerId: adminPayer.id, }); summary.transactions += 1; } const cardDefinitions = [ { key: "ultravioleta", name: "Nubank Ultravioleta", brand: CARD_BRANDS.MASTERCARD, status: CARD_STATUS, closingDay: "25", dueDay: "03", note: "Cartão principal para assinaturas, delivery e compras parceladas.", limit: "18995.50", logo: preferredLogo( ["nubank-ultravioleta.png", "nubank.png"], "nubank.png", ), accountId: createdAccounts.nubank, }, { key: "itaucard", name: "Itaucard Visa Infinite", brand: CARD_BRANDS.VISA, status: CARD_STATUS, closingDay: "15", dueDay: "22", note: "Cartão usado para compras grandes, eletrônicos e viagens.", limit: "30000.00", logo: preferredLogo(["itau.png"], "itau.png"), accountId: createdAccounts.itau, }, { key: "interblack", name: "Inter Black", brand: CARD_BRANDS.MASTERCARD, status: CARD_STATUS, closingDay: "10", dueDay: "17", note: "Cartão complementar para mercado e farmácia com cashback.", limit: "15000.00", logo: preferredLogo(["intermedium.png"], "intermedium.png"), accountId: createdAccounts.inter, }, ] as const; for (const definition of cardDefinitions) { const [created] = await tx .insert(cards) .values({ name: definition.name, brand: definition.brand, status: definition.status, closingDay: definition.closingDay, dueDay: definition.dueDay, note: definition.note, limit: definition.limit, logo: definition.logo, accountId: definition.accountId, userId: targetUser.id, }) .returning({ id: cards.id }); if (!created) { throw new Error(`Falha ao criar o cartao ${definition.name}.`); } createdCards[definition.key] = { id: created.id, name: definition.name, accountId: definition.accountId, closingDay: definition.closingDay, dueDay: definition.dueDay, }; summary.cards += 1; } const noteDefinitions = [ { title: "Planejar viagem para Disney", type: "nota" as const, description: "Separar passagens LATAM, hospedagem via Booking e ingressos. Meta: manter o pacote abaixo de R$ 18.000 dividido em 10x no Itaucard.", tasks: null, archived: false, }, { title: "Pendências financeiras do mês", type: "tarefa" as const, description: null, tasks: JSON.stringify([ { id: randomUUID(), text: "Revisar fatura do Itaucard antes do fechamento", completed: true, }, { id: randomUUID(), text: "Migrar reserva de emergência para CDB do Inter", completed: false, }, { id: randomUUID(), text: "Cancelar trial do HBO Max se não usar", completed: false, }, { id: randomUUID(), text: "Pagar IPVA antes do vencimento", completed: false, }, ]), archived: false, }, { title: "Renegociar plano Vivo Fibra", type: "nota" as const, description: "Comparar com Claro e TIM antes do reajuste anual. Verificar bônus de portabilidade e valor de instalação.", tasks: null, archived: false, }, { title: "Compras Black Friday 2025", type: "tarefa" as const, description: null, tasks: JSON.stringify([ { id: randomUUID(), text: "Comprar iPhone 16 Pro na Apple", completed: true, }, { id: randomUUID(), text: "Smart TV Samsung 65 polegadas no Magazine Luiza", completed: true, }, { id: randomUUID(), text: "Notebook Dell XPS na Amazon", completed: true, }, ]), archived: true, }, ] as const; await tx.insert(notes).values( noteDefinitions.map((noteItem) => ({ title: noteItem.title, type: noteItem.type, description: noteItem.description, tasks: noteItem.tasks, archived: noteItem.archived, userId: targetUser.id, })), ); summary.notes += noteDefinitions.length; const budgetDefinitions = [ { categoryName: "Mercado", baseAmount: 1100 }, { categoryName: "Restaurantes", baseAmount: 380 }, { categoryName: "Transporte", baseAmount: 700 }, { categoryName: "Moradia", baseAmount: 3600 }, { categoryName: "Lazer", baseAmount: 450 }, { categoryName: "Assinaturas", baseAmount: 480 }, { categoryName: "Saúde", baseAmount: 750 }, { categoryName: "Delivery", baseAmount: 250 }, { categoryName: "Vestuário", baseAmount: 350 }, ] as const; const budgetRows = periods.flatMap((period, index) => budgetDefinitions.map((budgetItem) => ({ amount: (budgetItem.baseAmount + index * 10).toFixed(2), period, userId: targetUser.id, categoryId: getCategoryId(budgetItem.categoryName), })), ); await tx.insert(budgets).values(budgetRows); summary.budgets += budgetRows.length; }); const seedTransactionRecords: TransactionInsert[] = []; const createRecords = (input: SeedTransactionInput) => { seedTransactionRecords.push( ...createTransactionRecords(input, targetUser.id, businessToday.date), ); }; // ── Receitas recorrentes ── createRecords({ name: "Globo", amount: 9500, purchaseDate: dateForPeriodDay(firstPeriod, 5), transactionType: "Receita", condition: "Recorrente", paymentMethod: "Transferência bancária", accountId: createdAccounts.itau, categoryId: getCategoryId("Salário"), payerId: adminPayer.id, recurrenceCount: options.months, note: "Salário mensal recebido via TED.", }); // ── Despesas recorrentes (moradia, contas, assinaturas) ── createRecords({ name: "Loft", amount: 3200, purchaseDate: dateForPeriodDay(firstPeriod, 5), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Pix", accountId: createdAccounts.itau, categoryId: getCategoryId("Moradia"), payerId: adminPayer.id, secondaryPayerId: createdPayers.mario, isSplit: true, primarySplitAmount: 1600, secondarySplitAmount: 1600, recurrenceCount: options.months, dueDate: dateForPeriodDay(firstPeriod, 8), note: "Aluguel do apartamento dividido com mario.", }); createRecords({ name: "Vivo Fibra", amount: 139.9, purchaseDate: dateForPeriodDay(firstPeriod, 2), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Boleto", accountId: createdAccounts.itau, categoryId: getCategoryId("Internet"), payerId: adminPayer.id, recurrenceCount: options.months, dueDate: dateForPeriodDay(firstPeriod, 12), note: "Internet 700 Mega da casa.", }); createRecords({ name: "Vivo", amount: 79.9, purchaseDate: dateForPeriodDay(firstPeriod, 9), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Assinaturas"), payerId: adminPayer.id, recurrenceCount: options.months, cardMeta: createdCards.ultravioleta, note: "Plano celular pós-pago.", }); createRecords({ name: "Enel", amount: 195.4, purchaseDate: dateForPeriodDay(firstPeriod, 3), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Boleto", accountId: createdAccounts.itau, categoryId: getCategoryId("Energia e água"), payerId: adminPayer.id, recurrenceCount: options.months, dueDate: dateForPeriodDay(firstPeriod, 18), note: "Conta mensal de energia.", }); createRecords({ name: "Sabesp", amount: 95.6, purchaseDate: dateForPeriodDay(firstPeriod, 4), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Boleto", accountId: createdAccounts.itau, categoryId: getCategoryId("Energia e água"), payerId: adminPayer.id, recurrenceCount: options.months, dueDate: dateForPeriodDay(firstPeriod, 20), note: "Conta mensal de água.", }); createRecords({ name: "Netflix", amount: 55.9, purchaseDate: dateForPeriodDay(firstPeriod, 9), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Assinaturas"), payerId: adminPayer.id, recurrenceCount: options.months, cardMeta: createdCards.ultravioleta, note: "Plano padrão com anúncios.", }); createRecords({ name: "Spotify", amount: 34.9, purchaseDate: dateForPeriodDay(firstPeriod, 8), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Assinaturas"), payerId: adminPayer.id, recurrenceCount: options.months, cardMeta: createdCards.ultravioleta, note: "Plano duo com mario.", }); createRecords({ name: "Disney Plus", amount: 33.9, purchaseDate: dateForPeriodDay(firstPeriod, 12), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Assinaturas"), payerId: adminPayer.id, recurrenceCount: options.months, cardMeta: createdCards.ultravioleta, note: "Streaming Disney+.", }); createRecords({ name: "HBO Max", amount: 39.9, purchaseDate: dateForPeriodDay(firstPeriod, 14), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Assinaturas"), payerId: adminPayer.id, recurrenceCount: options.months, cardMeta: createdCards.ultravioleta, note: "Streaming HBO Max.", }); createRecords({ name: "YouTube", amount: 28.9, purchaseDate: dateForPeriodDay(firstPeriod, 7), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Assinaturas"), payerId: adminPayer.id, recurrenceCount: options.months, cardMeta: createdCards.ultravioleta, note: "YouTube Premium individual.", }); createRecords({ name: "Amazon Prime", amount: 14.9, purchaseDate: dateForPeriodDay(firstPeriod, 10), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Assinaturas"), payerId: adminPayer.id, recurrenceCount: options.months, cardMeta: createdCards.ultravioleta, note: "Amazon Prime mensal.", }); createRecords({ name: "OpenAI", amount: 109.9, purchaseDate: dateForPeriodDay(firstPeriod, 13), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Assinaturas"), payerId: adminPayer.id, recurrenceCount: options.months, cardMeta: createdCards.ultravioleta, note: "ChatGPT Plus.", }); createRecords({ name: "Apple", amount: 14.9, purchaseDate: dateForPeriodDay(firstPeriod, 17), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Assinaturas"), payerId: adminPayer.id, recurrenceCount: options.months, cardMeta: createdCards.ultravioleta, note: "iCloud 200GB.", }); createRecords({ name: "Notion", amount: 49.9, purchaseDate: dateForPeriodDay(firstPeriod, 19), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Assinaturas"), payerId: adminPayer.id, recurrenceCount: options.months, cardMeta: createdCards.ultravioleta, note: "Notion plus mensal.", }); createRecords({ name: "Smart Fit", amount: 129.9, purchaseDate: dateForPeriodDay(firstPeriod, 11), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Saúde"), payerId: adminPayer.id, recurrenceCount: options.months, cardMeta: createdCards.ultravioleta, note: "Plano black mensal.", }); createRecords({ name: "Amil", amount: 489, purchaseDate: dateForPeriodDay(firstPeriod, 4), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Boleto", accountId: createdAccounts.itau, categoryId: getCategoryId("Saúde"), payerId: adminPayer.id, recurrenceCount: options.months, dueDate: dateForPeriodDay(firstPeriod, 10), note: "Plano de saúde com coparticipação.", }); createRecords({ name: "Porto Seguro", amount: 285, purchaseDate: dateForPeriodDay(firstPeriod, 6), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Boleto", accountId: createdAccounts.itau, categoryId: getCategoryId("Transporte"), payerId: adminPayer.id, recurrenceCount: options.months, dueDate: dateForPeriodDay(firstPeriod, 15), note: "Seguro auto mensal.", }); createRecords({ name: "Condomínio Loft", amount: 720, purchaseDate: dateForPeriodDay(firstPeriod, 1), transactionType: "Despesa", condition: "Recorrente", paymentMethod: "Pix", accountId: createdAccounts.itau, categoryId: getCategoryId("Moradia"), payerId: adminPayer.id, recurrenceCount: options.months, dueDate: dateForPeriodDay(firstPeriod, 10), note: "Taxa condominial mensal.", }); // ── Despesas e receitas variáveis (loop mensal) ── for (const [index, period] of periods.entries()) { const carrefourAmount = 580 + index * 24.5; const paoAcucarAmount = 360 + index * 18.2; const assaiAmount = 420 + index * 22; const uberAmount = 165 + index * 8.4; const noventaENoveAmount = 78 + index * 5.6; const shellAmount = 280 + index * 12.5; const ipirangaAmount = 195 + index * 9.8; const ifoodAmount = 142 + index * 8.7; const rappiAmount = 88 + index * 6.2; const mcdonaldsAmount = 52 + index * 4.1; const starbucksAmount = 78 + index * 5.3; const burgerKingAmount = 48 + index * 3.6; const drogasilAmount = 96 + index * 7.4; const raiaAmount = 72 + index * 5.8; const investmentYield = 84 + index * 6.4; // Mercado — estoura o orçamento createRecords({ name: "Carrefour", amount: carrefourAmount, purchaseDate: dateForPeriodDay(period, 6), transactionType: "Despesa", condition: "À vista", paymentMethod: "Pix", accountId: createdAccounts.nubank, categoryId: getCategoryId("Mercado"), payerId: adminPayer.id, secondaryPayerId: createdPayers.mario, isSplit: true, primarySplitAmount: carrefourAmount / 2, secondarySplitAmount: carrefourAmount / 2, note: "Compra grande do mês dividida com mario.", }); createRecords({ name: "Pão de Açúcar", amount: paoAcucarAmount, purchaseDate: dateForPeriodDay(period, 13), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.interblack.id, categoryId: getCategoryId("Mercado"), payerId: adminPayer.id, cardMeta: createdCards.interblack, note: "Hortifruti e frios da semana.", }); createRecords({ name: "Assaí", amount: assaiAmount, purchaseDate: dateForPeriodDay(period, 22), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.interblack.id, categoryId: getCategoryId("Mercado"), payerId: adminPayer.id, cardMeta: createdCards.interblack, note: "Atacado mensal: bebidas e itens não-perecíveis.", }); // Transporte createRecords({ name: "Uber", amount: uberAmount, purchaseDate: dateForPeriodDay(period, 14), transactionType: "Despesa", condition: "À vista", paymentMethod: "Pix", accountId: createdAccounts["mercado-pago"], categoryId: getCategoryId("Transporte"), payerId: adminPayer.id, note: "Corridas do dia a dia.", }); createRecords({ name: "99", amount: noventaENoveAmount, purchaseDate: dateForPeriodDay(period, 19), transactionType: "Despesa", condition: "À vista", paymentMethod: "Pix", accountId: createdAccounts["mercado-pago"], categoryId: getCategoryId("Transporte"), payerId: adminPayer.id, note: "Corridas e idas ao aeroporto.", }); createRecords({ name: "Shell", amount: shellAmount, purchaseDate: dateForPeriodDay(period, 9), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de débito", accountId: createdAccounts.itau, categoryId: getCategoryId("Transporte"), payerId: adminPayer.id, note: "Abastecimento principal do mês.", }); createRecords({ name: "Ipiranga", amount: ipirangaAmount, purchaseDate: dateForPeriodDay(period, 23), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de débito", accountId: createdAccounts.itau, categoryId: getCategoryId("Transporte"), payerId: adminPayer.id, note: "Reabastecimento de fim de mês.", }); // Delivery createRecords({ name: "iFood", amount: ifoodAmount, purchaseDate: dateForPeriodDay(period, 20), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Delivery"), payerId: adminPayer.id, cardMeta: createdCards.ultravioleta, note: "Pedidos de jantar.", }); createRecords({ name: "Rappi", amount: rappiAmount, purchaseDate: dateForPeriodDay(period, 27), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Delivery"), payerId: adminPayer.id, cardMeta: createdCards.ultravioleta, note: "Pedidos de fim de semana.", }); // Restaurantes — estoura o orçamento createRecords({ name: "McDonald's", amount: mcdonaldsAmount, purchaseDate: dateForPeriodDay(period, 16), transactionType: "Despesa", condition: "À vista", paymentMethod: "Pré-Pago | VR/VA", accountId: createdAccounts["mercado-pago"], categoryId: getCategoryId("Restaurantes"), payerId: adminPayer.id, note: "Almoço rápido no intervalo.", }); createRecords({ name: "Starbucks", amount: starbucksAmount, purchaseDate: dateForPeriodDay(period, 11), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Restaurantes"), payerId: adminPayer.id, cardMeta: createdCards.ultravioleta, note: "Café e snack do trabalho.", }); createRecords({ name: "Burger King", amount: burgerKingAmount, purchaseDate: dateForPeriodDay(period, 24), transactionType: "Despesa", condition: "À vista", paymentMethod: "Pré-Pago | VR/VA", accountId: createdAccounts["mercado-pago"], categoryId: getCategoryId("Restaurantes"), payerId: adminPayer.id, note: "Jantar rápido.", }); // Saúde / farmácia createRecords({ name: "Drogasil", amount: drogasilAmount, purchaseDate: dateForPeriodDay(period, 12), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de débito", accountId: createdAccounts.nubank, categoryId: getCategoryId("Saúde"), payerId: adminPayer.id, note: "Remédios e itens de farmácia.", }); createRecords({ name: "Raia", amount: raiaAmount, purchaseDate: dateForPeriodDay(period, 26), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de débito", accountId: createdAccounts.nubank, categoryId: getCategoryId("Saúde"), payerId: adminPayer.id, note: "Remédios e suplementos.", }); // Investimentos (rendimento) createRecords({ name: "XP Investimentos", amount: investmentYield, purchaseDate: dateForPeriodDay(period, 27), transactionType: "Receita", condition: "À vista", paymentMethod: "Transferência bancária", accountId: createdAccounts.inter, categoryId: getCategoryId("Investimentos"), payerId: adminPayer.id, note: "Rendimento líquido do CDB.", }); // Esporádicos por paridade do mês if (index % 2 === 0) { createRecords({ name: "Amazon", amount: 168 + index * 12.8, purchaseDate: dateForPeriodDay(period, 18), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.itaucard.id, categoryId: getCategoryId("Compras"), payerId: adminPayer.id, cardMeta: createdCards.itaucard, note: "Itens de casa e pequenos eletrônicos.", }); } if (index % 2 === 0) { createRecords({ name: "Mercado Livre", amount: 134 + index * 11.4, purchaseDate: dateForPeriodDay(period, 25), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.itaucard.id, categoryId: getCategoryId("Compras"), payerId: adminPayer.id, cardMeta: createdCards.itaucard, note: "Compras avulsas no marketplace.", }); } if (index % 2 === 1) { createRecords({ name: "Stone", amount: 1850 + index * 140, purchaseDate: dateForPeriodDay(period, 24), transactionType: "Receita", condition: "À vista", paymentMethod: "Pix", accountId: createdAccounts.itau, categoryId: getCategoryId("Freelance"), payerId: adminPayer.id, note: "Projeto freelance de consultoria.", }); } if (index % 3 === 0) { createRecords({ name: "Outback", amount: 248 + index * 11.2, purchaseDate: dateForPeriodDay(period, 21), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.itaucard.id, categoryId: getCategoryId("Restaurantes"), payerId: adminPayer.id, cardMeta: createdCards.itaucard, note: "Jantar de fim de semana.", }); } if (index % 3 === 1) { createRecords({ name: "Madero", amount: 198 + index * 9.5, purchaseDate: dateForPeriodDay(period, 17), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.itaucard.id, categoryId: getCategoryId("Restaurantes"), payerId: adminPayer.id, cardMeta: createdCards.itaucard, note: "Almoço de domingo.", }); } if (index % 2 === 0) { createRecords({ name: "Cinemark", amount: 142 + index * 8.5, purchaseDate: dateForPeriodDay(period, 21), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.itaucard.id, categoryId: getCategoryId("Lazer"), payerId: adminPayer.id, cardMeta: createdCards.itaucard, note: "Sessão IMAX para dois.", }); } } // ── Parcelados grandes ── createRecords({ name: "Apple", amount: 9999, purchaseDate: dateForPeriodDay(firstPeriod, 14), transactionType: "Despesa", condition: "Parcelado", paymentMethod: "Cartão de crédito", cardId: createdCards.itaucard.id, categoryId: getCategoryId("Compras"), payerId: adminPayer.id, installmentCount: 12, cardMeta: createdCards.itaucard, note: "iPhone 16 Pro 256GB, 12x sem juros.", }); createRecords({ name: "Samsung", amount: 5499, purchaseDate: dateForPeriodDay(firstPeriod, 26), transactionType: "Despesa", condition: "Parcelado", paymentMethod: "Cartão de crédito", cardId: createdCards.itaucard.id, categoryId: getCategoryId("Compras"), payerId: adminPayer.id, installmentCount: 10, cardMeta: createdCards.itaucard, note: "Smart TV QLED 65 polegadas.", }); createRecords({ name: "Dell", amount: 7999.2, purchaseDate: dateForPeriodDay(secondPeriod, 18), transactionType: "Despesa", condition: "Parcelado", paymentMethod: "Cartão de crédito", cardId: createdCards.itaucard.id, categoryId: getCategoryId("Compras"), payerId: adminPayer.id, installmentCount: 10, cardMeta: createdCards.itaucard, note: "Notebook Dell XPS 13 para o home office.", }); createRecords({ name: "Magazine Luiza", amount: 2299.5, purchaseDate: dateForPeriodDay(thirdPeriod, 7), transactionType: "Despesa", condition: "Parcelado", paymentMethod: "Cartão de crédito", cardId: createdCards.itaucard.id, categoryId: getCategoryId("Moradia"), payerId: adminPayer.id, installmentCount: 5, cardMeta: createdCards.itaucard, note: "Geladeira inverter para o apartamento.", }); createRecords({ name: "LATAM", amount: 4280.5, purchaseDate: dateForPeriodDay(thirdPeriod, 16), transactionType: "Despesa", condition: "Parcelado", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Viagem"), payerId: adminPayer.id, secondaryPayerId: createdPayers.eduardo, isSplit: true, primarySplitAmount: 2140.25, secondarySplitAmount: 2140.25, installmentCount: 6, cardMeta: createdCards.ultravioleta, note: "Passagens para Salvador, divididas com Eduardo.", }); createRecords({ name: "Booking", amount: 3690, purchaseDate: dateForPeriodDay(thirdPeriod, 17), transactionType: "Despesa", condition: "Parcelado", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Viagem"), payerId: adminPayer.id, installmentCount: 6, cardMeta: createdCards.ultravioleta, note: "Hospedagem da viagem.", }); // ── Compras avulsas (à vista) ── createRecords({ name: "Apple", amount: 4299, purchaseDate: dateForPeriodDay(secondPeriod, 5), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Compras"), payerId: adminPayer.id, cardMeta: createdCards.ultravioleta, note: "Apple Watch Series 10.", }); createRecords({ name: "Zara", amount: 689.8, purchaseDate: dateForPeriodDay(middlePeriod, 17), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Vestuário"), payerId: adminPayer.id, cardMeta: createdCards.ultravioleta, note: "Coleção de inverno.", }); createRecords({ name: "Renner", amount: 412.5, purchaseDate: dateForPeriodDay(thirdPeriod, 9), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Vestuário"), payerId: adminPayer.id, cardMeta: createdCards.ultravioleta, note: "Camisas e calças básicas.", }); createRecords({ name: "Nike", amount: 899.9, purchaseDate: dateForPeriodDay(firstPeriod, 23), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Vestuário"), payerId: adminPayer.id, cardMeta: createdCards.ultravioleta, note: "Tênis Air Zoom para academia.", }); // ── Educação, presentes, lazer ── createRecords({ name: "Alura", amount: 1499.9, purchaseDate: dateForPeriodDay(firstPeriod, 20), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Educação"), payerId: adminPayer.id, cardMeta: createdCards.ultravioleta, note: "Plano anual de desenvolvimento.", }); createRecords({ name: "Amazon", amount: 189, purchaseDate: dateForPeriodDay(thirdPeriod, 11), transactionType: "Despesa", condition: "À vista", paymentMethod: "Pix", accountId: createdAccounts.nubank, categoryId: getCategoryId("Presentes"), payerId: adminPayer.id, note: "Presente de aniversário do Eduardo.", }); createRecords({ name: "Eventim", amount: 480, purchaseDate: dateForPeriodDay(middlePeriod, 8), transactionType: "Despesa", condition: "Parcelado", paymentMethod: "Cartão de crédito", cardId: createdCards.ultravioleta.id, categoryId: getCategoryId("Lazer"), payerId: adminPayer.id, secondaryPayerId: createdPayers.mario, isSplit: true, primarySplitAmount: 240, secondarySplitAmount: 240, installmentCount: 3, cardMeta: createdCards.ultravioleta, note: "Ingressos para show, divididos com mario.", }); // ── Saúde / consultas ── createRecords({ name: "Hapvida", amount: 320, purchaseDate: dateForPeriodDay(middlePeriod, 9), transactionType: "Despesa", condition: "À vista", paymentMethod: "Boleto", accountId: createdAccounts.itau, categoryId: getCategoryId("Saúde"), payerId: adminPayer.id, dueDate: dateForPeriodDay(middlePeriod, 17), note: "Consulta odontológica particular.", }); createRecords({ name: "Amil", amount: 218.5, purchaseDate: dateForPeriodDay(secondPeriod, 26), transactionType: "Receita", condition: "À vista", paymentMethod: "Pix", accountId: createdAccounts.itau, categoryId: getCategoryId("Reembolso"), payerId: adminPayer.id, note: "Reembolso de consulta médica.", }); // ── Boleto em aberto (testa lembretes) ── createRecords({ name: "Detran", amount: 1284.32, purchaseDate: dateForPeriodDay(lastPeriod, 10), transactionType: "Despesa", condition: "À vista", paymentMethod: "Boleto", accountId: createdAccounts.itau, categoryId: getCategoryId("Transporte"), payerId: adminPayer.id, dueDate: dateForPeriodDay(lastPeriod, 25), note: "IPVA 2026 em aberto.", settlementBehavior: "open", }); // ── Estouro do Inter Black no último mês (testa barra destructive) ── createRecords({ name: "Magazine Luiza", amount: 2199, purchaseDate: dateForPeriodDay(lastPeriod, 4), transactionType: "Despesa", condition: "À vista", paymentMethod: "Cartão de crédito", cardId: createdCards.interblack.id, categoryId: getCategoryId("Moradia"), payerId: adminPayer.id, cardMeta: createdCards.interblack, note: "Air fryer e cafeteira para a cozinha.", }); // ── Transferências internas entre contas ── const transferCategoryId = getCategoryId(TRANSFER_CATEGORY_NAME); const pushTransfer = (params: { fromAccountId: string; toAccountId: string; amount: number; date: string; fromAccountName: string; toAccountName: string; }) => { const transferId = randomUUID(); const period = derivePeriodFromDate(params.date); const purchaseDate = parseLocalDateString(params.date); const note = `de ${params.fromAccountName} -> ${params.toAccountName}`; const sharedFields = { condition: TRANSFER_CONDITION, paymentMethod: TRANSFER_PAYMENT_METHOD, note, purchaseDate, transactionType: "Transferência" as const, period, isSettled: true, userId: targetUser.id, categoryId: transferCategoryId, payerId: adminPayer.id, transferId, }; seedTransactionRecords.push({ ...sharedFields, name: TRANSFER_ESTABLISHMENT_SAIDA, amount: `-${params.amount.toFixed(2)}`, accountId: params.fromAccountId, }); seedTransactionRecords.push({ ...sharedFields, name: TRANSFER_ESTABLISHMENT_ENTRADA, amount: params.amount.toFixed(2), accountId: params.toAccountId, }); }; // Repasse mensal do salário (Itaú → Nubank) para o dia a dia for (const period of periods) { pushTransfer({ fromAccountId: createdAccounts.itau, toAccountId: createdAccounts.nubank, amount: 4000, date: dateForPeriodDay(period, 6), fromAccountName: "Itaú Personnalité", toAccountName: "Nubank", }); } // Reserva de emergência no Inter (eventual) pushTransfer({ fromAccountId: createdAccounts.itau, toAccountId: createdAccounts.inter, amount: 1500, date: dateForPeriodDay(secondPeriod, 12), fromAccountName: "Itaú Personnalité", toAccountName: "Banco Inter", }); pushTransfer({ fromAccountId: createdAccounts.itau, toAccountId: createdAccounts.inter, amount: 2000, date: dateForPeriodDay(middlePeriod, 14), fromAccountName: "Itaú Personnalité", toAccountName: "Banco Inter", }); // Recargas pontuais para o Mercado Pago pushTransfer({ fromAccountId: createdAccounts.nubank, toAccountId: createdAccounts["mercado-pago"], amount: 250, date: dateForPeriodDay(firstPeriod, 18), fromAccountName: "Nubank", toAccountName: "Mercado Pago", }); pushTransfer({ fromAccountId: createdAccounts.nubank, toAccountId: createdAccounts["mercado-pago"], amount: 180, date: dateForPeriodDay(thirdPeriod, 14), fromAccountName: "Nubank", toAccountName: "Mercado Pago", }); await db.insert(transactions).values(seedTransactionRecords); summary.transactions += seedTransactionRecords.length; const { createdInvoices, createdInvoicePayments } = await seedInvoicesForCards({ userId: targetUser.id, adminPayerId: adminPayer.id, cardsByKey: createdCards, paymentCategoryId: categoriesByName.get("Pagamentos"), insertedTransactionRecords: seedTransactionRecords, }); summary.invoices += createdInvoices; summary.invoicePayments += createdInvoicePayments; summary.transactions += createdInvoicePayments; const inboxItemsData = [ { userId: targetUser.id, sourceApp: "com.nu.production", sourceAppName: "Nubank", originalTitle: "Compra aprovada", originalText: "Compra de R$ 142,80 aprovada no cartão Ultravioleta em IFOOD*IFOOD", notificationTimestamp: parseLocalDateString( dateForPeriodDay(lastPeriod, 3), ), parsedName: "iFood", parsedAmount: "142.80", status: "pending" as const, transactionId: null, processedAt: null, discardedAt: null, }, { userId: targetUser.id, sourceApp: "com.nu.production", sourceAppName: "Nubank", originalTitle: "Pix enviado", originalText: "Você enviou R$ 320,00 via Pix para mario Oliveira.", notificationTimestamp: parseLocalDateString( dateForPeriodDay(lastPeriod, 5), ), parsedName: "mario Oliveira", parsedAmount: "320.00", status: "pending" as const, transactionId: null, processedAt: null, discardedAt: null, }, { userId: targetUser.id, sourceApp: "br.com.itau.personnalite", sourceAppName: "Itaú Personnalité", originalTitle: "Débito em conta", originalText: "Débito de R$ 78,90 realizado. STARBUCKS COFFEE", notificationTimestamp: parseLocalDateString( dateForPeriodDay(lastPeriod, 7), ), parsedName: "Starbucks", parsedAmount: "78.90", status: "pending" as const, transactionId: null, processedAt: null, discardedAt: null, }, { userId: targetUser.id, sourceApp: "br.com.itau.personnalite", sourceAppName: "Itaú Personnalité", originalTitle: "Compra aprovada", originalText: "Compra de R$ 487,40 aprovada no cartão Itaucard em AMAZON BR. Limite disponível: R$ 1.512,60", notificationTimestamp: parseLocalDateString( dateForPeriodDay(lastPeriod, 10), ), parsedName: "Amazon", parsedAmount: "487.40", status: "pending" as const, transactionId: null, processedAt: null, discardedAt: null, }, { userId: targetUser.id, sourceApp: "com.mercadopago.wallet", sourceAppName: "Mercado Pago", originalTitle: null, originalText: "Pagamento de R$ 38,50 aprovado em UBER*TRIP", notificationTimestamp: parseLocalDateString( dateForPeriodDay(lastPeriod, 13), ), parsedName: "Uber", parsedAmount: "38.50", status: "pending" as const, transactionId: null, processedAt: null, discardedAt: null, }, { userId: targetUser.id, sourceApp: "com.nu.production", sourceAppName: "Nubank", originalTitle: "Compra aprovada", originalText: "Compra de R$ 224,90 aprovada no cartão Ultravioleta em SHOPEE*SHOPEE BR", notificationTimestamp: parseLocalDateString( dateForPeriodDay(lastPeriod, 18), ), parsedName: "Shopee", parsedAmount: "224.90", status: "pending" as const, transactionId: null, processedAt: null, discardedAt: null, }, { userId: targetUser.id, sourceApp: "com.nu.production", sourceAppName: "Nubank", originalTitle: "Compra aprovada", originalText: "Compra de R$ 89,90 aprovada no cartão Ultravioleta em NETFLIX.COM", notificationTimestamp: parseLocalDateString( dateForPeriodDay(lastPeriod, 21), ), parsedName: "Netflix", parsedAmount: "89.90", status: "pending" as const, transactionId: null, processedAt: null, discardedAt: null, }, { userId: targetUser.id, sourceApp: "com.mercadopago.wallet", sourceAppName: "Mercado Pago", originalTitle: null, originalText: "Você pagou R$ 92,50 via Pix para SHELL POSTO", notificationTimestamp: parseLocalDateString( dateForPeriodDay(lastPeriod, 22), ), parsedName: "Shell", parsedAmount: "92.50", status: "pending" as const, transactionId: null, processedAt: null, discardedAt: null, }, { userId: targetUser.id, sourceApp: "com.mercadolivre.mercadolibre", sourceAppName: "Mercado Livre", originalTitle: "Pedido confirmado", originalText: "Seu pedido de R$ 379,00 foi confirmado em MERCADO LIVRE BR", notificationTimestamp: parseLocalDateString( dateForPeriodDay(lastPeriod, 24), ), parsedName: "Mercado Livre", parsedAmount: "379.00", status: "pending" as const, transactionId: null, processedAt: null, discardedAt: null, }, { userId: targetUser.id, sourceApp: "com.apple.android.music", sourceAppName: "Apple", originalTitle: "Compra recebida", originalText: "Sua compra de R$ 19,90 em APPLE.COM/BILL foi processada.", notificationTimestamp: parseLocalDateString( dateForPeriodDay(lastPeriod, 25), ), parsedName: "Apple", parsedAmount: "19.90", status: "pending" as const, transactionId: null, processedAt: null, discardedAt: null, }, ]; await db.insert(inboxItems).values(inboxItemsData); summary.inboxItems += inboxItemsData.length; const finalPeriods = Array.from( new Set( seedTransactionRecords .map((record) => record.period ?? "") .filter(Boolean), ), ).sort(comparePeriods); const seededFrom = finalPeriods[0] ?? options.startPeriod; const seededTo = finalPeriods[finalPeriods.length - 1] ?? options.startPeriod; console.log("Seed concluído com sucesso."); console.log( JSON.stringify( { userId: targetUser.id, userName: targetUser.name, startPeriod: options.startPeriod, months: options.months, seededFrom, seededTo, todayPeriod: businessToday.period, summary, }, null, 2, ), ); } void main() .then(() => process.exit(0)) .catch((error) => { console.error( error instanceof Error ? error.message : "Erro inesperado ao popular conta.", ); process.exit(1); });