unit utsdb_anual;

{$mode delphi}

interface

uses
  Classes, math, xmatdefs, matreal, SysUtils, udbconpg, urobothttppost, uzipper, utsdb_parent;
const
  VERSION=1;

type

  { TVentanaAnual }

  TVentanaAnual = class(TVentana)
    frecuencia: NReal;

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

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

    // 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);

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

    // 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 = '');


    procedure Free;

    // (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;

    function dt_PrimerMedida: TDateTime;
    function dt_UltimaMedida: TDateTime;

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

    // 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;

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

   // 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; // -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; // -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;
// -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;

// 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;
  // -1 < dt_Ini, 0 en rango , +1 dt > dt_fin

  end;

  TCursorScada = class
    vh: TVentanaAnual; // Ventana activa
    vh_ant: TVentanaAnual; // 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;


implementation




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;


function TVentanaAnual.ArchiDestino(nidMedida: integer; dia: integer; post_str: string = '';
  flg_CreateDirs: boolean = False): string;
var
  res: string;
  Anio, mes, xdia: word;
begin
  decodedate(dia, anio, mes, xdia);
  res := carpeta_dump + DirectorySeparator + IntToStr(anio);
  if flg_CreateDirs and not DirectoryExists(res) then
    MkDir(res);
  res := res + DirectorySeparator + IntToStr(nidMedida);
  if flg_CreateDirs and not DirectoryExists(res) then
    MkDir(res);
  res := res + DirectorySeparator + 'm' + IntToStr(nidMedida) + 'd' +
    IntToStr(dia) + post_str + '.dat';
  Result := res;
end;


// 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;

// 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 TVentanaAnual.fvdt_locate_kAnterior_dt(var k: integer; const adtv: TDAOfRecMedida;
  dt: TDateTime): integer; // -1 < dt_Ini, 0 en rango , +1 dt > dt_fin
var
  rescod: integer;
  buscando: boolean;
begin
  rescod := 0;

  if k < 0 then
    k := 0
  else if k > high(adtv) then
    k := high(adtv);

  buscando := True;
  if not (adtv[k].dt <= dt) then // hay que retroceder
  begin
    Dec(k);
    while buscando and (k >= 0) do
    begin
      if (adtv[k].dt <= dt) then
        buscando := False
      else
        Dec(k);
    end;

    if buscando then
    begin
      k := 0;
      rescod := -1;
    end;
  end
  else   // hacia adelante
  begin
    while buscando and (k < high(adtv)) do
    begin
      if (dt < adtv[k + 1].dt) then
        buscando := False
      else
        Inc(k);
    end;
    if buscando then
      rescod := 1;
  end;
  Result := rescod;
end;

// 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 TVentanaAnual.fvdt_locate_kPosterior_dt(var k: integer; const adtv: TDAOfRecMedida;
  dt: TDateTime): integer; // -1 < dt_Ini, 0 en rango , +1 dt > dt_fin
var
  rescod: integer;
  buscando: boolean;
begin
  rescod := 0;
  buscando := True;
  if not (dt < adtv[k].dt) then // hay que avanzar
  begin
    Inc(k);
    while buscando and (k <= high(adtv)) do
    begin
      if (dt < adtv[k].dt) then
        buscando := False
      else
        Inc(k);
    end;
    if buscando then
    begin
      k := high(adtv);
      rescod := 1;
    end;
  end
  else  // hacia atras
  begin
    while buscando and (k > 0) do
    begin
      if (adtv[k - 1].dt <= dt) then
        buscando := False
      else
        Dec(k);
    end;
    if buscando then
    begin
      k := 0;
      rescod := -1;
    end;
  end;
  Result := rescod;
end;


// devuelve el valor con fecha inferior o igual a dt
// en kAnt retona el ordinal de la muestra de forma de poder usar ese índice
// para la siguiente llamada. Las muestras se suponen ordenadas por dt Creciente.
// kAnt debe estar entre 0 y high( adtv ) y es devuelto en ese rango.
// Si resultado = -1 se retorna kAnt en 0. Si resultado = 1 se retorna kAnt en
// high(adtv)
// Para el caso de rescod = -1, la función retorna -333333 como indicativo de
// que no tiene información valida.
// Para el caso de rescod = 1 , la función retorna el valor de la última muestra.
function TVentanaAnual.fvdt(var valor: double; const adtv: TDAOfRecMedida; dt: TDateTime;
  var kAnt: integer): integer;
  // -1 < dt_Ini, 0 en rango , +1 dt > dt_fin
var
  rescod: integer;
begin
  rescod := fvdt_locate_kAnterior_dt(kAnt, adtv, dt);
  valor := adtv[kAnt].v;
  Result := rescod;
end;


// muestrea el Promedio Móvil de la señal.
function TVentanaAnual.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;
  rescod := fvdt_locate_kAnterior_dt(kAntIni, adtv, dt_ini);
  if rescod <> 0 then
  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 TVentanaAnual.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;


  {TVentanaAnual}

constructor TVentanaAnual.Create(medida, dia: integer; adtv: TDAOfRecMedida;
  dt_Ultima_Actualizacion, dt_UltimoDump: TDateTime);
begin
  inherited Create(medida, dia, adtv, dt_Ultima_Actualizacion, dt_UltimoDump);
  VERSION_TV := VERSION;
  frecuencia := 366;
  {$IFDEF LINUX}
  carpeta_basura := CrearSiNoExiste(GetUserDir + 'basura');
  carpeta_dump := CrearSiNoExiste(GetUserDir + 'datos_smec');
  carpeta_log:=CrearSiNoExiste(GetUserDir+'log');
  {$ELSE}
  CrearSiNoExiste('c:\simsee');
  carpeta_basura := CrearSiNoExiste('c:\simsee\basura');
  CrearSiNoExiste('c:\simsee\adme_data');
  carpeta_log:=CrearSiNoExiste('c:\simsee\adme_data\log');
  carpeta_dump := CrearSiNoExiste('c:\simsee\adme_data\datos_smec');
  {$ENDIF}
end;

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

constructor TVentanaAnual.CreateFromStream(f: TStream);
begin
  inherited CreateFromStream(f);
  VERSION_TV := VERSION;
  {$IFDEF LINUX}
  carpeta_basura := CrearSiNoExiste(GetUserDir + 'basura');
  carpeta_dump := CrearSiNoExiste(GetUserDir + 'datos_smec');
  carpeta_log:=CrearSiNoExiste(GetUserDir+'log');
{$ELSE}
  CrearSiNoExiste('c:\simsee');
  carpeta_basura := CrearSiNoExiste('c:\simsee\basura');
  CrearSiNoExiste('c:\simsee\adme_data');
  carpeta_log:=CrearSiNoExiste('c:\simsee\adme_data\log');
  carpeta_dump := CrearSiNoExiste('c:\simsee\adme_data\datos_smec');
{$ENDIF}

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 TVentanaAnual.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 TVentanaAnual.WriteAppendToArchi(db: TDBPQCon; post_str: string = '');
var
  avh: TVentanaAnual;
  archi: string;
  n: integer;
begin
  if Length(adtv) = 0 then
  begin
    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) + ''' ); ');
    Exit;
  end;

  archi := ArchiDestino(medida, dia, post_str, True);
  if fileexists(archi) then
  begin
    avh := TVentanaAnual.CreateFromArchi(medida, dia, post_str);
    avh.Incorpore(adtv, 0, high(adtv));
    avh.WriteToArchi(db, post_str);
    avh.Free;
  end
  else
    WriteToArchi(db, post_str);
end;

procedure TVentanaAnual.WriteToArchi(db: TDBPQCon; post_str: string = '');
begin
  inherited WriteToArchi(db,post_str);
end;

procedure TVentanaAnual.WriteToArchi_XLT(archi: string);
var
  f: textfile;
  k: integer;
  archis: string;
begin
  archis := ChangeFileExt(archi, '.xlt');
  assignfile(f, archis);
  rewrite(f);
  writeln(f, 'frecuencia:', #9, frecuencia);
  writeln(f, 'medida:', #9, medida);
  writeln(f, 'dia:', #9, dia);
  writeln(f, 'dt_Ultima_Actualizacion:', #9, DateTimeToStr(dt_Ultima_Actualizacion));
  writeln(f, 'dt_Ultimo_Dump:', #9, DateTimeToStr(dt_Ultimo_Dump));
  writeln(f, 'NRecs:', #9, Length(adtv));
  writeln(f, 'kRec', #9, 'Fecha', #9, 'Valor', #9, 'Calidad');
  for k := 0 to high(adtv) do
    writeln(f, k, #9, adtv[k].dt, #9, adtv[k].v, #9, adtv[k].calidad);
  closefile(f);
end;


constructor TVentanaAnual.CreateFromArchi(medida, dia: integer; post_str: string = '');
var
  f: TFileStream;
  archi: string;
begin
  inherited CreateFromArchi(medida,dia,post_str);
  VERSION_TV := VERSION;
  frecuencia:= 366;
  {$IFDEF LINUX}
  carpeta_basura := CrearSiNoExiste(GetUserDir + 'basura');
  carpeta_dump := CrearSiNoExiste(GetUserDir + 'datos_smec');
  carpeta_log:=CrearSiNoExiste(GetUserDir+'log');
  {$ELSE}
  CrearSiNoExiste('c:\simsee');
  carpeta_basura := CrearSiNoExiste('c:\simsee\basura');
  CrearSiNoExiste('c:\simsee\adme_data');
  carpeta_log:=CrearSiNoExiste('c:\simsee\adme_data\log');
  carpeta_dump := CrearSiNoExiste('c:\simsee\adme_data\datos_smec');
  {$ENDIF}
end;


procedure TVentanaAnual.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 TVentanaAnual.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 TVentanaAnual.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 TVentanaAnual.dt_PrimerMedida: TDateTime;
begin
  Result := adtv[0].dt;
end;

function TVentanaAnual.dt_UltimaMedida: TDateTime;
begin
  Result := adtv[high(adtv)].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 TVentanaAnual.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 := TVentanaAnual.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 := TVentanaAnual.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 := TVentanaAnual.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 := TVentanaAnual.CreateFromArchi(nidMedida, dtDia);
    kMuestra := 0;
    if vh_ant <> nil then
      vh_ant.Free;
    if dtDia > kDiaDesde then
      vh_ant := TVentanaAnual.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:= TVentanaAnual.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;


//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;

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): TVentanaAnual;
  procedure SetVH(i: longint; aVH: TVentanaAnual);
  property Items[i: longint]: TVentanaAnual 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: TVentanaAnual;
begin
  inherited Create;
  f.Read(n, sizeOf(n));
  for k := 0 to n - 1 do
  begin
    aRec := TVentanaAnual.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): TVentanaAnual;
begin
  Result := inherited items[i];
end;

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

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

*)
