import { ViajeCobranzaService } from '../viaje-cobranza.service';

/**
 * Cobranza del viaje. Los montos de los recibos se miran por dos lados que no
 * se cruzan: lo que se CANCELÓ de cada factura (`monto_pagado`, bruto) y la
 * plata que ENTRÓ por cada medio (`recibo_cobro_medios_pago`, neto). La
 * diferencia entre los dos son ajustes: retenciones, notas de crédito aplicadas,
 * saldo a favor e intereses.
 */
describe('ViajeCobranzaService.cobranza', () => {
  const HOY = '2026-03-10';

  const crear = ({
    facturas = [] as any[],
    notas = [] as any[],
    recibos = [] as any[],
    recibosSinEnganchar = [] as any[],
    deudaAnterior = [] as any[],
    asignadosOtroViaje = [] as string[],
    ncReferencian = [] as any[],
    pagosMulti = [] as any[],
    pagosLegacy = [] as any[],
  }) => {
    const prisma: any = {
      factura_cab: {
        findMany: jest.fn().mockImplementation((args: any) =>
          // La segunda consulta a facturas es la deuda anterior del cliente
          // (facturas fuera del viaje con saldo).
          Promise.resolve(args?.where?.id?.notIn ? deudaAnterior : facturas),
        ),
      },
      nota_credito_cab: {
        findMany: jest.fn().mockImplementation((args: any) =>
          // Con `factura_cab_id` es la búsqueda de NC que referencian facturas del viaje.
          Promise.resolve(args?.where?.factura_cab_id ? ncReferencian : notas),
        ),
      },
      recibo_cobro_facturas: { groupBy: jest.fn().mockResolvedValue(pagosMulti) },
      recibo_cobro_detalle: { groupBy: jest.fn().mockResolvedValue(pagosLegacy) },
      recibos_cobro: {
        findMany: jest.fn().mockImplementation((args: any) =>
          Promise.resolve(args?.where?.id?.in ? recibos : recibosSinEnganchar),
        ),
      },
      viaje_documentos: {
        findMany: jest.fn().mockResolvedValue(asignadosOtroViaje.map((d) => ({ documento_id: d }))),
      },
      empresas_sucursales: { findMany: jest.fn().mockResolvedValue([]) },
      usuario_sucursal: { findMany: jest.fn().mockResolvedValue([]) },
    };
    const documentos: any = {
      idsPorTipo: jest.fn().mockImplementation((_id: string, tipo: string) =>
        Promise.resolve(
          tipo === 'FACTURA' ? facturas.map((f) => f.id)
          : tipo === 'RECIBO' ? recibos.map((r) => r.id)
          : tipo === 'NOTA_CREDITO' ? notas.map((n) => n.id)
          : [],
        ),
      ),
    };
    const viajes: any = { findOne: jest.fn().mockResolvedValue({ id: 'v-1', empresa_id: 'emp-1' }) };
    const monedas: any = { getCotizacionVigente: jest.fn().mockResolvedValue({ tasa: 7500 }) };
    const service = Object.create(ViajeCobranzaService.prototype) as any;
    service.prisma = prisma;
    // Los cálculos piden solo los vigentes; acá el filtro de bajas no es lo que se prueba.
    documentos.idsVigentes ??= documentos.idsPorTipo;
    service.documentos = documentos;
    service.viajes = viajes;
    service.monedas = monedas;
    // Sin alcance por sucursal en los tests: el helper real consulta tablas de
    // usuario que acá no importan.
    service.alcance = jest.fn().mockResolvedValue({ fiscal: null, operativo: undefined });
    service.hoy = () => HOY;
    return { service, prisma };
  };

  const factura = (id: string, o: any = {}) => ({
    id,
    dest: '001', dpunexp: '001', dnumdoc: id.replace(/\D/g, '').padStart(7, '0'),
    dfeemide: new Date('2026-03-02T12:00:00Z'),
    cliente_id: o.cliente ?? 'cli-1',
    clientes: { personas: { razon_social: o.nombre ?? 'TAVA I COMERCIAL' } },
    condicion_operacion: { descripcion: o.condicion ?? 'Crédito' },
    cobro_diferido: o.diferido ?? false,
    dvencpag: o.vence ? new Date(`${o.vence}T12:00:00Z`) : null,
    saldo_pendiente: o.saldo ?? 0,
    dticam: o.dticam ?? null,
    moneda: { codigo: o.moneda ?? 'PYG' },
    factura_subtotales: [{ dtotgralope: o.total ?? 1_000_000 }],
    factura_cuotas: o.cuotas ?? [],
    factura_forma_pagos: o.formas ?? [],
  });

  const recibo = (id: string, o: any = {}) => ({
    id,
    numero_recibo: o.numero ?? `001-001-${id}`,
    fecha_emision: new Date(`${o.fecha ?? '2026-03-03'}T12:00:00Z`),
    cliente_id: o.cliente ?? 'cli-1',
    clientes: { personas: { razon_social: o.nombre ?? 'TAVA I COMERCIAL' } },
    estado: o.estado ?? 'emitido',
    monto_total: o.total ?? 0,
    cotizacion: o.cotizacion ?? 1,
    moneda: { codigo: o.moneda ?? 'PYG' },
    cobrador_id: o.cobrador ?? null,
    rendicion_id: o.rendicion?.id ?? null,
    rendiciones_cobranza: o.rendicion ?? null,
    facturas_multi: o.facturas ?? [],
    medios_pago_multi: o.medios ?? [],
    recibo_cobro_detalle: o.detalle ?? [],
  });

  it('separa lo cancelado del viaje de la deuda anterior y la plata por medio', async () => {
    // Un recibo paga una factura del viaje (600.000) y una vieja (400.000),
    // con un cheque diferido y efectivo; hubo una retención de 50.000.
    const { service } = crear({
      facturas: [factura('f-1', { total: 1_000_000, saldo: 400_000, vence: '2026-04-01' })],
      recibos: [
        recibo('r-1', {
          total: 950_000,
          facturas: [
            { factura_cab_id: 'f-1', monto_pagado: 600_000 },
            { factura_cab_id: 'f-vieja', monto_pagado: 400_000 },
          ],
          medios: [
            { medio: 'CHEQUE', monto: 700_000, cheque_numero: '123', cheque_banco: 'ITAU',
              cheque_titular: 'TAVA', cheque_fecha_vencimiento: new Date('2026-03-20T12:00:00Z') },
            { medio: 'EFECTIVO', monto: 250_000 },
          ],
        }),
      ],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    expect(r.totales.vendido_base).toBe(1_000_000);
    expect(r.totales.cancelado_viaje_base).toBe(600_000);
    expect(r.totales.deuda_anterior_cobrada_base).toBe(400_000);
    expect(r.totales.recibido_base).toBe(950_000);
    // Cancelado (1.000.000) − recibido (950.000): la retención.
    expect(r.totales.ajustes_base).toBe(50_000);
    expect(r.totales.por_cobrar_base).toBe(400_000);

    const medios = Object.fromEntries(r.por_medio.map((m: any) => [m.medio, m.monto_base]));
    expect(medios.CHEQUE_DIFERIDO).toBe(700_000);
    expect(medios.EFECTIVO).toBe(250_000);

    const cli = r.clientes[0];
    expect(cli.nombre).toBe('TAVA I COMERCIAL');
    expect(cli.facturas).toEqual(['001-001-0000001']);
    expect(cli.cobrado_viaje_base).toBe(600_000);
    expect(cli.deuda_anterior_cobrada_base).toBe(400_000);
    expect(cli.saldo_base).toBe(400_000);
    expect(cli.medios).toEqual(expect.arrayContaining(['CHEQUE_DIFERIDO', 'EFECTIVO']));
  });

  it('cheque al día contra diferido por vencimiento, con total por semana', async () => {
    const { service } = crear({
      facturas: [factura('f-1', { total: 900_000 })],
      recibos: [
        recibo('r-1', {
          fecha: '2026-03-03',
          total: 900_000,
          facturas: [{ factura_cab_id: 'f-1', monto_pagado: 900_000 }],
          medios: [
            // Vence el mismo día del recibo: al día.
            { medio: 'CHEQUE', monto: 300_000, cheque_numero: '1', cheque_fecha_vencimiento: new Date('2026-03-03T12:00:00Z') },
            // Sin fecha de cobro no se sabe si es diferido: va aparte.
            { medio: 'CHEQUE', monto: 100_000, cheque_numero: '2', cheque_fecha_vencimiento: null },
            // Jueves 19/03 y sábado 21/03: misma semana (lunes 16/03).
            { medio: 'CHEQUE', monto: 200_000, cheque_numero: '3', cheque_fecha_vencimiento: new Date('2026-03-19T12:00:00Z') },
            { medio: 'CHEQUE', monto: 300_000, cheque_numero: '4', cheque_fecha_vencimiento: new Date('2026-03-21T12:00:00Z') },
          ],
        }),
      ],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    const medios = Object.fromEntries(r.por_medio.map((m: any) => [m.medio, m.monto_base]));
    expect(medios.CHEQUE_AL_DIA).toBe(300_000);
    expect(medios.CHEQUE_SIN_FECHA).toBe(100_000);
    expect(medios.CHEQUE_DIFERIDO).toBe(500_000);
    expect(r.cheques).toHaveLength(4);
    expect(r.cheques.filter((c: any) => c.diferido)).toHaveLength(2);
    expect(r.cheques_por_semana).toEqual([
      { semana: '2026-03-16', cantidad: 2, monto_base: 500_000 },
    ]);
  });

  it('el pendiente sale del saldo vivo y avisa recibos que pagan facturas del viaje sin estar enganchados', async () => {
    const { service } = crear({
      facturas: [factura('f-1', { total: 1_000_000, saldo: 0 })],
      recibos: [],
      recibosSinEnganchar: [
        { id: 'r-9', numero_recibo: '001-001-0000099', fecha_emision: new Date('2026-03-05T12:00:00Z'),
          monto_total: 1_200_000, cotizacion: 1, moneda: { codigo: 'PYG' }, cliente_id: 'cli-1',
          clientes: { personas: { razon_social: 'TAVA I COMERCIAL' } },
          // La consulta ya filtra las líneas a las facturas del viaje.
          facturas_multi: [{ monto_pagado: 1_000_000 }], recibo_cobro_detalle: [] },
        // Ya está en otro viaje: no se puede ofrecer enganchar.
        { id: 'r-8', numero_recibo: '001-001-0000098', fecha_emision: new Date('2026-03-05T12:00:00Z'),
          monto_total: 10, moneda: { codigo: 'PYG' }, clientes: null },
      ],
      asignadosOtroViaje: ['r-8'],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    expect(r.totales.por_cobrar_base).toBe(0);
    expect(r.totales.cancelado_viaje_base).toBe(0);
    expect(r.recibos_sin_enganchar).toEqual([
      expect.objectContaining({ id: 'r-9', numero: '001-001-0000099', cliente: 'TAVA I COMERCIAL', total: 1_200_000, aplicado_viaje: 1_000_000 }),
    ]);
    // Con esto vendido = cobrado + sin enganchar + pendiente cuadra aunque falte enganchar.
    expect(r.totales.cancelado_sin_enganchar_base).toBe(1_000_000);
    expect(r.clientes[0].cobrado_sin_enganchar_base).toBe(1_000_000);
  });

  it('control de rendición: rendido, en rendición, sin rendir y cobro en ruta sin registrar', async () => {
    const { service } = crear({
      facturas: [
        factura('f-1', { total: 100, saldo: 0 }),
        // Contado con cobro en ruta y todavía con saldo: el cobrador no lo registró.
        factura('f-2', { total: 500, saldo: 500, condicion: 'Contado', diferido: true }),
      ],
      recibos: [
        recibo('r-1', { total: 100, cobrador: 'c-1', rendicion: { id: 'rc-1', codigo: 'RC-1', estado: 'APROBADO', total_declarado: 100, total_verificado: 90, diferencia: 0 },
          facturas: [{ factura_cab_id: 'f-1', monto_pagado: 100 }], medios: [{ medio: 'EFECTIVO', monto: 100 }] }),
        recibo('r-2', { total: 200, cobrador: 'c-1', rendicion: { id: 'rc-2', codigo: 'RC-2', estado: 'PENDIENTE', total_declarado: 200, total_verificado: null },
          facturas: [{ factura_cab_id: 'f-x', monto_pagado: 200 }], medios: [{ medio: 'EFECTIVO', monto: 200 }] }),
        // Rendición rechazada: hay que volver a rendir, cuenta como sin rendir.
        recibo('r-3', { total: 300, cobrador: 'c-1', rendicion: { id: 'rc-3', codigo: 'RC-3', estado: 'RECHAZADO', total_declarado: 0, total_verificado: null },
          facturas: [{ factura_cab_id: 'f-x', monto_pagado: 300 }], medios: [{ medio: 'EFECTIVO', monto: 300 }] }),
        recibo('r-4', { total: 400, cobrador: 'c-1',
          facturas: [{ factura_cab_id: 'f-x', monto_pagado: 400 }], medios: [{ medio: 'EFECTIVO', monto: 400 }] }),
        // Sin cobrador: se cobró en la oficina, no entra al control.
        recibo('r-5', { total: 50, facturas: [{ factura_cab_id: 'f-x', monto_pagado: 50 }], medios: [{ medio: 'EFECTIVO', monto: 50 }] }),
        // Anulado: no suma en ningún lado.
        recibo('r-6', { total: 999, estado: 'anulado', cobrador: 'c-1',
          facturas: [{ factura_cab_id: 'f-1', monto_pagado: 999 }], medios: [{ medio: 'EFECTIVO', monto: 999 }] }),
      ],
    });

    const r = await service.cobranza('v-1', 'emp-1');
    const c = r.control;

    expect(c.rendido_base).toBe(100);
    expect(c.en_rendicion_base).toBe(200);
    expect(c.sin_rendir_base).toBe(700);
    expect(c.sin_rendir.map((x: any) => x.id).sort()).toEqual(['r-3', 'r-4']);
    expect(c.cobrado_oficina_base).toBe(50);
    expect(c.cobro_ruta_sin_registrar_base).toBe(500);
    expect(c.cobro_ruta_sin_registrar).toEqual([expect.objectContaining({ id: 'f-2', saldo: 500 })]);
    expect(c.rendiciones).toEqual(
      expect.arrayContaining([
        // total_verificado solo suma los medios verificados uno a uno (en DOBA,
        // REN-000004: 38,1 M declarados y 0,96 M "verificados" con diferencia 0).
        // La diferencia que vale es la de la rendición.
        expect.objectContaining({ codigo: 'RC-1', estado: 'APROBADO', diferencia: 0 }),
      ]),
    );
    expect(r.totales.recibido_base).toBe(1_050);
  });

  it('contado sin cobro en ruta queda como cobrado en caja y fuera del control', async () => {
    const { service } = crear({
      facturas: [
        factura('f-1', { total: 300_000, saldo: 0, condicion: 'Contado',
          formas: [{ dmontipag: 300_000, medio_pago: { descripcion: 'Efectivo' } }] }),
      ],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    expect(r.totales.cobrado_caja_base).toBe(300_000);
    expect(r.totales.cobrado_base).toBe(300_000);
    expect(r.totales.por_cobrar_base).toBe(0);
    expect(r.control.cobro_ruta_sin_registrar_base).toBe(0);
    expect(r.control.sin_rendir_base).toBe(0);
    const medios = Object.fromEntries(r.por_medio.map((m: any) => [m.medio, m.monto_base]));
    expect(medios.EFECTIVO).toBe(300_000);
    expect(r.clientes[0].condiciones).toEqual(['CONTADO']);
  });

  it('dos monedas: el equivalente usa la cotización del documento', async () => {
    const { service } = crear({
      facturas: [factura('f-1', { total: 100, saldo: 40, moneda: 'USD', dticam: 7_000, vence: '2026-03-01' })],
      recibos: [
        recibo('r-1', { total: 60, moneda: 'USD', cotizacion: 7_200,
          facturas: [{ factura_cab_id: 'f-1', monto_pagado: 60 }],
          medios: [{ medio: 'TRANSFERENCIA', monto: 60 }] }),
      ],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    expect(r.totales.vendido_base).toBe(700_000);
    expect(r.totales.cancelado_viaje_base).toBe(432_000);
    expect(r.totales.recibido_base).toBe(432_000);
    expect(r.totales.por_cobrar_base).toBe(280_000);
    // Venció el 01/03 y hoy es 10/03.
    expect(r.totales.por_cobrar_vencido_base).toBe(280_000);
    expect(r.clientes[0].saldo_vencido).toBe(true);
    expect(r.cotizacion_estimada).toBe(false);
  });

  it('crédito: vence con la próxima cuota con saldo, no con la ya pagada', async () => {
    const { service } = crear({
      facturas: [
        factura('f-1', { total: 200, saldo: 100, vence: '2026-02-01',
          cuotas: [
            { dvenccuo: new Date('2026-02-01T12:00:00Z'), saldo_pendiente: 0 },
            { dvenccuo: new Date('2026-04-01T12:00:00Z'), saldo_pendiente: 100 },
          ] }),
      ],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    expect(r.clientes[0].vencimiento).toBe('2026-04-01');
    expect(r.totales.por_cobrar_vencido_base).toBe(0);
    expect(r.clientes[0].condiciones).toEqual(['CREDITO']);
  });

  it('deuda anterior abierta del cliente: facturas fuera del viaje con saldo', async () => {
    const { service } = crear({
      facturas: [factura('f-1', { total: 100, saldo: 0 })],
      deudaAnterior: [
        { cliente_id: 'cli-1', saldo_pendiente: 250_000, dticam: null, moneda: { codigo: 'PYG' } },
        { cliente_id: 'cli-1', saldo_pendiente: 50_000, dticam: null, moneda: { codigo: 'PYG' } },
      ],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    expect(r.clientes[0].deuda_anterior_abierta_base).toBe(300_000);
  });

  it('recibo sin detalle de medios: el total va como "sin detalle"', async () => {
    const { service } = crear({
      facturas: [factura('f-1', { total: 100, saldo: 0 })],
      recibos: [recibo('r-1', { total: 100, facturas: [{ factura_cab_id: 'f-1', monto_pagado: 100 }], medios: [] })],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    const medios = Object.fromEntries(r.por_medio.map((m: any) => [m.medio, m.monto_base]));
    expect(medios.SIN_DETALLE).toBe(100);
    expect(r.totales.recibido_base).toBe(100);
  });

  it('nota de crédito resta de lo vendido del cliente', async () => {
    const { service } = crear({
      facturas: [factura('f-1', { total: 1_000, saldo: 0 })],
      notas: [{ id: 'n-1', cliente_id: 'cli-1', dticam: null, moneda: { codigo: 'PYG' }, nota_credito_subtotal: [{ dtotgralope: 200 }] }],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    expect(r.totales.vendido_base).toBe(800);
    expect(r.clientes[0].vendido_base).toBe(800);
    // Para el cuadre: facturado = cobrado + sin enganchar + pendiente; vendido = facturado − NC.
    expect(r.totales.facturado_base).toBe(1_000);
    expect(r.totales.notas_credito_base).toBe(200);
  });

  it('sin cotización en el documento usa la vigente y lo marca', async () => {
    const { service } = crear({ facturas: [factura('f-1', { total: 100, moneda: 'USD', dticam: null })] });

    const r = await service.cobranza('v-1', 'emp-1');

    expect(r.totales.vendido_base).toBe(100 * 7500);
    expect(r.vendido).toEqual({ USD: 100 });
    expect(r.cotizacion_estimada).toBe(true);
  });

  it('saldo: con cuotas manda la suma de las cuotas (los recibos LEGACY no actualizan la factura)', async () => {
    const { service } = crear({
      facturas: [
        factura('f-1', { total: 1_000, saldo: 1_000, condicion: 'Contado', diferido: true,
          cuotas: [{ dvenccuo: new Date('2026-03-02T12:00:00Z'), saldo_pendiente: 0 }] }),
        // Sin cuotas: queda el saldo de la factura.
        factura('f-2', { total: 500, saldo: 200, vence: '2026-04-01' }),
      ],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    expect(r.totales.por_cobrar_base).toBe(200);
    expect(r.control.cobro_ruta_sin_registrar_base).toBe(0);
  });

  it('recibo LEGACY: destino y medio salen de recibo_cobro_detalle', async () => {
    const { service } = crear({
      facturas: [factura('f-1', { total: 1_000, saldo: 0 })],
      recibos: [
        recibo('r-1', {
          total: 1_500,
          detalle: [
            { monto_pagado: 1_000, factura_cuotas: { factura_cab_id: 'f-1' }, medio_pago: { descripcion: 'Efectivo' } },
            { monto_pagado: 500, factura_cuotas: { factura_cab_id: 'f-vieja' }, medio_pago: { descripcion: 'Cheque' },
              numero_cheque: '77', banco_emisor: 'CONTINENTAL' },
          ],
        }),
      ],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    expect(r.totales.cancelado_viaje_base).toBe(1_000);
    expect(r.totales.deuda_anterior_cobrada_base).toBe(500);
    expect(r.totales.recibido_base).toBe(1_500);
    expect(r.totales.ajustes_base).toBe(0);
    const medios = Object.fromEntries(r.por_medio.map((m: any) => [m.medio, m.monto_base]));
    expect(medios.EFECTIVO).toBe(1_000);
    // El detalle legacy no guarda vencimiento del cheque.
    expect(medios.CHEQUE_SIN_FECHA).toBe(500);
    expect(medios.SIN_DETALLE).toBeUndefined();
    expect(r.cheques).toEqual([expect.objectContaining({ numero: '77', banco: 'CONTINENTAL', vencimiento: null, diferido: false })]);
  });

  it('deuda anterior abierta también usa el saldo de las cuotas', async () => {
    const { service } = crear({
      facturas: [factura('f-1', { total: 100, saldo: 0 })],
      deudaAnterior: [
        // Pagada por un recibo LEGACY: la factura dice 900 pero la cuota está en cero.
        { cliente_id: 'cli-1', saldo_pendiente: 900, dticam: null, moneda: { codigo: 'PYG' }, factura_cuotas: [{ saldo_pendiente: 0 }] },
        { cliente_id: 'cli-1', saldo_pendiente: 300, dticam: null, moneda: { codigo: 'PYG' }, factura_cuotas: [] },
      ],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    expect(r.clientes[0].deuda_anterior_abierta_base).toBe(300);
  });

  it('NC emitida y nunca aplicada: el saldo se topea con total − NC − lo cobrado', async () => {
    // En DOBA, 31 facturas anuladas con NC conservaron el saldo completo.
    const { service } = crear({
      facturas: [
        factura('f-1', { total: 1_000, saldo: 1_000, condicion: 'Contado', diferido: true }),
        // La NC ya bajó el saldo (38 casos en DOBA): no se resta otra vez.
        factura('f-2', { total: 500, saldo: 0 }),
        // NC parcial y un pago en plata: tope = 800 − 300 − 200 = 300.
        factura('f-3', { total: 800, saldo: 500, vence: '2026-04-01',
          cuotas: [{ id: 'cu-3', dvenccuo: new Date('2026-04-01T12:00:00Z'), saldo_pendiente: 500 }] }),
      ],
      ncReferencian: [
        { factura_cab_id: 'f-1', nota_credito_subtotal: [{ dtotgralope: 1_000 }] },
        { factura_cab_id: 'f-2', nota_credito_subtotal: [{ dtotgralope: 500 }] },
        { factura_cab_id: 'f-3', nota_credito_subtotal: [{ dtotgralope: 300 }] },
      ],
      pagosMulti: [{ factura_cab_id: 'f-3', _sum: { monto_pagado: 200, monto_nc: 0 } }],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    expect(r.totales.por_cobrar_base).toBe(300);
    expect(r.totales.nc_sin_aplicar_base).toBe(1_200);
    // La factura anulada con NC ya no es un "cobro en ruta sin registrar".
    expect(r.control.cobro_ruta_sin_registrar_base).toBe(0);
  });

  it('la NC aplicada dentro de un recibo no se cuenta dos veces en el tope', async () => {
    // Recibo canceló 1.000: 400 en plata y 600 con la NC. Saldo 0 → queda 0 y no hay NC "sin aplicar".
    const { service } = crear({
      facturas: [factura('f-1', { total: 1_000, saldo: 0 })],
      ncReferencian: [{ factura_cab_id: 'f-1', nota_credito_subtotal: [{ dtotgralope: 600 }] }],
      pagosMulti: [{ factura_cab_id: 'f-1', _sum: { monto_pagado: 1_000, monto_nc: 600 } }],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    expect(r.totales.por_cobrar_base).toBe(0);
    expect(r.totales.nc_sin_aplicar_base).toBe(0);
  });

  it('el tope también cuenta los pagos de recibos LEGACY (por cuota)', async () => {
    const { service } = crear({
      facturas: [
        factura('f-1', { total: 1_000, saldo: 900,
          cuotas: [{ id: 'cu-1', dvenccuo: new Date('2026-04-01T12:00:00Z'), saldo_pendiente: 900 }] }),
      ],
      ncReferencian: [{ factura_cab_id: 'f-1', nota_credito_subtotal: [{ dtotgralope: 500 }] }],
      pagosLegacy: [{ factura_cuota_id: 'cu-1', _sum: { monto_pagado: 100 } }],
    });

    const r = await service.cobranza('v-1', 'emp-1');

    // 1.000 − 500 − 100 = 400.
    expect(r.totales.por_cobrar_base).toBe(400);
    expect(r.totales.nc_sin_aplicar_base).toBe(500);
  });
});
