unit uActores;
{$MODE Delphi}

{xDEFINE RepRotUnidades_log}

interface

uses
  xmatdefs, usimplex, Classes, SysUtils,
  uglobs,
  usimprint,
  ufichasLPD, ucosa,
  ucosaparticipedemercado,
  ufechas,
  uCosaConNombre, uranddispos, uforzamientos, Math,
  {$IFDEF INTERMEDIARIO_DE_COSTO_FUTURO}
  uintermediariocostofuturo,
  {$ENDIF}
  uunidades,
  uFuentesAleatorias,
  uauxiliares;

resourcestring
  rsNombre = 'Nombre';
  rsTipoDeActor = 'Tipo de actor';
  rsInformacioNAdicional = 'Información adicional';

type
  PActor = ^TActor;

  { TActor }

  TActor = class(TCosaParticipeDeMercado)
  public
    nacimiento, muerte: TFecha;

    lpdUnidades: TFichasLPD;
    lpdForzamientos: TFichasLPD;

    flg_ShowVisorMantenimientosProgramados: boolean;

    paUnidades: TFichaUnidades;
    unidades_RotRep: array of TRotRep_rec;

    paForzamiento: TFichaForzamientos;

    ivar, ires, ivae: integer;
    ixr, ixd: integer;
    iauxr, iauxd: integer;

    // auxiliares para manejo de la Reserva Rotante
    flg_ReservaRotanteDisponible: boolean;
    ivar_ReservaRotante: integer;
    ires_ReservaRotante: integer;

    (*
    // Guarda el costo incurrido para generar la energía en el último paso.
    // En este costo deben ponerse los costos térmicos y no los costos asociados
    // a valorizar el agua (u otro recurso guardado en varialbles de estado).
    // Este valor se utiliza para calcular el costo de cada paso en la
    // optimización dinámica estocástica en base a la cual luego se determinan
    // los valores del agua de los diferentes embalses. También se dispone
    // de este valor durante la simulación para calcular el costo de la etapa.
    // Los actores deben fijar el valor de esta variable durante la lectura de
    // los resultados del paso.
    *)
    costoDirectoDelPaso: NReal;

    (* Los actores que tienen función de UTILIDAD pueden completar este valor *)
    utilidadDirectaDelPaso: NReal;

    // Remuneraciones al generador.
    Ingreso_PorDisponibilidad_: NReal;
    Ingreso_PorEnergia_: NReal;

    // Indexa los pagos
    fuenteInidicePrecios: TFuenteAleatoria;
    nombreBorneIndicePrecios: string;
    kBorneIndicePrecios: integer;

    procedure AddToCapasLst( capas: TList; padre: TCosa; hermanos: TListaDeCosas ); override;

    constructor Create(
      capa: integer; nombre: string; nacimiento, muerte: TFecha;
      lpdUnidades: TFichasLPD;
      xFuenteIdxP: TFuenteAleatoria; xBorneIdxP: string
      ); reintroduce;

    function Rec: TCosa_RecLnk; override;
    procedure BeforeRead(version, id_hilo: integer); override;
    procedure AfterRead(f:TArchiTexto); override;

    //Retorna el valor esperado de la potencia que requiere o entrega el actor
    function PotenciaFirme: NReal; virtual; abstract;

    //function InfoAd: string; override;

    // antes de correr luego de creados y resultas las referencias
    // se llama este proc. para que se preparen (creen los espacios de datos) necesarios.
    procedure PrepararMemoria(Catalogo: TCatalogoReferencias; globs: TGlobs); override;
    procedure RegistrarParametrosDinamicos(Catalogo: TCatalogoReferencias); override;

    //      procedure Sim_Inicio; virtual;
    procedure Sim_Cronica_Inicio; virtual;


    procedure prepararPaso_as; virtual; // preparación previa a los sorteos.
    procedure SorteosDelPaso(sortear: boolean); virtual;
    //aquí hacer todos los sorteos necesarios

    // evento previo a preapararPaso_ps
    // en este evento los actores que participan del cálculo de la
    // demanda neta para el postizado deben sumar o restar su phoraria
    procedure prepararPaso_ps_pre; virtual;

    procedure PrepararPaso_ps; override;
    // aquí leer el valor de las variables de estado y
    // del resultado de SorteosDelPaso para calcular lo necesario
    // para jugar en el paso de tiempo que se inicia


    procedure opt_leerSolucion(s: TSimplex); virtual;

    // para pasar datos al Flucar.
    procedure opt_WriteToFlucarStatusFile(var f: textfile); virtual;

    procedure Sim_IndexarIngresos; virtual;
    procedure Sim_Paso_Fin; virtual;
    procedure Sim_Cronica_Fin; virtual;
    //      procedure Sim_Fin; virtual;


(*+doc NecesitaIterar
  Esta función la llamamos luego de leer los resultados del paso para darle la
  oportunidad a los Actores de solicitar que se corra nuevamente este paso.
 El resultado est TRUE si el Actor estima que requiere iterar para ajustar su
 modelo en la resolución del paso (o etapa).
 El parámetro kIteracion es el número de iteración, y se pasa e 1 para la primer
 llamada de la función. El actor debe calcular un error relativo de ajuste de
 su modelo de acuerdo con los resultados leídos al final de la etapa y retornarlo
 en errRelativo, para que el algoritomo que contrala las iteraciones pueda
 tomar en cuenta la necesidad de iterar del actor. -doc*)
    function opt_necesitoIterar(kIteracion: integer; var errRelativo: NReal): boolean;
      virtual; abstract;


    // Si el actor tiene variables auxiliares debe usar este método para cargar
    // en el FRAME_1 de auxiliares el valor resultado de la optimización.
    procedure SetAux1; virtual;
    // Si el actor tiene variables auxiliares debe usar este método para acumular
    // el resultado de la optimización multiplicado por peso en el FRAME1 de
    //variables auxiliares.
    procedure AcumAux1(peso: NReal); virtual;


{$IFDEF SPXCONLOG}
    //Asigna nombre a las restricciones y variables que aporta el actor al simplex
    procedure spx_NombrarVariables(s: TSimplex; cnt_vars, cnt_ress: integer);
      virtual;
{$ENDIF}



    procedure entrarEnMantenimientoTotal; virtual; abstract;
    destructor destroy; override;


    // Escribe en fsal los encabezados de printResumenSim
    class procedure printEncabezadoResumenSim( SimPrint: TSimPrint );

    // Escribe en fsal una linea con Clase, nombre, e infoAd
    procedure printResumenSim( SimPrint: TSimPrint );

    {$IFDEF DUMP_TEXT_OPTRES}
    // procedimiento usado para salvar resultados al fin del cálculo del costo de cada
    // estrellita durante la optimización.
    // ATENCION NO USAR WRITELN, usar write( #9, valor [, #9, valor..] )
    // Los resultados de optimización se salvan en un archivo separado para cada
    // actor.
    procedure opt_PrintResultados_Encab(var fsal: textfile); virtual; abstract;
    procedure opt_PrintResultados(var fsal: textfile); virtual; abstract;
    {$ENDIF}

    //Si hay un problema resolviendo el simplex, la sala llama este procedimiento sobre
    //todos los actores para que escriban los valores de sus variables al momento de romperse
    procedure dump_Variables(var f: TextFile; charIndentacion: char); virtual;


    // llamar este procedimiento si hay cambio del factor de disponibilidad de las
    // unidades del actor para que se actuailcen las probabilidades de transición
    procedure ActualizarProbabilidadesReparacionYRotura_(
      Nuevo_FDisp, Nuevo_TMRep: NReal; kTipoUnidad: integer = 0);

    // retorna la cantidad de máquinas disponibles
    function Sorteos_RepRotUnidades: integer;
    function Sorteos_RepRotUnidades_k(kTipoUnidad: integer): integer;


    procedure PubliVars; override;


    class function CreateDefaultLPDUnidades_(cuantasUnidadesDiferentes: integer):
      TFichasLPD;
    class function CreateDefaultLPDForzamientos_(
      cuantasVariablesDiferentes: integer): TFichasLPD;

    function hayForzamientos: boolean;

    procedure ActivarCapas(const CapasActivas: TDAOfNint); override;


    // Como el factor de dispnonibilidad (FD) y el Tiempo Medio de Reapración  TMR
    // están en las fichas de parametros dinamicos de los actores, estas funciones
    // permiten acceder a nivel del actor a los valores actualizados.
    function get_pa_FD(kTipoUnidad: integer): NReal; virtual;
    function get_pa_TMR(kTipoUnidad: integer): NReal; virtual;

    // si El Actor tiene fichas dinámicas debiera sobreescribir
    // esta función retornando la clase de la misma.
    class function TipoFichaLPD: TClaseDeFichaLPD; virtual; abstract;

    {$IFDEF INTERMEDIARIO_DE_COSTO_FUTURO}
    procedure registrarVarYResEnIntermediario(intermediario:TIntermediarioCostoFuturo);virtual; abstract;
    {$ENDIF}


    // utilidad para eliminar fichas viejas
    procedure CleanOldersThan( dt: TDateTime ); override;


    private
      // variables auxiliares para lectura de versiones viejas
      AltaUnidades_CON_INCERTIDUMBRE_auxiliar_version100: boolean;
  end;


  TClaseDeActor = class of TActor;

procedure AlInicio;
procedure AlFinal;

//procedure gestionarCambioMantenimiento(actor : TCosa);

var
  //En cada implementación de actor al llamar al inherited registrarParametrosDinamicos
  //hay que asignar en este puntero el procedimiento que se quiere usar al cambiar
  //la ficha de unidades de ese actor en particular.
  //Si se le asigna un valor, luego de la llamada al inherited registrarParametrosDinamicos
  //se debe setear en nil
  procCambioFichaUnidades: TProcCalculosAdicionales;
  procCambioFichaForzamientos: TProcCalculosAdicionales;


// función para order listas de actores.
function Sort_Actores_By_DT_DESC(a1, a2: TActor): integer;

implementation

{$IFDEF RepRotUnidades_log}
var
  cnt_repRotUnidades_log: integer;
  flog_RepRotUnidades: textfile;

{$ENDIF}


function Sort_Actores_By_DT_DESC(a1, a2: TActor): integer;
begin
  if a1.nacimiento.dt < a2.nacimiento.dt then
    Result := 1
  else if a1.nacimiento.dt > a2.nacimiento.dt then
    Result := -1
  else
    Result := 0;
end;



procedure TActor.AddToCapasLst(capas: TList; padre: TCosa;
  hermanos: TListaDeCosas);
var
  k: integer;

begin
  inherited AddToCapasLst(capas, padre, hermanos );
  if lpdUnidades <> nil then
  for k:= 0 to lpdUnidades.count-1 do
    TCosa( lpdUnidades[k] ).AddToCapasLst( capas, self, lpdUnidades );

  if lpdForzamientos <> nil then
  for k:= 0 to lpdForzamientos.Count-1 do
    TCosa( lpdForzamientos[k] ).AddToCapasLst( capas, self, lpdForzamientos );
end;

//------------------
// Métodos de TActor
//==================
constructor TActor.Create(capa: integer; nombre: string; nacimiento,
  muerte: TFecha; lpdUnidades: TFichasLPD; xFuenteIdxP: TFuenteAleatoria;
  xBorneIdxP: string);
begin
  lpd := nil;
  inherited Create(capa, nombre);
  flg_ShowVisorMantenimientosProgramados := True;

  Self.globs := nil; // lo carga en prepararse
  Self.nacimiento := TFecha.Create_Clone(nacimiento);
  Self.muerte := TFecha.Create_Clone(muerte);
  self.lpdUnidades := lpdUnidades;
  if self.lpdUnidades <> nil then
    self.lpdUnidades.Propietario := self;
  self.lpdForzamientos := TFichasLPD.Create(capa, 'lpdForzamientos',
    self, TFichaForzamientos);
  self.fuenteInidicePrecios:= xFuenteIdxP;
  self.nombreBorneIndicePrecios:= xBorneIdxP;
  flg_ReservaRotanteDisponible:= false;
end;

function TActor.Rec: TCosa_RecLnk;
begin
  Result:=inherited Rec;
  Result.addCampoDef('nacimiento', nacimiento );
  Result.addCampoDef('muerte', muerte);
  Result.addCampoDef('AltaUnidades_CON_INCERTIDUMBRE', AltaUnidades_CON_INCERTIDUMBRE_auxiliar_version100, 90, 100, 'T' );
  Result.addCampoDef('lpdUnidades', TCosa(lpdUnidades));
  Result.addCampoDef('lpdForzamientos', TCosa(lpdForzamientos), 86 );
  Result.addCampoDef('flg_ShowVisorMantenimientosProgramados', flg_ShowVisorMantenimientosProgramados, 125, 0, 'T' );
  Result.addCampoDef_ref('fuenteInidicePrecios', TCosa(fuenteInidicePrecios), Self, 175 );
  Result.addCampoDef('nombreBorneIndicePrecios', nombreBorneIndicePrecios, 175 );
end;

procedure TActor.BeforeRead(version, id_hilo: integer);
begin
  lpd := nil;
  fuenteInidicePrecios:= nil;
  nombreBorneIndicePrecios:= '';
  inherited BeforeRead(version, id_hilo);
end;

procedure TActor.AfterRead(f:TArchiTexto);
var
    cantUnidades_Instaladas, cantUnidades_EnMantenimiento: TDAofNInt;
    AltaConIncertidumbre, InicioCronicaConIncertidumbre: TDAOfBoolean;
    u_Forzadas: TDAOfNReal;
    k, j: integer;
    aFicha: TFichaUnidades;
begin
  inherited AfterRead(f);
  if f.Version < 86 then
    lpdForzamientos := TFichasLPD.Create(0, 'lpdForzamientos', self,
      TFichaForzamientos);

  // sin no hay fichas de unidades, por defecto supongo que se trata de un actor
  // con un solo tipo de unidades y que inicia con una unidad disponible desde
  // el inicio de los tiempos.
  lpdUnidades.Propietario := self;
  if lpdUnidades.Count = 0 then
  begin
    SetLength(cantUnidades_Instaladas, 1);
    SetLength(cantUnidades_EnMantenimiento, 1);
    setlength(AltaConIncertidumbre, 1);
    setlength(InicioCronicaConIncertidumbre, 1);
    cantUnidades_Instaladas[0] := 1;
    cantUnidades_EnMantenimiento[0] := 0;
    AltaConIncertidumbre[0] := True;
    InicioCronicaConIncertidumbre[0] := False;
    aFicha := TFichaUnidades.Create(0, TFecha.Create_Str('0'), nil,
      cantUnidades_Instaladas, cantUnidades_EnMantenimiento,
      AltaConIncertidumbre, InicioCronicaConIncertidumbre);
    lpdUnidades.Add(aFicha);
  end;

  // Con esto arreglo que antes de la versión 100, el AltaConIncertidumbre
  // era una variable del Actor y no se podía especificar por ficha y por unidad.
  if f.Version < 100 then
  begin
    for k := 0 to lpdUnidades.Count - 1 do
    begin
      aFicha := lpdUnidades.Items[k] as TFichaUnidades;
      for j := 0 to high(aFicha.nUnidades_Instaladas) do
        aFicha.AltaConIncertidumbre[j] :=
          AltaUnidades_CON_INCERTIDUMBRE_auxiliar_version100;
    end;
  end;

  paForzamiento := nil;
  lpdForzamientos.Propietario := self;
  setlength(u_Forzadas, 1);
  u_Forzadas[0] := 0;
  if lpdForzamientos.Count = 0 then
    lpdForzamientos.add(TFichaForzamientos.Create(0, TFecha.Create_Str('0'),
      nil, False, u_Forzadas,False,false,false,nil,nil))
  else
  begin
    // ojo corrigo error de la version 86 q
    if TFichaLPD(lpdForzamientos.items[0]).ClassType = TFichaUnidades then
    begin
      for k := 0 to lpdForzamientos.Count - 1 do
        TFichaLPD(lpdForzamientos.items[0]).Free;
      lpdForzamientos.Clear;
      lpdForzamientos.add(TFichaForzamientos.Create(0, TFecha.Create_Str('0'),
        nil, False, u_Forzadas,False,false,false,nil,nil));
    end;
  end;
end;


function TActor.hayForzamientos: boolean;
begin
  Result := (globs.EstadoDeLaSala = CES_SIMULANDO) and (paForzamiento <> nil) and
    paForzamiento.activar_forzamiento;
end;


procedure TActor.ActivarCapas(const CapasActivas: TDAOfNint);
begin
  if lpd <> nil then
    lpd.ActivarCapas(CapasActivas);
  lpdUnidades.ActivarCapas(CapasActivas);
  lpdForzamientos.ActivarCapas(CapasActivas);
end;

{function TActor.infoAd: string;
var
  cadena: string;
begin
  cadena := inherited infoAd;
  if cadena <> '' then
    cadena := cadena + ', ';
  cadena := cadena + nacimiento.AsStr + ', ' + muerte.AsStr;
  Result := cadena;
end;}


procedure TActor.PrepararMemoria(Catalogo: TCatalogoReferencias; globs: TGlobs);

begin          //writeln(self.nombre);
  inherited PrepararMemoria(Catalogo, globs);

  setlength(self.unidades_RotRep,
    length(TFichaUnidades(self.lpdUnidades.items[0]).nUnidades_Instaladas));
  {$IFDEF CALC_MMEE}
  setlength(mmee_cv_paraPrecioSpot, globs.NPostes);
  setlength(mmee_IngresosSpot_USD, globs.NPostes);
  setlength(mmee_CargosAlSeguimientoDeLaDemanda_USD, globs.NPostes);
  {$ENDIF}
  CostoDirectoDelPaso := 0;
  UtilidadDirectaDelPaso := 0;
  Ingreso_PorDisponibilidad_ := 0;
  Ingreso_PorEnergia_ := 0;
  if fuenteInidicePrecios <> nil then
    kBorneIndicePrecios := fuenteInidicePrecios.idBorne( nombreBorneIndicePrecios )
  else
    kBorneIndicePrecios:= -1;
end;

procedure TActor.RegistrarParametrosDinamicos(Catalogo: TCatalogoReferencias);
var
  procCambioFicha: TProcCalculosAdicionales;
begin
  procCambioFicha := @procCambioFichaUnidades;
  lpdUnidades.expandirFichas(Catalogo, globs);
  lpdUnidades.RegistrarFichasAActualizar(Self, globs.ActualizadorLPD,
    @paUnidades, nil, procCambioFicha);

  paForzamiento := nil;
  if lpdForzamientos <> nil then
  begin
    procCambioFicha := @procCambioFichaForzamientos;
    lpdForzamientos.expandirFichas(Catalogo, globs);
    lpdForzamientos.RegistrarFichasAActualizar(Self, globs.ActualizadorLPD,
      @paForzamiento, nil, procCambioFicha);
  end;
end;

(*
function TActor.CostoDirectoDelPaso: NReal;
begin
  result:= 0;
end;
  *)

class procedure TActor.printEncabezadoResumenSim(SimPrint: TSimPrint);
begin
  SimPrint.printEncabezadoResumenSim(rsNombre, rsTipoDeActor, rsInformacionAdicional);
end;

procedure TActor.printResumenSim(SimPrint: TSimPrint);
begin
  SimPrint.printResumenSim( nombre, ClassName, InfoAd_);
end;

procedure TActor.dump_Variables(var f: TextFile; charIndentacion: char);
begin
  writeln(f, self.claseNombre);
end;

destructor TActor.destroy;
begin
  {$IFDEF CALC_MMEE}
  setlength(mmee_cv_paraPrecioSpot, 0);
  setlength(mmee_IngresosSpot_USD, 0);
  setlength(mmee_CargosAlSeguimientoDeLaDemanda_USD, 0);
  {$ENDIF}

  nacimiento.Free;
  muerte.Free;
  if lpdUnidades <> nil then
    lpdUnidades.Free;
  setlength(unidades_RotRep, 0);
  if lpdForzamientos <> nil then
     lpdForzamientos.Free;
  inherited destroy;
end;

procedure TActor.Sim_Cronica_Inicio;
var
  k: integer;
  pu: PRotRep_rec;
begin
  for k := 0 to high(paUnidades.nUnidades_Instaladas) do
  begin
    pu := @unidades_RotRep[k];
    pu.InicioCronicaConIncertidumbre := paUnidades.InicioCronicaConIncertidumbre[k];
    pu.AltaConIncertidumbre := paUnidades.AltaConIncertidumbre[k];


    // Por defecto suponemos Operativas todas las que no están en mantimientoProgramado
    pu.cnt_Operativas := paUnidades.nUnidades_Operativas[k];
    pu.cnt_OK := pu.cnt_Operativas;
    pu.cnt_Rotas := 0;

    ActualizarProbabilidadesReparacionYRotura_(get_pa_FD(k), get_pa_TMR(k), k);

    // Si está marcado que se inicie con incertidumbre sorteamos para reflejar
    // las probabilidad de estado estacionario entre las máquinas que no están
    // en mantenimiento Programado.
    if globs.ObligarInicioCronicaIncierto_1_Sim or pu.InicioCronicaConIncertidumbre then
      rndDispoNMaquinas_fd(pu.FDispo, pu.cnt_OK, pu.cnt_Rotas, sorteadorUniforme);
  end;
end;

procedure TActor.SorteosDelPaso(sortear: boolean);
begin
end;


procedure TActor.prepararPaso_as;
begin
end;

procedure TActor.prepararPaso_ps_pre;
begin
end;

procedure TActor.PrepararPaso_ps;
begin
end;




procedure TActor.Sim_Paso_Fin;
begin
end;



procedure TActor.Sim_Cronica_Fin;
begin
end;


procedure TActor.opt_leerSolucion(s: TSimplex);
begin
  // nada;
end;


procedure TActor.opt_WriteToFlucarStatusFile(var f: textfile);
begin
  // nada;
end;

procedure TActor.Sim_IndexarIngresos;
var
  idx: NReal;
begin
  if fuenteInidicePrecios <> nil then
  begin
    idx:= fuenteInidicePrecios.Bornera[ kBorneIndicePrecios ];
    Ingreso_PorDisponibilidad_:= Ingreso_PorDisponibilidad_ * idx;
    Ingreso_PorEnergia_:= Ingreso_PorEnergia_ * idx;
  end;
end;



// Si el actor tiene variables auxiliares debe usar este método para cargar
// en el FRAME_1 de auxiliares el valor resultado de la optimización.
procedure TActor.SetAux1;
begin
end;

// Si el actor tiene variables auxiliares debe usar este método para acumular
// el resultado de la optimización en el FRAME1 de variables auxiliares.
procedure TActor.AcumAux1(peso: NReal);
begin
end;

{$IFDEF SPXCONLOG}
procedure TActor.spx_NombrarVariables(s: TSimplex; cnt_vars, cnt_ress: integer );
var
  kvar, kres: integer;
  s_nombre: string;
  bres: boolean;

begin

  for kvar:= self.ivar to self.ivar + (cnt_vars -1 ) do
  begin
    bres:= getNombreVar( kvar, s_nombre );
    if bres then
      s.set_NombreVar( kvar, s_nombre )
    else
      s.set_NombreVar( kvar, self.nombre+'_x'+IntToStr( kvar - self.ivar +1 ) );
  end;

  for kres:= self.ires to self.ires + (cnt_ress -1 ) do
  begin
     bres:= getNombreRes( kres , s_nombre );
     if bres then
        s.set_NombreRest( kRes, s_nombre )
     else
        s.set_NombreRest( kRes, self.nombre+'_y'+IntToStr( kres - self.ires + 1) );
  end;
end;
{$ENDIF}


procedure TActor.ActualizarProbabilidadesReparacionYRotura_(
  Nuevo_FDisp, Nuevo_TMRep: NReal; kTipoUnidad: integer);
begin
  unidades_RotRep[kTipoUnidad].FDispo := Nuevo_FDisp;
  unidades_RotRep[kTipoUnidad].TMRep_h := Nuevo_TMRep;

  urandDispos.Calc_pRep_pRot(
    unidades_RotRep[kTipoUnidad].UsarSorteoEstatico,
    unidades_RotRep[kTipoUnidad].pRep, unidades_RotRep[kTipoUnidad].pRot,
    Nuevo_FDisp, Nuevo_TMRep, globs.HorasDelPaso);
end;


function TActor.Sorteos_RepRotUnidades: integer;
begin
  Result := Sorteos_RepRotUnidades_k(0);
end;


function TActor.Sorteos_RepRotUnidades_k(kTipoUnidad: integer): integer;
var
  pu: PRotRep_rec;
  nUnidadesOperativas, deltaUnidadesOperativas, d1: integer;

begin
  pu := @unidades_RotRep[kTipoUnidad];

  nUnidadesOperativas := paUnidades.nUnidades_Operativas[kTipoUnidad];
  deltaUnidadesOperativas := nUnidadesOperativas - pu.cnt_Operativas;

  // Hay variación en la cantidad de unidades instaladas fuera de mantenimiento Programado.
  if deltaUnidadesOperativas <> 0 then
  begin
    if deltaUnidadesOperativas > 0 then
    begin
      // Aumento de unidades fuera de mantenimiento programado.
      if paUnidades.AltaConIncertidumbre[kTipoUnidad] then
        // las subimos ROTAS y que juege la prob. de reparación
        pu.cnt_Rotas := pu.cnt_Rotas + deltaUnidadesOperativas
      else
        pu.cnt_OK := pu.cnt_Ok + deltaUnidadesOperativas;
    end
    else
    begin
      // baja de unidades fuera de mantenimiento programdo
      d1 := trunc(-deltaUnidadesOperativas * pu.cnt_Ok / pu.cnt_Operativas + 0.45);
      pu.cnt_OK := max(0, pu.cnt_OK - d1);
      pu.cnt_Rotas := nUnidadesOperativas - pu.cnt_OK;
      if pu.cnt_Rotas < 0 then
      begin
        pu.cnt_OK := pu.cnt_OK - pu.cnt_Rotas;
        pu.cnt_Rotas := 0;
      end;
    end;
    pu.cnt_Operativas := nUnidadesOperativas;
  end;


  if (globs.EstadoDeLaSala = CES_OPTIMIZANDO) or pu.UsarSorteoEstatico then
    rndDispoNMaquinas_fd(pu.FDispo, pu.cnt_OK, pu.cnt_Rotas, sorteadorUniforme)
  else
    rndDispoNMaquinas_RotRep(pu.cnt_OK, pu.cnt_Rotas, pu.pRep,
      pu.pRot, sorteadorUniforme);

{$IFDEF RepRotUnidades_log}
  Inc(cnt_repRotUnidades_log);
  writeln(flog_RepRotUnidades, cnt_repRotUnidades_log, #9, globs.kPaso_, #9,
    globs.kCronica, #9, globs.cntIteracionesDelPaso, #9, nombre, #9, pu.FDispo,
    #9, pu.cnt_OK, #9, pu.cnt_Rotas);
{$ENDIF}
  Result := pu.cnt_OK;
end;


procedure TActor.PubliVars;
begin
  inherited PubliVars;

  (* no es posible publicar un campo de paUnidades pues el puntero cambia
  PublicarVariablePVNI( 'nUnidades_Instaladas', 'u', paUnidades, paUnidades.nUnidades_Instaladas, false, true );
  PublicarVariablePVNI( 'nUnidades_EnMantenimiento','u', paUnidades, paUnidades.nUnidades_EnMantenimiento, false, true );
  *)
  PublicarVariableNR('cdp', '[USD]', 12, 0, CostoDirectoDelPaso, True);
  PublicarVariableNR('Ingreso_PorDisponibilidad', '[USD]', 12, 0,
    Ingreso_PorDisponibilidad_, True);
  PublicarVariableNR('Ingreso_PorEnergia', '[USD]', 12, 0, Ingreso_PorEnergia_, True);
end;

function TActor.get_pa_FD(kTipoUnidad: integer): NReal;
begin
  Result := 1.0;
end;

function TActor.get_pa_TMR(kTipoUnidad: integer): NReal;
begin
  Result := 0.0;
end;

procedure TActor.CleanOldersThan(dt: TDateTime);
begin
  inherited CleanOldersThan(dt); // para depurar la lista de ficlas lpd
  if lpdForzamientos <> nil then
    lpdForzamientos.CleanOldersThan( dt );
  if lpdUnidades <> nil then
    lpdUnidades.CleanOldersThan( dt );
end;

class function TActor.CreateDefaultLPDUnidades_(cuantasUnidadesDiferentes: integer):
TFichasLPD;
var
  res: TFichasLPD;
  cantUnidadesInstaladas: TDAofNInt;
  cantUnidadesEnMantenimiento: TDAofNInt;
  AltaConIncertidumbre, InicioCronicaConIncertidumbre: TDAofBoolean;
  i: integer;
  aFichaUnidades: TFichaunidades;
begin
  SetLength(cantUnidadesInstaladas, cuantasUnidadesDiferentes);
  SetLength(cantUnidadesEnMantenimiento, cuantasUnidadesDiferentes);
  setlength(AltaConIncertidumbre, cuantasUnidadesDiferentes);
  setlength(InicioCronicaConIncertidumbre, cuantasUnidadesDiferentes);

  for i := 0 to cuantasUnidadesDiferentes - 1 do
  begin
    cantUnidadesInstaladas[i] := 1;
    cantUnidadesEnMantenimiento[i] := 0;
  end;
  aFichaUnidades:= TFichaUnidades.Create(0, TFecha.Create_Str('0'), nil,
    cantUnidadesInstaladas, cantUnidadesEnMantenimiento, AltaConIncertidumbre,
    InicioCronicaConIncertidumbre);

  res := TFichasLPD.Create(0, 'lpdUnidades', nil, TFichaUnidades);
  res.Add( aFichaUnidades );
  Result := res;
end;

class function TActor.CreateDefaultLPDForzamientos_(
  cuantasVariablesDiferentes: integer): TFichasLPD;
var
  res: TFichasLPD;
  Forzamientos: TDAofNReal;
  i: integer;
begin
  SetLength(Forzamientos, cuantasVariablesDiferentes);
  for i := 0 to cuantasVariablesDiferentes - 1 do
    Forzamientos[i] := 0.0;
  res := TFichasLPD.Create(0, 'lpdFrorzamientos', nil, TFichaForzamientos);
  res.Add(TFichaForzamientos.Create(0, TFecha.Create_Str('0'), nil,
    False, Forzamientos,False,false,false,nil,nil));
  Result := res;
end;



procedure AlInicio;
begin
  {$IFDEF RepRotUnidades_log}
  cnt_repRotUnidades_log := 0;
  assignfile(flog_RepRotUnidades, 'repRotUnidades_log.xlt');
  rewrite(flog_RepRotUnidades);
  {$ENDIF}
end;

procedure AlFinal;
begin
  {$IFDEF RepRotUnidades_log}
  closefile(flog_RepRotUnidades);
  {$ENDIF}
end;

end.
