// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//
//	LibInventarios.rpt
//      Nombre del reporte en el sistema: n/a
//
//  Sistema: AdminPAQ v1.1
//
//	Este archivo contiene la biblioteca de funcione del módulo de inventarios.
//
//	Consideraciones especiales:
//
//	Elaboró:    Eduardo Pérez Figueroa
//	Fecha: 	    21 de Junio del 2001
//
//  Historia de revisiones:
//	Autor:      Eduardo Pérez Figueroa
//	Fecha:      11 de Octubre del 2001
//              Modificaciones a cálculo de costos debido al nuevo acumulado
//              del costo de las salidas y al costo contenido en el campo
//              cCostoEspecifico.
//
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// Historia de modificaciones
//  Fecha      Nombre   IDLotus                     Descripcion
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// 26/07/2002   RPC     SC 150      Se agregó la función fVerificarMovtosSinAfectar
//                                  que busca si un producto tiene al menos un 
//                                  movimiento de entrada o salida sin afectar
// 04/11/2002   DRdA    ND1458      En la función fVerificarMovtosSinAfectar 
//                                  regresaba un movimiento sin afectar cuando si
//                                  lo estaba por no excluirlo del filtro.
// 08/09/2004   LAP     N.D.2922    Adición de un nuevo método que calcule el
//                                  costo promedio de un producto a una fecha
//                                  determinada basandose en los ACumulados de
//                                  existencias y costo y los movimientos del
//                                  producto. Además esta función deberá
//                                  regresar en los argumentos el costo total y
//                                  la existencia total a dicha fecha.
//                                  - fCostoPromedioProducto
//                                  - fCostoInicialProducto
// 05/10/2004   LAP     N.D.2922    Regresar como costo del movimiento su costo
//                                  especifico, ya que esta función recostea los 
//                                  movimientos de salida y se tendrán diferencias 
//                                  entre los movimientos y los acumulados.
//                                  - fCostoMovimiento
// 01/12/2005   LAP     N.D.3010    Nuevo esquema de búsquedas parciales para agilizar los reportes
//                                  - fExistenciaInicialProducto
// 14/12/2005   LAP     N.D.3682    Nuevo esquema para calcular la existencia del 
//                                  producto a una fecha (Incluye los movimientos 
//                                  a dicho fecha).
//                                  - fExistenciaProducto
//                                  - fExistenciaPeriodo
// 23/02/2006   HVA     N.D.3868    Se duplican los costos cuando el producto tiene unidadades no convertibles.
// 24/02/2009   JENL    N.D.5748    ERROR DUPLICA LA INFORMACION DE EXITENCIAS AL EJECUTAR EL REPORTE INVENTARIO
//                                  ACTUAL GLOBAL EN UNIDADES
//                                  Se modifico: fExistenciaInicialProducto
//  23/04/2009  JENL    N.D.5766    COSTOS INICIALES INCORRECTOS AL EJECUTAR LOS REORTES DE EXISTENCIAS
//                                  Y COSTOS (COSTO PROMEDIO) Y EXISTENCIAS Y COSTOS (ULTIMO COSTO)
//                                  Se modifico: fCostoHistorico
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

Columnas 1; 1


// *****************************************************************************
//  fCostoEntradaCapa
//
//	Esta función obtiene o calcula el costo de un movimiento de entrada cuando
//  el método de costeo sea UEPS o PEPS.
//
// *****************************************************************************
DefFunc fCostoEntradaCapa(atMovimiento: Tabla; atMovimientoCapa: Tabla; atCapa: Tabla; atTipoCambio: Tabla; aIdMoneda; aCostoUnitario; aIdCapa)
Local lCosto

  lCosto = 0.0
  aCostoUnitario = 0.0
  aIdCapa = 0
  // Busca el movimiento de capa que corresponde a la entrada  
  // Puede encontrarse más de uno, pero hay que discriminar los lotes y pedimentos
  Mientras [ atMovimientoCapa.Busca[@Str(atMovimiento(cIdMovimiento);0)]; atMovimientoCapa->Encontro; atMovimientoCapa.Busca Siguiente[1] ]
    // ahora recupera la capa a partir del movimiento de capa
    atCapa.Busca[@Str(atMovimientoCapa(cIdCapa);0)]
    Si atCapa->Encontro
      // Discrimina lotes y pedimentos
      Si atCapa(cTipoCapa) != 5 // la capa no es de costeo UEPS o PEPS
        Continua
      FinSi
      // calcula el costo total en base al costo unitario
      lCosto = atCapa(cCosto) * atMovimiento(cUnidades)
      // Convierte el costo a la moneda del reporte. Nota: El costo SIEMPRE está expresado en la moneda base (ID = 1)
      lCosto = fConvierteTipoCambio(lCosto; 1; aIdMoneda; atMovimiento(cFecha); atTipoCambio)

      // Regresa el costo unitario
      aCostoUnitario = atCapa(cCosto)
      // Convierte el costo unitario a la moneda del reporte. Nota: El costo SIEMPRE está expresado en la moneda base (ID = 1)
      lCostoUnitario = fConvierteTipoCambio(lCostoUnitario; 1; aIdMoneda; atMovimiento(cFecha); atTipoCambio)

      // Regresa el ID de la capa
      aIdCapa = atMovimientoCapa(cIdCapa)
      FinCiclo
    FinSi   
  FinMientras
  
  Retorna lCosto 
FinFunc


// *****************************************************************************
//  fCostoEspecifico
//
//	Esta función obtiene el costo de un movimiento leyéndolo del campo
//  "cCostoEspecifico".
//
// *****************************************************************************
DefFunc fCostoEspecifico(atMovimiento: Tabla; atTipoCambio: Tabla; aIdMoneda)
Local lCosto

  // Por definición, el costo específico es costo total
  lCosto = atMovimiento(cCostoEspecifico)

  // Convierte el costo a la moneda del reporte. Nota: El costo SIEMPRE está expresado en la moneda base (ID = 1)
  lCosto = fConvierteTipoCambio(lCosto; 1; aIdMoneda; atMovimiento(cFecha); atTipoCambio)

  Retorna lCosto
FinFunc

//--------------------------------------------------------------------------------------------------
//  Nombre: fCostoHistorico
//  Proceso: Esta función obtiene el costo unitario promedio o último costo a una fecha determinada.
//  Entradas: 
//  Salidas: 
//  Consideraciones:
//
//  Historial de revisiones
//    Fecha     ID_SitePruebas              Descripción
//--------------------------------------------------------------------------------------------------
//  23/04/2009    N.D.5766        Se debe convertir al tipo de cambio de la fecha aFecha que es la
//                                que solicito el usuario (JENL).
//--------------------------------------------------------------------------------------------------
DefFunc fCostoHistorico(acIdProducto; acIdAlmacen; aFecha; aTipoCosto; atCostoHistorico: Tabla; atTipoCambio: Tabla; aIdMoneda)
Local lCostoHistorico, lFecha, lFechaTope

  lCostoHistorico = 0.0

  // Busca el registro de costo histórico de acuerdo a la fecha del movimiento
  atCostoHistorico.Busca[acIdProducto & ':' & acIdAlmacen & ':' & aFecha]
  Si !atCostoHistorico->Encontro
    // Busca la primera fecha del archivo
    atCostoHistorico.Busca[acIdProducto & ':' & acIdAlmacen]
    Si atCostoHistorico->Encontro
      lFechaTope = atCostoHistorico(cFechaCostoH) // Obtiene la primera fecha de costo histórico
    Sino
      Retorna lCostoHistorico // Regresa cero, pues no existe el costo histórico
    FinSi

    // Si la fecha tope es menor a la fecha requerida para el costo
    // Busca hasta encontrar una fecha menor en el histórico de costos
    Si lFechaTope < aFecha
      lFecha = aFecha
      InicioRepite
        lFecha = fFechaAnterior(lFecha)
        // Ya no existen registros de costo histórico del producto, regresa cero
        Si lFecha < lFechaTope 
          FinCiclo
        FinSi
        atCostoHistorico.Busca[acIdProducto & ':' & acIdAlmacen & ':' & lFecha]
      Repite !atCostoHistorico->Encontro
    Sino // Si la fecha tope es mayor que la fecha del movimiento, regresa cero pues no existe costo histórico
      Retorna lCostoHistorico
    FinSi   
  FinSi

  Si atCostoHistorico->Encontro
    // Regresa el costo según el método de costeo
    lCostoHistorico = aTipoCosto = 3? atCostoHistorico(cUltimoCostoH) : atCostoHistorico(cCostoH)
    // Convierte el costo histórico a la moneda del reporte. Nota: El costo SIEMPRE está expresado en la moneda base (ID = 1)
    //lCostoHistorico = fConvierteTipoCambio(lCostoHistorico; 1; aIdMoneda; atCostoHistorico(cFechaCostoH); atTipoCambio) //N.D.5766
    lCostoHistorico = fConvierteTipoCambio(lCostoHistorico; 1; aIdMoneda; aFecha; atTipoCambio)//N.D.5766
  FinSi

  Retorna lCostoHistorico
FinFunc


// *****************************************************************************
//  fCostoInicialPeriodo
//
//	Esta función lee el costo inicial de un determinado periodo en la tabla de 
//  existencias.
//
//  Parámetros:
//    aPeriodo = Número de periodo
//    atExistencia = Tabla de existencias
//
//  Salidas:
//    lCosto = Costo del periodo especificado
// *****************************************************************************
DefFunc fCostoInicialPeriodo(aFecha; aPeriodo; atExistencia: Tabla; atTipoCambio: Tabla; aIdMoneda)
Local lCosto, lCostoEntradas, lCostoSalidas, NUMCAMPOCOSTO1, NUMCAMPOENTRADAS1, NUMCAMPOSALIDAS1

  // Inicia la posición de los campos "cCostoEntradasPeriodo1" y "cCostoSalidasPeriodo1" (empezando en cero)
  NUMCAMPOENTRADAS1 = 33
  NUMCAMPOSALIDAS1 = 45
  // Inicia la posición del campo "cCostoPeriodo1" (empezando en cero)
  // NUMCAMPOCOSTO1 = 31

  Si aPeriodo = 1 // Si el periodo es el primero, obtiene el dato del saldo inicial del ejercicio
    lCostoEntradas = atExistencia(cCostoInicialEntradas)
    lCostoSalidas = atExistencia(cCostoInicialSalidas)
    lCosto = lCostoEntradas - lCostoSalidas
    // lCosto = atExistencia(cCostoInicial)
  Sino // Si el periodo no es el primero, toma el saldo final del periodo anterior
    // Ejemplo:
    // Si NUMCAMPOENTRADAS1 = 33 y aPeriodo = 4
    // Entonces el campo a leer es 33 + 4 - 2 = 35, el cual corresponde a cCostoEntradasPeriodo3
    lCostoEntradas = atExistencia->CampoVal[NUMCAMPOENTRADAS1 + aPeriodo - 2]
    lCostoSalidas = atExistencia->CampoVal[NUMCAMPOSALIDAS1 + aPeriodo - 2]
    lCosto = lCostoEntradas - lCostoSalidas    

    // Ejemplo:
    // Si NUMCAMPOCOSTO1 = 31 y aPeriodo = 4
    // Entonces el campo a leer es 31 + 4 - 2 = 33, el cual corresponde a cCostoPeriodo3
    // lCosto = atExistencia->CampoVal[NUMCAMPOCOSTO1 + aPeriodo - 2]
  Finsi

  // Convierte el costo a la moneda del reporte. Nota: El costo SIEMPRE está expresado en la moneda base (ID = 1)
  lCosto = fConvierteTipoCambio(lCosto; 1; aIdMoneda; aFecha; atTipoCambio)

  Retorna lCosto
FinFunc


// *****************************************************************************
//  fCostoMovimiento
//
//	Esta función obtiene o calcula el costo de un movimiento. Sirve para 
//  cualquier método de costeo que tenga definido el producto.
//
//  Parámetros 
//
//  Salidas 
//
// 05/10/2004   LAP     N.D.2922    Regresar como costo del movimiento su costo
//                                  especifico, ya que esta función recostea los 
//                                  movimientos de salida y se tendrán diferencias 
//                                  entre los movimientos y los acumulados.
// *****************************************************************************
DefFunc fCostoMovimiento(atProducto: Tabla; atMovimiento: Tabla; atCostoHistorico: Tabla; atTipoCambio: Tabla; aIdMoneda)
Local lCosto, lCostoHistorico

  lCosto = 0.0

  // N.D.2922 Inicio
  //Si atMovimiento(cAfectaExistencia) = 1 // es una entrada?
  //  lCosto = fCostoEspecifico(atMovimiento; atTipoCambio; aIdMoneda)
  //Sino // es una salida
  //  // Si el método de costeo no es promedio o ultimo costo
  //  Si atProducto(cMetodoCosteo) > 3
  //    lCosto = fCostoEspecifico(atMovimiento; atTipoCambio; aIdMoneda)
  //  Sino // Costo promedio y último costo, buscar el costo del movimiento de salida en la tabla de costos historicos
  //    lCostoHistorico = fCostoHistorico(atProducto(cIdProducto); atProducto(cMetodoCosteo) = 2? atMovimiento(cIdAlmacen) : 0; atMovimiento(cFecha); atProducto(cMetodoCosteo); atCostoHistorico; atTipoCambio; aIdMoneda)
  //    lCosto = lCostoHistorico * atMovimiento(cUnidades)
  //  FinSi
  //FinSi

  lCosto = fCostoEspecifico(atMovimiento; atTipoCambio; aIdMoneda)
  // N.D.2922 Fin

  Retorna lCosto
FinFunc


// *****************************************************************************
//  fIdDocumentoMovtoOwner
//
//	Esta función regresa el ID del documento del movimiento owner del 
//  movimiento oculto. La razón de ser de ésta función es que los movimientos
//  ocultos no heredan el campo cIdDocumento de su owner.
//
//  Parámetros:
//    atMovtoOculto = Movimiento oculto (características, componentes 
//    o traspasos)
//    atMovtoOwner = Movimiento owner
//
//  Salidas:
//    lCosto = Costo del periodo especificado
// *****************************************************************************
DefFunc fIdDocumentoMovtoOwner(atMovtoOculto: Tabla; atMovtoOwner: Tabla)

  Si atMovtoOculto(cIdMovtoOwner)
    atMovtoOwner.Busca[@Str(atMovtoOculto(cIdMovtoOwner);0)] // Busca el movimiento owner
    Si atMovtoOwner->Encontro
      Retorna atMovtoOwner(cIdDocumento)
    FinSi
  FinSi
  Retorna 0
FinFunc


// *****************************************************************************
//  fUnidadesEquivalentes
//
//	Esta función convierte unidades en una unidad base a unidades en una
//  unidad equivalente.
//
// *****************************************************************************
DefFunc fUnidadesEquivalentes(aUnidades; aIdUnidadBase; aIdUnidadEquivalente; atEquivalencia: Tabla)
Local aUnidadesEquivalentes

  aUnidadesEquivalentes = aUnidades
  Si aIdUnidadBase != aIdUnidadEquivalente
    // Busca la unidad equivalente a la unidad base
    atEquivalencia.Busca[aIdUnidadBase & ':' & aIdUnidadEquivalente]
    Si atEquivalencia->Encontro
      // Encontro la unidad equivalente?
      aUnidadesEquivalentes = aUnidades * atEquivalencia(cFactorConversion)      
    Finsi
  FinSi
  
  Retorna aUnidadesEquivalentes
FinFunc


// *****************************************************************************
//  fUnidades
//
//	Esta función obtiene las unidades del movimiento basado en el parámetro
//  aUnidad.
//
// *****************************************************************************
DefFunc fUnidades(aUnidad; aIdUnidadBase; aIdUnidad; atMovimiento: tabla; atEquivalencia: Tabla)
Local lUnidades

  lUnidades = 0
  Escoge aUnidad
    Caso 0 // Unidad base del producto
      lUnidades = atMovimiento(cUnidades)
    FinCaso
    Caso 1 // Unidad no equivalente
      lUnidades = atMovimiento(cUnidadesNC)
    FinCaso
    Caso 2 // Unidad equivalente
      lUnidades = fUnidadesEquivalentes(atMovimiento(cUnidades); aIdUnidadBase; aIdUnidad; atEquivalencia)
    FinCaso
  FinEscoge

  Retorna lUnidades
FinFunc

// *****************************************************************************
//  fUnidadesInventario
//
//  Esta función obtiene las unidades del movimiento de inventario.
//  No cambio parámetros para en la llamada cambiar sólo el nombre.
// *****************************************************************************
DefFunc fUnidadesInventario(aUnidad; aIdUnidadBase; aIdUnidad; atMovimiento: tabla; atEquivalencia: Tabla)
Local lUnidades

  lUnidades = 0
  Escoge aUnidad
    Caso 0 // Unidad base del producto
      lUnidades = atMovimiento(cUnidadesCapturadas)
    FinCaso
    Caso 1 // Unidad no equivalente
      lUnidades = atMovimiento(cUnidadesNC)
    FinCaso
    Caso 2 // Unidad equivalente
      lUnidades = fUnidadesEquivalentes(atMovimiento(cUnidadesCapturadas); aIdUnidadBase; aIdUnidad; atEquivalencia)
    FinCaso
  FinEscoge

  Retorna lUnidades
FinFunc



// *****************************************************************************
//  fExistenciaInicialPeriodo
//
//	Esta función lee la existencia inicial de un determinado periodo en la
//  tabla de existencias.
//
//  Parámetros 
//    aPeriodo = Número de periodo
//    atExistencia = Tabla de existencias
//
//  Salidas 
//    lExistencia = Existencia del periodo especificado
// *****************************************************************************
DefFunc fExistenciaInicialPeriodo(aPeriodo; aUnidad; aIdUnidadBase; aIdUnidad; atExistencia: Tabla; atEquivalencia: Tabla)
Local lExistencia, NUMCAMPOENTRADAS1, NUMCAMPOSALIDAS1

  // Inicia la posición del campo "cEntradasPeriodo1" y "cSalidasPeriodo1" (empezando en cero)
  NUMCAMPOENTRADAS1 = 9
  NUMCAMPOSALIDAS1 = 21
  // NUMCAMPOENTRADAS1 = 7
  // NUMCAMPOSALIDAS1 = 19

  Si aPeriodo = 1 // Si el periodo es el primero, obtiene el dato del saldo inicial del ejercicio
    lExistencia = atExistencia(cEntradasIniciales) - atExistencia(cSalidasIniciales)
    // lExistencia = atExistencia(cExistenciaInicial)
  Sino // Si el periodo no es el primero, toma el saldo final del periodo anterior
    // Ejemplo:
    // Si NUMCAMPOENTRADAS1 = 9 y aPeriodo = 4
    // Entonces el campo a leer es 9 + 4 - 2 = 11, el cual corresponde a cEntradasPeriodo3

    // Ejemplo:
    // Si NUMCAMPOENTRADAS1 = 7 y aPeriodo = 4
    // Entonces el campo a leer es 7 + 4 - 2 = 9, el cual corresponde a cEntradasPeriodo3
    lExistencia = atExistencia->CampoVal[NUMCAMPOENTRADAS1 + aPeriodo - 2] - atExistencia->CampoVal[NUMCAMPOSALIDAS1 + aPeriodo - 2]
  Finsi

  // Convierte las unidades
  Si aUnidad = 2 // se requiere el reporte en unidades equivalentes
    lExistencia = fUnidadesEquivalentes(lExistencia; aIdUnidadBase; aIdUnidad; atEquivalencia)
  FinSi

  Retorna lExistencia
FinFunc


// *****************************************************************************
//  fExistenciaInicialProducto
//
//	Esta función calcula la existencia inicial del producto expresada en unidades
//  base, equivalentes o no convertibles a una fecha determinada.
//
//  Parámetros 
//
//  Salidas 
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// Historia de modificaciones
//  Fecha      Nombre   IDLotus                     Descripcion
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// 01/12/2005   LAP     N.D.3010    Nuevo esquema de búsquedas parciales para agilizar los reportes
// 24/03/2009   JENL    N.D.5748    Omite el almácen global
// *****************************************************************************
DefFunc fExistenciaInicialProducto(acIdProducto; acIdAlmacen; aFecha; aIdEjercicio; aPeriodo; aUnidad; aIdUnidadBase; aIdUnidadEquivalente; atMovimiento: Tabla; atExistencia: Tabla; atEquivalencia: Tabla)
Local lExistenciaProducto // contiene el valor de retorno de la función
Local lInventarioInicialPeriodo, lMovimiento, lFechaInicial, lFechaFinal, lLlave, lUnidad

  // Inicia variables de existencia inicial
  lExistenciaProducto = 0
  lInventarioInicialPeriodo = 0

  // Arma la llave según el parámetro del ID del almacén
  Si acIdAlmacen = 0 // se solicita existencia general?
    lLlave = acIdProducto & ':' & aIdEjercicio
  Sino // Se solicita existencia de un almacén en particular?
    // Se solicita existencia en unidades no equivalentes?
    Si aUnidad = 1
      lLlave = acIdProducto & ':' & aIdEjercicio & ':' & acIdAlmacen & ':2'
    Sino  
      lLlave = acIdProducto & ':' & aIdEjercicio & ':' & acIdAlmacen & ':1'
    FinSi
  FinSi

  // Como es un registro por cada almacén se realiza un ciclo para leerlos todos
  Mientras[atExistencia.Busca[lLlave]; atExistencia->Encontro; atExistencia.Busca Siguiente[2]]
    // Discrimina el registro en función de la unidad del reporte
    // aUnidad = 0 -> Unidad base
    // aUnidad = 1 -> Unidad no equivalente
    // aUnidad = 2 -> Unidad equivalente
    // atExistencia(cTipoExistencia) = 1 -> Unidad base
    // atExistencia(cTipoExistencia) = 2 -> Unidad no equivalente
    Si (aUnidad = 1 y atExistencia(cTipoExistencia) = 1) o (aUnidad != 1 y atExistencia(cTipoExistencia) = 2)
      Continua
    Finsi
    
    Si atExistencia(cIdAlmacen) > 0 //N.D.5748
      // Obtiene el Inventario final del período anterior
      lInventarioInicialPeriodo = lInventarioInicialPeriodo + fExistenciaInicialPeriodo(aPeriodo; aUnidad; aIdUnidadBase; aIdUnidadEquivalente; atExistencia; atEquivalencia)
    Finsi
    
    // Si se solicitó existencia por almacén, rompe el ciclo 
    Si acIdAlmacen != 0
      FinCiclo
    FinSi
  FinMientras

  // Guarda el valor de la variable aUnidad
  // La intención es hacer un poco más rápida la función evitando leer la tabla de equivalencias
  // por cada movimiento cuando se pide la existencia en unidades equivalentes.
  Si aUnidad = 2
    lUnidad = aUnidad
    aUnidad = 0 // asume unidades base
  FinSi

  lNeto = 0

  // N.D.3010 Inicio
  // Lee los movimientos de entrada y salida de inventarios, desde el inicio del periodo hasta un día anterior 
  //  a la fecha de inicio del reporte
  lFechaInicial = @Substr(aFecha;0;6) & '01'
  lFechaFinal = fFechaAnterior(aFecha)

  // NOTA: Es obligatorio volver a fijar el índice a fin de que las búsquedas parciales funcionen correctamente
  atMovimiento.UsaIndice['iProductoFechaAfecta']
  // Localización del primer movimiento del producto
  atMovimiento.Busca[acIdProducto & ':' & lFechaInicial]
  Si !atMovimiento->Encontro
    // Busquedas parciales: Cursor en el registro más cercano que cumple con la llave
    // NOTA: La propiedad Llave de la tabla nos regresa la llave más cercana que cumple con el criterio de la búsqueda
    //       La llave está en formato cadena y no tiene ningún separador entre los campos, por lo que es necesario revisar
    //       en FoxPro la definición del índice para descomponer los elementos de la Llave
    // Revisar si la llave actual corresponde al Producto / Almacén y al Rango de Fechas.
    Si @Col(acIdProducto;0) = @Val(@SUBSTR(atMovimiento->Llave;0;11))
      Si @SUBSTR(atMovimiento->Llave;11;8) >= lFechaInicial Y @SUBSTR(atMovimiento->Llave;11;8) <= lFechaFinal
        // Realizar nuevamente la búsqueda a fin de posicionarnos en el registro y activar la propiedad Encontro
        lFechaInicial = @SUBSTR(atMovimiento->Llave;11;8)
        atMovimiento.Busca[acIdProducto & ':' & lFechaInicial]
      FinSi
    FinSi        
//!    Si atMovimiento(cIdProducto) = acIdProducto
//!      Si atMovimiento(cFecha) >= lFechaInicial Y atMovimiento(cFecha) <= lFechaFinal
//!        lFechaInicial = atMovimiento(cFecha)
//!      FinSi
//!    FinSi          
  FinSi

  // N.D.3010 Recorrer los movimientos hasta que sea diferente producto, almacén o se rompa el rango de fechas
//!  Mientras[atMovimiento.Busca[@Str(acIdProducto;0)]; atMovimiento->Encontro; atMovimiento.Busca Siguiente[1]]
  Mientras atMovimiento->Encontro Y (atMovimiento(cFecha) <= lFechaFinal)
//!    Si atMovimiento(cFecha) > lFechaFinal
//!      FinCiclo
//!    FinSi
    // Discrimina movimientos sin afectar
    Si atMovimiento(cAfectadoInventario) = 0      
      // N.D.3010 Búsqueda del siguiente registro que cumpla con el producto
      atMovimiento.BUSCA SIGUIENTE[1]
      Continua
    FinSi
    // Discrimina movimientos que no son entradas ni salidas
    Si atMovimiento(cAfectaExistencia) != 1 y atMovimiento(cAfectaExistencia) != 2
      // N.D.3010 Búsqueda del siguiente registro que cumpla con el producto
      atMovimiento.BUSCA SIGUIENTE[1]
      Continua
    FinSi
    // Discrimina movimientos cuyo almacén no corresponde al solicitado
    Si acIdAlmacen != 0 y atMovimiento(cIdAlmacen) != acIdAlmacen
      // N.D.3010 Búsqueda del siguiente registro que cumpla con el producto
      atMovimiento.BUSCA SIGUIENTE[1]
      Continua
    FinSi
    lMovimiento = fUnidades(aUnidad; aIdUnidadBase; aIdUnidadEquivalente; atMovimiento; atEquivalencia)
    Si atMovimiento(cAfectaExistencia) = 1 // es una entrada?
      lNeto = lNeto + lMovimiento
    Sino // es una salida
      lNeto = lNeto - lMovimiento
    FinSi
    // N.D.3010 Búsqueda del siguiente registro que cumpla con el producto
    atMovimiento.BUSCA SIGUIENTE[1]
//!    FinMientras // Busca los demás movimientos en la misma fecha
//!    // Incrementa la fecha para busca el siguiente día
//!    lFechaInicial = fFechaSiguiente(lFechaInicial)  
  FinMientras // Busca los demás movimientos del mismo producto
  // N.D.3010 Fin

  Si lUnidad = 2 // Se solicitó el reporte en unidades equivalentes?
    lNeto = fUnidadesEquivalentes(lNeto; aIdUnidadBase; aIdUnidadEquivalente; atEquivalencia)
  FinSi

  // Existencia del producto = Saldo del periodo anterior + Movimientos del periodo
  lExistenciaProducto = lInventarioInicialPeriodo + lNeto  

  Retorna lExistenciaProducto
FinFunc

// *****************************************************************************
// Nombre:      fCostoPromedioProducto
// Descripción: Calcula el costo promedio de un producto a una fecha determinada.
//              Además regresa en los argumentos el costo total y la existencia.
//              Esto la hace a fecha determinada y para todos o un almacén en 
//              especifico.
//              El calculo se realiza a través de la tabla de Aculumados de 
//              existencia y costos (MGW10030) y la tabla de movimientos. La
//              tabla de Acumulados de existencia y costo se detalla por ejercicio,
//              producto y almacén .
//
//              Proceso:
//              De la tabla de Acumulados de existencias y costos se toma el 
//              resultado de la resta del acumulado de entradas menos el acumulado
//              de salidas de costos, pertenecientes al periodo anterior al de 
//              la fecha recibida. A partir de aqui se barren los movimientos
//              del producto a partir del dia 1 y hasta el dia anterior a la fecha
//              recibida. Los movimientos de entrada se suman y los movimientos de
//              salida se restan (en costo). De esta forma se obtiene el costo total
//              a dicha fecha. Lo mismo sucede para las unidades.
//              Al final el costo promedio es la división del costo total entre la
//              existencia total.
//              Ejemplo:
//              Se desea obtener el costo del Producto N a la fecha del 15 de septiembre
//              de 2004 para todos los almacenes.  
//              En este caso se busca en MGW10030 el registro del producto N, para el 
//              ejercicio 2004 y para el almacén cero (Todos los almacenes). En este
//              registro se toma el acumulado de entradas del mes de Agosto y se le
//              resta el acumulado de salidas del mismo mes. Hasta aqui tenemos el costo
//              total al 31 de agosto. Ahora se recorren los movimientos del producto N
//              en todos los almacenes y que tengan fecha del 1 de septiembre y hasta el
//              14 de septiembre, sumando las entradas y restando las salidas.
//              Ahora si tenemos el costo del producto N para todos los almacenes al 15
//              de septiembre.
//
// Parámetros:
//            acIdProducto  - Id del producto
//            acIdAlmacen   - Id del almacén (Cero si es para todos los almacenes)
//            aFecha        - Fecha
//            aIdEjercicio  - Id del ejercicio de la fecha
//            aPeriodo      - Número de periodo al que corresponde la fecha
//            atMovimiento  - Tabla de movimientos
//            atExistencia  - Tabla de existencia
//            atProducto    - Tabla de productos
//            atCostoHistorico  - Tabla de costo historico
//            atTipoCambio  - Tabla de tipos de cambio
//            aIdMoneda     - Id de la moneda para expresar el costo
//            aCostoTotal       - En este argumento regresa el costo total
//            aExistenciaTotal  - En este argumento regresa la existencia total
// Salidas:
// -----------------------------------------------------------------------------
// Historial de Cambios
// Fecha        Id        Autor   Descripción
// 08/09/2004   N.D.2922  LAP     Implementación inicial
// *****************************************************************************
DefFunc fCostoPromedioProducto(acIdProducto; acIdAlmacen; aFecha; aIdEjercicio; aPeriodo; atMovimiento:Tabla; atExistencia:Tabla; atProducto:Tabla; atCostoHistorico:Tabla; atTipoCambio:Tabla; aIdMoneda; aCostoTotal; aExistenciaTotal)
Local lCostoPromedio
Local lLlave 
Local lExistenciaInicialPeriodo, lCostoInicialPeriodo
Local lFechaInicial, lFechaFinal
Local lExistencia, lCosto, lUnidadesMovto, lCostoMovto
Local NUMCAMPOENTRADAS1, NUMCAMPOSALIDAS1
Local NUMCAMPOCOSTOENTRADAS1, NUMCAMPOCOSTOSALIDAS1

  // Inicialización de variables para conocer la posición de los campos en la tabla de
  //  Existencia MGW10030
  // Inicia la posición del campo "cEntradasPeriodo1" y "cSalidasPeriodo1"
  NUMCAMPOENTRADAS1 = 9
  NUMCAMPOSALIDAS1 = 21
  // Inicia la posición del campo "cCostoEntradasPeriodo1" y "cCostoSalidasPeriodo1" 
  NUMCAMPOCOSTOENTRADAS1 = 33
  NUMCAMPOCOSTOSALIDAS1 = 45

  // Inicia los argumentos en los cuales regresará la existencia y el costo inicial
  aCostoTotal = 0
  aExistenciaTotal = 0

  // Inicia variables de existencia/costo inicial del periodo
  lExistenciaInicialPeriodo = 0
  lCostoInicialPeriodo = 0

  // Arma la llave según el parámetro del ID del almacén
  Si acIdAlmacen = 0 
    // Existencia general
    lLlave = acIdProducto & ':' & aIdEjercicio
  Sino 
    // Existencia de un almacén en particular
    lLlave = acIdProducto & ':' & aIdEjercicio & ':' & acIdAlmacen & ':1'
  FinSi

  // Como es un registro por cada almacén se realiza un ciclo para leerlos todos
  Mientras[atExistencia.Busca[lLlave]; atExistencia->Encontro; atExistencia.Busca Siguiente[2]]
    // No procesar los registros de existencias para unidades no convertibles	
    Si atExistencia(cTipoExistencia) = 2
      Continua
    FinSi

    Si aPeriodo = 1 
      // Si el periodo es el primero, obtiene el dato del saldo inicial del ejercicio
      lExistencia = atExistencia(cEntradasIniciales) - atExistencia(cSalidasIniciales)
      lCosto = atExistencia(cCostoInicialEntradas) - atExistencia(cCostoInicialSalidas)
    Sino 
      // Si el periodo no es el primero, toma el saldo final del periodo anterior
      // Ejemplo:
      // Si NUMCAMPOENTRADAS1 = 9 y aPeriodo = 4
      // Entonces el campo a leer es 9 + 4 - 2 = 11, el cual corresponde a cEntradasPeriodo3
      lExistencia = atExistencia->CampoVal[NUMCAMPOENTRADAS1 + aPeriodo - 2] - atExistencia->CampoVal[NUMCAMPOSALIDAS1 + aPeriodo - 2]
      lCosto = atExistencia->CampoVal[NUMCAMPOCOSTOENTRADAS1 + aPeriodo - 2] - atExistencia->CampoVal[NUMCAMPOCOSTOSALIDAS1 + aPeriodo - 2]
    Finsi

    lExistenciaInicialPeriodo = lExistenciaInicialPeriodo + lExistencia
    // Acumulado de costos en Moneda Base
    lCostoInicialPeriodo = lCostoInicialPeriodo + lCosto

    // Si se solicitó existencia por almacén, rompe el ciclo 
    Si acIdAlmacen != 0
      FinCiclo
    FinSi
  FinMientras

  lExistencia = 0
  lCosto = 0

  // Lee los movimientos de entrada y salida de inventarios 
  // desde el inicio del periodo hasta un día anterior a la fecha de inicio del reporte
  lFechaInicial = @Substr(aFecha;0;6) & '01'
  lFechaFinal = fFechaAnterior(aFecha)
  Mientras lFechaInicial <= lFechaFinal 
    Mientras [atMovimiento.Busca[@Str(acIdProducto;0) & ':' & lFechaInicial]; atMovimiento->Encontro; atMovimiento.Busca Siguiente[2]]
      // Discriminación de movimientos sin afectar
      Si atMovimiento(cAfectadoInventario) = 0      
        Continua
      FinSi

      // Discriminación de movimientos que no son entradas ni salidas
      Si atMovimiento(cAfectaExistencia) != 1 y atMovimiento(cAfectaExistencia) != 2
        Continua
      FinSi

      // Discriminación de movimientos cuyo almacén no corresponde al solicitado
      Si acIdAlmacen != 0 y atMovimiento(cIdAlmacen) != acIdAlmacen
        Continua
      FinSi

      lUnidadesMovto = atMovimiento(cUnidades)
      // Costo del movimiento en Moneda Base
      lCostoMovto = fCostoMovimiento(atProducto; atMovimiento; atCostoHistorico; atTipoCambio; 1)
      Si atMovimiento(cAfectaExistencia) = 1 
        // Entrada
        lExistencia = lExistencia + lUnidadesMovto
        lCosto = lCosto + lCostoMovto
      Sino 
        // Salida
        lExistencia = lExistencia - lUnidadesMovto
        lCosto = lCosto - lCostoMovto
      FinSi
    FinMientras 

    // Incrementa la fecha para busca el siguiente día
    lFechaInicial = fFechaSiguiente(lFechaInicial)
  FinMientras

  // Existencia del producto = Saldo del periodo anterior + Movimientos del periodo
  aExistenciaTotal = lExistenciaInicialPeriodo + lExistencia
  aCostoTotal = lCostoInicialPeriodo + lCosto

  // Costo Total expresado en la Moneda requerida
  aCostoTotal = fConvierteTipoCambio(aCostoTotal; 1; aIdMoneda; aFecha; atTipoCambio)       

  // En caso de existencia negativa regresar como costo promedio el Ultimo costo
  Si aExistenciaTotal > 0
    lCostoPromedio = aCostoTotal / aExistenciaTotal
  Sino
    // Expresar el Ultimo Costo en la moneda requerida
    lCostoPromedio = fCostoHistorico(acIdProducto; acIdAlmacen; aFecha; 3; atCostoHistorico; atTipoCambio; aIdMoneda)
  FinSi

  Retorna lCostoPromedio
FinFunc

// *****************************************************************************
//  fCostoInicialProducto
//
//	Esta función calcula el costo total inicial del producto a una fecha 
//  determinada.
//
//  Parámetros 
//
//  Salidas 
// -----------------------------------------------------------------------------
// Historial de Cambios
// Fecha        Id        Autor   Descripción
// 08/09/2004   N.D.2922  LAP     En la obtención del costo inicial no tomar el
//                                costo histórico; ya que este tiene ajustes de
//                                redondeo y puede no ser exacto. En su lugar
//                                utilizar el nuevo método fCostoPromedioProducto;
//                                el cual utiliza la tabla de Aculumado de existencias
//                                y costos (MGW10030) y la tabla de movimientos
// *****************************************************************************
DefFunc fCostoInicialProducto(acIdProducto; acIdAlmacen; aFecha; aIdEjercicio; aPeriodo; atProducto:Tabla; atMovimiento:Tabla; atExistencia:Tabla; atCostoHistorico:Tabla; atTipoCambio:Tabla; aIdMoneda)
// Valor de retorno de la función
Local lCostoProducto 
Local lInventarioInicialPeriodo, lMovimiento, lLlave, lExistencia
// Fechas
Local lFechaInicial, lFechaFinal, lFechaFinPeriodo

  // el método de costeo del producto es promedio global?
  Si atProducto(cMetodoCosteo) = 1 
    // N.D.2922 Inicio
    // Calcula el costo total a partir del costo histórico del producto y la existencia en la unidad base
    //lFechaInicial = fFechaAnterior(aFecha)
    //lCostoHistorico = fCostoHistorico(acIdProducto; acIdAlmacen; lFechaInicial; atProducto(cMetodoCosteo); atCostoHistorico; atTipoCambio; aIdMoneda)
    //lExistencia = fExistenciaInicialProducto(acIdProducto; acIdAlmacen; aFecha; aIdEjercicio; aPeriodo; 0; 0; 0; atMovimiento; atExistencia; atExistencia)    
    lCostoHistorico = fCostoPromedioProducto(acIdProducto; acIdAlmacen; aFecha; aIdEjercicio; aPeriodo; atMovimiento; atExistencia; atProducto; atCostoHistorico; atTipoCambio; aIdMoneda; &lCostoProducto; &lExistencia)
    // N.D.2922 Fin
  Sino
    // Calcula la fecha de fin del período anterior, útil para obtener el tipo de cambio  
    lFechaFinPeriodo = @Substr(aFecha;0;6) & '01'
    lFechaFinPeriodo = fFechaAnterior(lFechaFinPeriodo)
  
    lCostoProducto = 0.0
    lInventarioInicialPeriodo = 0.0

    // Arma la llave según el parámetro del ID del almacén
    Si acIdAlmacen = 0 
      // Inventario general
      lLlave = atProducto(cIdProducto) & ':' & aIdEjercicio
    Sino 
      // Inventario de un almacén en particular
      lLlave = atProducto(cIdProducto) & ':' & aIdEjercicio & ':' & acIdAlmacen & ':1'
    FinSi

    // Como es un registro por cada almacén se realiza un ciclo para leerlos todos
    Mientras[atExistencia.Busca[lLlave]; atExistencia->Encontro; atExistencia.Busca Siguiente[2]]
      // Discrimina el registro de la unidad no convertible
      Si atExistencia(cTipoExistencia) = 2
        Continua
      FinSi

      // Obtiene el Inventario final del período anterior
      lInventarioInicialPeriodo = lInventarioInicialPeriodo + fCostoInicialPeriodo(lFechaFinPeriodo; aPeriodo; atExistencia; atTipoCambio; aIdMoneda)

      // Si se solicitó inventario por almacén, rompe el ciclo 
      Si acIdAlmacen != 0
        FinCiclo
      FinSi    
    FinMientras

    lNeto = 0

    // Lee los movimientos de entrada y salida de inventarios desde el inicio
    // del periodo hasta un día anterior a la fecha de inicio del reporte
    lFechaInicial = @Substr(aFecha;0;6) & '01'
    lFechaFinal = fFechaAnterior(aFecha)

    // Si la fecha inicial cae en el día primero, entonces no hay nada que leer    
    Mientras lFechaInicial <= lFechaFinal 
      Mientras [ atMovimiento.Busca[atProducto(cIdProducto) & ':' & lFechaInicial]; atMovimiento->Encontro; atMovimiento.Busca Siguiente[2] ]
        // Discriminación de movimientos sin afectar
        Si atMovimiento(cAfectadoInventario) = 0      
          Continua
        FinSi

        // Discriminación de movimientos que no son entradas ni salidas
        Si atMovimiento(cAfectaExistencia) != 1 y atMovimiento(cAfectaExistencia) != 2
          Continua
        FinSi

        // Discriminación de movimientos cuyo almacén no corresponde al solicitado
        Si acIdAlmacen != 0 y atMovimiento(cIdAlmacen) != acIdAlmacen
          Continua
        FinSi

        // Obtiención del costo del movimiento
        lMovimiento = fCostoMovimiento(atProducto; atMovimiento; atCostoHistorico; atTipoCambio; aIdMoneda)
        Si atMovimiento(cAfectaExistencia) = 1 
          // Entrada
          lNeto = lNeto + lMovimiento
        Sino 
          // Salida
          lNeto = lNeto - lMovimiento
        FinSi
      FinMientras

      // Incrementa la fecha para buscar el siguiente día
      lFechaInicial = fFechaSiguiente(lFechaInicial)
    FinMientras

    // Costo del producto = Saldo del periodo anterior + Movimientos del periodo
    lCostoProducto = lInventarioInicialPeriodo + lNeto  
  FinSi

  Retorna lCostoProducto
FinFunc


// *****************************************************************************
//  fGastosProrrateados
//
//	Esta función calcula los gastos prorrateados que pertenecen a un
//  movimiento de un documento.
//
// *****************************************************************************
DefFunc fGastosProrrateados(atMovimiento: Tabla; atDocumento: Tabla)
Local lGastosProrrateados, lTotalGastos, lTotalMovimiento, lTotalDocumento

  // calcula el total de gastos de compra
  lTotalGastos = atDocumento(cGasto1) + atDocumento(cGasto2) + atDocumento(cGasto3)
  // Calcula el total del movimiento
  // No se consideran impuestos ni retenciones
  // Sí se consideran descuentos del movimiento
  lTotalMovimiento = atMovimiento(cNeto) - atMovimiento(cDescuento1) - atMovimiento(cDescuento2) - atMovimiento(cDescuento3) - atMovimiento(cDescuento4) - atMovimiento(cDescuento5)
  // Calcula el total del movimiento
  // No se consideran impuestos ni retenciones
  // No se consideran descuentos globales del documento
  lTotalDocumento = atDocumento(cNeto) - atDocumento(cDescuentoMov)
  // La parte proporcional de los gastos se calcula con una regla de tres:
  // Si al total de documentos le corresponde el total de gastos entonces
  // cuanto le corresponde al total de un movimiento
  lGastosProrrateados = lTotalMovimiento * lTotalGastos / lTotalDocumento

  Retorna lGastosProrrateados  
FinFunc


// *****************************************************************************
//  fStockMinMax
//
//  Esta función lee y calcula el stock mínimo y máximo del
//  producto para un almacén en particular o general.
//
//  Parámetros 
//
//  Salidas 
// *****************************************************************************
DefFunc fStockMinMax(acIdProducto; acIdAlmacen; aUnidad; aIdUnidadBase; aIdUnidadEquivalente; aStockMinimo; aStockMaximo; atExistenciaMinMax: Tabla; atEquivalencia: Tabla)

  // Asume cero en el stock mínimo y máximo
  aStockMinimo = 0.0
  aStockMaximo = 0.0

  Si acIdAlmacen = 0 // Si se solictó general
    // Utiliza el índice cIdProducto
    atExistenciaMinMax.Usaindice['cIdProducto']   // cIdProducto
    // Realiza un ciclo para todos los almacenes
    Mientras [ atExistenciaMinMax.Busca[@Str(acIdProducto;0)]; atExistenciaMinMax->Encontro; atExistenciaMinMax.Busca Siguiente[1] ]
      // Si se solicitó en unidades no convertibles
      Si aUnidad = 1
        aStockMinimo = aStockMinimo + atExistenciaMinMax(cExistMinNoConvertible)
        aStockMaximo = aStockMaximo + atExistenciaMinMax(cExistMaxNoConvertible)
      Sino // Unidades base o equivalentes
        aStockMinimo = aStockMinimo + atExistenciaMinMax(cExistenciaMinBase)
        aStockMaximo = aStockMaximo + atExistenciaMinMax(cExistenciaMaxBase)
      FinSi
    FinMientras
  Sino // Es de un almacén en particular
    // Utiliza el índice iAlmacenProducto
    atExistenciaMinMax.Usaindice['iAlmacenProducto']   // cIdAlmacen+cIdProducto
    // Arma la llave del almacén solicitado
    atExistenciaMinMax.Busca[acIdAlmacen & ':' & acIdProducto]
    Si atExistenciaMinMax->Encontro
      // Si se solicitó en unidades no convertibles
      Si aUnidad = 1
        aStockMinimo = atExistenciaMinMax(cExistMinNoConvertible)
        aStockMaximo = atExistenciaMinMax(cExistMaxNoConvertible)
      Sino // Unidades base o equivalentes
        aStockMinimo = atExistenciaMinMax(cExistenciaMinBase)
        aStockMaximo = atExistenciaMinMax(cExistenciaMaxBase)
      FinSi
    FinSi
  FinSi

  // Convierte a unidades equivalentes de ser necesario
  Si aUnidad = 2
    aStockMinimo = fUnidadesEquivalentes(aStockMinimo; aIdUnidadBase; aIdUnidadEquivalente; atEquivalencia)
    aStockMaximo = fUnidadesEquivalentes(aStockMaximo; aIdUnidadBase; aIdUnidadEquivalente; atEquivalencia)
  FinSi

FinFunc


// *****************************************************************************
//  fVerificarMovtosSinAfectar
//
//  Esta función busca si un producto tiene movimientos sin afectar antes 
//  de la fecha de corte. Si es así regresa la fecha del primer movimiento
//  sin afectar
//
//  Parámetros 
//    acIdProducto = Identificador del producto
//    atMovimiento = Tabla de movimientos
//    atMovtoOwner = Tabla de movimientos dueños de movimientos ocultos
//    atDocumento  = Tabla de documentos
//
//  Salidas 
//    Fecha del primer movimiento sin afectar
//    
// *****************************************************************************
DefFunc fVerificarMovtosSinAfectar(acIdProducto; atMovimiento: Tabla; atMovtoOwner: Tabla; atDocumento: Tabla)
Local lCiclo, lFechaSinAfectar, lIdDocumentoDe, lListaIdDocumentoDe, lIndiceAntMovimiento, lIndiceAntMovtoOwner, lIndiceAntDocumento

  aExistenciaCongelada = ''
  lFechaSinAfectar = ''
  // Respaldar los índices de las tablas 
  lIndiceAntMovimiento = atMovimiento->Indice
  lIndiceAntMovtoOwner = atMovtoOwner->Indice
  lIndiceAntDocumento = atDocumento->Indice

  // Indice para recorrido de movimientos
  atMovimiento.Usaindice['iProductoDoctoDeAfecFecha']  // cIdProducto+cIdDocumentoDe+cAfectadoInventario+cFecha
  atMovtoOwner.Usaindice['']                           // Indice primario: cIdMovimiento
  atDocumento.Usaindice['']                            // Indice primario: cIdDocumento 

  // Lista de documentos a considerar: Remisiones, Facturas, Devoluciones sobre venta,
  // Devoluciones de remisión, Productos en consignación, Compras, Devoluciones al proveedor,
  // Devoluciones de consignación, Entradas, Salidas, Traspasos
  lListaIdDocumentoDe = '3,4,5,6,18,19,20,21,32,33,34'

  Mientras [ lCiclo = 0; lCiclo < 11; lCiclo = lCiclo + 1 ]
    lIdDocumentoDe = @Escoge(lListaIdDocumentoDe;lCiclo)

    // Busca los movimientos que no han afectado existencias ni costos
    Mientras [ atMovimiento.Busca[acIdProducto & ':' & lIdDocumentoDe & ':0']; atMovimiento->Encontro; atMovimiento.Busca Siguiente[3] ]
      // Inicia N.D.1458
      // Si llega al final del rango solicitado se sale
      Si atMovimiento(cIdProducto) != acIdProducto O atMovimiento(cIdDocumentoDe) != lIdDocumentoDe O atMovimiento(cAfectadoInventario) != 0
        FinCiclo
      FinSi
      // Fin N.D.1458
      // Discrimina los movimientos que no son entradas ni salidas
      Si atMovimiento(cAfectaExistencia) != 1 y atMovimiento(cAfectaExistencia) != 2
        Continua
      FinSi
      // Discrimina los movimientos de documentos cancelados
      Si atMovimiento(cIdDocumento)
        atDocumento.Busca[@Str(atMovimiento(cIdDocumento);0)]
      Sino // es un movimiento oculto
        // Busca el ID del documento en el movimiento owner
        lIdDocumento = fIdDocumentoMovtoOwner(atMovimiento; atMovtoOwner) 
        atDocumento.Busca[@Str(lIdDocumento;0)]
      Finsi
      Si atDocumento->Encontro Y atDocumento(cCancelado) = 1   
        Continua   // El docto esta cancelado
      Finsi
      // Si el movimiento no esta cancelado entonces es un error 
      lFechaSinAfectar = atMovimiento(cFecha)
      FinCiclo
    FinMientras  // Mientras hay movtos sin afectar en existencias y costos

    // Si todavía no se encontraron movtos sin afectar en existencias y costos 
    // entonces buscar los movtos sin afectar sólo en costos
    Si lFechasinAfectar = ''
      // Busca los movimientos que no han afectado costos
      Mientras [ atMovimiento.Busca[acIdProducto & ':' & lIdDocumentoDe & ':2']; atMovimiento->Encontro; atMovimiento.Busca Siguiente[3] ]
        // Inicia N.D.1458
        // Si llega al final del rango solicitado se sale
        Si atMovimiento(cIdProducto) != acIdProducto O atMovimiento(cIdDocumentoDe) != lIdDocumentoDe O atMovimiento(cAfectadoInventario) != 2
          FinCiclo
        FinSi
        // Fin N.D.1458
        // Discrimina los movimientos que no son entradas ni salidas
        Si atMovimiento(cAfectaExistencia) != 1 y atMovimiento(cAfectaExistencia) != 2
          Continua
        FinSi
        // Discrimina los movimientos de documentos cancelados
        Si atMovimiento(cIdDocumento)
          atDocumento.Busca[@Str(atMovimiento(cIdDocumento);0)]
        Sino // es un movimiento oculto
          // Busca el ID del documento en el movimiento owner
          lIdDocumento = fIdDocumentoMovtoOwner(atMovimiento; atMovtoOwner) 
          atDocumento.Busca[@Str(lIdDocumento;0)]
        Finsi
        Si atDocumento->Encontro Y atDocumento(cCancelado) = 1   
          Continua   // El docto esta cancelado
        Finsi
        // Si el movimiento no esta cancelado entonces es un error 
        lFechaSinAfectar = atMovimiento(cFecha)
        FinCiclo
      FinMientras  // Mientras hay movtos sin afectar en costos
    FinSi  // Si todavía no se encuentran movtos sin afectar 
  FinMientras  // Mientras hay movimientos del documento de 

  // Restaura los índices originales
  atMovimiento.Usaindice[lIndiceAntMovimiento]
  atMovtoOwner.Usaindice[lIndiceAntMovtoOwner]
  atDocumento.Usaindice[lIndiceAntDocumento]

  Retorna lFechaSinAfectar
FinFunc

// *****************************************************************************
//  mVerificarExistenciaCongelada
//
//  Verifica si un producto tiene tiene existencias congeladas
//
//  Parámetros 
//    acIdProducto  = Identificador del producto
//    acIdAlmacen   = Identificador del almacén, 0 buscar en todos los almacenes
//    acIdEjercicio = Identificador del ejercicio actual
//    atExistencia  = Tabla de movimientos
//
//  Salidas 
//    True  = El producto tiene existencias congeladas
//    False = El producto no tiene existencias congeladas
//    
// *****************************************************************************
DefFunc fVerificarExistenciaCongelada(acIdProducto; acIdAlmacen; acIdEjercicio; atExistencia: Tabla)
Local lIndiceAntExistencia, lExistenciaCongelada

  lExistenciaCongelada = 0

  // Respaldar el índice de la tabla de existencias
  lIndiceAntExistencia = atExistencia->Indice

  // Se busca si el producto tiene existencias congeladas en cualquier almacén
  Si acIdAlmacen = 0   
    atExistencia.Usaindice['iProdEjerTipo']         // cIdProducto+cIdEjercicio+cTipoExistencia
    Mientras [ atExistencia.Busca[acIdProducto & ':' & acIdEjercicio & ':1']; atExistencia->Encontro; atExistencia.Busca Siguiente[2] ]
      // verificar si esta congelda, si es así no tiene caso continuar
      Si atExistencia->Encontro Y atExistencia(cBanCongelado)  
        lExistenciaCongelada = atExistencia(cBanCongelado)
        FinCiclo
      FinSi 
    FinMientras
  // Se busca si el producto tiene existencias congeladas en un almacén en particular
  SiNo
    atExistencia.Usaindice['iProdEjerAlmacenTipo']  // cIdProducto+cIdEjercicio+cIdAlmacen+cTipoExistencia
    atExistencia.Busca[acIdProducto & ':' & acIdEjercicio & ':' & acIdAlmacen & ':1' ]
    // La existencia en el almacén existe, verificar si esta congelda
    Si atExistencia->Encontro
      lExistenciaCongelada = atExistencia(cBanCongelado)
    FinSi 
  FinSi  // Si se buscan existencias congeladas en todos los almacenes o en uno sólo

  // Regresar al índice original de existencias
  atExistencia.Usaindice[lIndiceAntExistencia]

  Retorna lExistenciaCongelada
FinFunc


// *****************************************************************************
// Nombre:    fExistenciaPeriodo
// Objetivo:  Esta función lee la existencia total de un determinado periodo en la
//            tabla de existencias.
// *****************************************************************************
DefFunc fExistenciaPeriodo(aPeriodo; aUnidad; aIdUnidadBase; aIdUnidad; atExistencia:Tabla; atEquivalencia:Tabla)
Local lExistencia, NUMCAMPOENTRADAS1, NUMCAMPOSALIDAS1

  // Inicia la posición del campo "cEntradasPeriodo1" y "cSalidasPeriodo1" (empezando en cero)
  // Ejemplo: Si NUMCAMPOENTRADAS1 = 9 y aPeriodo = 4
  // Entonces el campo a leer es 9 + 4 - 1 = 12, el cual corresponde a cEntradasPeriodo4
  NUMCAMPOENTRADAS1 = 9
  NUMCAMPOSALIDAS1 = 21

  // Existencia del periodo
  Si aPeriodo = 0
    lExistencia = atExistencia(cEntradasIniciales) - atExistencia(cSalidasIniciales)
  Sino
    lExistencia = atExistencia->CampoVal[NUMCAMPOENTRADAS1 + aPeriodo - 1] - atExistencia->CampoVal[NUMCAMPOSALIDAS1 + aPeriodo - 1]
  FinSi

  // Convierte las unidades
  Si aUnidad = 2 
    // se requiere el reporte en unidades equivalentes
    lExistencia = fUnidadesEquivalentes(lExistencia; aIdUnidadBase; aIdUnidad; atEquivalencia)
  FinSi

  Retorna lExistencia
FinFunc


// *****************************************************************************
// Nombre:    fExistenciaProducto
// Objetivo:  Esta función calcula la existencia de un producto a una fecha determinada
//            expresa la existencia en unidades base, equivalentes o no convertibles
//  Salidas 
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// Historia de modificaciones
//  Fecha      Nombre   IDLotus                     Descripcion
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
// 01/12/2005   LAP     N.D.3010    Nuevo esquema de búsquedas parciales para agilizar los reportes
// 14/12/2005   LAP     N.D.3682    Nuevo esquema para calcular la existencia del producto a una fecha 
//                                  (Incluye los movimientos a dicho fecha).
// *****************************************************************************
DefFunc fExistenciaProducto(acIdProducto; acIdAlmacen; aFecha; aUnidad; aIdUnidadBase; aIdUnidadEquivalente; atMovimiento:Tabla; atExistencia:Tabla; atEquivalencia:Tabla; atEjercicio:Tabla )
Local lExistenciaProducto
Local lExistenciaInicial, lUnidadesMovto, lExistenciaPeriodo
Local lFechaInicial, lFechaFinal, lLlave, lUnidad
Local aIdEjercicio  
Local aPeriodo      
Local aEsPeriodoCompleto

  // Obtención del periodo y ejercicio de la fecha, a fin de leer el acumulado del mes anterior
  fEjercicioPeriodo(aFecha; &aPeriodo; &aIdEjercicio; atEjercicio)
  
  // En caso de que la fecha sea el último dia del mes entonces se debe de leer el acumulado del 
  //  mes actual y no barrer los movimientos
  // Formato fecha: aaaammdd
  aEsPeriodoCompleto = 0
  // En caso de que el siguiente dia corresponda a otro mes, entonces se desea la existencia del
  //  mes completo, para lo cual solo se lee el acumulado del mes y no se barren movimientos
  Si @Substr(fFechaSiguiente(aFecha); 4; 2) !=  @Substr(aFecha; 4; 2)
    aEsPeriodoCompleto = 1
  FinSi

  // Inicia variables de existencia inicial
  lExistenciaProducto = 0
  lExistenciaInicial = 0
  lExistenciaPeriodo = 0

  // Arma la llave según el parámetro del ID del almacén
  Si acIdAlmacen = 0 
    // Existencia general
    lLlave = acIdProducto & ':' & aIdEjercicio
  Sino 
    // Existencia de un almacén en particular
    Si aUnidad = 1
      // Existencia en unidades no equivalentes
      lLlave = acIdProducto & ':' & aIdEjercicio & ':' & acIdAlmacen & ':2'
    Sino  
      // Existencia en unidades base
      lLlave = acIdProducto & ':' & aIdEjercicio & ':' & acIdAlmacen & ':1'
    FinSi
  FinSi

  // Como es un registro por cada almacén se realiza un ciclo para leerlos todos
  Mientras[atExistencia.Busca[lLlave]; atExistencia->Encontro; atExistencia.Busca Siguiente[2]]
    // Discrimina el registro en función de la unidad del reporte
    // aUnidad = 0 -> Unidad base
    // aUnidad = 1 -> Unidad no equivalente
    // aUnidad = 2 -> Unidad equivalente
    // atExistencia(cTipoExistencia) = 1 -> Unidad base
    // atExistencia(cTipoExistencia) = 2 -> Unidad no equivalente
    Si (aUnidad = 1 y atExistencia(cTipoExistencia) = 1) o (aUnidad != 1 y atExistencia(cTipoExistencia) = 2)
      Continua
    Finsi

    Si aEsPeriodoCompleto = 1
      // En caso de ser el periodo completo entonces el acumulado del periodo
      lExistenciaInicial = lExistenciaInicial + fExistenciaPeriodo(aPeriodo; aUnidad; aIdUnidadBase; aIdUnidadEquivalente; atExistencia; atEquivalencia)
    Sino
      // En caso de no ser el periodo completo entonces el acumulado del periodo anterior
      lExistenciaInicial = lExistenciaInicial + fExistenciaPeriodo(aPeriodo-1; aUnidad; aIdUnidadBase; aIdUnidadEquivalente; atExistencia; atEquivalencia)
    FinSi
   
    Si acIdAlmacen != 0
      // Si se solicitó existencia por almacén, rompe el ciclo 
      FinCiclo
    FinSi
  FinMientras

  Si aEsPeriodoCompleto = 0
    // En caso de no ser el periodo completo entonces barrer los movimientos desde el dia 1 hasta la fecha indicada
  
    // Guarda el valor de la variable aUnidad
    // La intención es hacer un poco más rápida la función evitando leer la tabla de equivalencias
    // por cada movimiento cuando se pide la existencia en unidades equivalentes.
    Si aUnidad = 2
      lUnidad = aUnidad
      aUnidad = 0 // asume unidades base
    FinSi

    // N.D.3010 Inicio
    // Lee los movimientos de entrada y salida de inventarios, desde el inicio del periodo hasta la fecha indicada
    lFechaInicial = @Substr(aFecha;0;6) & '01'
    lFechaFinal = aFecha

    // NOTA: Es obligatorio volver a fijar el índice a fin de que las búsquedas parciales funcionen correctamente
    atMovimiento.UsaIndice['iProductoFechaAfecta']
    // Localización del primer movimiento del producto
    atMovimiento.Busca[acIdProducto & ':' & lFechaInicial]
    Si !atMovimiento->Encontro
      // Busquedas parciales: Cursor en el registro más cercano que cumple con la llave
      // NOTA: La propiedad Llave de la tabla nos regresa la llave más cercana que cumple con el criterio de la búsqueda
      //       La llave está en formato cadena y no tiene ningún separador entre los campos, por lo que es necesario revisar
      //       en FoxPro la definición del índice para descomponer los elementos de la Llave
      // Revisar si la llave actual corresponde al Producto / Almacén y al Rango de Fechas.
      Si @Col(acIdProducto;0) = @Val(@SUBSTR(atMovimiento->Llave;0;11))
        Si @SUBSTR(atMovimiento->Llave;11;8) >= lFechaInicial Y @SUBSTR(atMovimiento->Llave;11;8) <= lFechaFinal
          // Realizar nuevamente la búsqueda a fin de posicionarnos en el registro y activar la propiedad Encontro
          lFechaInicial = @SUBSTR(atMovimiento->Llave;11;8)
          atMovimiento.Busca[acIdProducto & ':' & lFechaInicial]
        FinSi
      FinSi        
    FinSi

    // N.D.3010 Recorrer los movimientos hasta que sea diferente producto, almacén o se rompa el rango de fechas
    Mientras atMovimiento->Encontro Y (atMovimiento(cFecha) <= lFechaFinal)
      // Discrimina movimientos sin afectar
      Si atMovimiento(cAfectadoInventario) = 0      
        // N.D.3010 Búsqueda del siguiente registro que cumpla con el producto
        atMovimiento.BUSCA SIGUIENTE[1]
        Continua
      FinSi
      // Discrimina movimientos que no son entradas ni salidas
      Si atMovimiento(cAfectaExistencia) != 1 y atMovimiento(cAfectaExistencia) != 2
        // N.D.3010 Búsqueda del siguiente registro que cumpla con el producto
        atMovimiento.BUSCA SIGUIENTE[1]
        Continua
      FinSi
      // Discrimina movimientos cuyo almacén no corresponde al solicitado
      Si acIdAlmacen != 0 y atMovimiento(cIdAlmacen) != acIdAlmacen
        // N.D.3010 Búsqueda del siguiente registro que cumpla con el producto
        atMovimiento.BUSCA SIGUIENTE[1]
        Continua
      FinSi
      lUnidadesMovto = fUnidades(aUnidad; aIdUnidadBase; aIdUnidadEquivalente; atMovimiento; atEquivalencia)
      Si atMovimiento(cAfectaExistencia) = 1 
        // Movimiento de entrada
        lExistenciaPeriodo = lExistenciaPeriodo + lUnidadesMovto
      Sino
        // Movimiento de salida
        lExistenciaPeriodo = lExistenciaPeriodo - lUnidadesMovto
      FinSi
      // N.D.3010 Búsqueda del siguiente registro que cumpla con el producto
      atMovimiento.BUSCA SIGUIENTE[1]
    FinMientras // Busca los demás movimientos del mismo producto
    // N.D.3010 Fin

    Si lUnidad = 2 Y lExistenciaPeriodo != 0
      // Se solicitó el reporte en unidades equivalentes?
      lExistenciaPeriodo = fUnidadesEquivalentes(lExistenciaPeriodo; aIdUnidadBase; aIdUnidadEquivalente; atEquivalencia)
    FinSi
  FinSi

  lExistenciaProducto = lExistenciaInicial + lExistenciaPeriodo
  Retorna lExistenciaProducto
FinFunc
