unit utsdb_parent;

{$mode delphi}

interface

uses
  Classes, Math, xmatdefs, matreal, SysUtils, udbconpg, urobothttppost, uzipper;

const
  dt_1h = 1.0 / 24.0; // 1 hora
  dt_12h = 12.0 / 24.0; // 12 horas
  dt_1ms = 1.0 / (24.0 * 60 * 60 * 1000); // 1 milisegundo.
  dt_1m = 1 / (24.0 * 60); // 1 minuto
  dt_10m = 10.0 * dt_1m; // 10 minutos
  dt_1s = 1.0 / (24.0 * 60 * 60); // 1 segundo
  dt_10s = 10.0 * dt_1s; // 10 segundos

type
  PRecMedida = ^TRecMedida;

  TRecMedida = record
    dt: TDateTime;
    v: double;
    calidad: integer;
  end;
  TDAOfRecMedida = array of TRecMedida;

  { TVentana }

  TVentana = class
    medida, dia: integer;
    dt_Ultima_Actualizacion, dt_Ultimo_Dump: TDateTime;
    adtv: TDAOfRecMedida;

    // variables auxiliares para funciones de interpolación.
    vdt_rescod: integer; // -1 < dt_Ini, 0 en rango , +1 dt > dt_fin
    vdt_kAntIni: integer;
    vdt_kantFin: integer;

    flg_modificado: boolean;

    url_adme_data_srv: string;//:= 'www.adme.com.uy/zeros/getvhdiario.php';
    VERSION_TV: integer;//:=1
    carpeta_dump, carpeta_log, carpeta_basura: string;

    constructor Create(medida, dia: integer; adtv: TDAOfRecMedida; dt_Ultima_Actualizacion, dt_UltimoDump: TDateTime); virtual;
    constructor CreateFromArchi(medida, dia: integer; post_str: string = ''); virtual;

    // si db <> nil actualiza la tabla tsdb_diarios
    procedure WriteToArchi(db: TDBPQCon; post_str: string = ''); virtual;
    procedure WriteToArchi_XLT(archi: string); virtual;abstract;

    // Incorpora al vector de muestras de la ventana desde kPrimera hasta kUltima
    // inclusive del vector  adtvx pasado como parámetro.
    procedure Incorpore(const adtvx: TDAOfRecMedida; kPrimera, kUltima: integer); virtual;

    // Crea un TVentana con el subtramo kPrimera .. kUltima
    function CrearHijo(kPrimera, kUltima: integer): TVentana; virtual;

    // Si el archivo existe incorpora las muestras a las existentes
    // si db <> nil actualiza la tabla tsdb_diarios
    procedure WriteAppendToArchi(db: TDBPQCon; post_str: string = ''); virtual; abstract;


    procedure Free; virtual;

    // (vdt) devuelve el resultado para el tiempo dt. El codigo de resultado
    // de la última llamada se guarda en vdt_rescod del objeto y puede
    // ser consultado a continuación de la llamada.
    function vdt(dt: TDateTime): double; virtual;

    function dt_PrimerMedida: TDateTime; virtual;
    function dt_UltimaMedida: TDateTime; virtual; abstract;

    constructor CreateFromStream(f: TStream); virtual;
    // si db <> nil actualiza la tabla tsdb_diarios
    procedure WriteToStream(db: TDBPQCon; f: TStream); virtual;

    // Retorna el camino completo al archivo con datos bajados del Scada (vía el soap ute).
    // El resultado es: carpeta_dump_scada/{YYYY}/{nidMedida}/m{nidMedida}d{dt_ini}.dat
    function ArchiDestino(nidMedida: integer; dia: integer; post_str: string=
      ''; flg_CreateDirs: boolean=False): string; virtual; abstract;

    function GetRemote_Ventana(pasaporte: string; medida, dia: integer): boolean; virtual;

    // retorna en (k) el índice de la ficha tal que
    // adtv[k].dt < dt <= adtv[k+1].dt
    // la búsqueda se inica en k para ser más eficiente si se pasa como parámetro
    // el k de la última búsqueda.
    function fvdt_locate_kAnterior_dt(var k: integer; const adtv: TDAOfRecMedida; dt: TDateTime): integer;virtual; abstract; // -1 < dt_Ini, 0 en rango , +1 dt > dt_fin

    // retorna en (k) el índice de la ficha tal que
    // adtv[k-1].dt <= dt < adtv[k].dt
    // la búsqueda se inica en k para ser más eficiente si se pasa como parámetro
    // el k de la última búsqueda.
    function fvdt_locate_kPosterior_dt(var k: integer; const adtv: TDAOfRecMedida; dt: TDateTime): integer;virtual; abstract;// -1 < dt_Ini, 0 en rango , +1 dt > dt_fin


    // devuelve el valor con fecha inferior o igual a dt
    // Inicia la búsqueda desde el (kIni) pasado como parámetro por lo
    // cual la función es más eficiente si se pasa el mismo valor devuelto
    // por la última llamada. Devuelve en kIni la posición de la ficha
    // con dt anterior al buscado.
    function fvdt(var valor: double; const adtv: TDAOfRecMedida; dt: TDateTime; var kAnt: integer): integer;virtual; abstract;
    // -1 < dt_Ini, 0 en rango , +1 dt > dt_fin


    // muestrea el Promedio Móvil de la señal.
    function fvdt_pm(var valor: double; const adtv: TDAOfRecMedida; dt: TDateTime; // tiempo de muestreo del promedio móvil
      ddt: TDateTime; // ventana de promediación.
      var kAntIni, kAntFin: integer): integer; virtual;

    // muestrea el Promedio Móvil de la señal correpondiente
    // a una posición angular expresda en grados.
    function fAngdt_pm(var valor: double; const adtv: TDAOfRecMedida; dt: TDateTime; // tiempo de muestreo del promedio móvil
      ddt: TDateTime; // ventana de promediación.
      var kAntIni, kAntFin: integer; flg_EsAngulo: boolean): integer; virtual;
    // -1 < dt_Ini, 0 en rango , +1 dt > dt_fin

  end;

  TCursorScada = class
    vh: TVentana; // Ventana activa
    vh_ant: TVentana; // Ventana anterior a la activa
    nidMedida: integer; // identificador único de la medida
    kDiaDesde, kDiaHasta: integer; // Rango de días en que se supone hay info.
    defVal_SinDato: NReal;
    flg_Angulo: boolean;

    constructor Create(nidMedida: integer; kDiaDesde, kDiaHasta: integer;
    // Rango en días en que hay información.
      DefVal_SinDato: NReal = -222222; xflg_Angulo: boolean = False);


    //resCod: -1: dt < dt_primera_muestra, 0: dt en rango; 1: dt > dt_ultima_muestra
    function getval(dt: TDateTime; var ResCod: integer): NReal;

    // Retorna la muestra anterior al inicio del rango
    // Nil si no encuentra
    function Primera(dtIni: TDateTime): PRecMedida;

    // Mientras que las muestras estén en rango retorna la misma.
    // retorna NIL si ya recorró todo el rango.
    function Siguiente(dtFin: TDateTime): PRecMedida;

    // incializa la logica de muestreo uniforme del cursor para generar valores
    // comenzando en dtInicio hasta dtFin y de a palso dtDelta. El resultado es -1
    // si no hay datos para inicializar, 0 si hay datos y 1 si no hay datos para le futuro.
    // Observar que retorna el primer valor.
    // El parámetro Suavizar indica si hay que interpolar entre muestras o tomar el último valor
    function Muestreador_Iniciar(dtInicio, dtFin, dtDelta: TDateTime; var ResCod: integer; Suavizar: boolean): NReal;

    // Da la próxima muestra del muestreo uniforme inicializado con Muestrear_Iniciar.
    // en dt retorna el timestamp de la muestra y en ResCod retorna:
    // -1 Llamada no válida. Por ej. ya se terminó y sigue llamando.
    // 0 todo ok, resultado válido en cuanto a rango temporal. Si hay vacío
    // 1 no quedan datos futuros se retorna el DefVal
    // 2 El valor retornado es válido, pero es la última muestra del muestreo definido.
    //   En la llamada a NextSimple, cuando se retorna 2 es sinónimo de EOF.
    // 3 Es la simultaenidad de las condiciones 1 y 2 anteriores.
    function Muestreador_NextSimple(var dt: TDateTime; var ResCod: integer): NReal;

    // Da la próxima muestra del muestreo uniforme inicializado con Muestrear_Iniciar.
    // en dt retorna el timestamp de la muestra y en ResCod retorna 0 si el muestreo
    // es válido y 1 si finalizó (no hay más iformación para muestrear).
    // El resultado es el promedio móvil entre el muestreo anterior y el correspondiente
    // al devuelto en dt.
    // -1 Llamada no válida. Por ej. ya se terminó y sigue llamando.
    // 0 todo ok, resultado válido en cuanto a rango temporal. Si hay vacío
    // 1 no quedan datos futuros se retorna el DefVal
    // 2 El valor retornado es válido, pero es la última muestra del muestreo definido.
    //   En la llamada a NextPromedio, cuando se retorna 2 es sinónimo de EOF.
    // 3 Es la simultaenidad de las condiciones 1 y 2 anteriores.
    function Muestreador_NextPromedio(var dt: TDateTime; var ResCod: integer): NReal;

    procedure Free;
  private
    kMuestra: integer; // auxiliar para acelerar búsqueda
    Muestreador_Acum: NReal;
    Muestreador_Medida, Muestreador_Medida_sig: TRecMedida;
    Muestreador_dtInicio, Muestreador_dtFin, Muestreador_dtDelta, Muestreador_dtUltimoMuestreo: TDateTime;
    Muestreador_k: integer; // ordinal de la muestra 0 .. N-1
    Muestreador_N: integer; // largo del la serie muestreada.
    Muestreador_Suavizar: boolean; // si está a TRUE interpola entre muestras
    // sino usa el valor anterior.

    // lo mismo que el anterior pero supone que la señal es una posición angular
    function Muestreador_NextPromedio_Angulo_Deg(var dt: TDateTime; var ResCod: integer): NReal;

  end;



(* Para menejo de las información completa, definimos una tabla en el servidor
  que es reconstruible a partir de las VentansDiarias de todas las medidas.
  En las instalaciones locales, supondremos que hay un archivo por cada medida
  que tiene esta misma información y que se usa para saber comparando con el
  servidor que ventanas hay que actualizar

CREATE TABLE tsdb_diarios(
  medida int REFERENCES codigosmedidasscada ( nid ),
  dia int,
  dt_ultima_acutalizacion timestamp,
  dt_ultimo_dump timestamp,
  almacen int default 0 );

CREATE UNIQUE INDEX tsdb_diarios_idx ON tsdb_diarios ( medida, dia );
  *)



// Retorna string con dt1 y dt2 separadas por un guión bajo
// y con el formato YYYYMMDDhhmmss_YYYMMDDhhmmss
function IntervaloToStr(dt1, dt2: TDateTime): string;

// Fija el formato de fecha como dd/mm/yyyy para que funcione el StrToDateTime
// acorde a ese formato.
procedure SetFormatoFechaUruguay;

// Compara dos registros. Retorna TRUE si coinciden en todo.
function rec_dtv_iguales(const rec1, rec2: TRecMedida; flg_comp_fechas: boolean = True): boolean;

// Elimina registros duplicados. Retorna la cantidad de eliminados
function recs_dtv_eliminar_duplicados(var res: TDAOfRecMedida; flg_dejar_ultimo_rec: boolean = False; flg_comp_fechas: boolean = True): integer;

// Ordena por dt creciente. Retorna la cantidad de swaps de registros realizada.
// Si el resultado es CERO indica que el vector ya estaba ordenado.
// El método usado es el QuickSort
function recs_dtv_sort(var res: TDAOfRecMedida): integer;

// Retorna la estimación lineal a partir de las muestras mA y mB.
// En resCod se retorna -1 si dt < mA.dt, 0 si mA.dt <= dt <= mB.dt y 1 si dt > mB.dt
function InterPolLinealEntreMuestras(const mA, mB: TRecMedida; dt: TDateTime; resCod: integer): NReal;

// Retorna la estimación lineal a partir de las muestras mA y mB.
// En resCod se retorna -1 si dt < mA.dt, 0 si mA.dt <= dt <= mB.dt y 1 si dt > mB.dt
function InterPolLinealEntreMuestras_AngDeg(const mA, mB: TRecMedida; dt: TDateTime; resCod: integer): NReal;


function CosDeg(ang: NReal): NReal;
function SinDeg(ang: NReal): NReal;
function FaseDeg(x, y: NReal): NReal;

function CrearSiNoExiste(carpeta: string): string;


implementation

function CrearSiNoExiste(carpeta: string): string;
begin
  if not DirectoryExists(carpeta) then
    try
      mkdir(carpeta)
    except
      on E: Exception do
        raise Exception.Create('Error al intentar crear la carpeta: ' + carpeta);
    end;
  Result := carpeta;
end;

function CosDeg(ang: NReal): NReal;
begin
  Result := cos(DegToRad(ang));
end;

function SinDeg(ang: NReal): NReal;
begin
  Result := sin(DegToRad(ang));
end;

function FaseDeg(x, y: NReal): NReal;
var
  res: NReal;
begin
  if abs(x) < 1E-10 then
    if y > 1E-10 then
      Result := 90
    else if y < -1E-10 then
      Result := 360 - 90
    else
      Result := 0
  else
  begin
    res := RadToDeg(Arctan(y / x));
    if x < 0 then
      res := 180 + res;

    if res > 360 then
      Result := res - 360
    else if res < 0 then
      Result := res + 360
    else
      Result := res;
  end;
end;

function InterPolLinealEntreMuestras(const mA, mB: TRecMedida; dt: TDateTime; resCod: integer): NReal;
var
  res: NReal;
  deltaT, m: NReal;

  procedure CasoDegenerado;
  begin
    if dt > mB.dt then
    begin
      resCod := 1;
      Result := mB.v;
    end
    else if dt < mA.dt then
    begin
      resCod := -1;
      Result := mA.v;
    end
    else
    begin
      resCod := 0;
      Result := (mA.v + mB.v) / 2.0;
    end;
  end;

begin
  deltaT := mB.dt - mA.dt;
  if abs(deltaT) < 1E-15 then
    CasoDegenerado
  else
  begin
    m := (mB.v - mA.v) / deltaT;
    Result := mA.v + m * (dt - mA.dt);
    if dt > mB.dt then
      resCod := 1
    else if dt < mA.dt then
      resCod := -1
    else
      resCod := 0;
  end;
end;

function InterPolLinealEntreMuestras_AngDeg(const mA, mB: TRecMedida; dt: TDateTime; resCod: integer): NReal;
var
  res: NReal;
  deltaT, mx, my: NReal;

  procedure CasoDegenerado;
  begin
    if dt > mB.dt then
    begin
      resCod := 1;
      Result := mB.v;
    end
    else if dt < mA.dt then
    begin
      resCod := -1;
      Result := mA.v;
    end
    else
    begin
      resCod := 0;
      mx := (CosDeg(mA.v) + CosDeg(mB.v)) / 2.0;
      my := (SinDeg(mA.v) + SinDeg(mB.v)) / 2.0;
      Result := FaseDeg(mx, my);
    end;
  end;

begin
  deltaT := mB.dt - mA.dt;
  if abs(deltaT) < 1E-15 then
    CasoDegenerado
  else
  begin
    mx := CosDeg(mA.v);
    mx := mx + (CosDeg(mB.v) - mx) / deltaT * (dt - mA.dt);
    my := SinDeg(mA.v);
    my := my + (SinDeg(mB.v) - my) / deltaT * (dt - mA.dt);
    Result := FaseDeg(mx, my);
    if dt > mB.dt then
      resCod := 1
    else if dt < mA.dt then
      resCod := -1
    else
      resCod := 0;
  end;
end;

procedure SetFormatoFechaUruguay;
begin
  DefaultFormatSettings.ThousandSeparator := ',';
  DefaultFormatSettings.DecimalSeparator := '.';
  DefaultFormatSettings.DateSeparator := '/';
  DefaultFormatSettings.ListSeparator := ';';
  DefaultFormatSettings.ShortDateFormat := 'd/m/y';
  DefaultFormatSettings.LongDateFormat := 'dd" "mmmm" "yyyy';
  DefaultFormatSettings.ShortTimeFormat := 'hh:nn';
  DefaultFormatSettings.LongTimeFormat := 'hh:nn:ss';
end;

function IntervaloToStr(dt1, dt2: TDateTime): string;
begin
  Result := FormatDateTime('YYYYMMDDhhmmss', dt1) + '_' + FormatDateTime(
    'YYYYMMDDhhmmss', dt2);
end;

{TRecMedida}

// Compara dos registros. Retorna TRUE si coinciden en todo.
function rec_dtv_iguales(const rec1, rec2: TRecMedida; flg_comp_fechas: boolean = True): boolean;
var
  res: boolean;
begin
  res := (rec1.v = rec2.v) and (rec1.calidad = rec2.calidad);
  if flg_comp_fechas then
    Result := res and (rec1.dt = rec2.dt)
  else
    Result := res;
end;

function recs_dtv_eliminar_duplicados(var res: TDAOfRecMedida; flg_dejar_ultimo_rec: boolean = False; flg_comp_fechas: boolean = True): integer;
var
  cnt, j, k: integer;
begin
  cnt := length(res);
  j := high(res);

  if flg_dejar_ultimo_rec then
    Dec(j);

  while j > 0 do
  begin
    if rec_dtv_iguales(res[j - 1], res[j], flg_comp_fechas) then
    begin
      for k := j + 1 to cnt - 1 do
        res[k - 1] := res[k];
      Dec(cnt);
    end;
    Dec(j);
  end;

  if cnt <> length(res) then
  begin
    Result := length(res) - cnt;
    setlength(res, cnt);
  end
  else
    Result := 0;
end;

function Recs_Partition_QuickSortInc(var A: TDAOfRecMedida; First, Last: integer): integer;
var
  Right, Left: integer;
  V, z: TRecMedida;
  cnt_swaps: integer;
begin
  V := A[(First + Last) div 2];
  Right := First;
  Left := Last;
  cnt_swaps := 0;
  repeat
    while (A[Right].dt < V.dt) do
      Right := Right + 1;
    while (A[Left].dt > V.dt) do
      Left := Left - 1;
    if (Right <= Left) then
    begin
      if A[Right].dt <> A[Left].dt then
      begin
        z := A[Right];
        A[Right] := A[Left];
        A[Left] := z;
        Inc(cnt_swaps);
      end;
      Right := Right + 1;
      Left := Left - 1;
    end;
  until Right > Left;
  if (First < Left) then
    cnt_swaps := cnt_swaps + Recs_Partition_QuickSortInc(A, First, Left);
  if (Right < Last) then
    cnt_swaps := cnt_swaps + Recs_Partition_QuickSortInc(A, Right, Last);
  Result := cnt_swaps;
end;

function recs_dtv_sort(var res: TDAOfRecMedida): integer;
begin
  Result := Recs_Partition_QuickSortInc(res, 0, high(res));
end;

{TVentana}


// muestrea el Promedio Móvil de la señal.
function TVentana.fvdt_pm(var valor: double; const adtv: TDAOfRecMedida; dt: TDateTime; // tiempo de muestreo del promedio móvil
  ddt: TDateTime; // ventana de promediación.
  var kAntIni, kAntFin: integer): integer;
  // -1 < dt_Ini, 0 en rango , +1 dt > dt_fin
var
  rescod: integer;
  k: integer;
  acum_vxdt, acum_dt: double;
  delta_dt: double;
  dt_ini: TDateTime;

begin
  dt_ini := dt - ddt;        //este no lo entendi mvarela@22/2
  rescod := fvdt_locate_kAnterior_dt(kAntIni, adtv, dt_ini);
  if rescod <> 0 then        //fuera del rango seria el primer valor
  begin
    kAntFin := kAntIni;
    valor := adtv[kAntIni].v;
    Result := rescod;
    exit;
  end;

  rescod := fvdt_locate_kAnterior_dt(kAntFin, adtv, dt);
  if rescod <> 0 then
  begin
    valor := adtv[kAntFin].v;
    Result := rescod;
    exit;
  end;

  if kAntIni = kAntFin then
  begin
    valor := adtv[kAntFin].v;
    Result := rescod;
    exit;
  end;

  delta_dt := adtv[kAntIni + 1].dt - dt_ini;
  acum_vxdt := adtv[kAntIni].v * delta_dt;
  acum_dt := delta_dt;

  for k := kAntIni + 1 to kAntFin - 1 do
  begin
    delta_dt := adtv[k + 1].dt - adtv[k].dt;
    acum_vxdt := acum_vxdt + adtv[k].v * delta_dt;
    acum_dt := acum_dt + delta_dt;
  end;

  delta_dt := dt - adtv[kAntFin].dt;
  acum_vxdt := acum_vxdt + adtv[kAntFin].v * delta_dt;
  acum_dt := acum_dt + delta_dt;

  if acum_dt > AsumaCero then
    valor := acum_vxdt / acum_dt
  else
    valor := adtv[kAntIni].v;

  Result := rescod;
end;


// muestrea el Promedio Móvil de la señal correpondiente
// a una posición angular expresda en grados.
function TVentana.fAngdt_pm(var valor: double; const adtv: TDAOfRecMedida; dt: TDateTime; // tiempo de muestreo del promedio móvil
  ddt: TDateTime; // ventana de promediación.
  var kAntIni, kAntFin: integer; flg_EsAngulo: boolean): integer;
  // -1 < dt_Ini, 0 en rango , +1 dt > dt_fin
var
  rescod: integer;
  k: integer;
  acum_vxdt, acum_vydt, acum_dt: double;
  delta_dt: double;
  dt_ini: TDateTime;

begin
  dt_ini := dt - ddt;
  rescod := self.fvdt_locate_kAnterior_dt(kAntIni, adtv, dt_ini);
  if rescod <> 0 then
  begin
    kAntFin := kAntIni;
    valor := adtv[kAntIni].v;
    Result := rescod;
    exit;
  end;

  rescod := self.fvdt_locate_kAnterior_dt(kAntFin, adtv, dt);
  if rescod <> 0 then
  begin
    valor := adtv[kAntFin].v;
    Result := rescod;
    exit;
  end;

  if kAntIni = kAntFin then
  begin
    valor := adtv[kAntFin].v;
    Result := rescod;
    exit;
  end;

  delta_dt := adtv[kAntIni + 1].dt - dt_ini;
  acum_vxdt := CosDeg(adtv[kAntIni].v) * delta_dt;
  acum_vydt := SinDeg(adtv[kAntIni].v) * delta_dt;
  acum_dt := delta_dt;

  for k := kAntIni + 1 to kAntFin - 1 do
  begin
    delta_dt := adtv[k + 1].dt - adtv[k].dt;
    acum_vxdt := acum_vxdt + CosDeg(adtv[k].v) * delta_dt;
    acum_vydt := acum_vydt + SinDeg(adtv[k].v) * delta_dt;
    acum_dt := acum_dt + delta_dt;
  end;

  delta_dt := dt - adtv[kAntFin].dt;
  acum_vxdt := acum_vxdt + CosDeg(adtv[kAntFin].v) * delta_dt;
  acum_vydt := acum_vydt + SinDeg(adtv[kAntFin].v) * delta_dt;
  acum_dt := acum_dt + delta_dt;

  if acum_dt > AsumaCero then
  begin
    if abs(acum_vxdt) < 1e-10 then
      if acum_vydt > 0 then
        valor := 90
      else
        valor := 360 - 90
    else
      valor := radtodeg(ArcTan(acum_vydt / acum_vxdt));
  end
  else
    valor := adtv[kAntIni].v;

  Result := rescod;
end;


{TVentana}

constructor TVentana.Create(medida, dia: integer; adtv: TDAOfRecMedida; dt_Ultima_Actualizacion, dt_UltimoDump: TDateTime);
begin
  inherited Create;
  self.medida := medida;
  self.dia := dia;
  self.adtv := adtv;
  flg_modificado := True;
  self.dt_Ultima_Actualizacion := dt_Ultima_Actualizacion;
  self.dt_Ultimo_Dump := dt_UltimoDump;
  vdt_rescod := 0;
  vdt_kAntIni := 0;
  vdt_kAntFin := 0;
end;

function TVentana.CrearHijo(kPrimera, kUltima: integer): TVentana;
var
  res: TVentana;
  xadtv: TDAOfRecMedida;
begin
  xadtv := copy(adtv, kPrimera, kUltima - kPrimera + 1);
  res := TVentana.Create(medida, dia, xadtv, dt_Ultima_Actualizacion,
    dt_Ultimo_Dump);
  Result := res;
end;

constructor TVentana.CreateFromStream(f: TStream);
var
  n: integer;
  menos_version: integer;

begin
  inherited Create;
  flg_modificado := False;
  f.Read(menos_version, sizeOf(menos_version));
  if menos_version >= 0 then
    raise Exception.Create('TVentanaDiara.CreateFromStream menos_version>= 0');
  f.Read(medida, SizeOf(medida));
  f.Read(dia, SizeOf(dia));
  f.Read(dt_Ultima_Actualizacion, SizeOf(dt_Ultima_Actualizacion));
  f.Read(dt_Ultimo_Dump, SizeOf(dt_Ultimo_Dump));
  f.Read(n, sizeOf(n));
  if n < 0 then
    adtv := nil
  else
  begin
    setlength(adtv, n);
    f.Read(adtv[0], n * SizeOf(TRecMedida));
  end;

  vdt_rescod := 0;
  vdt_kAntIni := 0;
  vdt_kAntFin := 0;
end;



type
  TRecDiaMed = class
    kDia: integer;
    kPrimera, kUltima: integer;
    constructor Create(kDia_: integer; kPrimera_: integer);
  end;

constructor TRecDiaMed.Create(kDia_: integer; kPrimera_: integer);
begin
  inherited Create;
  kDia := kDia_;
  kPrimera := kPrimera_;
  kUltima := -1;
end;


procedure TVentana.Incorpore(const adtvx: TDAOfRecMedida; kPrimera, kUltima: integer);
var
  kIni, kFin, rescodIni, rescodFin: integer;
  res: TDAOfRecMedida;
  NDatos: integer;
  NDatosx: integer;
  k: integer;
begin
  kIni := 0;
  rescodIni := fvdt_locate_kAnterior_dt(kIni, adtv, adtvx[kPrimera].dt);
  kFin := kIni;
  rescodFin := fvdt_locate_kPosterior_dt(kFin, adtv, adtvx[kUltima].dt);

  NDatos := length(adtv);
  NDatosx := (kUltima - kPrimera + 1);
  // hay cinco casos posibles
  if rescodFin = -1 then
  begin
    // caso 1) La ventana a agregar está antes que la ventana de datos.
    setlength(res, NDatosx + NDatos);
    for k := kPrimera to kUltima do
      res[k - kPrimera] := adtvx[k];
    for k := 0 to high(adtv) do
      res[NDatosx + k] := adtv[k];
  end
  else if rescodIni = -1 then
  begin
    // caso 2) La ventana a agregar se solapa con el inicio de la ventana de datos
    setlength(res, NDatosx + NDatos - (kFin + 1));
    for k := kPrimera to kUltima do
      res[k - kPrimera] := adtvx[k];
    for k := kFin + 1 to high(adtv) do
      res[NDatosx + k] := adtv[k];
  end
  else if rescodFin = 0 then
    // ( rescodFin = 0 ) => (rescodIni <= 0)
    // y ya sabemos que not (rescodIni = -1 ) por lo cual rescodIni = 0
  begin
    // caso 3) La ventana a agregar está dentro de la ventana de datos
    setlength(res, kIni + 1 + NDatosx + (NDAtos - (kFin + 1)));
    for k := 0 to kIni do
      res[k] := adtv[k];
    for k := kPrimera to kUltima do
      res[kIni + 1 + k - kPrimera] := adtvx[k];
    for k := kIni + 1 + NDatosx to high(res) do
      res[k] := adtv[k - (kIni + 1 + NDatosx) + kFin + 1];
  end
  else if rescodIni = 0 then
    // llegamos aquí si rescodFin = 1
  begin
    // caso 4) La ventana a agregar se solapa con el fin de la ventana de datos
    // 0 ..kIni, kIni+1, ... high(avdt)
    //           kPrimera, .. .., ...........kUltima
    setlength(res, kIni + 1 + NDatosx);
    for k := 0 to kIni do
      res[k] := adtv[k];
    for k := kPrimera to kUltima do
      res[kIni + 1 + k - kPrimera] := adtvx[k];
  end
  else
  begin
    // caso 5) La ventana a agregar es posterior a la ventana de datos.
    // 0 ..kIni, kIni+1, ... high(avdt)
    //           kPrimera, .. .., ...........kUltima
    setlength(res, NDatos + NDatosx);
    for k := 0 to high(adtv) do
      res[k] := adtv[k];
    for k := kPrimera to kUltima do
      res[NDatos + k - kPrimera] := adtvx[k];
  end;
  setlength(adtv, 0);
  adtv := res;
end;

procedure TVentana.WriteToArchi(db: TDBPQCon; post_str: string = '');
var
  f: TFileStream;
  archi: string;
begin
  archi := ArchiDestino(medida, dia, post_str, True);
  f := TFileStream.Create(archi, fmCreate + fmOpenWrite);
  WriteToStream(db, f);
  f.Free;
end;

constructor TVentana.CreateFromArchi(medida, dia: integer; post_str: string = '');
var
  f: TFileStream;
  archi: string;
begin
  archi := ArchiDestino(medida, dia, post_str, True);
  if fileexists(archi) then
  begin
    f := TFileStream.Create(archi, fmOpenRead);
    CreateFromStream(f);
    f.Free;
  end
  else
    self.Create(medida, dia, nil, -1, -1);
end;


procedure TVentana.WriteToStream(db: TDBPQCon; f: TStream);
var
  n: integer;
begin
  //  if not flg_modificado then
  //    exit;
  dt_Ultima_Actualizacion := now;
  n := -VERSION_TV;
  f.Write(n, sizeOf(n));
  f.Write(medida, SizeOf(medida));
  f.Write(dia, SizeOf(dia));
  f.Write(dt_Ultima_Actualizacion, SizeOf(dt_Ultima_Actualizacion));
  f.Write(dt_Ultimo_Dump, SizeOf(dt_Ultimo_Dump));
  if adtv = nil then
  begin
    n := -1;
    f.Write(n, sizeOf(n));
  end
  else
  begin
    n := length(adtv);
    f.Write(n, sizeOf(n));
    f.Write(adtv[0], n * SizeOf(TRecMedida));
  end;

  n := DB.exec('UPDATE tsdb_diarios SET dt_ultima_actualizacion =''' +
    FormatDateTime('YYYY-MM-DD hh:nn:ss', dt_Ultima_Actualizacion) +
    ''', dt_ultimo_dump = ''' + FormatDateTime('YYYY-MM-DD hh:nn:ss',
    dt_Ultimo_Dump) + ''' ' + 'WHERE medida = ' + IntToStr(medida) +
    ' AND dia = ' + IntToStr(dia) + '; ');
  if n = 0 then
    DB.exec(
      'INSERT INTO tsdb_diarios ( medida, dia, dt_ultima_actualizacion, dt_ultimo_dump ) ' + ' VALUES ( ' + IntToStr(medida) + ', ' + IntToStr(dia) +
      ', ''' + FormatDateTime('YYYY-MM-DD hh:nn:ss', dt_Ultima_Actualizacion) +
      ''', ''' + FormatDateTime('YYYY-MM-DD hh:nn:ss', dt_Ultimo_Dump) + ''' ); ');

end;

procedure TVentana.Free;
begin
  if adtv <> nil then
    setlength(adtv, 0);
  inherited Free;
end;

// (vdt) devuelve el resultado para el tiempo dt. El codigo de resultado
// de la última llamada se guarda en vdt_rescod del objeto y puede
// ser consultado a continuación de la llamada.
// Por defecto al crear el objeto se inicializa flg_vdt_interpolar = TRUE
// lo que hace que la función vdt interpola entre las marcas de tiempo
// si se quiere que no interpole se debe fijar flg_vdt_interpolar = FALSE
function TVentana.vdt(dt: TDateTime): double;
var
  val: double;
begin
  if adtv <> nil then
    vdt_rescod := fvdt(val, self.adtv, dt, vdt_kAntIni)
  else
    vdt_rescod := -2;
  Result := val;
end;


function TVentana.dt_PrimerMedida: TDateTime;
begin
  Result := adtv[0].dt;
end;


// Genera archivo con series 10minutales compatible con AnalisisSerial
procedure GenSeries10m_sas(const archi: string; const nidMedida: array of integer; const nombresSeries: array of string; dt_Desde, dt_Hasta: TDateTime; metodo: integer; // 0: getval ; 1: Simple ; 2: Promedio
  flg_Suavizar: boolean;
  // Aplica sobre metodo 1 y 2. Si FALSE el método 1 debiera dar lo mismo que el 0
  seriesAngulo: TSetOfByte);
var
  delta_dt: TDateTime;
  archi_scada: string;
  cs: array of TCursorScada;
  kDato, k: integer;
  jMedida, kMedida: integer;
  dt: TDateTime;
  val: double;
  sal: textfile;
  NSeries: integer;
  NPuntos: integer;
  flg_Terminar: boolean;
  anio, mes, dia, hora, minuto, segundo, mili_segundo: word;
  a: integer;
  resCod: integer;
  dir: string;
  buff: array[1..1024 * 1024] of byte;
begin
  SetFormatoFechaUruguay;

  dir := ExtractFileDir(archi);
  if not DirectoryExists(dir) then
    mkdir(dir);
  NSeries := length(nidMedida);
  setlength(cs, NSeries);
  assignfile(sal, archi);
  rewrite(sal);
  SetTextBuf(sal, buff);
  decodedate(dt_Desde, anio, mes, dia);
  decodetime(dt_Desde, hora, minuto, segundo, mili_segundo);

  NPuntos := trunc((dt_Hasta - dt_Desde) * 24 * 60 / 10);
  writeln(sal, NSeries, #9'NSeries');
  writeln(sal, anio, #9, mes, #9, dia, #9, hora, #9, minuto, #9,
    segundo, #9, '// año  mes dia hora minuto segundo  fecha de la primera muestra');
  writeln(sal, 0.1666666667, #9, '// Período de muestreo en horas');
  writeln(sal, NPuntos, #9, 'NPuntos');
  writeln(sal, 1, #9, 'Puntos por ciclo');
  for jMedida := 0 to high(nidMedida) do
    Write(sal, #9, nombresSeries[jMedida]);
  writeln(sal);

  kDato := 0;
  dt := dt_Desde;
  delta_dt := 10.0 / (24 * 60);
  flg_Terminar := False;


  for jMedida := 0 to NSeries - 1 do
    if nidMedida[jMedida] > 0 then
      cs[jMedida] := TCursorScada.Create(nidMedida[jMedida], trunc(
        dt_Desde) - 1, trunc(dt_Hasta) + 1, -22222222, jMedida in seriesAngulo)
    else
      cs[jMedida] := nil;

  while not flg_Terminar and (kDato < nPuntos) and (dt <= dt_Hasta) do
  begin
    dt := dt_Desde + kDato * delta_dt;
    Write(sal, double(dt): 12: 6);

    for jMedida := 0 to NSeries - 1 do
    begin

      if cs[jMedida] <> nil then
      begin

        if metodo = 0 then
          val := cs[jMedida].getval(dt, resCod)
        else if kDato = 0 then
          val := cs[jMedida].Muestreador_Iniciar(dt_Desde, dt_Hasta, dt_10m, resCod, flg_Suavizar)
        else if metodo = 1 then
          val := cs[jMedida].Muestreador_NextSimple(dt, ResCod)
        else
          val := cs[jMedida].Muestreador_NextPromedio(dt, ResCod);
        if rescod >= 0 then
          Write(sal, #9, val)
        else
          Write(sal, #9, -121111);
      end
      else
      begin
        if nidMedida[jMedida] < -100 then
          Write(sal, #9, -nidMedida[jMedida] / 100.0)
        else
          Write(sal, #9, -111111);
      end;
    end;
    writeln(sal);
    Inc(kDato);
  end;

  closefile(sal);
  for jMedida := 0 to NSeries - 1 do
    if cs[jMedida] <> nil then
      cs[jMedida].Free;

  setlength(cs, 0);
end;



function TVentana.GetRemote_Ventana(pasaporte: string; medida, dia: integer): boolean;
var
  funzip: TUnZipper;
  carpeta: string;
  archi: string;
  archi_zip: string;
  archi_dbk: string;
  rbt: TRobotHttpPost;
  anio, mes, xdia: word;
begin
  decodedate(dia, anio, mes, xdia);
  archi := self.ArchiDestino(medida, dia, '', True);
  archi_zip := ChangeFileExt(archi, '.zip');

  if fileexists(archi_zip) then
    deletefile(archi_zip);
  rbt := TRobotHttpPost.Create(url_adme_data_srv, '', '');
  rbt.AddCampo('p', pasaporte);
  rbt.AddCampo('a', anio);
  rbt.AddCampo('m', medida);
  rbt.AddCampo('d', dia);
  rbt.post_storeFile('POST', archi_zip);
  rbt.Free;

  if not FileExists(archi_zip) then
  begin
    Result := False;
    exit;
  end;

  if fileexists(archi) then
  begin
    archi_dbk := ChangeFileExt(archi, '.dbk');
    if fileexists(archi_dbk) then
      deletefile(archi_dbk);
    renamefile(archi, archi_dbk);
  end
  else
    archi_dbk := '';
  try
    funzip := TUnZipper.Create;
    carpeta := ExtractFilePath(archi_zip);
    funzip.OutputPath := carpeta;
    funzip.UnZipAllFiles(archi_zip);
    funzip.Free;
    deletefile(archi_zip);
    Result := fileexists(archi);
  except
    on E: Exception do
    begin
      if archi_dbk <> '' then
        renamefile(archi_dbk, archi);
      Result := False;
    end;
  end;
end;

constructor TCursorScada.Create(nidMedida: integer; kDiaDesde, kDiaHasta: integer;
  // Rango en días en que hay información.
  DefVal_SinDato: NReal = -222222; xflg_Angulo: boolean = False);
begin
  inherited Create;

  self.NidMedida := nidMedida;
  self.kDiaDesde := kDiaDesde;
  self.kDiahasta := kDiaHasta;
  self.defVal_SinDato := defVal_SinDato;
  vh := TVentana.CreateFromArchi(nidMedida, kDiaDesde);
  kMuestra := 0;
  vh_ant := nil;
  flg_Angulo := xflg_Angulo;

end;

function TCursorScada.getval(dt: TDateTime; var resCod: integer): NReal;
var
  dtDia: integer;
  v: NReal;
  xResCod: integer;
begin
  dtDia := trunc(dt);
  if vh = nil then
  begin
    vh := TVentana.CreateFromArchi(nidMedida, dtDia);
    kMuestra := 0;
    if vh.adtv = nil then
    begin
      resCod := -3;
      Result := defVal_SinDato;
      exit;
    end;
    if vh_ant <> nil then
      vh_ant.Free;
    if dtDia > kDiaDesde then
      vh_ant := TVentana.CreateFromArchi(nidMedida, dtDia - 1)
    else
      vh_ant := nil;
  end
  else if dtDia <> vh.dia then
  begin
    if dtDia < kDiaDesde then
    begin
      resCod := -1;
      Result := defVal_SinDato;
      exit;
    end;
    if dtDia > kDiaHasta then
    begin
      resCod := 1;
      Result := defVal_SinDato;
      exit;
    end;

    vh.Free;
    vh := TVentana.CreateFromArchi(nidMedida, dtDia);
    kMuestra := 0;
    if vh_ant <> nil then
      vh_ant.Free;
    if dtDia > kDiaDesde then
      vh_ant := TVentana.CreateFromArchi(nidMedida, dtDia - 1)
    else
      vh_ant := nil;
  end;

  if vh.adtv = nil then
  begin
    Result := defVal_SinDato;
    xresCod := -3;
    exit;
  end;

  xresCod := vh.fvdt(v, vh.adtv, dt, kMuestra);

  if xresCod < 0 then
  begin
    if (vh_ant <> nil) and (vh_ant.adtv <> nil) then
    begin
      kMuestra := high(vh_ant.adtv);
      xresCod := vh.fvdt(v, vh_ant.adtv, dt, kMuestra);
      if xresCod >= 0 then
      begin
        Result := v;
        resCod := 0;
        exit;
      end;
    end;
    resCod := -1;
    Result := defVal_SinDato;
    exit;
  end;

  rescod := 0;
  Result := v;
end;



// Retorna la muestra anterior al inicio del rango
function TCursorScada.Primera(dtIni: TDateTime): PRecMedida;
var
  v: NReal;
  resCod: integer;
begin
  v := Self.getval(dtIni, resCod);
  if resCod <> 0 then
    Result := nil
  else
    Result := @vh.adtv[kMuestra];
end;

// Mientras que las muestras estén en rango retorna la misma.
// retorna NIL si ya recorrió todo el rango.
function TCursorScada.Siguiente(dtFin: TDateTime): PRecMedida;
begin
  if (vh = nil) or (vh.adtv = nil) then
  begin
    Result := nil;
    exit;
  end;

  if kMuestra = high(vh.adtv) then
  begin
    if vh_ant <> nil then
      vh_ant.Free;
    vh_ant := vh;
    vh := TVentana.CreateFromArchi(nidMedida, vh_ant.dia + 1);
    kMuestra := 0;
    if vh.adtv = nil then
    begin
      vh.Free;
      vh := nil;
      Result := nil;
      exit;
    end;
  end
  else
    Inc(kMuestra);

  if vh.adtv[kMuestra].dt <= dtFin then
    Result := @vh.adtv[kMuestra]
  else
    Result := nil;
end;



function TCursorScada.Muestreador_Iniciar(dtInicio, dtFin, dtDelta: TDateTime; var ResCod: integer; suavizar: boolean): NReal;
var
  xprm: PRecMedida;
begin
  Muestreador_dtInicio := dtInicio;
  Muestreador_dtFin := dtFin;
  Muestreador_dtDelta := dtDelta;
  Muestreador_dtUltimoMuestreo := dtInicio;
  Muestreador_k := 0;
  Muestreador_N := trunc((dtFin - dtInicio) / dtDelta) + 1;
  Muestreador_Suavizar := Suavizar;
  xprm := Primera(Muestreador_dtInicio);
  resCod := 0;

  if xprm = nil then
  begin
    resCod := -1;
    Result := defVal_SinDato;
    Muestreador_Medida.dt := -1; // con esto indicamos que no tuvimos suerte
  end
  else
    Muestreador_Medida := xprm^;

  xprm := Siguiente(Muestreador_dtFin);
  if xprm = nil then
  begin
    ResCod := 1; // no hay más muestras
    Muestreador_Medida_sig.dt := -1;
  end
  else
    Muestreador_MEdida_sig := xprm^;
  Result := Muestreador_Medida.v;
end;

function TCursorScada.Muestreador_NextSimple(var dt: TDateTime; var ResCod: integer): NReal;
var
  xprm: PRecMedida;
begin
  Inc(Muestreador_k);
  if Muestreador_k >= Muestreador_N then
  begin
    Result := defVal_SinDato;
    ResCod := -1;
    exit;
  end;

  dt := Muestreador_dtInicio + Muestreador_k * Muestreador_dtDelta;
  Muestreador_dtUltimoMuestreo := dt;
  if dt < Muestreador_Medida_sig.dt then
  begin
    // No es necesario avanzar las muestras.
    Result := Muestreador_Medida.v;
    resCod := 0;
    exit;
  end;

  // avanzamos si podemos.
  xprm := @Muestreador_Medida_sig;
  while (xprm <> nil) and (xprm^.dt <= dt) do
  begin
    Muestreador_Medida := xprm^;
    xprm := Siguiente(Muestreador_dtFin);
  end;

  if xprm = nil then
  begin
    Result := defVal_SinDato;
    resCod := 1; // no quedan más muestras.
  end
  else
  begin
    Muestreador_Medida_sig := xprm^;
    Result := Muestreador_Medida.v;
    resCod := 0;
  end;

  if Muestreador_k = Muestreador_N then
    resCod := resCod + 2;

end;

function TCursorScada.Muestreador_NextPromedio(var dt: TDateTime; var ResCod: integer): NReal;
var
  xprm: PRecMedida;
  acum: NReal;
  dtAnt: TDateTime;
  valor: NReal;

begin
  if flg_Angulo then
  begin
    Result := Muestreador_NextPromedio_Angulo_Deg(dt, rescod);
    exit;
  end;

  Inc(Muestreador_k);
  if Muestreador_k >= Muestreador_N then
  begin
    Result := defVal_SinDato;
    ResCod := -1;
    exit;
  end;

  dt := Muestreador_dtInicio + Muestreador_k * Muestreador_dtDelta;
  Muestreador_dtUltimoMuestreo := dt;
  if dt < Muestreador_Medida_sig.dt then
  begin
    // No es necesario avanzar las muestras.
    if Muestreador_Suavizar then
      Result := InterPolLinealEntreMuestras(Muestreador_Medida,
        Muestreador_Medida_sig, dt - Muestreador_dtDelta / 2.0, resCod)
    else
      Result := Muestreador_Medida.v;
    resCod := 0;
    exit;
  end;

  Acum := 0;
  dtAnt := dt - Muestreador_dtDelta;
  // avanzamos si podemos.
  xprm := @Muestreador_Medida_sig;
  while (xprm <> nil) and (xprm^.dt <= dt) do
  begin

    if Muestreador_Suavizar then
      valor := InterPolLinealEntreMuestras(Muestreador_Medida,
        Muestreador_Medida_sig, (xprm^.dt + dtAnt) / 2.0, resCod)
    else
      valor := Muestreador_Medida.v;
    acum := acum + valor * (xprm^.dt - dtAnt);

    Muestreador_Medida := xprm^;
    xprm := Siguiente(Muestreador_dtFin);
    dtAnt := Muestreador_Medida.dt;
  end;


  if xprm = nil then
  begin
    Result := defVal_SinDato;
    resCod := 1; // no quedan más muestras.
  end
  else
  begin
    if Muestreador_Suavizar then
      valor := InterPolLinealEntreMuestras(Muestreador_Medida,
        Muestreador_Medida_sig, (dt + dtAnt) / 2.0, resCod)
    else
      valor := Muestreador_Medida.v;


    acum := acum + valor * (dt - dtAnt);

    Result := acum / Muestreador_dtDelta;
    resCod := 0;
    Muestreador_Medida_sig := xprm^;
  end;

  if Muestreador_k = Muestreador_N then
    resCod := resCod + 2;

end;


function TCursorScada.Muestreador_NextPromedio_Angulo_Deg(var dt: TDateTime; var ResCod: integer): NReal;
var
  xprm: PRecMedida;
  acum_x, acum_y: NReal;
  dtAnt: TDateTime;
  valor: NReal;

begin
  Inc(Muestreador_k);
  if Muestreador_k >= Muestreador_N then
  begin
    Result := defVal_SinDato;
    ResCod := -1;
    exit;
  end;

  dt := Muestreador_dtInicio + Muestreador_k * Muestreador_dtDelta;
  Muestreador_dtUltimoMuestreo := dt;
  if dt < Muestreador_Medida_sig.dt then
  begin
    // No es necesario avanzar las muestras.
    if Muestreador_Suavizar then
      Result := InterPolLinealEntreMuestras(Muestreador_Medida,
        Muestreador_Medida_sig, dt - Muestreador_dtDelta / 2.0, resCod)
    else
      Result := Muestreador_Medida.v;
    resCod := 0;
    exit;
  end;

  Acum_x := 0;
  Acum_y := 0;
  dtAnt := dt - Muestreador_dtDelta;
  // avanzamos si podemos.
  xprm := @Muestreador_Medida_sig;
  while (xprm <> nil) and (xprm^.dt <= dt) do
  begin

    if Muestreador_Suavizar then
      valor := InterPolLinealEntreMuestras_AngDeg(Muestreador_Medida,
        Muestreador_Medida_sig, (xprm^.dt + dtAnt) / 2.0, resCod)
    else
      valor := Muestreador_Medida.v;
    acum_x := acum_x + CosDeg(valor) * (xprm^.dt - dtAnt);
    acum_y := acum_y + SinDeg(valor) * (xprm^.dt - dtAnt);

    Muestreador_Medida := xprm^;
    xprm := Siguiente(Muestreador_dtFin);
    dtAnt := Muestreador_Medida.dt;
  end;


  if xprm = nil then
  begin
    Result := defVal_SinDato;
    resCod := 1; // no quedan más muestras.
  end
  else
  begin
    if Muestreador_Suavizar then
      valor := InterPolLinealEntreMuestras_AngDeg(Muestreador_Medida,
        Muestreador_Medida_sig, (dt + dtAnt) / 2.0, resCod)
    else
      valor := Muestreador_Medida.v;

    acum_x := acum_x + CosDeg(valor) * (dt - dtAnt);
    acum_y := acum_y + SinDeg(valor) * (dt - dtAnt);
    Result := FaseDeg(acum_x, acum_y);
    resCod := 0;
    Muestreador_Medida_sig := xprm^;
  end;

  if Muestreador_k = Muestreador_N then
    resCod := resCod + 2;

end;


procedure TCursorScada.Free;
begin
  if vh_ant <> nil then
    vh_ant.Free;
  if vh <> nil then
    vh.Free;
  inherited Free;
end;


initialization

end.
(* definición de lista de ventanas diarias por si es necesario

TLstOfVentanaDiaria = class(TList)
  constructor Create;
  procedure Free;
  procedure WriteToStream(f: TStream);
  constructor CreateFromStream(f: TStream);
  procedure WriteToArchi(archi: string);
  constructor CreateFromArchi(archi: string);

  function GetVH(i: longint): TVentana;
  procedure SetVH(i: longint; aVH: TVentana);
  property Items[i: longint]: TVentana read GetVH write SetVH; default;
end;



constructor TLstOfVentanaDiaria.Create;
begin
  inherited Create;
end;


procedure TLstOfVentanaDiaria.WriteToStream(f: TStream);
var
  n: integer;
  k: integer;
begin
  n := Count;
  f.Write(n, sizeOf(n));
  for k := 0 to Count - 1 do
    items[k].WriteToStream(f);
end;

constructor TLstOfVentanaDiaria.CreateFromStream(f: TStream);
var
  n: integer;
  k: integer;
  aRec: TVentana;
begin
  inherited Create;
  f.Read(n, sizeOf(n));
  for k := 0 to n - 1 do
  begin
    aRec := TVentana.CreateFromStream(f);
    add(aRec);
  end;
end;

procedure TLstOfVentanaDiaria.WriteToArchi(archi: string);
var
  f: TFileStream;
begin
  f := TFileStream.Create(archi, fmCreate + fmOpenWrite);
  WriteToStream(f);
  f.Free;
end;

constructor TLstOfVentanaDiaria.CreateFromArchi(archi: string);
var
  f: TFileStream;
begin
  f := TFileStream.Create(archi, fmOpenRead);
  CreateFromStream(f);
  f.Free;
end;

function TLstOfVentanaDiaria.GetVH(i: longint): TVentana;
begin
  Result := inherited items[i];
end;

procedure TLstOfVentanaDiaria.SetVH(i: longint; aVH: TVentana);
begin
  inherited items[i] := aVH;
end;

procedure TLstOfVentanaDiaria.Free;
var
  k: integer;
  avh: TVentana;
begin
  for k := 0 to Count - 1 do
  begin
    avh := items[k];
    avh.Free;
  end;
  inherited Free;
end;

*)
