import { crearIntegracionMock } from './__helpers__/integracion-mock';
import { DIMENSIONES_ITEM, DIMENSIONES_POR_ORIGEN } from './reglas-mapeo';

// Reglas por dimensión de cabecera (spec 2026-10-13 "reglas de cuenta por
// dimensión", §5.1): cada integrar* arma el contexto del documento una vez y
// resuelve todas sus cuentas con él. Sin reglas, el asiento es el de siempre.

const lineas = (s: any) => s.asientos.crearConfirmado.mock.calls[0][3].lineas;
const dimensiones = (s: any) => s.mapeo.crearResolvedor.mock.calls[0][1].dimensiones;
const facturaVenta = (over: any = {}) => ({
  id: 'f1', empresa_id: 'emp', estado: 'Aprobado', cliente_id: 'cli1', dest: '002', dpunexp: '001', dnumdoc: '0000010',
  dfeemide: new Date('2026-10-13'), cobro_diferido: false, condicion_operacion: { codigo: 2 }, moneda: { codigo: 'PYG' }, dticam: 1,
  factura_subtotales: [{ dtotgralope: 1100, diva10: 100, diva5: 0, dsub10: 1100, dsub5: 0, dsubexe: 0, dsubexo: 0 }],
  factura_forma_pagos: [], ...over,
});
const baseVenta = (f: any, extra: any = {}) => ({
  factura_cab: { findUnique: jest.fn().mockResolvedValue(f) },
  inm_cobros: { findFirst: jest.fn().mockResolvedValue(null) },
  empresas_sucursales: { findFirst: jest.fn().mockResolvedValue({ id: 'suc-cde' }) },
  clientes: { findUnique: jest.fn().mockResolvedValue({ tipo_cliente: 'mayorista' }) },
  ...extra,
});
const regla = (concepto: string, cuenta_id: string, dimension: string, valores: string[]) => ({
  id: 'r1', concepto, orden: 1, modo: 'CUENTA', cuenta_id, estado: 'ACTIVA',
  vigente_desde: new Date('2026-01-01'), vigente_hasta: null, condiciones: [{ dimension, operador: 'EN', valores }],
}) as any;

it('sin reglas, la factura de venta arma exactamente las mismas líneas que antes', async () => {
  const s = crearIntegracionMock(baseVenta(facturaVenta()));
  await s.integrarFacturaVenta('f1');
  expect(lineas(s).map((l: any) => [l.cuenta_id, l.debe_moneda, l.haber_moneda, l.descripcion])).toEqual([
    ['cta-CLIENTES', 1100, 0, 'Clientes'],
    ['cta-VENTAS_10', 0, 1000, 'Ventas 10%'],
    ['cta-IVA_DEBITO_10', 0, 100, 'IVA Débito 10%'],
  ]);
  expect(lineas(s).every((l: any) => l.mapeo_nivel === 'DEFECTO')).toBe(true);
});

it('regla MONEDA=USD en VENTAS_10 cambia solo esa cuenta y deja traza REGLA', async () => {
  const s = crearIntegracionMock(baseVenta(facturaVenta({ moneda: { codigo: 'USD' }, dticam: 7300 })), [], {}, [
    { id: 'r1', concepto: 'VENTAS_10', orden: 1, modo: 'CUENTA', cuenta_id: 'cta-VENTAS_USD', estado: 'ACTIVA',
      vigente_desde: new Date('2026-01-01'), vigente_hasta: null, condiciones: [{ dimension: 'MONEDA', operador: 'EN', valores: ['USD'] }] },
  ]);
  await s.integrarFacturaVenta('f1');
  const ventas = lineas(s).find((l: any) => l.descripcion === 'Ventas 10%');
  expect(ventas).toMatchObject({ cuenta_id: 'cta-VENTAS_USD', mapeo_nivel: 'REGLA', mapeo_regla_id: 'r1', mapeo_concepto: 'VENTAS_10' });
  expect(lineas(s).find((l: any) => l.descripcion === 'Clientes').cuenta_id).toBe('cta-CLIENTES');
});

it('[I1] regla activa con cuenta de agrupación: sin asiento, y la constancia dice qué regla fue', async () => {
  const s = crearIntegracionMock(baseVenta(facturaVenta({ moneda: { codigo: 'USD' }, dticam: 7300 })), [], {}, [
    regla('VENTAS_10', 'grupo-4.1', 'MONEDA', ['USD']),
  ]);
  await expect(s.integrarFacturaVenta('f1')).resolves.toBeUndefined();
  expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
  expect(s.avisosMapeo.registrarFaltaMapeo).toHaveBeenCalledWith({
    empresaId: 'emp', origenTipo: 'factura_cab', origenId: 'f1', conceptos: ['VENTAS_10'],
    motivo: 'CUENTA_INUTILIZABLE', regla: { id: 'r1', orden: 1, concepto: 'VENTAS_10' },
  });
});

it('venta: SUCURSAL sale de la sucursal cuyo punto_establecimiento es el dest de la factura', async () => {
  const s = crearIntegracionMock(baseVenta(facturaVenta()), [], {}, [
    { id: 'r1', concepto: 'VENTAS_10', orden: 1, modo: 'CUENTA', cuenta_id: 'cta-VENTAS_CDE', estado: 'ACTIVA',
      vigente_desde: new Date('2026-01-01'), vigente_hasta: null, condiciones: [{ dimension: 'SUCURSAL', operador: 'EN', valores: ['suc-cde'] }] },
  ]);
  await s.integrarFacturaVenta('f1');
  expect(s.prisma.empresas_sucursales.findFirst.mock.calls[0][0].where).toEqual({ empresa_id: 'emp', punto_establecimiento: '002' });
  expect(lineas(s).find((l: any) => l.descripcion === 'Ventas 10%').cuenta_id).toBe('cta-VENTAS_CDE');
});

it('venta POS: CANAL = POS cuando una forma de pago tiene sesión de caja', async () => {
  const f = facturaVenta({ condicion_operacion: { codigo: 1 }, factura_forma_pagos: [{ medio_pago_id: 1, dmontipag: 1100, dticamtipag: null, sesion_caja_id: 'ses1' }] });
  const s = crearIntegracionMock(baseVenta(f), [], {}, [
    { id: 'r1', concepto: 'VENTAS_10', orden: 1, modo: 'CUENTA', cuenta_id: 'cta-VENTAS_POS', estado: 'ACTIVA',
      vigente_desde: new Date('2026-01-01'), vigente_hasta: null, condiciones: [{ dimension: 'CANAL', operador: 'EN', valores: ['POS'] }] },
  ]);
  await s.integrarFacturaVenta('f1');
  expect(lineas(s).find((l: any) => l.descripcion === 'Ventas 10%').cuenta_id).toBe('cta-VENTAS_POS');
});

const compra = { id: 'c1', empresa_id: 'emp', sucursal_id: 'suc-cde', condicion_compra: 'CREDITO', total: 1100, total_iva_10: 100, total_iva_5: 0,
  moneda: { codigo: 'PYG' }, cotizacion: null, fecha_emision: new Date('2026-10-13'), proveedor_id: 'p1', numero: '1', estado: 'confirmada' };
const baseCompra = (proveedor: any, extra: any = {}) => ({
  compra_cab: { findUnique: jest.fn().mockResolvedValue(compra) },
  config_compras: { findUnique: jest.fn().mockResolvedValue(null) },
  proveedores: { findFirst: jest.fn().mockResolvedValue(proveedor) },
  ...extra,
});

it('regla SUCURSAL en INVENTARIO de una compra (sucursal_id de la compra)', async () => {
  const s = crearIntegracionMock(baseCompra({ tipo_entidad: 'PROVEEDOR_LOCAL', cuenta_contable_id: null }), [], {}, [
    { id: 'r1', concepto: 'INVENTARIO', orden: 1, modo: 'CUENTA', cuenta_id: 'cta-INV_CDE', estado: 'ACTIVA',
      vigente_desde: new Date('2026-01-01'), vigente_hasta: null, condiciones: [{ dimension: 'SUCURSAL', operador: 'EN', valores: ['suc-cde'] }] },
  ]);
  await s.integrarFacturaCompra('c1');
  expect(lineas(s).find((l: any) => l.cuenta_id === 'cta-INV_CDE')).toMatchObject({ mapeo_nivel: 'REGLA', mapeo_concepto: 'INVENTARIO' });
});

it('la cuenta propia del proveedor queda con traza FICHA', async () => {
  const s = crearIntegracionMock(baseCompra({ tipo_entidad: 'PROVEEDOR_LOCAL', cuenta_contable_id: 'cta-PROPIA' }, {
    cont_plan_cuentas: { findUnique: jest.fn().mockResolvedValue({ id: 'cta-PROPIA', active: true, acepta_movimientos: true }) },
  }));
  await s.integrarFacturaCompra('c1');
  const haber = lineas(s).find((l: any) => l.haber_moneda > 0);
  expect(haber).toMatchObject({ cuenta_id: 'cta-PROPIA', mapeo_nivel: 'FICHA', mapeo_concepto: 'FICHA_PROVEEDOR' });
});

it('regla TIPO_PROVEEDOR en el pasivo del proveedor (concepto por tipo de entidad)', async () => {
  const s = crearIntegracionMock(baseCompra({ tipo_entidad: 'PROVEEDOR_EXTERIOR', cuenta_contable_id: null }), [], {}, [
    regla('PROVEEDORES_EXTERIOR', 'cta-PROV_EXT_USD', 'TIPO_PROVEEDOR', ['PROVEEDOR_EXTERIOR']),
  ]);
  await s.integrarFacturaCompra('c1');
  expect(lineas(s).find((l: any) => l.haber_moneda > 0)).toMatchObject({ cuenta_id: 'cta-PROV_EXT_USD', mapeo_nivel: 'REGLA' });
});

it('venta contado: la cuenta de un medio de pago configurado queda con traza MEDIO_PAGO', async () => {
  const f = facturaVenta({ condicion_operacion: { codigo: 1 }, factura_forma_pagos: [{ medio_pago_id: 3, dmontipag: 1100, dticamtipag: null, sesion_caja_id: null }] });
  const s = crearIntegracionMock(baseVenta(f));
  s.mediosPago.resolverPorMedio.mockResolvedValue(new Map([[3, 'cta-BANCO']]));
  await s.integrarFacturaVenta('f1');
  expect(lineas(s).find((l: any) => l.debe_moneda > 0)).toMatchObject({ cuenta_id: 'cta-BANCO', mapeo_nivel: 'MEDIO_PAGO' });
  expect(dimensiones(s).CANAL).toBe('FACTURA');
});

describe('gasto', () => {
  const gasto = (over: any = {}) => ({
    id: 'g1', empresa_id: 'emp', sucursal_id: 'suc-cde', anulado: false, deducible: true, forma_pago: 'CREDITO', proveedor_id: 'p1',
    moneda: { codigo: 'PYG' }, cotizacion: null, fecha_emision: new Date('2026-10-13'), total: 1100, total_iva_10: 100, total_iva_5: 0,
    gasto_det: [{ subtotal: 1100, monto_iva: 100, tipo_gasto: { cuenta_contable_id: 'cta-TIPO', descripcion: 'Fletes' } }],
    ...over,
  });
  const base = (g: any, extra: any = {}) => ({
    gasto_cab: { findUnique: jest.fn().mockResolvedValue(g) },
    config_compras: { findUnique: jest.fn().mockResolvedValue(null) },
    tes_reglas: { findMany: jest.fn().mockResolvedValue([]) },
    proveedores: { findFirst: jest.fn().mockResolvedValue({ tipo_entidad: 'ACREEDOR_VARIO', cuenta_contable_id: null }) },
    // Medios que la empresa marcó como que no mueven dinero (por defecto, ninguno).
    empresa_medio_pago: { findFirst: jest.fn().mockResolvedValue(null) },
    ...extra,
  });

  it('la cuenta del tipo de gasto queda con traza FICHA; regla SUCURSAL en IVA_CREDITO_10', async () => {
    const s = crearIntegracionMock(base(gasto()), [], {}, [regla('IVA_CREDITO_10', 'cta-IVA_CDE', 'SUCURSAL', ['suc-cde'])]);
    await s.integrarGasto('g1');
    expect(lineas(s).find((l: any) => l.cuenta_id === 'cta-TIPO')).toMatchObject({ mapeo_nivel: 'FICHA', mapeo_concepto: 'FICHA_TIPO_GASTO' });
    expect(lineas(s).find((l: any) => l.descripcion === 'IVA Crédito 10%')).toMatchObject({ cuenta_id: 'cta-IVA_CDE', mapeo_nivel: 'REGLA' });
    expect(lineas(s).find((l: any) => l.haber_moneda > 0)).toMatchObject({ cuenta_id: 'cta-ACREEDORES_VARIOS', mapeo_nivel: 'DEFECTO' });
  });

  it('la cuenta contable de la cuenta de tesorería queda con traza FICHA', async () => {
    const s = crearIntegracionMock(base(gasto({ forma_pago: 'CONTADO', proveedor_id: null, cuenta_tesoreria_id: 't1' }), {
      tes_cuentas: { findUnique: jest.fn().mockResolvedValue({ cuenta_contable_id: 'cta-BANCO_USD' }) },
    }));
    await s.integrarGasto('g1');
    expect(lineas(s).find((l: any) => l.haber_moneda > 0)).toMatchObject({ cuenta_id: 'cta-BANCO_USD', mapeo_nivel: 'FICHA', mapeo_concepto: 'FICHA_CUENTA_TESORERIA' });
  });

  it('sin GASTOS_GENERALES mapeado, un gasto cuyos ítems tienen tipo con cuenta igual se contabiliza', async () => {
    const s = crearIntegracionMock(base(gasto()), ['GASTOS_GENERALES']);
    await s.integrarGasto('g1');
    expect(s.asientos.crearConfirmado).toHaveBeenCalledTimes(1);
    expect(lineas(s).find((l: any) => l.cuenta_id === 'cta-TIPO')).toBeTruthy();
  });

  it('sin GASTOS_GENERALES mapeado, un ítem sin tipo de gasto sí lo frena', async () => {
    const s = crearIntegracionMock(base(gasto({ gasto_det: [{ subtotal: 1100, monto_iva: 100, tipo_gasto: null }] })), ['GASTOS_GENERALES']);
    await s.integrarGasto('g1').catch(() => undefined);
    expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
  });

  it('sin IVA_NO_DEDUCIBLE mapeado, un gasto sin factura y sin IVA igual se contabiliza', async () => {
    const s = crearIntegracionMock(
      base(gasto({ deducible: false, total_iva_10: 0, gasto_det: [{ subtotal: 1100, monto_iva: 0, tipo_gasto: { cuenta_contable_id: 'cta-TIPO', descripcion: 'Viático' } }] })),
      ['IVA_NO_DEDUCIBLE'],
    );
    await s.integrarGasto('g1');
    expect(s.asientos.crearConfirmado).toHaveBeenCalledTimes(1);
  });

  it('sin IVA_NO_DEDUCIBLE mapeado, un gasto sin factura con IVA sí lo frena', async () => {
    const s = crearIntegracionMock(base(gasto({ deducible: false })), ['IVA_NO_DEDUCIBLE']);
    await s.integrarGasto('g1').catch(() => undefined);
    expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
  });

  describe('pagado con "Pago por anticipo" (DOBA, factura de despacho 0140296)', () => {
    const anticipo = (over: any = {}) =>
      gasto({
        forma_pago: 'CONTADO', proveedor_id: null, medio_pago_id: 'mp-anticipo',
        medio_pago: { codigo: 11, descripcion: 'Pago por anticipo' }, cuenta_tesoreria_id: 't1', ...over,
      });
    const conCuentaBanco = { tes_cuentas: { findUnique: jest.fn().mockResolvedValue({ cuenta_contable_id: 'cta-BANCO_FAMILIAR' }) } };

    it('acredita la cuenta "Al pagar" del medio (anticipo de despacho), no el banco elegido', async () => {
      const s = crearIntegracionMock(base(anticipo(), conCuentaBanco));
      s.mediosPago.resolverPorMedio.mockResolvedValue(new Map([['mp-anticipo', 'cta-ANTICIPO_DESPACHO']]));
      await s.integrarGasto('g1');
      const haber = lineas(s).filter((l: any) => l.haber_moneda > 0);
      expect(haber).toHaveLength(1);
      expect(haber[0]).toMatchObject({ cuenta_id: 'cta-ANTICIPO_DESPACHO', haber_moneda: 1100, mapeo_nivel: 'MEDIO_PAGO' });
      expect(s.prisma.tes_cuentas.findUnique).not.toHaveBeenCalled();
    });

    it('sin cuenta "Al pagar" no cae al banco: queda pendiente con un aviso', async () => {
      const s = crearIntegracionMock(base(anticipo(), conCuentaBanco));
      await s.integrarGasto('g1').catch(() => undefined);
      expect(s.asientos.crearConfirmado).not.toHaveBeenCalled();
    });
  });

  describe('pagado con "Otro" que la empresa marcó como que no mueve dinero (DOBA, oct-2026)', () => {
    const conOtro = (over: any = {}) =>
      gasto({
        forma_pago: 'CONTADO', proveedor_id: null, medio_pago_id: 'mp-otro',
        medio_pago: { codigo: 22, descripcion: 'Otro' }, cuenta_tesoreria_id: 't1', ...over,
      });
    const banco = { tes_cuentas: { findUnique: jest.fn().mockResolvedValue({ cuenta_contable_id: 'cta-BANCO_FAMILIAR' }) } };

    it('marcado: acredita Otros acreedores (la cuenta "Al pagar" del medio), no el banco', async () => {
      const s = crearIntegracionMock(
        base(conOtro(), { ...banco, empresa_medio_pago: { findFirst: jest.fn().mockResolvedValue({ no_mueve_caja: true }) } }),
      );
      s.mediosPago.resolverPorMedio.mockResolvedValue(new Map([['mp-otro', 'cta-OTROS_ACREEDORES']]));
      await s.integrarGasto('g1');
      const haber = lineas(s).filter((l: any) => l.haber_moneda > 0);
      expect(haber).toHaveLength(1);
      expect(haber[0]).toMatchObject({ cuenta_id: 'cta-OTROS_ACREEDORES', mapeo_nivel: 'MEDIO_PAGO' });
    });

    it('sin marcar: sigue yendo al banco, como siempre', async () => {
      const s = crearIntegracionMock(base(conOtro(), banco));
      s.mediosPago.resolverPorMedio.mockResolvedValue(new Map([['mp-otro', 'cta-OTROS_ACREEDORES']]));
      await s.integrarGasto('g1');
      const haber = lineas(s).filter((l: any) => l.haber_moneda > 0);
      expect(haber[0]).toMatchObject({ cuenta_id: 'cta-BANCO_FAMILIAR' });
    });
  });

  it('gasto en USD pagado desde una cuenta en guaraníes: todo en guaraníes a la cotización del gasto y cuadra', async () => {
    const s = crearIntegracionMock(
      base(gasto({ forma_pago: 'CONTADO', proveedor_id: null, cuenta_tesoreria_id: 't1', moneda: { codigo: 'USD' }, cotizacion: 7300, total: 110, total_iva_10: 10, gasto_det: [{ subtotal: 110, monto_iva: 10, tipo_gasto: { cuenta_contable_id: 'cta-TIPO', descripcion: 'Fletes' } }] }), {
        tes_cuentas: { findUnique: jest.fn().mockResolvedValue({ cuenta_contable_id: 'cta-BANCO_PYG' }) },
      }),
    );
    await s.integrarGasto('g1');
    const ls = lineas(s);
    const debe = ls.reduce((a: number, l: any) => a + Number(l.debe_moneda || 0), 0);
    const haber = ls.reduce((a: number, l: any) => a + Number(l.haber_moneda || 0), 0);
    expect(ls.find((l: any) => l.haber_moneda > 0)).toMatchObject({ cuenta_id: 'cta-BANCO_PYG', haber_moneda: 803000 });
    expect(ls.find((l: any) => l.cuenta_id === 'cta-TIPO')).toMatchObject({ debe_moneda: 730000 });
    expect(debe).toBe(haber);
    expect(s.asientos.crearConfirmado.mock.calls[0][3]).toMatchObject({ moneda_origen: 'PYG', glosa: expect.stringContaining('[USD x 7300]') });
  });
});

it('la vista previa pasa incluirBorradores al resolvedor (opcionesMapeo)', async () => {
  const s = crearIntegracionMock(baseVenta(facturaVenta()));
  s.opcionesMapeo = { incluirBorradores: true };
  await s.integrarFacturaVenta('f1');
  expect(s.mapeo.crearResolvedor.mock.calls[0][1]).toMatchObject({ fecha: new Date('2026-10-13'), incluirBorradores: true });
});

describe('cada documento publica solo las dimensiones de DIMENSIONES_POR_ORIGEN', () => {
  const cabecera = (origen: string) => DIMENSIONES_POR_ORIGEN[origen].filter((d) => !DIMENSIONES_ITEM.has(d)).sort();
  const publicadas = (s: any) => Object.keys(dimensiones(s)).sort();

  it('factura de venta', async () => {
    const s = crearIntegracionMock(baseVenta(facturaVenta()));
    await s.integrarFacturaVenta('f1');
    expect(publicadas(s)).toEqual(cabecera('factura_cab'));
    expect(dimensiones(s)).toEqual({ SUCURSAL: 'suc-cde', MONEDA: 'PYG', TIPO_CLIENTE: 'mayorista', CANAL: 'FACTURA' });
  });

  it('NC de venta: sin formas de pago no publica CANAL', async () => {
    const nc = { id: 'nc1', empresa_id: 'emp', estado: 'Aprobado', cliente_id: 'cli1', dest: '002', dpunexp: '001', dnumdoc: '1',
      dfeemide: new Date('2026-10-13'), moneda: { codigo: 'USD' }, dticam: 7000,
      nota_credito_subtotal: [{ dtotgralope: 11, diva10: 1, diva5: 0, dsub10: 11, dsub5: 0, dsubexe: 0, dsubexo: 0 }] };
    const s = crearIntegracionMock({ ...baseVenta(null), nota_credito_cab: { findUnique: jest.fn().mockResolvedValue(nc) } }, [], {}, [
      regla('VENTAS_10', 'cta-DEV_USD', 'MONEDA', ['USD']),
    ]);
    await s.integrarNotaCredito('nc1');
    expect(publicadas(s)).toEqual(cabecera('nota_credito_cab'));
    expect(dimensiones(s).CANAL).toBeUndefined();
    expect(lineas(s).find((l: any) => l.descripcion === 'Devolución ventas 10%')).toMatchObject({ cuenta_id: 'cta-DEV_USD', mapeo_nivel: 'REGLA' });
  });

  it('factura de compra', async () => {
    const s = crearIntegracionMock(baseCompra({ tipo_entidad: 'PROVEEDOR_LOCAL', cuenta_contable_id: null }));
    await s.integrarFacturaCompra('c1');
    expect(publicadas(s)).toEqual(cabecera('compra_cab'));
    expect(dimensiones(s)).toEqual({ SUCURSAL: 'suc-cde', MONEDA: 'PYG', TIPO_PROVEEDOR: 'PROVEEDOR_LOCAL' });
  });

  it('NC de compra (sin sucursal propia: publica la de su compra)', async () => {
    const nc = { id: 'ncc1', empresa_id: 'emp', proveedor_id: 'p1', compra_id: 'c1', compra_cab: { sucursal_id: 'suc-cde' }, moneda: { codigo: 'PYG' }, cotizacion: null,
      fecha_emision: new Date('2026-10-13'), numero: '1', total: 1100, total_iva_10: 100, total_iva_5: 0 };
    const s = crearIntegracionMock({
      nota_credito_compra_cab: { findUnique: jest.fn().mockResolvedValue(nc) },
      proveedores: { findFirst: jest.fn().mockResolvedValue({ tipo_entidad: 'PROVEEDOR_LOCAL', cuenta_contable_id: null }) },
    });
    await s.integrarNotaCreditoCompra('ncc1');
    expect(publicadas(s)).toEqual(cabecera('nota_credito_compra_cab'));
    expect(dimensiones(s)).toEqual({ SUCURSAL: 'suc-cde', MONEDA: 'PYG', TIPO_PROVEEDOR: 'PROVEEDOR_LOCAL' });
    expect(lineas(s).map((l: any) => [l.cuenta_id, l.debe_moneda, l.haber_moneda])).toEqual([
      ['cta-PROVEEDORES', 1100, 0], ['cta-INVENTARIO', 0, 1000], ['cta-IVA_CREDITO_10', 0, 100],
    ]);
  });

  it('NC de compra sin compra: no publica SUCURSAL', async () => {
    const nc = { id: 'ncc1', empresa_id: 'emp', proveedor_id: 'p1', compra_id: null, compra_cab: null, moneda: { codigo: 'PYG' }, cotizacion: null,
      fecha_emision: new Date('2026-10-13'), numero: '1', total: 1100, total_iva_10: 100, total_iva_5: 0 };
    const s = crearIntegracionMock({
      nota_credito_compra_cab: { findUnique: jest.fn().mockResolvedValue(nc) },
      proveedores: { findFirst: jest.fn().mockResolvedValue({ tipo_entidad: 'PROVEEDOR_LOCAL', cuenta_contable_id: null }) },
    });
    await s.integrarNotaCreditoCompra('ncc1');
    expect(dimensiones(s)).toEqual({ MONEDA: 'PYG', TIPO_PROVEEDOR: 'PROVEEDOR_LOCAL' });
  });

  it('gasto', async () => {
    const g = { id: 'g1', empresa_id: 'emp', sucursal_id: 'suc-cde', anulado: false, deducible: true, forma_pago: 'CREDITO', proveedor_id: 'p1',
      moneda: { codigo: 'PYG' }, cotizacion: null, fecha_emision: new Date('2026-10-13'), total: 1000, total_iva_10: 0, total_iva_5: 0,
      gasto_det: [{ subtotal: 1000, monto_iva: 0, tipo_gasto: null }] };
    const s = crearIntegracionMock({
      gasto_cab: { findUnique: jest.fn().mockResolvedValue(g) },
      config_compras: { findUnique: jest.fn().mockResolvedValue(null) },
      proveedores: { findFirst: jest.fn().mockResolvedValue({ tipo_entidad: 'PROVEEDOR_LOCAL', cuenta_contable_id: null }) },
    });
    await s.integrarGasto('g1');
    expect(publicadas(s)).toEqual(cabecera('gasto_cab'));
  });

  it('despacho de importación (moneda y proveedor del embarque)', async () => {
    const despacho = { id: 'd1', empresa_id: 'emp', estado: 'CONFIRMADO', embarque_id: 'e1', numero_despacho: 'D-1', iva_importacion_gs: 0,
      fecha_despacho: new Date('2026-10-13'), usuario_id: null, embarque: { numero: 'E-1', proveedor_id: 'p1', moneda: { codigo: 'USD' } } };
    const s = crearIntegracionMock({
      imp_despachos: { findUnique: jest.fn().mockResolvedValue(despacho) },
      imp_componentes_costo: { findMany: jest.fn().mockResolvedValue([{ importe_gs: 1000, concepto: { afecta_costo: true } }]) },
      proveedores: { findFirst: jest.fn().mockResolvedValue({ tipo_entidad: 'PROVEEDOR_EXTERIOR', cuenta_contable_id: null }) },
    }, ['IMPORTACIONES_EN_CURSO']);
    await s.integrarCierreDespacho('d1');
    expect(publicadas(s)).toEqual(cabecera('imp_despacho'));
    expect(dimensiones(s)).toEqual({ MONEDA: 'USD', TIPO_PROVEEDOR: 'PROVEEDOR_EXTERIOR' });
  });

  it('cierre de importación (moneda del embarque)', async () => {
    const embarque = { id: 'e1', empresa_id: 'emp', numero: 'IMP-1', estado: 'CERRADO', usuario_id: 'u1', moneda: { codigo: 'USD' }, despacho: null };
    const s = crearIntegracionMock({
      imp_embarques: { findUnique: jest.fn().mockResolvedValue(embarque) },
      imp_despachos: { findMany: jest.fn().mockResolvedValue([{ id: 'd1' }]) },
      imp_componentes_costo: { findMany: jest.fn().mockResolvedValue([]), findFirst: jest.fn() },
      tes_movimientos: { findMany: jest.fn().mockResolvedValue([]) },
      nota_credito_compra_cab: { findMany: jest.fn().mockResolvedValue([]) },
      cont_documentos: {
        findFirst: jest.fn().mockResolvedValue(null),
        findMany: jest.fn().mockResolvedValue([{ id: 'doc-d1' }]),
        create: jest.fn().mockResolvedValue({ id: 'doc-cierre' }),
      },
      cont_asientos: { findMany: jest.fn().mockResolvedValue([{ id: 'a1' }]) },
      cont_asientos_det: { aggregate: jest.fn().mockResolvedValue({ _sum: { debe_pyg: 800, haber_pyg: 0 } }) },
    });
    await s.integrarCierreImportacion('e1');
    expect(publicadas(s)).toEqual(cabecera('imp_cierre'));
    expect(lineas(s).every((l: any) => l.mapeo_nivel === 'DEFECTO')).toBe(true);
  });
});

describe('traza por línea (no por cuenta)', () => {
  const conCinco = () => facturaVenta({
    factura_subtotales: [{ dtotgralope: 1310, diva10: 100, diva5: 10, dsub10: 1100, dsub5: 210, dsubexe: 0, dsubexo: 0 }],
  });
  const compartidas: Record<string, string> = { VENTAS_10: 'cta-VENTAS', VENTAS_5: 'cta-VENTAS', IVA_DEBITO_10: 'cta-IVA', IVA_DEBITO_5: 'cta-IVA' };
  const traza = (s: any, desc: string) => {
    const l = lineas(s).find((x: any) => x.descripcion === desc);
    return [l.cuenta_id, l.mapeo_concepto, l.mapeo_nivel, l.mapeo_regla_id];
  };

  it('dos conceptos con la misma cuenta: cada línea dice su concepto, resuelva primero cuál resuelva', async () => {
    for (const lento of ['VENTAS_10', 'VENTAS_5', 'IVA_DEBITO_10', 'IVA_DEBITO_5']) {
      const s = crearIntegracionMock(baseVenta(conCinco()));
      s.mapeo.getCuentaPorConcepto.mockImplementation(async (_e: string, c: string) => {
        if (c === lento) await new Promise((r) => setTimeout(r, 5)); // invierte el orden en que terminan
        return compartidas[c] ?? `cta-${c}`;
      });
      await s.integrarFacturaVenta('f1');
      expect(traza(s, 'Ventas 10%')).toEqual(['cta-VENTAS', 'VENTAS_10', 'DEFECTO', undefined]);
      expect(traza(s, 'Ventas 5%')).toEqual(['cta-VENTAS', 'VENTAS_5', 'DEFECTO', undefined]);
      expect(traza(s, 'IVA Débito 10%')).toEqual(['cta-IVA', 'IVA_DEBITO_10', 'DEFECTO', undefined]);
      expect(traza(s, 'IVA Débito 5%')).toEqual(['cta-IVA', 'IVA_DEBITO_5', 'DEFECTO', undefined]);
      expect(lineas(s).some((l: any) => 'concepto' in l)).toBe(false);
    }
  });

  it('una regla cuya cuenta es la por defecto de otro concepto: solo la línea de la regla lleva mapeo_regla_id', async () => {
    const s = crearIntegracionMock(baseVenta(conCinco()), [], {}, [regla('VENTAS_10', 'cta-VENTAS_5', 'MONEDA', ['PYG'])]);
    await s.integrarFacturaVenta('f1');
    expect(traza(s, 'Ventas 10%')).toEqual(['cta-VENTAS_5', 'VENTAS_10', 'REGLA', 'r1']);
    expect(traza(s, 'Ventas 5%')).toEqual(['cta-VENTAS_5', 'VENTAS_5', 'DEFECTO', undefined]);
  });

  it('despacho: reglas por MONEDA y TIPO_PROVEEDOR del embarque', async () => {
    const despacho = { id: 'd1', empresa_id: 'emp', estado: 'CONFIRMADO', embarque_id: 'e1', numero_despacho: 'D-1', iva_importacion_gs: 100,
      fecha_despacho: new Date('2026-10-13'), usuario_id: null, embarque: { numero: 'E-1', proveedor_id: 'p1', moneda: { codigo: 'USD' } } };
    const s = crearIntegracionMock({
      imp_despachos: { findUnique: jest.fn().mockResolvedValue(despacho) },
      imp_componentes_costo: { findMany: jest.fn().mockResolvedValue([{ importe_gs: 1100, concepto: { afecta_costo: true } }]) },
      proveedores: { findFirst: jest.fn().mockResolvedValue({ tipo_entidad: 'PROVEEDOR_EXTERIOR', cuenta_contable_id: null }) },
    }, ['IMPORTACIONES_EN_CURSO'], {}, [
      regla('IMPORTACION_TRIBUTOS', 'cta-TRIB_USD', 'MONEDA', ['USD']),
      { ...regla('PROVEEDORES_EXTERIOR', 'cta-PROV_EXT', 'TIPO_PROVEEDOR', ['PROVEEDOR_EXTERIOR']), id: 'r2' },
    ]);
    await s.integrarCierreDespacho('d1');
    expect(traza(s, 'Costo de importación')).toEqual(['cta-TRIB_USD', 'IMPORTACION_TRIBUTOS', 'REGLA', 'r1']);
    expect(traza(s, 'IVA Crédito importación')).toEqual(['cta-IVA_CREDITO_10', 'IVA_CREDITO_10', 'DEFECTO', undefined]);
    expect(traza(s, 'Proveedores y fisco (despacho D-1)')).toEqual(['cta-PROV_EXT', 'PROVEEDORES_EXTERIOR', 'REGLA', 'r2']);
  });

  it('NC de compra: regla TIPO_PROVEEDOR en INVENTARIO; el proveedor por defecto', async () => {
    const nc = { id: 'ncc1', empresa_id: 'emp', proveedor_id: 'p1', compra_id: null, moneda: { codigo: 'PYG' }, cotizacion: null,
      fecha_emision: new Date('2026-10-13'), numero: '1', total: 1100, total_iva_10: 100, total_iva_5: 0 };
    const s = crearIntegracionMock({
      nota_credito_compra_cab: { findUnique: jest.fn().mockResolvedValue(nc) },
      proveedores: { findFirst: jest.fn().mockResolvedValue({ tipo_entidad: 'PROVEEDOR_LOCAL', cuenta_contable_id: null }) },
    }, [], {}, [regla('INVENTARIO', 'cta-INV_LOCAL', 'TIPO_PROVEEDOR', ['PROVEEDOR_LOCAL'])]);
    await s.integrarNotaCreditoCompra('ncc1');
    expect(traza(s, 'Reversión Inventario/Gasto')).toEqual(['cta-INV_LOCAL', 'INVENTARIO', 'REGLA', 'r1']);
    expect(traza(s, 'Proveedor (NC compra)')).toEqual(['cta-PROVEEDORES', 'PROVEEDORES', 'DEFECTO', undefined]);
    expect(traza(s, 'Reversión IVA Crédito 10%')).toEqual(['cta-IVA_CREDITO_10', 'IVA_CREDITO_10', 'DEFECTO', undefined]);
  });
});
