import { BadRequestException, Injectable, NotFoundException, Logger } from '@nestjs/common';
import { Prisma, presupuesto_estado } from '@prisma/client';
import { AuditService } from 'src/audit/audit.service';
import { ListaPreciosService } from 'src/lista-precios/lista-precios.service';
import { MonedasService } from 'src/monedas/monedas.service';
import { OfertasService } from 'src/ofertas/ofertas.service';
import { PrismaService } from 'src/prisma/prisma.service';
import { alcanceSucursalUsuario, filtroSucursalOperativo } from 'src/utils/alcance-sucursal';
import { formatDateResponse } from 'src/utils/utilidades';
import { CreatePedidoItemDto } from './dto/create-pedido-item.dto';
import { CreatePedidoDto } from './dto/create-pedido.dto';
import { CreateOrdenVentaDto } from './dto/create-orden-venta.dto';
import { QueryOrdenesVentaDto } from './dto/query-ordenes-venta.dto';
import { QueryPedidosDto } from './dto/query-pedidos.dto';
import { UpdatePedidoItemDto } from './dto/update-pedido-item.dto';
import { UpdatePedidoDto } from './dto/update-pedido.dto';
import { logoEmisor, nombreFantasiaEmisor } from 'src/utils/emisor-comprobante';

export enum EstadoPedido {
  BORRADOR = 'borrador',
  CONFIRMADO = 'confirmado',
  FACTURADO = 'facturado',
  CANCELADO = 'cancelado',
}

export enum EstadoOrdenVenta {
  BORRADOR = 'borrador',
  CONFIRMADA = 'confirmada',
  APROBADA = 'aprobada',
  EN_PROCESO = 'en_proceso',
  FACTURADA_PARCIAL = 'facturada_parcial',
  FACTURADA = 'facturada',
  CANCELADA = 'cancelada',
  VENCIDA = 'vencida',
}

const PRESUPUESTO_ESTADO_FACTURADO = 'FACTURADO' as unknown as presupuesto_estado;

type OrdenOrigen = {
  tipo?: 'manual' | 'presupuesto' | 'solicitud_credito' | 'importacion' | 'otro';
  id?: string | null;
};

const TRANSICIONES_ORDEN: Record<string, EstadoOrdenVenta[]> = {
  borrador:          [EstadoOrdenVenta.CONFIRMADA, EstadoOrdenVenta.CANCELADA],
  confirmada:        [EstadoOrdenVenta.APROBADA, EstadoOrdenVenta.EN_PROCESO, EstadoOrdenVenta.CANCELADA],
  aprobada:          [EstadoOrdenVenta.EN_PROCESO, EstadoOrdenVenta.CANCELADA],
  en_proceso:        [EstadoOrdenVenta.FACTURADA_PARCIAL, EstadoOrdenVenta.FACTURADA, EstadoOrdenVenta.CANCELADA],
  facturada_parcial: [EstadoOrdenVenta.FACTURADA, EstadoOrdenVenta.CANCELADA],
  facturada:         [],
  cancelada:         [],
  vencida:           [],
};

@Injectable()
export class PedidosService {
  private readonly logger = new Logger(PedidosService.name);
  constructor(
    private readonly prisma: PrismaService,
    private readonly auditService: AuditService,
    private readonly listaPreciosService: ListaPreciosService,
    private readonly MonedasService: MonedasService,
    private readonly ofertasService: OfertasService,
  ) {}

  // ═══════════ POLÍTICA DE STOCK ═══════════
  // Políticas: 'estricto' = bloquea, 'advertencia' = permite con warning, 'libre' = sin restricción
  // Se configura por sucursal en empresas_sucursales.config.politica_stock
  // Default: 'advertencia' (permite vender pero retorna warning)

  /**
   * IDs de sucursales a las que el usuario tiene acceso; `null` = sin
   * restricciones.
   *
   * Delega en el helper compartido: la implementación que había acá no miraba
   * el acceso elevado, así que un superadmin con una sucursal asignada quedaba
   * limitado a esa — el mismo problema que hubo que corregir en Tesorería.
   */
  private async getSucursalesUsuario(userId: string, empresaId: string): Promise<string[] | null> {
    const alcance = await alcanceSucursalUsuario(this.prisma, userId, empresaId);
    return alcance ? alcance.sucursalIds : null;
  }

  private async getPoliticaStock(
    sucursalId: string | null,
    empresaId?: string,
  ): Promise<'estricto' | 'advertencia' | 'libre'> {
    let sucursal: any = null;

    if (sucursalId) {
      sucursal = await this.prisma.empresas_sucursales.findUnique({
        where: { id: sucursalId },
        select: { config: true },
      });
    } else if (empresaId) {
      // Fallback: buscar la primera sucursal activa de la empresa
      sucursal = await this.prisma.empresas_sucursales.findFirst({
        where: { empresa_id: empresaId, active: true },
        select: { config: true },
        orderBy: { created_at: 'asc' },
      });
    }

    const config = sucursal?.config as Record<string, any> | null;
    const politica = config?.politica_stock;
    if (['estricto', 'advertencia', 'libre'].includes(politica)) return politica;
    return 'advertencia';
  }

  private async validarStockItem(
    tx: Prisma.TransactionClient,
    productoId: string,
    cantidadRequerida: number,
    sucursalIds: string[] | null, // null = sin restricción (todos los depósitos de la empresa)
    empresaId: string,
    manejaInventario: boolean,
  ): Promise<{ permitido: boolean; warning?: string; stockDisponible?: number }> {
    // Si el producto no maneja inventario, siempre permitido
    if (!manejaInventario) return { permitido: true };

    // Obtener política de la primera sucursal accesible
    const sucursalParaPolitica = sucursalIds?.length ? sucursalIds[0] : null;
    const politica = await this.getPoliticaStock(sucursalParaPolitica, empresaId);
    if (politica === 'libre') return { permitido: true };

    // Buscar stock en depósitos de las sucursales del usuario
    const whereDeposito: any = { empresa_id: empresaId, active: true };
    if (sucursalIds?.length) {
      whereDeposito.sucursal_id = { in: sucursalIds };
    }

    const stocks = await tx.stock_deposito.findMany({
      where: {
        producto_id: productoId,
        deposito: whereDeposito,
      },
      select: { cantidad_disponible: true, cantidad_reservada: true },
    });

    const totalDisponible = stocks.reduce(
      (sum, s) => sum + Number(s.cantidad_disponible) - Number(s.cantidad_reservada),
      0,
    );

    if (totalDisponible >= cantidadRequerida) {
      return { permitido: true, stockDisponible: totalDisponible };
    }

    // Stock insuficiente
    if (politica === 'estricto') {
      return {
        permitido: false,
        stockDisponible: totalDisponible,
        warning: `Stock insuficiente. Disponible: ${totalDisponible}, requerido: ${cantidadRequerida}`,
      };
    }

    // advertencia: permitir pero avisar
    return {
      permitido: true,
      stockDisponible: totalDisponible,
      warning: `Stock insuficiente (disponible: ${totalDisponible}). Se generará stock negativo.`,
    };
  }

  // ═══════════ PEDIDO CRUD ═══════════

  async create(dto: CreatePedidoDto, empresa_id: string, user_id: string) {
    const cliente = await this.prisma.clientes.findFirst({
      where: { id: dto.cliente_id, deleted: false, personas: { empresa_id } },
      include: { personas: { select: { razon_social: true } } },
    });
    if (!cliente) throw new NotFoundException('Cliente no encontrado o no pertenece a su empresa');
    const clienteNombre = cliente.personas?.razon_social || cliente.nombre_fantasia || 'N/A';

    const stockWarnings: string[] = [];
    const sucursalesUsuario = await this.getSucursalesUsuario(user_id, empresa_id);
    const pedido = await this.prisma.$transaction(async (tx) => {
      const created = await tx.pedidos.create({
        data: {
          empresa_id,
          cliente_id: dto.cliente_id,
          sucursal_id: dto.sucursal_id ?? null,
          deposito_id: dto.deposito_id ?? null,
          vendedor_id: dto.vendedor_id ?? null,
          moneda_id: dto.moneda_id ?? null,
          condicion_operacion_id: dto.condicion_operacion_id ?? null,
          lista_precios_id: dto.lista_precios_id ?? null,
          observaciones: dto.observaciones ?? null,
          referencia_interna: dto.referencia_interna ?? null,
          fecha_vencimiento: dto.fecha_vencimiento ? new Date(dto.fecha_vencimiento) : null,
          estado: 'borrador',
          creado_por: user_id,
        },
      });
      await this.logHistorial(tx, created.id, 'creado', user_id, `Pedido creado para ${clienteNombre}`, {
        cliente_id: dto.cliente_id,
      });

      if (dto.items?.length) {
        // Validar stock de cada item antes de agregar
        for (const item of dto.items) {
          const prod = await tx.productos.findFirst({
            where: { id: item.producto_id, empresa_id },
            select: { maneja_inventario: true, descripcion: true },
          });
          let factor = 1;
          if (item.presentacion_id) {
            const pres = await tx.producto_presentaciones.findUnique({ where: { id: item.presentacion_id } });
            if (pres) factor = Number(pres.factor_conversion);
          }
          const check = await this.validarStockItem(
            tx,
            item.producto_id,
            item.cantidad * factor,
            sucursalesUsuario,
            empresa_id,
            prod?.maneja_inventario ?? false,
          );
          if (!check.permitido) {
            throw new BadRequestException(`"${prod?.descripcion}": ${check.warning}`);
          }
          if (check.warning) stockWarnings.push(`${prod?.descripcion}: ${check.warning}`);

          await this.addItemInternal(tx, created.id, item, empresa_id, dto.lista_precios_id, dto.cliente_id);
        }
        await this.recalcularTotales(tx, created.id);
      }
      return created;
    });

    this.logger.log(`Pedido creado: ${pedido.id}`, 'PedidosService');
    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'CREATE',
      entity_type: 'pedido',
      entity_id: pedido.id,
      descripcion: `Pedido creado`,
    });
    const result = await this.findOneInternal(pedido.id);
    return {
      message: 'Pedido creado exitosamente',
      data: result,
      ...(stockWarnings.length > 0 && { stock_warnings: stockWarnings }),
    };
  }

  async findAll(query: QueryPedidosDto, empresa_id: string, usuarioId?: string) {
    const page = parseInt(query.page || '1', 10);
    const limit = parseInt(query.limit || '20', 10);
    const where: Prisma.pedidosWhereInput = { empresa_id, active: true };
    if (query.estado) where.estado = query.estado;
    if (query.cliente_id) where.cliente_id = query.cliente_id;
    if (query.vendedor_id) where.vendedor_id = query.vendedor_id;

    // El filtro por sucursal estaba comentado, así que el listado mostraba los
    // pedidos de toda la empresa. Ahora respeta el alcance del usuario.
    const alcance = await alcanceSucursalUsuario(this.prisma, usuarioId, empresa_id);
    const filtroSucursal = filtroSucursalOperativo(alcance, query.sucursal_id);
    if (filtroSucursal !== undefined) where.sucursal_id = filtroSucursal;
    if (query.search) {
      where.OR = [
        { numero_pedido: { contains: query.search, mode: 'insensitive' } },
        { referencia_interna: { contains: query.search, mode: 'insensitive' } },
        { cliente: { personas: { razon_social: { contains: query.search, mode: 'insensitive' } } } },
      ];
    }

    if (query.fecha_desde || query.fecha_hasta) {
      where.fecha_pedido = {};
      if (query.fecha_desde) where.fecha_pedido.gte = new Date(query.fecha_desde);
      if (query.fecha_hasta) where.fecha_pedido.lte = new Date(query.fecha_hasta);
    }
    const [data, total] = await Promise.all([
      this.prisma.pedidos.findMany({
        where,
        include: {
          cliente: {
            select: { id: true, nombre_fantasia: true, personas: { select: { razon_social: true, ruc: true } } },
          },
          vendedor: { select: { id: true, nombre: true, apellido: true } },
          sucursal: { select: { id: true, descripcion: true } },
          creador: { select: { id: true, username: true } },
          moneda: { select: { id: true, codigo: true, simbolo: true } },
          _count: { select: { detalle: true } },
        },
        orderBy: { created_at: 'desc' },
        skip: (page - 1) * limit,
        take: limit,
      }),
      this.prisma.pedidos.count({ where }),
    ]);
    const dataFormat = data.map((item) => ({
      ...item,
      fecha_pedido: formatDateResponse(item.fecha_pedido),
      fecha_confirmacion: formatDateResponse(item.fecha_confirmacion),
      fecha_vencimiento: formatDateResponse(item.fecha_vencimiento),
    }));
    return { dataFormat, pagination: { total, page, limit, totalPages: Math.ceil(total / limit) } };
  }

  async findOne(id: string, empresa_id: string) {
    const pedido = await this.findOneInternal(id);
    if (!pedido) throw new NotFoundException('Pedido no encontrado');
    if (pedido.empresa_id !== empresa_id) throw new BadRequestException('El pedido no pertenece a su empresa');
    return pedido;
  }

  async update(id: string, dto: UpdatePedidoDto, empresa_id: string, user_id: string) {
    await this.assertPedidoEditable(id, empresa_id);
    if (dto.cliente_id) {
      const cl = await this.prisma.clientes.findFirst({
        where: { id: dto.cliente_id, deleted: false, personas: { empresa_id } },
      });
      if (!cl) throw new NotFoundException('Cliente no encontrado');
    }
    await this.prisma.$transaction(async (tx) => {
      await tx.pedidos.update({
        where: { id },
        data: {
          ...(dto.cliente_id && { cliente_id: dto.cliente_id }),
          ...(dto.sucursal_id !== undefined && { sucursal_id: dto.sucursal_id || null }),
          ...(dto.deposito_id !== undefined && { deposito_id: dto.deposito_id || null }),
          ...(dto.vendedor_id !== undefined && { vendedor_id: dto.vendedor_id || null }),
          ...(dto.moneda_id !== undefined && { moneda_id: dto.moneda_id || null }),
          ...(dto.condicion_operacion_id !== undefined && {
            condicion_operacion_id: dto.condicion_operacion_id || null,
          }),
          ...(dto.lista_precios_id !== undefined && { lista_precios_id: dto.lista_precios_id || null }),
          ...(dto.observaciones !== undefined && { observaciones: dto.observaciones }),
          ...(dto.referencia_interna !== undefined && { referencia_interna: dto.referencia_interna }),
          ...(dto.fecha_vencimiento !== undefined && {
            fecha_vencimiento: dto.fecha_vencimiento ? new Date(dto.fecha_vencimiento) : null,
          }),
          updated_at: new Date(),
        },
      });
      await this.logHistorial(tx, id, 'item_modificado', user_id, 'Datos del pedido actualizados', {
        campos: Object.keys(dto),
      });
    });
    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'UPDATE',
      entity_type: 'pedido',
      entity_id: id,
      descripcion: 'Pedido actualizado',
    });
    return { message: 'Pedido actualizado exitosamente', data: await this.findOneInternal(id) };
  }

  async remove(id: string, empresa_id: string, user_id: string) {
    await this.assertPedidoEditable(id, empresa_id);
    await this.prisma.pedidos.update({ where: { id }, data: { active: false, updated_at: new Date() } });
    await this.auditService.log({
      empresa_id,
      user_id,
      action: 'DELETE',
      entity_type: 'pedido',
      entity_id: id,
      descripcion: 'Pedido eliminado',
    });
    return { message: 'Pedido eliminado exitosamente' };
  }

  // ═══════════ ITEMS ═══════════

  async addItem(pedidoId: string, dto: CreatePedidoItemDto, empresa_id: string, user_id: string) {
    const pedido = await this.assertPedidoEditable(pedidoId, empresa_id);

    // Validar stock antes de agregar
    const producto = await this.prisma.productos.findFirst({
      where: { id: dto.producto_id, empresa_id },
      select: { maneja_inventario: true },
    });
    let factor = 1;
    if (dto.presentacion_id) {
      const pres = await this.prisma.producto_presentaciones.findUnique({ where: { id: dto.presentacion_id } });
      if (pres) factor = Number(pres.factor_conversion);
    }
    const cantidadBase = dto.cantidad * factor;

    // Obtener sucursales accesibles del usuario
    const sucursalesUsuario = await this.getSucursalesUsuario(user_id, empresa_id);

    const stockCheck = await this.validarStockItem(
      this.prisma,
      dto.producto_id,
      cantidadBase,
      sucursalesUsuario,
      empresa_id,
      producto?.maneja_inventario ?? false,
    );

    if (!stockCheck.permitido) {
      throw new BadRequestException(stockCheck.warning);
    }

    await this.prisma.$transaction(async (tx) => {
      const item = await this.addItemInternal(
        tx,
        pedidoId,
        dto,
        empresa_id,
        pedido.lista_precios_id,
        pedido.cliente_id,
      );
      await this.recalcularTotales(tx, pedidoId);
      await this.logHistorial(tx, pedidoId, 'item_agregado', user_id, `Item agregado: ${item.descripcion}`, {
        item_id: item.id,
        cantidad: dto.cantidad,
      });
    });

    const result = await this.findOneInternal(pedidoId);
    return {
      message: stockCheck.warning ? `Item agregado. ${stockCheck.warning}` : 'Item agregado al pedido',
      data: result,
      ...(stockCheck.warning && { stock_warning: stockCheck.warning }),
    };
  }

  async updateItem(pedidoId: string, itemId: string, dto: UpdatePedidoItemDto, empresa_id: string, user_id: string) {
    await this.assertPedidoEditable(pedidoId, empresa_id);
    const item = await this.prisma.pedido_detalle.findFirst({ where: { id: itemId, pedido_id: pedidoId } });
    if (!item) throw new NotFoundException('Item no encontrado en este pedido');

    await this.prisma.$transaction(async (tx) => {
      let factor = Number(item.factor_conversion);
      if (dto.presentacion_id !== undefined && dto.presentacion_id !== item.presentacion_id) {
        if (dto.presentacion_id) {
          const pres = await tx.producto_presentaciones.findUnique({ where: { id: dto.presentacion_id } });
          if (!pres) throw new NotFoundException('Presentación no encontrada');
          factor = Number(pres.factor_conversion);
        } else {
          factor = 1;
        }
      }
      const cant = dto.cantidad ?? Number(item.cantidad);
      const cantBase = cant * factor;
      const precio = dto.precio_negociado ?? Number(item.precio_negociado);
      const descPct = dto.descuento_porcentaje ?? Number(item.descuento_porcentaje);
      const descMonto = precio * cantBase * (descPct / 100);
      const subtotal = precio * cantBase - descMonto;

      await tx.pedido_detalle.update({
        where: { id: itemId },
        data: {
          ...(dto.presentacion_id !== undefined && { presentacion_id: dto.presentacion_id || null }),
          cantidad: cant,
          factor_conversion: factor,
          cantidad_base: cantBase,
          precio_negociado: precio,
          descuento_porcentaje: descPct,
          descuento_monto: descMonto,
          subtotal,
          observacion: dto.observacion !== undefined ? dto.observacion : item.observacion,
          updated_at: new Date(),
        },
      });
      await this.recalcularTotales(tx, pedidoId);
      await this.logHistorial(
        tx,
        pedidoId,
        dto.precio_negociado !== undefined ? 'precio_negociado' : 'item_modificado',
        user_id,
        `Item modificado: ${item.descripcion}`,
        { item_id: itemId },
      );
    });
    return { message: 'Item actualizado', data: await this.findOneInternal(pedidoId) };
  }

  async removeItem(pedidoId: string, itemId: string, empresa_id: string, user_id: string) {
    await this.assertPedidoEditable(pedidoId, empresa_id);
    const item = await this.prisma.pedido_detalle.findFirst({ where: { id: itemId, pedido_id: pedidoId } });
    if (!item) throw new NotFoundException('Item no encontrado en este pedido');
    await this.prisma.$transaction(async (tx) => {
      await tx.pedido_detalle.delete({ where: { id: itemId } });
      await this.recalcularTotales(tx, pedidoId);
      await this.logHistorial(tx, pedidoId, 'item_eliminado', user_id, `Item eliminado: ${item.descripcion}`, {
        item_id: itemId,
      });
    });
    return { message: 'Item eliminado del pedido', data: await this.findOneInternal(pedidoId) };
  }

  // ═══════════ GESTIÓN DE STOCK EN PEDIDOS ═══════════

  /**
   * Obtiene el depósito principal de la sucursal, o el primero disponible de la empresa.
   */
  private async getDepositoPrincipal(
    tx: Prisma.TransactionClient,
    sucursalId: string | null,
    empresaId: string,
  ): Promise<string | null> {
    const where: any = { empresa_id: empresaId, active: true };
    if (sucursalId) where.sucursal_id = sucursalId;

    const deposito = await tx.depositos.findFirst({
      where,
      orderBy: [{ es_principal: 'desc' }, { created_at: 'asc' }],
      select: { id: true },
    });
    return deposito?.id || null;
  }

  /**
   * Resuelve el deposito_id efectivo para un item:
   * item.deposito_id > pedido.deposito_id > depósito principal de la sucursal
   */
  private async resolverDepositoItem(
    tx: Prisma.TransactionClient,
    itemDepositoId: string | null,
    pedidoDepositoId: string | null,
    sucursalId: string | null,
    empresaId: string,
  ): Promise<string | null> {
    if (itemDepositoId) return itemDepositoId;
    if (pedidoDepositoId) return pedidoDepositoId;
    return this.getDepositoPrincipal(tx, sucursalId, empresaId);
  }

  /**
   * Reserva stock para todos los items de un pedido (al confirmar).
   */
  private async reservarStockPedido(
    tx: Prisma.TransactionClient,
    pedidoId: string,
    pedidoDepositoId: string | null,
    sucursalId: string | null,
    empresaId: string,
  ) {
    const items = await tx.pedido_detalle.findMany({
      where: { pedido_id: pedidoId },
      include: { producto: { select: { maneja_inventario: true } } },
    });

    for (const item of items) {
      if (!item.producto?.maneja_inventario) continue;
      const cantidad = Number(item.cantidad_base);
      const depositoId = await this.resolverDepositoItem(tx, item.deposito_id, pedidoDepositoId, sucursalId, empresaId);
      if (!depositoId) continue;

      // Guardar el depósito resuelto en el detalle para trazabilidad
      if (!item.deposito_id) {
        await tx.pedido_detalle.update({ where: { id: item.id }, data: { deposito_id: depositoId } });
      }

      await tx.stock_deposito.upsert({
        where: { deposito_id_producto_id: { deposito_id: depositoId, producto_id: item.producto_id } },
        create: {
          deposito_id: depositoId,
          producto_id: item.producto_id,
          cantidad_disponible: 0,
          cantidad_reservada: cantidad,
        },
        update: { cantidad_reservada: { increment: cantidad }, updated_at: new Date() },
      });
    }
  }

  /**
   * Libera las reservas de stock de un pedido (al cancelar).
   */
  private async liberarReservasPedido(tx: Prisma.TransactionClient, pedidoId: string) {
    const items = await tx.pedido_detalle.findMany({
      where: { pedido_id: pedidoId },
      include: { producto: { select: { maneja_inventario: true } } },
    });

    for (const item of items) {
      if (!item.producto?.maneja_inventario || !item.deposito_id) continue;
      const cantidad = Number(item.cantidad_base);

      const stock = await tx.stock_deposito.findUnique({
        where: { deposito_id_producto_id: { deposito_id: item.deposito_id, producto_id: item.producto_id } },
      });
      if (!stock) continue;

      const cantidadLiberar = Math.min(cantidad, Number(stock.cantidad_reservada));
      if (cantidadLiberar > 0) {
        await tx.stock_deposito.update({
          where: { deposito_id_producto_id: { deposito_id: item.deposito_id, producto_id: item.producto_id } },
          data: { cantidad_reservada: { decrement: cantidadLiberar }, updated_at: new Date() },
        });
      }
    }
  }

  /**
   * Descuenta stock real (al facturar). Usa el deposito_id guardado en cada item.
   */
  private async descontarStockPedido(tx: Prisma.TransactionClient, pedidoId: string) {
    const items = await tx.pedido_detalle.findMany({
      where: { pedido_id: pedidoId },
      include: { producto: { select: { maneja_inventario: true } } },
    });

    for (const item of items) {
      if (!item.producto?.maneja_inventario || !item.deposito_id) continue;
      const cantidad = Number(item.cantidad_base);

      await tx.stock_deposito.upsert({
        where: { deposito_id_producto_id: { deposito_id: item.deposito_id, producto_id: item.producto_id } },
        create: {
          deposito_id: item.deposito_id,
          producto_id: item.producto_id,
          cantidad_disponible: -cantidad,
          cantidad_reservada: 0,
        },
        update: { cantidad_disponible: { decrement: cantidad }, updated_at: new Date() },
      });
    }
  }

  // ═══════════ FLUJO DE ESTADOS ═══════════

  async confirmar(id: string, empresa_id: string, user_id: string) {
    const pedido = await this.assertPedidoEstado(id, empresa_id, 'borrador');
    const items = await this.prisma.pedido_detalle.findMany({
      where: { pedido_id: id },
      include: { producto: { select: { maneja_inventario: true, descripcion: true } } },
    });
    if (items.length === 0) throw new BadRequestException('El pedido no tiene items');

    // Validar stock de todos los items antes de confirmar
    const sucursalesUsuario = await this.getSucursalesUsuario(user_id, empresa_id);
    const warnings: string[] = [];
    for (const item of items) {
      const check = await this.validarStockItem(
        this.prisma,
        item.producto_id,
        Number(item.cantidad_base),
        sucursalesUsuario,
        empresa_id,
        item.producto?.maneja_inventario ?? false,
      );
      if (!check.permitido) {
        throw new BadRequestException(`No se puede confirmar: "${item.producto?.descripcion}" ${check.warning}`);
      }
      if (check.warning) {
        warnings.push(`${item.producto?.descripcion}: ${check.warning}`);
      }
    }

    const numero = await this.prisma.$transaction(async (tx) => {
      const num = await this.reservarNumeroPedido(tx, empresa_id, pedido.sucursal_id);
      await tx.pedidos.update({
        where: { id },
        data: {
          estado: EstadoPedido.CONFIRMADO,
          numero_pedido: num,
          fecha_confirmacion: new Date(),
          updated_at: new Date(),
        },
      });
      // Reservar stock de todos los items
      await this.reservarStockPedido(tx, id, pedido.deposito_id, pedido.sucursal_id, empresa_id);
      await this.logHistorial(tx, id, 'confirmado', user_id, `Pedido confirmado: ${num}`, { numero_pedido: num });
      return num;
    });
    this.logger.log(`Pedido confirmado: ${id} → ${numero}`, 'PedidosService');
    return {
      message: `Pedido confirmado: ${numero}`,
      data: await this.findOneInternal(id),
      ...(warnings.length > 0 && { stock_warnings: warnings }),
    };
  }

  async cancelar(id: string, empresa_id: string, user_id: string, motivo?: string) {
    const pedido = await this.prisma.pedidos.findUnique({ where: { id } });
    if (!pedido) throw new NotFoundException('Pedido no encontrado');
    if (pedido.empresa_id !== empresa_id) throw new BadRequestException('No pertenece a su empresa');
    if ([EstadoPedido.FACTURADO, EstadoPedido.CANCELADO].includes(pedido.estado as EstadoPedido))
      throw new BadRequestException(`No se puede cancelar en estado "${pedido.estado}"`);
    const estadoAnterior = pedido.estado;
    await this.prisma.$transaction(async (tx) => {
      await tx.pedidos.update({ where: { id }, data: { estado: EstadoPedido.CANCELADO, updated_at: new Date() } });
      // Si estaba confirmado, liberar reservas de stock
      if (estadoAnterior === EstadoPedido.CONFIRMADO) {
        await this.liberarReservasPedido(tx, id);
      }
      await this.logHistorial(tx, id, 'cancelado', user_id, motivo || 'Pedido cancelado', {
        estado_anterior: estadoAnterior,
      });
    });
    return { message: 'Pedido cancelado exitosamente', data: await this.findOneInternal(id) };
  }

  async marcarFacturado(id: string, facturaId: string, empresa_id: string, user_id: string) {
    const pedido = await this.prisma.pedidos.findUnique({ where: { id } });
    if (!pedido) throw new NotFoundException('Pedido no encontrado');
    if (pedido.empresa_id !== empresa_id) throw new BadRequestException('No pertenece a su empresa');
    if (pedido.estado !== EstadoPedido.CONFIRMADO)
      throw new BadRequestException(`No se puede facturar en estado "${pedido.estado}"`);
    await this.prisma.$transaction(async (tx) => {
      await tx.pedidos.update({
        where: { id },
        data: { estado: EstadoPedido.FACTURADO, factura_id: facturaId, updated_at: new Date() },
      });
      // Liberar reservas y descontar stock real
      await this.liberarReservasPedido(tx, id);
      await this.descontarStockPedido(tx, id);
      await this.logHistorial(tx, id, 'facturado', user_id, `Facturado: ${facturaId}`, { factura_id: facturaId });
    });
    return { message: 'Pedido facturado', data: await this.findOneInternal(id) };
  }

  async findPendientesCaja(empresa_id: string, sucursal_id?: string) {
    const where: Prisma.pedidosWhereInput = { empresa_id, active: true, estado: EstadoPedido.CONFIRMADO };
    if (sucursal_id) {
      where.OR = [{ sucursal_id }, { sucursal_id: null }];
    }
    this.prisma.pedidos.findMany({
      where,
      include: {
        cliente: {
          select: { id: true, nombre_fantasia: true, personas: { select: { razon_social: true, ruc: true } } },
        },
        vendedor: { select: { id: true, nombre: true, apellido: true } },
        detalle: {
          include: {
            producto: { select: { id: true, descripcion: true } },
            presentacion: { select: { id: true, nombre: true, abreviatura: true } },
          },
        },
        _count: { select: { detalle: true } },
      },
      orderBy: { fecha_confirmacion: 'asc' },
    });
  }

  async getHistorial(pedidoId: string, empresa_id: string) {
    const pedido = await this.prisma.pedidos.findUnique({ where: { id: pedidoId } });
    if (!pedido) throw new NotFoundException('Pedido no encontrado');
    if (pedido.empresa_id !== empresa_id) throw new BadRequestException('No pertenece a su empresa');
    return this.prisma.pedido_historial.findMany({
      where: { pedido_id: pedidoId },
      include: { usuario: { select: { id: true, username: true } } },
      orderBy: { created_at: 'desc' },
    });
  }

  // ═══════════ DESCUENTO GLOBAL ═══════════

  async aplicarDescuentoGlobal(pedidoId: string, empresa_id: string, user_id: string, porcentaje: number) {
    await this.assertPedidoEditable(pedidoId, empresa_id);

    if (porcentaje < 0 || porcentaje > 100) {
      throw new BadRequestException('El porcentaje debe estar entre 0 y 100');
    }

    await this.prisma.$transaction(async (tx) => {
      // Guardar el porcentaje en la cabecera
      await tx.pedidos.update({
        where: { id: pedidoId },
        data: { descuento_global_porcentaje: porcentaje, updated_at: new Date() },
      });

      // Recalcular totales (esto prorratea el descuento por item)
      await this.recalcularTotales(tx, pedidoId);

      await this.logHistorial(
        tx,
        pedidoId,
        'descuento_global',
        user_id,
        porcentaje > 0 ? `Descuento global aplicado: ${porcentaje}%` : 'Descuento global removido',
        { porcentaje },
      );
    });

    this.logger.log(`Descuento global ${porcentaje}% aplicado a pedido ${pedidoId}`, 'PedidosService');
    return { message: 'Descuento global aplicado', data: await this.findOneInternal(pedidoId) };
  }

  // ═══════════ CONTEXTO DE NEGOCIACIÓN ═══════════

  async contextoNegociacion(clienteId: string, empresaId: string) {
    const cliente = await this.prisma.clientes.findFirst({
      where: { id: clienteId, deleted: false, personas: { empresa_id: empresaId } },
      select: {
        id: true,
        nombre_fantasia: true,
        cod_cliente: true,
        tipo_cliente: true,
        zona: true,
        lista_precios_id: true,
        limite_credito: true,
        credito_habilitado: true,
        saldo_pendiente: true,
        moneda_credito: true,
        bloqueado_credito: true,
        fecha_ultimo_pago: true,
        dias_mora_maximo: true,
        lista_precios: {
          select: {
            id: true,
            nombre: true,
            codigo: true,
            moneda: true,
            descripcion: true,
            tipo_aplicacion: true,
            prioridad: true,
            aplica_descuento_general: true,
            descuento_porcentaje: true,
            aplica_recargo_general: true,
            recargo_porcentaje: true,
            redondeo_activo: true,
            redondeo_tipo: true,
            redondeo_decimales: true,
            fecha_inicio: true,
            fecha_fin: true,
            _count: { select: { lista_precios_productos: true } },
          },
        },
        personas: {
          select: {
            razon_social: true,
            ruc: true,
            dv: true,
            direccion: true,
            telefono: true,
            email: true,
          },
        },
      },
    });
    if (!cliente) throw new NotFoundException('Cliente no encontrado');

    // Crédito
    const limiteCredito = Number(cliente.limite_credito) || 0;
    const saldoPendiente = Number(cliente.saldo_pendiente) || 0;
    const credito = {
      habilitado: cliente.credito_habilitado !== false,
      sin_tope: limiteCredito <= 0,
      limite: limiteCredito > 0 ? limiteCredito : null,
      saldo_pendiente: saldoPendiente,
      saldo_disponible: limiteCredito > 0 ? Math.max(0, limiteCredito - saldoPendiente) : null,
      porcentaje_uso: limiteCredito > 0 ? Math.round((saldoPendiente / limiteCredito) * 100) : null,
      bloqueado: cliente.bloqueado_credito || false,
      moneda: cliente.moneda_credito || 'PYG',
      fecha_ultimo_pago: cliente.fecha_ultimo_pago,
    };

    // Productos frecuentes del cliente (basado en pedidos/facturas anteriores)
    const productosFrecuentes = await this.prisma.$queryRaw<
      Array<{
        producto_id: string;
        descripcion: string;
        veces_comprado: bigint;
        ultima_compra: Date;
        cantidad_total: number;
      }>
    >`
      SELECT pd.producto_id, pd.descripcion,
             COUNT(*)::bigint AS veces_comprado,
             MAX(p.created_at) AS ultima_compra,
             SUM(pd.cantidad_base)::numeric AS cantidad_total
      FROM pedido_detalle pd
      JOIN pedidos p ON p.id = pd.pedido_id
      WHERE p.empresa_id = ${empresaId}::uuid
        AND p.cliente_id = ${clienteId}::uuid
        AND p.estado NOT IN ('cancelado')
        AND p.active = true
      GROUP BY pd.producto_id, pd.descripcion
      ORDER BY veces_comprado DESC
      LIMIT 20
    `;

    // Productos más vendidos de la empresa
    const productosTopVentas = await this.prisma.$queryRaw<
      Array<{ producto_id: string; descripcion: string; veces_vendido: bigint; cantidad_total: number }>
    >`
      SELECT pd.producto_id, pd.descripcion,
             COUNT(DISTINCT pd.pedido_id)::bigint AS veces_vendido,
             SUM(pd.cantidad_base)::numeric AS cantidad_total
      FROM pedido_detalle pd
      JOIN pedidos p ON p.id = pd.pedido_id
      WHERE p.empresa_id = ${empresaId}::uuid
        AND p.estado NOT IN ('cancelado')
        AND p.active = true
        AND p.created_at >= NOW() - INTERVAL '90 days'
      GROUP BY pd.producto_id, pd.descripcion
      ORDER BY veces_vendido DESC
      LIMIT 20
    `;

    // Últimas compras del cliente
    const ultimasCompras = await this.prisma.pedidos.findMany({
      where: {
        empresa_id: empresaId,
        cliente_id: clienteId,
        active: true,
        estado: { notIn: ['cancelado'] },
      },
      select: {
        id: true,
        numero_pedido: true,
        estado: true,
        total: true,
        fecha_pedido: true,
        fecha_confirmacion: true,
        _count: { select: { detalle: true } },
      },
      orderBy: { created_at: 'desc' },
      take: 10,
    });

    const ultimasComprasFormat = ultimasCompras.map((item) => ({
      ...item,
      fecha_pedido: formatDateResponse(item.fecha_pedido),
      fecha_confirmacion: formatDateResponse(item.fecha_confirmacion),
    }));

    // Ofertas activas
    const ofertasActivas = await this.ofertasService.getOfertasActivas(empresaId);

    // Condiciones de pago disponibles
    const condicionesPago = await this.prisma.condicion_operacion.findMany({
      select: { id: true, descripcion: true, codigo: true },
      orderBy: { codigo: 'asc' },
    });

    // Monedas disponibles
    const monedas = await this.MonedasService.findEmpresaMonedas(empresaId);

    return {
      cliente: {
        id: cliente.id,
        nombre_fantasia: cliente.nombre_fantasia,
        cod_cliente: cliente.cod_cliente,
        tipo_cliente: cliente.tipo_cliente,
        zona: cliente.zona,
        personas: cliente.personas,
        lista_precios: cliente.lista_precios,
      },
      credito,
      productos_frecuentes: productosFrecuentes.map((p) => ({
        ...p,
        veces_comprado: Number(p.veces_comprado),
        cantidad_total: Number(p.cantidad_total),
        ultima_compra: formatDateResponse(p.ultima_compra),
      })),
      productos_top_ventas: productosTopVentas.map((p) => ({
        ...p,
        veces_vendido: Number(p.veces_vendido),
        cantidad_total: Number(p.cantidad_total),
      })),
      ultimas_compras: ultimasComprasFormat,
      ofertas_activas: ofertasActivas.map((o) => ({
        id: o.id,
        nombre: o.nombre,
        descripcion: o.descripcion,
        tipo: o.tipo,
        valor: Number(o.valor),
        fecha_fin: formatDateResponse(o.fecha_fin),
        aplicaciones: (o as any).oferta_aplicacion || [],
      })),
      condiciones_pago: condicionesPago,
      monedas,
    };
  }

  // ═══════════ INTERNOS ═══════════

  private async addItemInternal(
    tx: Prisma.TransactionClient,
    pedidoId: string,
    dto: CreatePedidoItemDto,
    empresa_id: string,
    lista_precios_id?: string | null,
    cliente_id?: string,
  ) {
    const producto = await tx.productos.findFirst({
      where: { id: dto.producto_id, empresa_id },
      select: {
        id: true,
        descripcion: true,
        precio: true,
        unidad_medida_id: true,
        unidades_de_medida: { select: { representacion: true, descripcion: true } },
        porcentaje_iva: true,
        proporcion_iva: true,
      },
    });
    if (!producto) throw new NotFoundException(`Producto ${dto.producto_id} no encontrado`);

    let factor = 1;
    let um = producto.unidades_de_medida?.representacion || 'UNI';
    if (dto.presentacion_id) {
      const pres = await tx.producto_presentaciones.findUnique({ where: { id: dto.presentacion_id } });
      if (!pres) throw new NotFoundException('Presentación no encontrada');
      if (pres.producto_id !== dto.producto_id)
        throw new BadRequestException('La presentación no pertenece al producto');
      factor = Number(pres.factor_conversion);
      um = pres.abreviatura || pres.nombre?.substring(0, 5) || um;
    }

    // Precio desde lista de precios
    let precioLista = Number(producto.precio || 0);
    try {
      const info = await this.listaPreciosService.obtenerPrecioProducto({
        productoId: dto.producto_id,
        clienteId: cliente_id,
        empresaId: empresa_id,
      });
      precioLista = info.precioFinal;
    } catch {
      /* fallback al precio base */
    }

    // Precio de presentación
    if (dto.presentacion_id) {
      const pres = await tx.producto_presentaciones.findUnique({ where: { id: dto.presentacion_id } });
      if (pres?.precio && Number(pres.precio) > 0) {
        precioLista = Number(pres.precio);
      } else {
        precioLista = precioLista * factor;
      }
    }

    const precioNeg = dto.precio_negociado ?? precioLista;
    const cantBase = dto.cantidad * factor;
    const descPct = dto.descuento_porcentaje ?? 0;
    const descMonto = precioNeg * cantBase * (descPct / 100);
    const subtotal = precioNeg * cantBase - descMonto;
    const pctIva = producto.porcentaje_iva ?? 10;
    const propIva = producto.proporcion_iva
      ? Number(producto.proporcion_iva)
      : pctIva === 10
        ? 11
        : pctIva === 5
          ? 21
          : 0;
    const montoIva = propIva > 0 ? subtotal / propIva : 0;

    const maxItem = await tx.pedido_detalle.aggregate({ where: { pedido_id: pedidoId }, _max: { item: true } });

    return tx.pedido_detalle.create({
      data: {
        pedido_id: pedidoId,
        producto_id: dto.producto_id,
        presentacion_id: dto.presentacion_id ?? null,
        descripcion: producto.descripcion || '',
        unidad_medida: um,
        cantidad: dto.cantidad,
        factor_conversion: factor,
        cantidad_base: cantBase,
        precio_lista: precioLista,
        precio_negociado: precioNeg,
        descuento_porcentaje: descPct,
        descuento_monto: descMonto,
        subtotal,
        porcentaje_iva: pctIva,
        proporcion_iva: propIva,
        monto_iva: montoIva,
        cant_pendiente: dto.cantidad,
        item: (maxItem._max.item ?? 0) + 1,
        observacion: dto.observacion ?? null,
      },
    });
  }

  // ══════════════════════════════════════════════════════════════
  // ÓRDENES DE VENTA
  // ══════════════════════════════════════════════════════════════

  async crearOrden(dto: CreateOrdenVentaDto, empresa_id: string, user_id: string, origen?: OrdenOrigen) {
    const origenTipo = origen?.tipo ?? 'manual';
    const origenId = origen?.id ?? null;

    const orden = await this.prisma.$transaction(async (tx) => {
      const created = await tx.pedidos.create({
        data: {
          empresa_id,
          cliente_id: dto.cliente_id,
          sucursal_id: dto.sucursal_id ?? null,
          deposito_id: dto.deposito_id ?? null,
          vendedor_id: dto.vendedor_id ?? null,
          moneda_id: dto.moneda_id ?? null,
          cotizacion_usd_gs: dto.tipo_cambio ?? null,
          condicion_pago_id: dto.condicion_pago_id ?? null,
          lista_precios_id: dto.lista_precios_id ?? null,
          descuento_global_porcentaje: dto.descuento_global_porcentaje ?? 0,
          observaciones: dto.observaciones ?? null,
          referencia_interna: dto.referencia_interna ?? null,
          fecha_venc_orden: dto.fecha_venc_orden ? new Date(dto.fecha_venc_orden) : null,
          tipo_facturacion: dto.tipo_facturacion ?? 'total',
          origen_tipo: origenTipo,
          origen_id: origenId,
          presupuesto_id: origenTipo === 'presupuesto' ? origenId : null,
          tipo_pedido: 'orden_venta',
          estado_orden: EstadoOrdenVenta.BORRADOR,
          creado_por: user_id,
          active: true,
        },
      });
      if (dto.items?.length) {
        for (const item of dto.items) {
          await this.addItemInternal(tx, created.id, item, empresa_id, dto.lista_precios_id, dto.cliente_id);
        }
        await this.recalcularTotales(tx, created.id);
      }
      await this.logHistorial(tx, created.id, 'creado', user_id, 'Orden de venta creada');
      return created;
    });
    return { message: 'Orden de venta creada', data: await this.findOneInternal(orden.id) };
  }

  async findAllOrdenes(query: QueryOrdenesVentaDto, empresa_id: string) {
    const page = Math.max(1, parseInt(query.page ?? '1', 10));
    const limit = Math.min(100, parseInt(query.limit ?? '20', 10));
    const where: Prisma.pedidosWhereInput = {
      empresa_id,
      tipo_pedido: 'orden_venta',
      active: true,
    };
    if (query.estado_orden) where.estado_orden = query.estado_orden as any;
    if (query.cliente_id) where.cliente_id = query.cliente_id;
    if (query.vendedor_id) where.vendedor_id = query.vendedor_id;
    if (query.sucursal_id) where.sucursal_id = query.sucursal_id;
    if (query.fecha_desde || query.fecha_hasta) {
      where.fecha_pedido = {};
      if (query.fecha_desde) (where.fecha_pedido as any).gte = new Date(query.fecha_desde);
      if (query.fecha_hasta) (where.fecha_pedido as any).lte = new Date(query.fecha_hasta + 'T23:59:59');
    }
    const [total, data] = await Promise.all([
      this.prisma.pedidos.count({ where }),
      this.prisma.pedidos.findMany({
        where,
        skip: (page - 1) * limit,
        take: limit,
        orderBy: { created_at: 'desc' },
        include: {
          cliente: { select: { id: true, nombre_fantasia: true, personas: { select: { razon_social: true, ruc: true } } } },
          vendedor: { select: { id: true, nombre: true, apellido: true } },
          sucursal: { select: { id: true, descripcion: true } },
          moneda: { select: { id: true, codigo: true, simbolo: true } },
          _count: { select: { detalle: true } },
        },
      }),
    ]);
    return { total, page, limit, data };
  }

  async statsOrdenes(query: QueryOrdenesVentaDto, empresa_id: string) {
    const base: Prisma.pedidosWhereInput = {
      empresa_id,
      tipo_pedido: 'orden_venta',
      active: true,
    };
    if (query.fecha_desde || query.fecha_hasta) {
      base.fecha_pedido = {};
      if (query.fecha_desde) (base.fecha_pedido as any).gte = new Date(query.fecha_desde);
      if (query.fecha_hasta) (base.fecha_pedido as any).lte = new Date(query.fecha_hasta + 'T23:59:59');
    }

    const estados = ['borrador', 'confirmada', 'aprobada', 'en_proceso', 'facturada_parcial', 'facturada', 'cancelada', 'vencida'];
    const [counts, montoActivo, totalRegistros] = await Promise.all([
      Promise.all(
        estados.map((e) =>
          this.prisma.pedidos.count({ where: { ...base, estado_orden: e as any } }).then((c) => [e, c]),
        ),
      ),
      this.prisma.pedidos.aggregate({
        where: { ...base, estado_orden: { in: ['confirmada', 'aprobada', 'en_proceso', 'facturada_parcial'] as any[] } },
        _sum: { total: true },
      }),
      this.prisma.pedidos.count({ where: base }),
    ]);

    return {
      total: totalRegistros,
      por_estado: Object.fromEntries(counts),
      monto_activo: Number(montoActivo._sum.total ?? 0),
    };
  }

  async findOneOrden(id: string, empresa_id: string) {
    const orden = await this.findOneInternal(id);
    if (orden.empresa_id !== empresa_id) throw new BadRequestException('No pertenece a su empresa');
    if (orden.tipo_pedido !== 'orden_venta') throw new BadRequestException('No es una orden de venta');
    return orden;
  }

  async updateOrden(id: string, dto: Partial<CreateOrdenVentaDto>, empresa_id: string, user_id: string) {
    const orden = await this.assertOrdenEstado(id, empresa_id, EstadoOrdenVenta.BORRADOR);
    await this.prisma.$transaction(async (tx) => {
      await tx.pedidos.update({
        where: { id },
        data: {
          cliente_id: dto.cliente_id ?? orden.cliente_id,
          sucursal_id: dto.sucursal_id !== undefined ? dto.sucursal_id : orden.sucursal_id,
          deposito_id: dto.deposito_id !== undefined ? dto.deposito_id : orden.deposito_id,
          vendedor_id: dto.vendedor_id !== undefined ? dto.vendedor_id : orden.vendedor_id,
          moneda_id: dto.moneda_id !== undefined ? dto.moneda_id : orden.moneda_id,
          condicion_pago_id: dto.condicion_pago_id !== undefined ? dto.condicion_pago_id : (orden as any).condicion_pago_id,
          lista_precios_id: dto.lista_precios_id !== undefined ? dto.lista_precios_id : orden.lista_precios_id,
          descuento_global_porcentaje:
            dto.descuento_global_porcentaje !== undefined
              ? dto.descuento_global_porcentaje
              : (orden as any).descuento_global_porcentaje,
          observaciones: dto.observaciones !== undefined ? dto.observaciones : orden.observaciones,
          referencia_interna: dto.referencia_interna !== undefined ? dto.referencia_interna : orden.referencia_interna,
          fecha_venc_orden: dto.fecha_venc_orden !== undefined ? (dto.fecha_venc_orden ? new Date(dto.fecha_venc_orden) : null) : (orden as any).fecha_venc_orden,
          tipo_facturacion: dto.tipo_facturacion ?? (orden as any).tipo_facturacion,
          updated_at: new Date(),
        },
      });

      // Sincronizar ítems: reemplazar todos si se envía la lista
      if (Array.isArray(dto.items) && dto.items.length > 0) {
        await tx.pedido_detalle.deleteMany({ where: { pedido_id: id } });
        const clienteId = dto.cliente_id ?? orden.cliente_id ?? undefined;
        const listaPreciosId = dto.lista_precios_id ?? orden.lista_precios_id ?? undefined;
        for (const item of dto.items) {
          await this.addItemInternal(tx, id, item, empresa_id, listaPreciosId, clienteId);
        }
      }

      await this.logHistorial(tx, id, 'actualizado', user_id, 'Orden actualizada');
    });
    return { message: 'Orden actualizada', data: await this.findOneInternal(id) };
  }

  async confirmarOrden(id: string, empresa_id: string, user_id: string) {
    const orden = await this.assertOrdenEstado(id, empresa_id, EstadoOrdenVenta.BORRADOR);
    const items = await this.prisma.pedido_detalle.findMany({
      where: { pedido_id: id },
      include: { producto: { select: { maneja_inventario: true, descripcion: true } } },
    });
    if (items.length === 0) throw new BadRequestException('La orden no tiene items');

    // Leer configuración de empresa y sucursal
    const empresa = await this.prisma.empresas.findUnique({
      where: { id: empresa_id },
      select: { orden_requiere_aprobacion: true, orden_monto_aprobacion: true },
    });
    const sucursal = orden.sucursal_id
      ? await this.prisma.empresas_sucursales.findUnique({
          where: { id: orden.sucursal_id },
          select: { config: true },
        })
      : null;
    const reservarStock = (sucursal?.config as any)?.orden_reserva_stock === true;

    const warnings: string[] = [];
    if (reservarStock) {
      const sucursalesUsuario = await this.getSucursalesUsuario(user_id, empresa_id);
      for (const item of items) {
        const check = await this.validarStockItem(
          this.prisma,
          item.producto_id,
          Number(item.cantidad_base),
          sucursalesUsuario,
          empresa_id,
          item.producto?.maneja_inventario ?? false,
        );
        if (!check.permitido) {
          throw new BadRequestException(`Sin stock: "${item.producto?.descripcion}" ${check.warning}`);
        }
        if (check.warning) warnings.push(`${item.producto?.descripcion}: ${check.warning}`);
      }
    }

    const totalOrden = Number((orden as any).total ?? 0);
    const requiereAprobacion =
      empresa?.orden_requiere_aprobacion &&
      (Number(empresa.orden_monto_aprobacion ?? 0) === 0 || totalOrden >= Number(empresa.orden_monto_aprobacion));

    // Sin aprobación requerida → auto-aprobada; con aprobación → espera supervisor
    const nuevoEstado = requiereAprobacion ? EstadoOrdenVenta.CONFIRMADA : EstadoOrdenVenta.APROBADA;

    await this.prisma.$transaction(async (tx) => {
      const num = await this.reservarNumeroPedido(tx, empresa_id, orden.sucursal_id);
      await tx.pedidos.update({
        where: { id },
        data: {
          estado_orden: nuevoEstado,
          numero_pedido: num,
          fecha_confirmacion: new Date(),
          precio_fijado: true,
          requiere_aprobacion: requiereAprobacion ?? false,
          updated_at: new Date(),
        },
      });
      if (reservarStock) {
        await this.reservarStockPedido(tx, id, orden.deposito_id, orden.sucursal_id, empresa_id);
        await tx.pedidos.update({ where: { id }, data: { stock_reservado: true } });
      }
      await this.logHistorial(tx, id, 'confirmada', user_id,
        requiereAprobacion
          ? `Orden confirmada, pendiente aprobación: ${num}`
          : `Orden confirmada y aprobada automáticamente: ${num}`,
        { numero_pedido: num, requiere_aprobacion: requiereAprobacion },
      );
    });
    return {
      message: requiereAprobacion
        ? 'Orden confirmada — pendiente de aprobación del supervisor'
        : 'Orden confirmada y aprobada automáticamente',
      data: await this.findOneInternal(id),
      ...(warnings.length && { stock_warnings: warnings }),
    };
  }

  async aprobarOrden(id: string, empresa_id: string, user_id: string) {
    const orden = await this.assertOrdenTransicion(id, empresa_id, EstadoOrdenVenta.APROBADA);
    await this.prisma.$transaction(async (tx) => {
      await tx.pedidos.update({
        where: { id },
        data: {
          estado_orden: EstadoOrdenVenta.EN_PROCESO,
          aprobado_por: user_id,
          fecha_aprobacion: new Date(),
          updated_at: new Date(),
        },
      });
      await this.logHistorial(tx, id, 'aprobada', user_id, 'Orden aprobada');
    });
    return { message: 'Orden aprobada', data: await this.findOneInternal(id) };
  }

  async iniciarOrden(id: string, empresa_id: string, user_id: string) {
    await this.assertOrdenTransicion(id, empresa_id, EstadoOrdenVenta.EN_PROCESO);
    await this.prisma.$transaction(async (tx) => {
      await tx.pedidos.update({
        where: { id },
        data: { estado_orden: EstadoOrdenVenta.EN_PROCESO, updated_at: new Date() },
      });
      await this.logHistorial(tx, id, 'en_proceso', user_id, 'Orden iniciada — en proceso');
    });
    return { message: 'Orden en proceso', data: await this.findOneInternal(id) };
  }

  async cancelarOrden(id: string, empresa_id: string, user_id: string, motivo?: string) {
    const orden = await this.assertOrdenTransicion(id, empresa_id, EstadoOrdenVenta.CANCELADA);
    await this.prisma.$transaction(async (tx) => {
      await tx.pedidos.update({
        where: { id },
        data: { estado_orden: EstadoOrdenVenta.CANCELADA, updated_at: new Date() },
      });
      if ((orden as any).stock_reservado) {
        await this.liberarReservasPedido(tx, id);
        await tx.pedidos.update({ where: { id }, data: { stock_reservado: false } });
      }
      await this.logHistorial(tx, id, 'cancelada', user_id, motivo || 'Orden cancelada');
    });
    return { message: 'Orden cancelada', data: await this.findOneInternal(id) };
  }

  async facturarOrden(id: string, facturaId: string, empresa_id: string, user_id: string) {
    const orden = await this.prisma.pedidos.findUnique({ where: { id } });
    if (!orden) throw new NotFoundException('Orden no encontrada');
    if (orden.empresa_id !== empresa_id) throw new BadRequestException('No pertenece a su empresa');
    if (orden.tipo_pedido !== 'orden_venta') throw new BadRequestException('No es una orden de venta');
    const estadoActual = (orden as any).estado_orden as string;
    if (!['aprobada', 'en_proceso', 'facturada_parcial'].includes(estadoActual)) {
      throw new BadRequestException(`No se puede facturar una orden en estado "${estadoActual}"`);
    }
    await this.prisma.$transaction(async (tx) => {
      // Obtener ítems facturados para decrementar cant_pendiente
      const facturaDets = facturaId
        ? await tx.factura_det.findMany({
            where: { factura_cab_id: facturaId },
            select: { producto_id: true, dcantproser: true },
          })
        : [];

      // Decrementar cant_pendiente en cada pedido_detalle correspondiente
      const detOrden = await tx.pedido_detalle.findMany({
        where: { pedido_id: id },
        select: { id: true, producto_id: true, cantidad: true, cant_pendiente: true },
      });
      for (const fd of facturaDets) {
        const det = detOrden.find((d) => d.producto_id === fd.producto_id);
        if (det) {
          const actual = det.cant_pendiente != null ? Number(det.cant_pendiente) : Number(det.cantidad);
          const nuevo = Math.max(0, actual - Number(fd.dcantproser ?? 0));
          await tx.pedido_detalle.update({ where: { id: det.id }, data: { cant_pendiente: nuevo } });
        }
      }

      // Determinar nuevo estado: si todos los ítems quedan en 0 → facturada, sino → facturada_parcial
      const detalleActualizado = await tx.pedido_detalle.findMany({
        where: { pedido_id: id },
        select: { cant_pendiente: true, cantidad: true },
      });
      const todoFacturado = detalleActualizado.every((d) => {
        const pend = d.cant_pendiente != null ? Number(d.cant_pendiente) : Number(d.cantidad);
        return pend <= 0;
      });
      const nuevoEstado = todoFacturado ? EstadoOrdenVenta.FACTURADA : EstadoOrdenVenta.FACTURADA_PARCIAL;

      await tx.pedidos.update({
        where: { id },
        data: { estado_orden: nuevoEstado, updated_at: new Date() },
      });

      if (todoFacturado && (orden as any).stock_reservado) {
        await this.liberarReservasPedido(tx, id);
        await this.descontarStockPedido(tx, id);
        await tx.pedidos.update({ where: { id }, data: { stock_reservado: false } });
      }
      // Obtener número formateado de la factura para el historial
      const facturaCab = facturaId
        ? await tx.factura_cab.findUnique({ where: { id: facturaId }, select: { dest: true, dpunexp: true, dnumdoc: true } })
        : null;
      const facturaNumero = facturaCab ? `${facturaCab.dest}-${facturaCab.dpunexp}-${facturaCab.dnumdoc}` : facturaId;
      await this.logHistorial(tx, id, nuevoEstado, user_id, `Facturada: ${facturaNumero}`, { factura_id: facturaId });

      // Si la OV proviene de un presupuesto, al completar la facturación lo marcamos
      // como FACTURADO y registramos el cambio en historial del presupuesto.
      if (todoFacturado && (orden as any).presupuesto_id) {
        const presupuesto = await tx.presupuesto_cab.findFirst({
          where: { id: (orden as any).presupuesto_id, empresa_id, deleted: false },
          select: { id: true, estado: true },
        });
        if (presupuesto && presupuesto.estado === presupuesto_estado.CONVERTED) {
          await tx.presupuesto_cab.update({
            where: { id: presupuesto.id },
            data: { estado: PRESUPUESTO_ESTADO_FACTURADO, updated_at: new Date() },
          });
          await tx.presupuesto_historial.create({
            data: {
              presupuesto_id: presupuesto.id,
              estado_anterior: presupuesto_estado.CONVERTED,
              estado_nuevo: PRESUPUESTO_ESTADO_FACTURADO,
              usuario_id: user_id ?? null,
              comentario: `Facturado desde OV ${orden.numero_pedido ?? id}${facturaNumero ? ` · Factura ${facturaNumero}` : ''}`,
            },
          });
        }
      }
    });
    return { message: 'Orden marcada como facturada', data: await this.findOneInternal(id) };
  }

  async vencerOrdenesExpiradas() {
    const hoy = new Date();
    hoy.setHours(0, 0, 0, 0);
    const ordenes = await this.prisma.pedidos.findMany({
      where: {
        tipo_pedido: 'orden_venta',
        estado_orden: { in: ['confirmada', 'aprobada', 'en_proceso'] as any[] },
        fecha_venc_orden: { lt: hoy },
        active: true,
      },
      select: { id: true, empresa_id: true },
    });
    for (const orden of ordenes) {
      await this.prisma.$transaction(async (tx) => {
        await tx.pedidos.update({
          where: { id: orden.id },
          data: { estado_orden: EstadoOrdenVenta.VENCIDA, updated_at: new Date() },
        });
        await this.liberarReservasPedido(tx, orden.id);
        await tx.pedidos.update({ where: { id: orden.id }, data: { stock_reservado: false } });
        await this.logHistorial(tx, orden.id, 'vencida', 'system', 'Orden vencida automáticamente por cron');
      });
    }
    this.logger.log(`Vencimiento: ${ordenes.length} órdenes marcadas como vencidas`, 'PedidosService');
    return ordenes.length;
  }

  async getItemsFacturadosOrden(id: string, empresa_id: string) {
    const orden = await this.prisma.pedidos.findUnique({ where: { id }, select: { empresa_id: true, tipo_pedido: true } });
    if (!orden) throw new NotFoundException('Orden no encontrada');
    if (orden.empresa_id !== empresa_id) throw new BadRequestException('No pertenece a su empresa');

    const detalle = await this.prisma.pedido_detalle.findMany({
      where: { pedido_id: id },
      select: {
        id: true, producto_id: true, descripcion: true,
        cantidad: true, cant_pendiente: true, precio_negociado: true,
        producto: { select: { cod_producto: true, descripcion: true } },
      },
    });

    const facturas = await this.prisma.factura_cab.findMany({
      where: { pedido_id: id, estado: { not: 'Anulada' } },
      select: { dest: true, dpunexp: true, dnumdoc: true },
    });

    const items = detalle.map((d) => {
      const cantOrdenada = Number(d.cantidad ?? 0);
      const cantPendiente = d.cant_pendiente != null ? Number(d.cant_pendiente) : cantOrdenada;
      const cantFacturada = Math.max(0, cantOrdenada - cantPendiente);
      return {
        pedido_detalle_id: d.id,
        producto_id: d.producto_id,
        cod_producto: d.producto?.cod_producto ?? '',
        descripcion: d.descripcion || d.producto?.descripcion || '',
        cantidad_ordenada: cantOrdenada,
        cantidad_facturada: cantFacturada,
        cantidad_pendiente: cantPendiente,
        precio: Number(d.precio_negociado ?? 0),
      };
    });

    return {
      orden_id: id,
      facturas: facturas.map((f) => `${f.dest}-${f.dpunexp}-${f.dnumdoc}`),
      items,
      completamente_facturada: items.every((i) => i.cantidad_pendiente === 0),
    };
  }

  async generateOrdenPdf(id: string, empresa_id: string): Promise<Buffer> {
    const orden = await this.prisma.pedidos.findUnique({
      where: { id },
      include: {
        cliente: {
          select: { nombre_fantasia: true, personas: { select: { razon_social: true, ruc: true, dv: true } } },
        },
        vendedor: { select: { nombre: true, apellido: true } },
        sucursal: { select: { descripcion: true, logo: true, nombre_fantasia: true } },
        deposito: { select: { descripcion: true } },
        moneda: { select: { codigo: true, simbolo: true } },
        condicion_pago: { select: { descripcion: true } },
        detalle: {
          include: {
            producto: { select: { id: true, descripcion: true, cod_producto: true } },
          },
          orderBy: { item: 'asc' },
        },
        empresa: {
          select: { ruc: true, dv: true, razon_social: true, nombre_fantasia: true, celular: true, email: true, logo: true },
        },
      },
    });

    if (!orden) throw new NotFoundException('Orden no encontrada');
    if (orden.empresa_id !== empresa_id) throw new BadRequestException('No pertenece a su empresa');
    if (orden.tipo_pedido !== 'orden_venta') throw new BadRequestException('No es una orden de venta');

    const empresa = (orden as any).empresa ?? {};
    const cli = orden.cliente as any;
    const persona = cli?.personas;

    const payload = {
      tipo: 'view',
      numero_orden: orden.numero_pedido ?? 'S/N',
      fecha_emision: orden.fecha_pedido,
      estado: orden.estado_orden,
      moneda: (orden.moneda as any)?.codigo ?? 'PYG',
      fecha_venc_orden: (orden as any).fecha_venc_orden,
      observaciones: orden.observaciones,
      empresa: {
        ruc: empresa.ruc,
        dv: empresa.dv,
        razon_social: empresa.razon_social,
        celular: empresa.celular,
        email: empresa.email,
        // Logo y nombre comercial de la sucursal de la orden (si no tiene, los de la
        // empresa): mismo criterio que la factura, el recibo y el presupuesto.
        nombre_fantasia: nombreFantasiaEmisor(orden.sucursal, empresa),
        logo_url: logoEmisor(orden.sucursal, empresa),
      },
      cliente: {
        razon_social: persona?.razon_social ?? cli?.nombre_fantasia,
        ruc: persona?.ruc,
        dv: persona?.dv,
      },
      vendedor: orden.vendedor
        ? { nombre: `${(orden.vendedor as any).nombre} ${(orden.vendedor as any).apellido ?? ''}`.trim() }
        : undefined,
      sucursal: (orden.sucursal as any)?.descripcion,
      deposito: (orden.deposito as any)?.descripcion,
      condicion_pago: (orden as any).condicion_pago?.descripcion,
      items: (orden.detalle as any[]).map((d) => ({
        cod_producto: d.producto?.cod_producto ?? "",
        descripcion: d.descripcion ?? d.producto?.descripcion,
        cantidad: Number(d.cantidad),
        precio_unitario: Number(d.precio_negociado),
        descuento_porcentaje: Number(d.descuento_porcentaje ?? 0),
        descuento_monto: Number(d.descuento_monto ?? 0),
      })),
      totales: {
        subtotal: Number((orden as any).subtotal ?? 0),
        iva10: Number((orden as any).iva10 ?? 0),
        iva5: Number((orden as any).iva5 ?? 0),
        exentas: Number((orden as any).exentas ?? 0),
        total: Number((orden as any).total ?? 0),
      },
    };

    const apiUrl = process.env.API_GENERADOR_PDF;
    if (!apiUrl) throw new BadRequestException('API_GENERADOR_PDF no configurado');

    const response = await fetch(`${apiUrl}/api/orden-venta/generate-pdf`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });

    if (!response.ok) {
      const err = await response.text();
      this.logger.error(`msv-kude orden-venta error ${response.status}: ${err}`);
      throw new BadRequestException('Error al generar PDF de la orden');
    }

    const arrayBuffer = await response.arrayBuffer();
    return Buffer.from(arrayBuffer);
  }

  private async assertOrdenEstado(id: string, empresa_id: string, estadoEsperado: EstadoOrdenVenta) {
    const orden = await this.prisma.pedidos.findUnique({ where: { id } });
    if (!orden) throw new NotFoundException('Orden no encontrada');
    if (orden.empresa_id !== empresa_id) throw new BadRequestException('No pertenece a su empresa');
    if (orden.tipo_pedido !== 'orden_venta') throw new BadRequestException('No es una orden de venta');
    if (orden.estado_orden !== estadoEsperado)
      throw new BadRequestException(`La orden debe estar en estado "${estadoEsperado}" (actual: "${orden.estado_orden}")`);
    return orden;
  }

  private async assertOrdenTransicion(id: string, empresa_id: string, nuevoEstado: EstadoOrdenVenta) {
    const orden = await this.prisma.pedidos.findUnique({ where: { id } });
    if (!orden) throw new NotFoundException('Orden no encontrada');
    if (orden.empresa_id !== empresa_id) throw new BadRequestException('No pertenece a su empresa');
    if (orden.tipo_pedido !== 'orden_venta') throw new BadRequestException('No es una orden de venta');
    const permitidos = TRANSICIONES_ORDEN[orden.estado_orden as string] ?? [];
    if (!permitidos.includes(nuevoEstado))
      throw new BadRequestException(`No se puede pasar de "${orden.estado_orden}" a "${nuevoEstado}"`);
    return orden;
  }

  private async recalcularTotales(tx: Prisma.TransactionClient, pedidoId: string) {
    const pedido = await tx.pedidos.findUnique({
      where: { id: pedidoId },
      select: { descuento_global_porcentaje: true },
    });
    const descGlobalPct = Number(pedido?.descuento_global_porcentaje || 0);

    const items = await tx.pedido_detalle.findMany({ where: { pedido_id: pedidoId } });
    let subtotal = 0,
      totalDesc = 0,
      totalIva = 0,
      totalDescGlobal = 0;

    for (const it of items) {
      const precioNeg = Number(it.precio_negociado);
      const descMonto = Number(it.descuento_monto || 0);
      const cantidad = Number(it.cantidad);

      // Descuento global prorrateado, aplicado sobre el NETO del ítem (precio − desc. particular),
      // igual que el POS/Factura y el presupuesto. Antes se aplicaba sobre el bruto, lo que
      // producía totales distintos cuando el ítem también tenía descuento propio.
      const descGlobalItem = descGlobalPct > 0 ? ((precioNeg - descMonto) * descGlobalPct) / 100 : 0;

      // Subtotal = (precio_negociado - descuento_monto - descuento_global_item) * cantidad
      const subtotalItem = (precioNeg - descMonto - descGlobalItem) * cantidad;
      const subtotalFinal = descGlobalPct > 0 ? Math.max(subtotalItem, 0) : Number(it.subtotal);

      // IVA recalculado sobre el subtotal FINAL (tras descuento global), usando la
      // proporción del ítem (11 = IVA 10%, 21 = IVA 5%, 0 = exento). Así el IVA del
      // detalle queda consistente con el total tras el descuento global.
      const propIva = Number(it.proporcion_iva || 0);
      const montoIvaItem = propIva > 0 ? subtotalFinal / propIva : 0;

      // Actualizar descuento_global_item, subtotal e IVA en cada detalle
      if (descGlobalPct > 0) {
        await tx.pedido_detalle.update({
          where: { id: it.id },
          data: {
            descuento_global_item: descGlobalItem,
            subtotal: subtotalFinal,
            monto_iva: montoIvaItem,
            updated_at: new Date(),
          },
        });
      }

      subtotal += subtotalFinal;
      totalDesc += descMonto;
      totalDescGlobal += descGlobalItem * cantidad;
      totalIva += montoIvaItem;
    }

    await tx.pedidos.update({
      where: { id: pedidoId },
      data: {
        subtotal,
        total_descuento: totalDesc + totalDescGlobal,
        total_descuento_global: totalDescGlobal,
        total_iva: totalIva,
        total: subtotal,
        updated_at: new Date(),
      },
    });
  }

  /**
   * Reserva un número de pedido atómicamente usando numeraciones_documento.
   * Busca una numeración activa de tipo "Pedido" (tipo_documento.codigo = 200)
   * para la sucursal del pedido. Si no encuentra, usa fallback secuencial.
   * Formato: est-pexp-número (ej: 001-001-0000001)
   */
  private async reservarNumeroPedido(
    tx: Prisma.TransactionClient,
    empresa_id: string,
    sucursal_id?: string,
  ): Promise<string> {
    // Buscar numeración de tipo Pedido (codigo=200) con bloqueo pesimista
    const tipoPedido = await tx.tipo_documento.findFirst({ where: { codigo: 200 } });
    if (!tipoPedido) {
      return this.fallbackNumeroPedido(tx, empresa_id);
    }

    // Buscar numeración activa para pedidos, preferiblemente de la sucursal
    let numeracionId: string | null = null;
    if (sucursal_id) {
      const numSuc = await tx.$queryRaw<Array<{ id: string }>>`
        SELECT nd.id FROM numeraciones_documento nd
        JOIN empresas_puntos_expedicion epe ON epe.id = nd.punto_expedicion_id
        WHERE nd.empresa_id = ${empresa_id}::uuid
          AND nd.tipo_documento_id = ${tipoPedido.id}::uuid
          AND nd.active = true
          AND epe.sucursal_id = ${sucursal_id}::uuid
        LIMIT 1
      `;
      if (numSuc.length > 0) numeracionId = numSuc[0].id;
    }

    // Si no hay para la sucursal, buscar cualquiera de la empresa
    if (!numeracionId) {
      const numEmp = await tx.$queryRaw<Array<{ id: string }>>`
        SELECT id FROM numeraciones_documento
        WHERE empresa_id = ${empresa_id}::uuid
          AND tipo_documento_id = ${tipoPedido.id}::uuid
          AND active = true
        LIMIT 1
      `;
      if (numEmp.length > 0) numeracionId = numEmp[0].id;
    }

    // Si no hay numeración configurada, usar fallback
    if (!numeracionId) {
      return this.fallbackNumeroPedido(tx, empresa_id);
    }

    // Bloqueo pesimista: SELECT FOR UPDATE
    const numeraciones = await tx.$queryRaw<
      Array<{
        id: string;
        numero_actual: number;
        numero_final: number | null;
        active: boolean;
        punto_expedicion_id: string;
      }>
    >`SELECT id, numero_actual, numero_final, active, punto_expedicion_id
      FROM numeraciones_documento
      WHERE id = ${numeracionId}::uuid
      FOR UPDATE`;

    if (numeraciones.length === 0) {
      return this.fallbackNumeroPedido(tx, empresa_id);
    }

    const numeracion = numeraciones[0];
    if (numeracion.numero_final && numeracion.numero_actual >= numeracion.numero_final) {
      throw new BadRequestException('Se alcanzó el número final de la numeración de pedidos');
    }

    // Obtener punto de expedición y establecimiento
    const puntoExp = await tx.empresas_puntos_expedicion.findUnique({
      where: { id: numeracion.punto_expedicion_id },
      include: { empresas_sucursales: { select: { punto_establecimiento: true } } },
    });

    const est = puntoExp?.empresas_sucursales?.punto_establecimiento || '001';
    const pexp = puntoExp?.punto_expedicion || '001';
    const num = String(numeracion.numero_actual).padStart(7, '0');

    // Incrementar atómicamente
    await tx.numeraciones_documento.update({
      where: { id: numeracionId },
      data: { numero_actual: numeracion.numero_actual + 1 },
    });

    return `${est}-${pexp}-${num}`;
  }

  /** Fallback: genera PED-NNNNNNN secuencial (sin locking, para empresas sin numeración configurada) */
  private async fallbackNumeroPedido(tx: Prisma.TransactionClient, empresa_id: string): Promise<string> {
    const ultimo = await tx.pedidos.findFirst({
      where: { empresa_id, numero_pedido: { not: null } },
      orderBy: { numero_pedido: 'desc' },
      select: { numero_pedido: true },
    });
    const siguiente = ultimo?.numero_pedido ? parseInt(ultimo.numero_pedido.replace(/\D/g, ''), 10) + 1 : 1;
    return `PED-${String(siguiente).padStart(7, '0')}`;
  }

  private async findOneInternal(id: string) {
    const data = await this.prisma.pedidos.findUnique({
      where: { id },
      include: {
        cliente: {
          select: { id: true, nombre_fantasia: true, personas: { select: { razon_social: true, ruc: true } } },
        },
        vendedor: { select: { id: true, nombre: true, apellido: true } },
        sucursal: { select: { id: true, descripcion: true } },
        deposito: { select: { id: true, descripcion: true, codigo: true, es_principal: true } },
        moneda: { select: { id: true, codigo: true, descripcion: true, simbolo: true } },
        condicion_operacion: { select: { id: true, descripcion: true } },
        lista_precios: { select: { id: true, nombre: true, codigo: true } },
        factura: { select: { id: true, dnumdoc: true } },
        creador: { select: { id: true, username: true } },
        detalle: {
          include: {
            producto: {
              select: {
                id: true,
                descripcion: true,
                cod_producto: true,
                codigo_barra: true,
                producto_imagenes: { take: 1, select: { path: true } },
                unidades_de_medida: { select: { representacion: true } },
              },
            },
            presentacion: { select: { id: true, nombre: true, abreviatura: true, factor_conversion: true } },
            deposito: { select: { id: true, descripcion: true, codigo: true } },
          },
          orderBy: { item: 'asc' },
        },
        historial: {
          orderBy: { created_at: 'desc' },
          take: 10,
          include: { usuario: { select: { id: true, username: true } } },
        },
      },
    });
    if (!data) return null;
    return {
      ...data,
      fecha_pedido: formatDateResponse(data.fecha_pedido),
      fecha_confirmacion: formatDateResponse(data.fecha_confirmacion),
      fecha_vencimiento: formatDateResponse(data.fecha_vencimiento),
    };
  }

  private async assertPedidoEditable(id: string, empresa_id: string) {
    const pedido = await this.prisma.pedidos.findUnique({ where: { id } });
    if (!pedido) throw new NotFoundException('Pedido no encontrado');
    if (pedido.empresa_id !== empresa_id) throw new BadRequestException('No pertenece a su empresa');
    if (![EstadoPedido.BORRADOR, EstadoPedido.CONFIRMADO].includes(pedido.estado as EstadoPedido))
      throw new BadRequestException(`No se puede editar en estado "${pedido.estado}"`);
    return pedido;
  }

  private async assertPedidoEstado(id: string, empresa_id: string, estadoEsperado: string) {
    const pedido = await this.prisma.pedidos.findUnique({ where: { id } });
    if (!pedido) throw new NotFoundException('Pedido no encontrado');
    if (pedido.empresa_id !== empresa_id) throw new BadRequestException('No pertenece a su empresa');
    if (pedido.estado !== estadoEsperado)
      throw new BadRequestException(`El pedido debe estar en estado "${estadoEsperado}" (actual: "${pedido.estado}")`);
    return pedido;
  }

  private async logHistorial(
    tx: Prisma.TransactionClient,
    pedidoId: string,
    accion: string,
    userId: string,
    descripcion: string,
    datos?: any,
  ) {
    await tx.pedido_historial.create({
      data: { pedido_id: pedidoId, accion, descripcion, usuario_id: userId, datos: datos ?? undefined },
    });
  }
}
