import { assert, assertEquals } from "jsr:@std/assert@1"; import { callCoreFn } from "./rpc.ts"; import { withTestDb } from "./test_helpers.ts"; import type { PgDb } from "./pg.ts"; const TENANT_ID = 999003; async function seed(db: PgDb) { await db.prepare( `INSERT INTO companies (code, name, kind, tenant_id) VALUES ('PNDCO', 'Pend Co', 'principal', ?)`, ).run(TENANT_ID); const companyId = await db.lastInsertId(); await db.prepare( `INSERT INTO projects (code, name, status, theme_id, company_id, tenant_id) VALUES ('PND-1', 'Obra Pend', 'activo', 'arctec-dos-logos-fold', ?, ?)`, ).run(companyId, TENANT_ID); const projectId = await db.lastInsertId(); await db.prepare( `INSERT INTO budget_items (project_id, code, description, unit, quantity, unit_price, amount) VALUES (?, 'MAT-1', 'Cemento', 'bulto', 1, 100, 100)`, ).run(projectId); await db.prepare( `INSERT INTO workers (first_name, last_name_p, last_name_m, curp, rfc, nss, phone, email, address, hire_type, company_id, position, risk_code, work_type, daily_wage, tenant_id, status) VALUES ('Ana', 'Lopez', 'Diaz', 'CURPPEND0001', 'RFCPEND0001', 'NSSPEND0001', '9991111111', 'ana@example.com', 'Calle 1', 'PNDCO', ?, 'Ayudante', 'rojo', 'N', 400, ?, 'activo')`, ).run(companyId, TENANT_ID); const workerId = await db.lastInsertId(); await db.prepare( `INSERT INTO assignments (worker_id, project_id, active, start_date) VALUES (?, ?, true, DATE '2026-09-21')`, ).run(workerId, projectId); return { companyId, projectId, workerId }; } Deno.test("proveedor: RFC duplicado e inactivo fuera del listado activo", async () => { await withTestDb(async (db) => { const created = await callCoreFn(db, "core.fn_supplier_create", { tenant_id: TENANT_ID, name: "Aceros", rfc: "ACE123456AB1", regimen_fiscal: "601", postal_code: "97000", }); assertEquals(created.ok, true, JSON.stringify(created)); const dup = await callCoreFn(db, "core.fn_supplier_create", { tenant_id: TENANT_ID, name: "Otro", rfc: "ACE123456AB1", }); assertEquals(dup.ok, false); const id = (created.data as { supplier: { id: number } }).supplier.id; const off = await callCoreFn(db, "core.fn_supplier_update", { tenant_id: TENANT_ID, id, status: "inactivo" }); assertEquals(off.ok, true); const listed = await callCoreFn(db, "core.fn_supplier_list", { tenant_id: TENANT_ID, status: "activo", q: "aceros" }); const rows = (listed.data as { suppliers: { name: string }[] }).suppliers; assert(!rows.some((s) => s.name === "Aceros")); }); }); Deno.test("facturas: RFC conocido, desconocido, UUID repetido y un solo gasto", async () => { await withTestDb(async (db) => { const fx = await seed(db); await callCoreFn(db, "core.fn_supplier_create", { tenant_id: TENANT_ID, name: "Aceros", rfc: "ACE123456AB1" }); const known = await callCoreFn(db, "core.fn_invoice_import", { tenant_id: TENANT_ID, company_id: fx.companyId, rows: [{ direction: "recibida", rfc: "ACE123456AB1", supplier_name: "Aceros", uuid: "UUID-1", folio: "10", invoice_date: "2026-09-01", description: "Cemento", amount: 100, iva: 16, total: 116, project_code: "PND-1", item_code: "MAT-1", }], }); assertEquals(known.ok, true, JSON.stringify(known)); const again = await callCoreFn(db, "core.fn_invoice_import", { tenant_id: TENANT_ID, company_id: fx.companyId, rows: [{ direction: "recibida", rfc: "ACE123456AB1", uuid: "UUID-1", invoice_date: "2026-09-01", amount: 100, }], }); assertEquals((again.data as { skipped: number }).skipped, 1); const unknown = await callCoreFn(db, "core.fn_invoice_import", { tenant_id: TENANT_ID, company_id: fx.companyId, rows: [{ direction: "recibida", rfc: "XXX123456AB1", uuid: "UUID-2", invoice_date: "2026-09-02", amount: 50, supplier_name: "Nuevo" }], }); assertEquals((unknown.data as { review: number }).review, 1); const expenses = await db.prepare( `SELECT COUNT(*)::int AS n FROM expense_entries WHERE source = 'invoice' AND tenant_id = ?`, ).get(TENANT_ID) as { n: number }; assertEquals(expenses.n, 1); }); }); Deno.test("nómina: bono, descuento, horas, festivo y semana pagada", async () => { await withTestDb(async (db) => { const fx = await seed(db); const week = await callCoreFn(db, "core.fn_payroll_week_ensure", { tenant_id: TENANT_ID, week_start: "2026-09-21" }); assertEquals(week.ok, true, JSON.stringify(week)); const weekId = (week.data as { week_id: number }).week_id; await callCoreFn(db, "core.fn_holiday_save", { tenant_id: TENANT_ID, holiday_date: "2026-09-21", name: "Puente" }); const bono = await callCoreFn(db, "core.fn_payroll_adjustment_save", { tenant_id: TENANT_ID, week_id: weekId, worker_id: fx.workerId, project_id: fx.projectId, kind: "bono", concept: "Bono", amount: 100, }); assertEquals(bono.ok, true, JSON.stringify(bono)); const missing = await callCoreFn(db, "core.fn_payroll_adjustment_save", { tenant_id: TENANT_ID, week_id: weekId, worker_id: fx.workerId, kind: "descuento", concept: "Falta", amount: 40, }); assertEquals(missing.ok, false); await callCoreFn(db, "core.fn_payroll_adjustment_save", { tenant_id: TENANT_ID, week_id: weekId, worker_id: fx.workerId, project_id: fx.projectId, kind: "descuento", concept: "Herramienta", amount: 40, note: "Perdió herramienta", }); await callCoreFn(db, "core.fn_payroll_adjustment_save", { tenant_id: TENANT_ID, week_id: weekId, worker_id: fx.workerId, project_id: fx.projectId, kind: "horas", concept: "Extra", hours: 8, }); const bundle = await callCoreFn(db, "core.fn_payroll_week_bundle", { week_id: weekId, tenant_id: TENANT_ID }); assertEquals(bundle.ok, true, JSON.stringify(bundle)); await db.prepare(`UPDATE payroll_weeks SET status = 'paid' WHERE id = ?`).run(weekId); const blocked = await callCoreFn(db, "core.fn_payroll_adjustment_save", { tenant_id: TENANT_ID, week_id: weekId, worker_id: fx.workerId, kind: "bono", concept: "Tarde", amount: 1, }); assertEquals(blocked.ok, false); }); }); Deno.test("préstamo: edición sin pagos y saldo con pagos", async () => { await withTestDb(async (db) => { const fx = await seed(db); const created = await callCoreFn(db, "core.fn_loan_create", { tenant_id: TENANT_ID, worker_id: fx.workerId, delivered: 1000, commission_enabled: false, }); assertEquals(created.ok, true, JSON.stringify(created)); const id = (created.data as { id: number }).id; const edited = await callCoreFn(db, "core.fn_loan_update", { id, delivered: 800, commission_pct: 0, note: "Corregido" }); assertEquals(edited.ok, true, JSON.stringify(edited)); const week = await callCoreFn(db, "core.fn_payroll_week_ensure", { tenant_id: TENANT_ID, week_start: "2026-09-21" }); const weekId = (week.data as { week_id: number }).week_id; await db.prepare(`INSERT INTO loan_payments (loan_id, week_id, amount) VALUES (?, ?, 200)`).run(id, weekId); const statement = await callCoreFn(db, "core.fn_loan_statement", { id }); const loan = (statement.data as { loan: { balance: number; delivered: number } }).loan; assertEquals(Number(loan.delivered), 800); assertEquals(Number(loan.balance), 600); const locked = await callCoreFn(db, "core.fn_loan_update", { id, delivered: 50 }); const after = await callCoreFn(db, "core.fn_loan_statement", { id }); assertEquals(Number((after.data as { loan: { delivered: number } }).loan.delivered), 800); assertEquals(locked.ok, true); }); }); Deno.test("finiquito: el neto es líneas menos descuentos y préstamo", async () => { await withTestDb(async (db) => { const fx = await seed(db); await callCoreFn(db, "core.fn_loan_create", { tenant_id: TENANT_ID, worker_id: fx.workerId, delivered: 300, commission_enabled: false, }); const saved = await callCoreFn(db, "core.fn_settlement_save", { tenant_id: TENANT_ID, worker_id: fx.workerId, company_id: fx.companyId, project_id: fx.projectId, kind: "finiquito", termination_date: "2026-09-24", lines: [{ concept: "Sueldo", amount: 1000, note: "" }], }); assertEquals(saved.ok, true, JSON.stringify(saved)); const id = (saved.data as { id: number }).id; const got = await callCoreFn(db, "core.fn_settlement_get", { id }); const net = Number((got.data as { settlement: { net: number } }).settlement.net); assertEquals(net, 700); }); }); Deno.test("dispersión: definitiva no se corrige", async () => { await withTestDb(async (db) => { const fx = await seed(db); const week = await callCoreFn(db, "core.fn_payroll_week_ensure", { tenant_id: TENANT_ID, week_start: "2026-09-21" }); const weekId = (week.data as { week_id: number }).week_id; const opened = await callCoreFn(db, "core.fn_dispersion_get", { tenant_id: TENANT_ID, week_id: weekId }); const id = (opened.data as { dispersion: { id: number } }).dispersion.id; await callCoreFn(db, "core.fn_dispersion_cut", { id }); const fin = await callCoreFn(db, "core.fn_dispersion_finalize", { id }); assertEquals(fin.ok, true); const line = await db.prepare(`SELECT id FROM payroll_dispersion_lines WHERE dispersion_id = ? LIMIT 1`).get(id) as { id: number } | undefined; if (line) { const upd = await callCoreFn(db, "core.fn_dispersion_line_update", { line_id: line.id, amount: 1 }); assertEquals(upd.ok, false); } assert(fx.workerId > 0); }); }); Deno.test("historial: alta, baja y reingreso", async () => { await withTestDb(async (db) => { const fx = await seed(db); const alta = await callCoreFn(db, "core.fn_worker_movement_list", { worker_id: fx.workerId }); const kinds = (alta.data as { movements: { kind: string }[] }).movements.map((m) => m.kind); assert(kinds.includes("alta")); await callCoreFn(db, "core.fn_worker_set_pipeline", { id: fx.workerId, tenant_id: TENANT_ID, pipeline_status: "baja" }); await callCoreFn(db, "core.fn_worker_set_pipeline", { id: fx.workerId, tenant_id: TENANT_ID, pipeline_status: "activo" }); const later = await callCoreFn(db, "core.fn_worker_movement_list", { worker_id: fx.workerId }); const all = (later.data as { movements: { kind: string }[] }).movements.map((m) => m.kind); assert(all.includes("baja")); assert(all.includes("reingreso")); }); }); Deno.test("reporte mensual e IMSS con jornal distinto", async () => { await withTestDb(async (db) => { const fx = await seed(db); await callCoreFn(db, "core.fn_payroll_week_ensure", { tenant_id: TENANT_ID, week_start: "2026-09-07" }); await callCoreFn(db, "core.fn_payroll_week_ensure", { tenant_id: TENANT_ID, week_start: "2026-09-14" }); const report = await callCoreFn(db, "core.fn_payroll_report", { tenant_id: TENANT_ID, date_from: "2026-09-01", date_to: "2026-09-30", worker_id: fx.workerId, }); assertEquals(report.ok, true, JSON.stringify(report)); await callCoreFn(db, "core.fn_worker_imss_wage_set", { id: fx.workerId, imss_daily_wage: 250 }); const quote = await callCoreFn(db, "core.fn_imss_quote", { tenant_id: TENANT_ID, worker_id: fx.workerId, date_from: "2026-09-01", date_to: "2026-09-30", }); const row = (quote.data as { quotes: { daily_wage: number; imss_daily_wage: number; used_real_wage: boolean }[] }).quotes[0]; assertEquals(Number(row.daily_wage), 400); assertEquals(Number(row.imss_daily_wage), 250); assertEquals(row.used_real_wage, false); }); });