import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
import { alcanceSucursalUsuario, filtroFiscalPorSucursal } from 'src/utils/alcance-sucursal';
import { assertRangoFechas } from 'src/common/utils/fecha-rango.util';

type RentabilidadQuery = {
  fecha_desde?: string;
  fecha_hasta?: string;
  limit?: string | number;
  sucursal_id?: string;
  deposito_id?: string;
  producto_id?: string;
  cliente_id?: string;
};

type DetalleRentabilidad = {
  producto_id: string;
  factura_cab_id: string;
  dcantproser: Prisma.Decimal;
  dtotopeitem: Prisma.Decimal;
  costo_unitario_venta: Prisma.Decimal | null;
  costo_total_venta: Prisma.Decimal | null;
  productos: {
    id: string;
    descripcion: string;
    cod_producto: string | null;
    precio_costo: Prisma.Decimal | null;
    categoria: {
      id: string;
      descripcion: string;
    } | null;
  };
  factura_cab: {
    cliente_id: string;
    dest: string | null;
    clientes: {
      id: string;
      personas: {
        razon_social: string;
        ruc: string | null;
        dv: string | null;
      };
    };
  };
  deposito: {
    sucursal_id: string;
    sucursal: {
      id: string;
      descripcion: string;
    };
  } | null;
};

type ProductoRentabilidad = {
  producto_id: string;
  codigo: string;
  descripcion: string;
  categoria_id: string | null;
  categoria: string;
  cantidad_vendida: number;
  ingresos: number;
  costo: number;
  utilidad: number;
  margen_pct: number;
  utilidad_neta: number;
  margen_neto_pct: number;
  facturas: number;
};

type ClienteRentabilidad = {
  cliente_id: string;
  razon_social: string;
  ruc: string;
  cantidad_vendida: number;
  ingresos: number;
  costo: number;
  utilidad: number;
  margen_pct: number;
  utilidad_neta: number;
  margen_neto_pct: number;
  facturas: number;
};

type SucursalRentabilidad = {
  sucursal_id: string;
  sucursal: string;
  cantidad_vendida: number;
  ingresos: number;
  costo: number;
  utilidad: number;
  margen_pct: number;
  utilidad_neta: number;
  margen_neto_pct: number;
  facturas: number;
};

type OverlayFactura = {
  ingresos: number;
  overlayTotal: number;
  comision: number;
  cobranza: number;
  financiero: number;
  mora: number;
};

@Injectable()
export class ReportesRentabilidadService {
  constructor(private readonly prisma: PrismaService) {}

  async getRentabilidadProductos(empresaId: string, query: RentabilidadQuery, usuarioId?: string) {
    const detalles = await this.fetchDetalles(empresaId, query, usuarioId);
    const { productos, resumen } = await this.aggregate(detalles, empresaId);
    const limit = this.resolveLimit(query.limit, 100);

    return {
      filtros: this.buildFiltros(query),
      resumen,
      total: productos.length,
      items: productos.slice(0, limit),
    };
  }

  async getRentabilidadClientes(empresaId: string, query: RentabilidadQuery, usuarioId?: string) {
    const detalles = await this.fetchDetalles(empresaId, query, usuarioId);
    const { clientes, resumen } = await this.aggregate(detalles, empresaId);
    const limit = this.resolveLimit(query.limit, 100);

    return {
      filtros: this.buildFiltros(query),
      resumen,
      total: clientes.length,
      items: clientes.slice(0, limit),
    };
  }

  async getRentabilidadSucursales(empresaId: string, query: RentabilidadQuery, usuarioId?: string) {
    const detalles = await this.fetchDetalles(empresaId, query, usuarioId);
    const { sucursales, resumen } = await this.aggregate(detalles, empresaId);
    const limit = this.resolveLimit(query.limit, 100);

    return {
      filtros: this.buildFiltros(query),
      resumen,
      total: sucursales.length,
      items: sucursales.slice(0, limit),
    };
  }

  async getRentabilidadDashboard(empresaId: string, query: RentabilidadQuery, usuarioId?: string) {
    const detalles = await this.fetchDetalles(empresaId, query, usuarioId);
    const { productos, clientes, sucursales, resumen } = await this.aggregate(detalles, empresaId);

    return {
      filtros: this.buildFiltros(query),
      resumen,
      total_productos: productos.length,
      total_clientes: clientes.length,
      total_sucursales: sucursales.length,
      productos_con_perdida: productos.filter((item) => item.utilidad < 0).length,
      clientes_con_perdida: clientes.filter((item) => item.utilidad < 0).length,
      top_productos: productos.slice(0, 5),
      top_clientes: clientes.slice(0, 5),
      top_sucursales: sucursales.slice(0, 5),
    };
  }

  private async fetchDetalles(empresaId: string, query: RentabilidadQuery, usuarioId?: string) {
    // Evita escaneos gigantes: el rango de fechas no puede superar ~1 año.
    assertRangoFechas(query.fecha_desde, query.fecha_hasta);
    // La sucursal de una venta es la del establecimiento de la factura (`dest`),
    // no la del depósito de cada línea: una línea descontada de un depósito
    // equivocado no convierte la venta en otra sucursal. El filtro "Sucursal" de
    // la pantalla se cruza con el alcance del usuario (una sucursal que no tiene
    // asignada devuelve vacío).
    const alcance = await alcanceSucursalUsuario(this.prisma, usuarioId, empresaId);
    const whereFacturaCab = this.buildFacturaCabWhere(
      empresaId,
      query,
      await filtroFiscalPorSucursal(this.prisma, alcance, query.sucursal_id),
    );
    const whereDetalle: Prisma.factura_detWhereInput = {
      factura_cab: whereFacturaCab,
    };

    if (query.producto_id) {
      whereDetalle.producto_id = query.producto_id;
    }

    if (query.deposito_id) {
      whereDetalle.deposito_id = query.deposito_id;
    }

    const detalles = await this.prisma.factura_det.findMany({
      where: whereDetalle,
      select: {
        producto_id: true,
        factura_cab_id: true,
        dcantproser: true,
        dtotopeitem: true,
        costo_unitario_venta: true,
        costo_total_venta: true,
        productos: {
          select: {
            id: true,
            descripcion: true,
            cod_producto: true,
            precio_costo: true,
            categoria: {
              select: {
                id: true,
                descripcion: true,
              },
            },
          },
        },
        factura_cab: {
          select: {
            cliente_id: true,
            dest: true,
            clientes: {
              select: {
                id: true,
                personas: {
                  select: {
                    razon_social: true,
                    ruc: true,
                    dv: true,
                  },
                },
              },
            },
          },
        },
        deposito: {
          select: {
            sucursal_id: true,
            sucursal: {
              select: {
                id: true,
                descripcion: true,
              },
            },
          },
        },
      },
    });

    return detalles as DetalleRentabilidad[];
  }

  private async aggregate(detalles: DetalleRentabilidad[], empresaId: string) {
    const [overlayPorFactura, sucursalesEmpresa] = await Promise.all([
      this.computeOverlayPorFactura(empresaId, detalles),
      this.prisma.empresas_sucursales.findMany({
        where: { empresa_id: empresaId },
        select: { id: true, descripcion: true, punto_establecimiento: true },
      }),
    ]);
    const sucursalPorEstablecimiento = new Map(
      sucursalesEmpresa
        .filter((suc) => suc.punto_establecimiento)
        .map((suc) => [suc.punto_establecimiento as string, suc]),
    );

    const productoMap = new Map<
      string,
      Omit<ProductoRentabilidad, 'utilidad' | 'margen_pct' | 'utilidad_neta' | 'margen_neto_pct'> & {
        costoNeto: number;
        facturasSet: Set<string>;
      }
    >();
    const clienteMap = new Map<
      string,
      Omit<ClienteRentabilidad, 'utilidad' | 'margen_pct' | 'utilidad_neta' | 'margen_neto_pct'> & {
        costoNeto: number;
        facturasSet: Set<string>;
      }
    >();
    const sucursalMap = new Map<
      string,
      Omit<SucursalRentabilidad, 'utilidad' | 'margen_pct' | 'utilidad_neta' | 'margen_neto_pct'> & {
        costoNeto: number;
        facturasSet: Set<string>;
      }
    >();

    let ingresosTotal = 0;
    let costoTotal = 0;
    let costoNetoTotal = 0;
    let cantidadTotal = 0;
    let comisionTotal = 0;
    let cobranzaTotal = 0;
    let financieroTotal = 0;
    let moraTotal = 0;

    for (const det of detalles) {
      const cantidad = this.toNumber(det.dcantproser);
      const ingresos = this.toNumber(det.dtotopeitem);
      const costo = this.resolveCostoDetalle(det, cantidad);

      const overlayFactura = overlayPorFactura.get(det.factura_cab_id);
      const overlayShare =
        overlayFactura && overlayFactura.ingresos > 0
          ? overlayFactura.overlayTotal * (ingresos / overlayFactura.ingresos)
          : 0;
      const costoNeto = costo + overlayShare;

      if (overlayFactura && overlayFactura.ingresos > 0) {
        const proporcion = ingresos / overlayFactura.ingresos;
        comisionTotal += overlayFactura.comision * proporcion;
        cobranzaTotal += overlayFactura.cobranza * proporcion;
        financieroTotal += overlayFactura.financiero * proporcion;
        moraTotal += overlayFactura.mora * proporcion;
      }

      ingresosTotal += ingresos;
      costoTotal += costo;
      costoNetoTotal += costoNeto;
      cantidadTotal += cantidad;

      const productoId = det.producto_id;
      const productoEntry = productoMap.get(productoId) || {
        producto_id: productoId,
        codigo: det.productos?.cod_producto || 'S/C',
        descripcion: det.productos?.descripcion || 'Sin descripción',
        categoria_id: det.productos?.categoria?.id || null,
        categoria: det.productos?.categoria?.descripcion || 'Sin categoría',
        cantidad_vendida: 0,
        ingresos: 0,
        costo: 0,
        costoNeto: 0,
        facturas: 0,
        facturasSet: new Set<string>(),
      };
      productoEntry.cantidad_vendida += cantidad;
      productoEntry.ingresos += ingresos;
      productoEntry.costo += costo;
      productoEntry.costoNeto += costoNeto;
      productoEntry.facturasSet.add(det.factura_cab_id);
      productoMap.set(productoId, productoEntry);

      const clienteId = det.factura_cab?.cliente_id;
      if (!clienteId) continue;

      const razonSocial = det.factura_cab?.clientes?.personas?.razon_social || 'Sin nombre';
      const rucRaw = det.factura_cab?.clientes?.personas?.ruc || '';
      const dv = det.factura_cab?.clientes?.personas?.dv;
      const ruc = rucRaw ? `${rucRaw}-${dv ?? ''}` : '-';

      const clienteEntry = clienteMap.get(clienteId) || {
        cliente_id: clienteId,
        razon_social: razonSocial,
        ruc,
        cantidad_vendida: 0,
        ingresos: 0,
        costo: 0,
        costoNeto: 0,
        facturas: 0,
        facturasSet: new Set<string>(),
      };
      clienteEntry.cantidad_vendida += cantidad;
      clienteEntry.ingresos += ingresos;
      clienteEntry.costo += costo;
      clienteEntry.costoNeto += costoNeto;
      clienteEntry.facturasSet.add(det.factura_cab_id);
      clienteMap.set(clienteId, clienteEntry);

      // Por establecimiento de la factura; el depósito queda sólo como respaldo
      // para una factura cuyo `dest` no coincide con ninguna sucursal.
      const sucursalFactura = det.factura_cab?.dest ? sucursalPorEstablecimiento.get(det.factura_cab.dest) : undefined;
      const sucursalId = sucursalFactura?.id || det.deposito?.sucursal_id || 'SIN_SUCURSAL';
      const sucursalDesc = sucursalFactura?.descripcion || det.deposito?.sucursal?.descripcion || 'Sin sucursal';
      const sucursalEntry = sucursalMap.get(sucursalId) || {
        sucursal_id: sucursalId,
        sucursal: sucursalDesc,
        cantidad_vendida: 0,
        ingresos: 0,
        costo: 0,
        costoNeto: 0,
        facturas: 0,
        facturasSet: new Set<string>(),
      };
      sucursalEntry.cantidad_vendida += cantidad;
      sucursalEntry.ingresos += ingresos;
      sucursalEntry.costo += costo;
      sucursalEntry.costoNeto += costoNeto;
      sucursalEntry.facturasSet.add(det.factura_cab_id);
      sucursalMap.set(sucursalId, sucursalEntry);
    }

    const conNeto = <T extends { ingresos: number; costo: number; costoNeto: number }>(item: T) => {
      const utilidad = this.round(item.ingresos - item.costo, 4);
      const margen = item.ingresos > 0 ? this.round((utilidad / item.ingresos) * 100, 2) : 0;
      const utilidadNeta = this.round(item.ingresos - item.costoNeto, 4);
      const margenNeto = item.ingresos > 0 ? this.round((utilidadNeta / item.ingresos) * 100, 2) : 0;
      return { utilidad, margen, utilidadNeta, margenNeto };
    };

    const productos: ProductoRentabilidad[] = Array.from(productoMap.values())
      .map((item) => {
        const { utilidad, margen, utilidadNeta, margenNeto } = conNeto(item);
        return {
          producto_id: item.producto_id,
          codigo: item.codigo,
          descripcion: item.descripcion,
          categoria_id: item.categoria_id,
          categoria: item.categoria,
          cantidad_vendida: this.round(item.cantidad_vendida, 4),
          ingresos: this.round(item.ingresos, 4),
          costo: this.round(item.costo, 4),
          utilidad,
          margen_pct: margen,
          utilidad_neta: utilidadNeta,
          margen_neto_pct: margenNeto,
          facturas: item.facturasSet.size,
        };
      })
      .sort((a, b) => b.utilidad - a.utilidad || b.ingresos - a.ingresos);

    const clientes: ClienteRentabilidad[] = Array.from(clienteMap.values())
      .map((item) => {
        const { utilidad, margen, utilidadNeta, margenNeto } = conNeto(item);
        return {
          cliente_id: item.cliente_id,
          razon_social: item.razon_social,
          ruc: item.ruc,
          cantidad_vendida: this.round(item.cantidad_vendida, 4),
          ingresos: this.round(item.ingresos, 4),
          costo: this.round(item.costo, 4),
          utilidad,
          margen_pct: margen,
          utilidad_neta: utilidadNeta,
          margen_neto_pct: margenNeto,
          facturas: item.facturasSet.size,
        };
      })
      .sort((a, b) => b.utilidad - a.utilidad || b.ingresos - a.ingresos);

    const sucursales: SucursalRentabilidad[] = Array.from(sucursalMap.values())
      .map((item) => {
        const { utilidad, margen, utilidadNeta, margenNeto } = conNeto(item);
        return {
          sucursal_id: item.sucursal_id,
          sucursal: item.sucursal,
          cantidad_vendida: this.round(item.cantidad_vendida, 4),
          ingresos: this.round(item.ingresos, 4),
          costo: this.round(item.costo, 4),
          utilidad,
          margen_pct: margen,
          utilidad_neta: utilidadNeta,
          margen_neto_pct: margenNeto,
          facturas: item.facturasSet.size,
        };
      })
      .sort((a, b) => b.utilidad - a.utilidad || b.ingresos - a.ingresos);

    const utilidadTotal = this.round(ingresosTotal - costoTotal, 4);
    const margenGeneral = ingresosTotal > 0 ? this.round((utilidadTotal / ingresosTotal) * 100, 2) : 0;
    const utilidadNetaTotal = this.round(ingresosTotal - costoNetoTotal, 4);
    const margenNetoGeneral = ingresosTotal > 0 ? this.round((utilidadNetaTotal / ingresosTotal) * 100, 2) : 0;

    return {
      productos,
      clientes,
      sucursales,
      resumen: {
        ingresos_total: this.round(ingresosTotal, 4),
        costo_total: this.round(costoTotal, 4),
        utilidad_total: utilidadTotal,
        margen_pct: margenGeneral,
        utilidad_neta_total: utilidadNetaTotal,
        margen_neto_pct: margenNetoGeneral,
        desglose_costos_credito: {
          comision_venta: this.round(comisionTotal, 4),
          costo_cobranza: this.round(cobranzaTotal, 4),
          costo_financiero: this.round(financieroTotal, 4),
          mora_esperada: this.round(moraTotal, 4),
        },
        cantidad_total: this.round(cantidadTotal, 4),
        lineas: detalles.length,
      },
    };
  }

  /**
   * Overlay de costos de operación a crédito por factura (Fase 2). Se calcula a nivel
   * factura (no línea) porque comisión/cobranza/financiero/mora dependen de la factura
   * completa (vendedor, plazo, si tuvo cuotas), y después se prorratea a cada línea según
   * su participación en el ingreso de la factura. Devuelve mapa vacío si no hay detalles;
   * si no existe config para la empresa, se asume desactivada (overlay 0, neto = bruto)
   * pero igual se calcula la comisión real/por vendedor, que no depende de esa config.
   */
  private async computeOverlayPorFactura(
    empresaId: string,
    detalles: DetalleRentabilidad[],
  ): Promise<Map<string, OverlayFactura>> {
    const overlayMap = new Map<string, OverlayFactura>();
    if (detalles.length === 0) return overlayMap;

    const porFactura = new Map<string, { ingresos: number; costo: number }>();
    for (const det of detalles) {
      const facturaId = det.factura_cab_id;
      const entry = porFactura.get(facturaId) || { ingresos: 0, costo: 0 };
      entry.ingresos += this.toNumber(det.dtotopeitem);
      entry.costo += this.resolveCostoDetalle(det, this.toNumber(det.dcantproser));
      porFactura.set(facturaId, entry);
    }

    const facturaIds = [...porFactura.keys()];

    const [config, facturas, cuotasPorFactura, comisionesPorFactura] = await Promise.all([
      this.prisma.costos_operacion_credito.findUnique({ where: { empresa_id: empresaId } }),
      this.prisma.factura_cab.findMany({
        where: { id: { in: facturaIds } },
        select: {
          id: true,
          dfeemide: true,
          vendedor_id: true,
          vendedor: { select: { comision_venta: true, comision_porcentaje: true } },
        },
      }),
      this.prisma.factura_cuotas.groupBy({
        by: ['factura_cab_id'],
        where: { factura_cab_id: { in: facturaIds } },
        _max: { dvenccuo: true },
        _count: { _all: true },
      }),
      this.prisma.comisiones.groupBy({
        by: ['factura_id'],
        where: { factura_id: { in: facturaIds }, tipo: 'venta' },
        _sum: { monto_comision: true },
      }),
    ]);

    const facturaInfoMap = new Map(facturas.map((f) => [f.id, f]));
    const cuotasInfoMap = new Map(cuotasPorFactura.map((c) => [c.factura_cab_id, c]));
    const comisionRealMap = new Map(
      comisionesPorFactura
        .filter((c) => c.factura_id)
        .map((c) => [c.factura_id as string, this.toNumber(c._sum.monto_comision)]),
    );

    const configActivo = config?.activo ?? false;
    const comisionPctDefault = this.toNumber(config?.comision_venta_pct);
    const cobranzaPct = this.toNumber(config?.costo_cobranza_pct);
    const financieroPctMensual = this.toNumber(config?.costo_financiero_pct_mensual);
    const moraPct = this.toNumber(config?.mora_esperada_pct);

    for (const [facturaId, datos] of porFactura) {
      const facturaInfo = facturaInfoMap.get(facturaId);
      const cuotasInfo = cuotasInfoMap.get(facturaId);
      const esCredito = !!cuotasInfo && cuotasInfo._count._all > 0;

      // 1) Comisión de venta: aplica a toda venta (contado o crédito), no depende de `activo`.
      //    Prioridad: comisión real registrada > % propio del vendedor > default de la config.
      let comision = comisionRealMap.get(facturaId) ?? 0;
      if (comision <= 0 && facturaInfo?.vendedor_id) {
        const pctVendedor =
          this.toNumber(facturaInfo.vendedor?.comision_venta) ||
          this.toNumber(facturaInfo.vendedor?.comision_porcentaje);
        if (pctVendedor > 0) comision = datos.ingresos * (pctVendedor / 100);
      }
      if (comision <= 0 && configActivo && comisionPctDefault > 0) {
        comision = datos.ingresos * (comisionPctDefault / 100);
      }

      let cobranza = 0;
      let financiero = 0;
      let mora = 0;

      if (esCredito && configActivo) {
        cobranza = datos.ingresos * (cobranzaPct / 100);
        mora = datos.ingresos * (moraPct / 100);

        if (financieroPctMensual > 0 && facturaInfo?.dfeemide && cuotasInfo?._max.dvenccuo) {
          const dias = Math.max(0, (cuotasInfo._max.dvenccuo.getTime() - facturaInfo.dfeemide.getTime()) / 86400000);
          const meses = Math.max(1, dias / 30);
          financiero = datos.costo * (financieroPctMensual / 100) * meses;
        }
      }

      overlayMap.set(facturaId, {
        ingresos: datos.ingresos,
        overlayTotal: comision + cobranza + financiero + mora,
        comision,
        cobranza,
        financiero,
        mora,
      });
    }

    return overlayMap;
  }

  private resolveCostoDetalle(det: DetalleRentabilidad, cantidad: number): number {
    // 1) Costo real capturado en la factura (FIFO/lote al momento de la venta).
    const costoTotal = this.toNumber(det.costo_total_venta);
    if (costoTotal > 0) return costoTotal;

    const costoUnit = this.toNumber(det.costo_unitario_venta);
    if (costoUnit > 0 && cantidad > 0) {
      return this.round(costoUnit * cantidad, 4);
    }

    // 2) Fallback: costo actual del producto (precio_costo) × cantidad. Cubre ventas
    // hechas antes de cargar el costo, o migradas sin costo. Si el producto tampoco
    // tiene precio_costo, queda 0 (no hay costo cargado → margen sale 100%).
    const precioCostoProducto = this.toNumber(det.productos?.precio_costo);
    if (precioCostoProducto > 0 && cantidad > 0) {
      return this.round(precioCostoProducto * cantidad, 4);
    }

    return 0;
  }

  /**
   * `filtroSucursal` ya resuelto a `{ dest: { in: [...] } }`: la factura no
   * tiene `sucursal_id`, se ata por el punto de establecimiento.
   */
  private buildFacturaCabWhere(
    empresaId: string,
    query: RentabilidadQuery,
    filtroSucursal?: { dest: { in: string[] } } | null,
  ): Prisma.factura_cabWhereInput {
    const where: Prisma.factura_cabWhereInput = {
      empresa_id: empresaId,
      ...(filtroSucursal ?? {}),
      estado: {
        notIn: ['ANULADA', 'Anulada', 'anulada', 'ANULADO', 'Anulado', 'anulado'],
      },
      // Solo ventas válidas (mismo criterio fiscal que el Libro IVA Ventas / Resumen):
      // aprobadas en SIFEN (o sin SIFEN) y sin cancelación aprobada. Evita contar
      // rechazadas y pendientes/error como ingresos.
      AND: [
        { OR: [{ estado_sifen: 'Aprobado' }, { estado_sifen: null }] },
        {
          OR: [
            { evento_aplicado: null },
            { evento_aplicado: { notIn: ['ECAN', 'EINO', 'EINU'] } },
            { estado_evento: { not: 'Aprobado' } },
          ],
        },
      ],
    };

    if (query.cliente_id) {
      where.cliente_id = query.cliente_id;
    }

    if (query.fecha_desde || query.fecha_hasta) {
      where.dfeemide = {};
      if (query.fecha_desde) where.dfeemide.gte = this.parseDateBoundary(query.fecha_desde, false);
      if (query.fecha_hasta) where.dfeemide.lte = this.parseDateBoundary(query.fecha_hasta, true);
    }

    return where;
  }

  private parseDateBoundary(value: string, endOfDay: boolean): Date {
    const parts = value.split('-').map(Number);
    if (parts.length === 3 && parts.every((n) => Number.isFinite(n))) {
      const [year, month, day] = parts;
      return endOfDay
        ? new Date(year, month - 1, day, 23, 59, 59, 999)
        : new Date(year, month - 1, day, 0, 0, 0, 0);
    }

    const parsed = new Date(value);
    if (Number.isNaN(parsed.getTime())) {
      return endOfDay ? new Date(9999, 0, 1, 23, 59, 59, 999) : new Date(1970, 0, 1);
    }

    if (endOfDay) {
      parsed.setHours(23, 59, 59, 999);
    } else {
      parsed.setHours(0, 0, 0, 0);
    }
    return parsed;
  }

  private buildFiltros(query: RentabilidadQuery) {
    return {
      fecha_desde: query.fecha_desde || null,
      fecha_hasta: query.fecha_hasta || null,
      sucursal_id: query.sucursal_id || null,
      deposito_id: query.deposito_id || null,
      producto_id: query.producto_id || null,
      cliente_id: query.cliente_id || null,
    };
  }

  private resolveLimit(value: string | number | undefined, fallback: number): number {
    const parsed = Number(value);
    if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
    return Math.min(500, Math.trunc(parsed));
  }

  private toNumber(value: Prisma.Decimal | number | string | null | undefined): number {
    const n = Number(value ?? 0);
    return Number.isFinite(n) ? n : 0;
  }

  private round(value: number, decimals = 2): number {
    const factor = Math.pow(10, decimals);
    return Math.round((value + Number.EPSILON) * factor) / factor;
  }
}
