unit utsdb_diario;

(* rch2016011011118
*)
{$mode delphi}
interface

uses
  Classes, Math, xmatdefs,
  matreal,
  SysUtils,
  {$IFDEF pq_direct}
  udbconpg,
  {$ELSE}
  urosx,
  {$ENDIF}

  {$IFDEF FEDE_DB}
  DB,
  {$ENDIF}

  urobothttppost, zipper;

const
  VERSION_TVH = 1;
  C_VALOR_SIN_DATO = -222222;
  C_VALOR_NO_HAY_MEDIDAS = -333333;
  C_VALOR_FALLO_CALIDAD = -555555;
  C_CAlIDAD_SINDATO = -1520; // No es una medida real, es una SIN_DATO del muestreador

var
  carpeta_dump_scada, carpeta_log_scada, carpeta_basura, carpeta_series10m: string;

const
  url_adme_data_srv = 'latorre.adme.com.uy/zeros/getvhdiario.php';

const
  dt_1h = 1.0 / 24.0; // 1 hora
  dt_12h = 12.0 / 24.0; // 12 horas
  dt_100us = 1.0 / (24.0 * 60 * 60 * 10000); // 100 microsegundos.
  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;

  { TVentanaDiaria }

  TVentanaDiaria = 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;

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

    // si db <> nil actualiza la tabla tsdb_diarios
    procedure WriteToArchi(db: TDBPQCon; post_str: string = '');
    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.
    // Elimina los datos pre-existentes posteriores al inicio de los nuevos datos
    procedure Incorpore(const adtvx: TDAOfRecMedida; kPrimera, kUltima: integer);

    // Lo mismo que la anterior pero Incrusta sin eliminar los datos posteriores
    // a la ultima muestra de los nuevos datos
    procedure Incorpore_Incrustando(const adtvx: TDAOfRecMedida;
      kPrimera, kUltima: integer);

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

    // 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;
    function ultimo_valor: double;

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

    procedure UpdateInsert_tsdbDiarios(db: TDBPQCon);
  end;

  TDAOfVentanaDiaria = array of TVentanaDiaria;


  { TCursorScada
  Esta clase tiene por propósito permitir recorrer la información de una
  medida del Scada (almacenda en archivos diarios).
  Al crear una instancia se especifica un rango de días en el que el cursor
  puede moverse.

  }
  TCursorScada = class
    vh: TVentanaDiaria; // Ventana activa
    vh_ant: TVentanaDiaria; // 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_FiltrarPorCalidad: boolean;
    flg_Angulo: boolean;
    flg_Cuadrado: boolean;



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


    // resCod: -1: dt < dt_primera_muestra, 0: dt en rango; 1: dt > dt_ultima_muestra
    // Primero intenta buscar dentro del mismo día de dt, si la primer muestra
    // de la vh correspondiente a trunc( dt) es posterior a dt, entonces busca
    // en los días anteriores el primero con muestras y retorna el valor.
    // Para obtener el valor usa la función fdtv por lo cual retorna la constante
    // C_VALOR_FALLO_CALIDAD si la muestra tiene código de calidad <> 0.
    function getval(dt: TDateTime; out ResCod: integer): NReal;

    // Retorna la muestra anterior al inicio del rango
    // Nil si no encuentra
    // Actualiza vh y vh_ant de forma que ambas tengan muestras.
    // si vh_ant = nil implica no encontró una ventana anterior
    // a vh dentro del rango del cursor que tenga muestras.
    function Primera(dtIni: TDateTime): PRecMedida;

    // Mientras que las muestras estén en rango retorna la siguiente.
    // retorna NIL si ya recorrió todo el rango.
    // Si es necesario avanza vh y vh_ant de forma de que ambas tenga muestras
    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 el 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;
      out 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(out dt: TDateTime; out 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;

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


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

// 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.
// Si el código de calidad de la muestra <> 0 retorna en (valor) la constante
// C_VALOR_FALLO_CALIDAD
function fvdt(out 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(out 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
// Genera archivo con muestras 10 minutales de la medida nidMedida
// buscando en los archivos scada desde dtIni hasta dtFin
// el resultado se guarda en "archi" pasado como parámetro.
// El formato de los archivos es 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; // Array con series que deben ser considerada ángulos Deg
  kSeriteVelParaTurbulencia: integer);

//VERSIÓN PARA uadme_data_dnc
procedure GenSeries10m_sas_admedata(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: TDAOfBoolean; // Array con series que deben ser considerada ángulos Deg
  kSeriteVelParaTurbulencia: integer);

// Genera series con intervalo dt_EntreMuestras
procedure GenSeries_sas(const archi: string; const nidMedida: array of integer;
  const nombresSeries: array of string; dt_Desde, dt_Hasta: TDateTime;
  dt_EntreMuestras: TDateTime; metodo: integer; flg_Suavizar: boolean;
  seriesAngulo: TSetOfByte; kSeriteVelParaTurbulencia: integer);

// VERSIÓN PARA uadme_data_dnc
procedure GenSeries_sas_admedata(const archi: string;
  const nidMedida: array of integer; const nombresSeries: array of string;
  dt_Desde, dt_Hasta: TDateTime; dt_EntreMuestras: TDateTime;
  metodo: integer; flg_Suavizar: boolean; seriesAngulo: TDAOfBoolean;
  kSeriteVelParaTurbulencia: integer);



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

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

// 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
// La bandera flg_ConCalidad se impone a FALSE si el código de calidad de la o las
// muestras que afectan el resultado es <> 0. ATENCION, la bandera solo se baja a FALSE
// si hay problemas con la calidad, no se sube a TRUE si no lo hay. Quien llama el procedimiento
// se debe preocupar de la inicialización de la bandera de calidad.
function InterPolLinealEntreMuestras(const mA, mB: TRecMedida;
  dt: TDateTime; out resCod: integer; var flg_ConCalidad: boolean): 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
// La bandera flg_ConCalidad se impone a FALSE si el código de calidad de la o las
// muestras que afectan el resultado es <> 0. ATENCION, la bandera solo se baja a FALSE
// si hay problemas con la calidad, no se sube a TRUE si no lo hay. Quien llama el procedimiento
// se debe preocupar de la inicialización de la bandera de calidad.
function InterPolLinealEntreMuestras_AngDeg(const mA, mB: TRecMedida;
  dt: TDateTime; out resCod: integer; var flg_ConCalidad: boolean): NReal;


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

function CrearSiNoExiste(carpeta: string): string;

procedure BorreArchivos(nidMedida: integer; diaDesde, DiaHasta: integer;
  post_str: string = '');



// Retorna TRUE si logra información de la medida en todo el rango.
function BajarLoNecesario(db: TDBPQCon;
  medida, kDia_Desde, kDia_Hasta: integer): boolean;


// Retorna TVectR con la serie muestreada en el rango especificado.
function getSerie_VectR(nidMedida: integer; dt_PrimeraMuestra: TDateTime;
  dt_EntreMuestras: TDateTime; NMuestras: integer; metodo: integer;
  flg_Suavizar: boolean; flg_SerieAnguloDEG: boolean): TVectR;


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; out resCod: integer; var flg_ConCalidad: boolean): NReal;
var
  deltaT: NReal;
  pa, pb: NReal;

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

begin
  deltaT := mB.dt - mA.dt;
  if abs(deltaT) < dt_1ms then
    CasoDegenerado
  else
  begin
    pa := mB.dt - dt;
    pb := dt - mA.dt;

    if (abs(pa) < dt_1ms) or (abs(pb) < dt_1ms) then
    begin
      // Si está MUY cerca de una de las muestras
      // le permitimos mirar solo esa muestra
      if pa > pb then
      begin // gana el mA
        if mA.calidad >= 0 then
          Result := mA.v
        else
        begin
          flg_ConCalidad := False;
          Result := C_VALOR_FALLO_CALIDAD;
        end;
      end
      else
      begin  // gana el mB
        if mB.calidad >= 0 then
          Result := mB.v
        else
        begin
          flg_ConCalidad := False;
          Result := C_VALOR_FALLO_CALIDAD;
        end;
      end;
    end
    else
    begin
      // juegan las dos muestras
      if (mA.calidad >= 0) and (mB.calidad >= 0) then
        Result := (mA.v * pa + mB.v * pb) / deltaT
      else
      begin
        flg_ConCalidad := False;
        Result := C_VALOR_FALLO_CALIDAD;
      end;
    end;

    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; out resCod: integer; var flg_ConCalidad: boolean): NReal;
var
  deltaT, mx, my: NReal;
  pa, pb: NReal;


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

begin
  deltaT := mB.dt - mA.dt;
  if abs(deltaT) < 1E-15 then
    CasoDegenerado
  else
  begin

    pa := mB.dt - dt;
    pb := dt - mA.dt;

    if (abs(pa) < dt_1ms) or (abs(pb) < dt_1ms) then
    begin
      // Si está MUY cerdca de una de las muestras
      // le permitimos mirar solo esa muestra
      if pa > pb then
      begin // gana el mA
        if mA.calidad >= 0 then
        begin
          mx := CosDeg(mA.v);
          my := SinDeg(mA.v);
          Result := FaseDeg(mx, my);
        end
        else
        begin
          flg_ConCalidad := False;
          Result := C_VALOR_FALLO_CALIDAD;
        end;
      end
      else
      begin  // gana el mB
        if mB.calidad >= 0 then
        begin
          mx := CosDeg(mB.v);
          my := SinDeg(mB.v);
          Result := FaseDeg(mx, my);
        end
        else
        begin
          flg_ConCalidad := False;
          Result := C_VALOR_FALLO_CALIDAD;
        end;
      end;
    end
    else
    begin
      // juegan las dos muestras
      if (mA.calidad >= 0) and (mB.calidad >= 0) then
      begin
        mx := (CosDeg(mA.v) * pa + CosDeg(mB.v) * pb) / deltaT;
        my := (SinDeg(mA.v) * pa + SinDeg(mB.v) * pb) / deltaT;
        Result := FaseDeg(mx, my);
      end
      else
      begin
        flg_ConCalidad := False;
        Result := C_VALOR_FALLO_CALIDAD;
      end;
    end;

    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;

procedure BorreArchivos(nidMedida: integer; diaDesde, DiaHasta: integer;
  post_str: string = '');
var
  archi: string;
  kDia: integer;
begin
  for kDia := diaDesde to Diahasta do
  begin
    archi := ArchiDestino(nidMedida, kdia, post_str, True);
    if fileexists(archi) then
      deletefile(archi);
  end;
end;



function 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_scada + 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
  if length(res) > 0 then
    Result := Recs_Partition_QuickSortInc(res, 0, high(res))
  else
    Result := 0;
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 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 dt >= 42336.013 then
  //   writeln();

  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 + dt_100us) < adtv[k + 1].dt) then
        // DV@20180705 agrego el dt_1ms porque los dts estaban siendo iguales pero por un error de redondeo daba que el de la izquierda era menor
        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 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.
// Si la Calidad de la medida es <> 0 retorna -555555
function fvdt(out 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);
  if rescod = -1 then
    valor := C_VALOR_NO_HAY_MEDIDAS
  else if adtv[kAnt].calidad >= 0 then
    valor := adtv[kAnt].v
  else
    valor := C_VALOR_FALLO_CALIDAD;
  Result := rescod;
end;


// muestrea el Promedio Móvil de la señal.
function fvdt_pm(out 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;
label
  lbl_FalloCalidad;

begin
  dt_ini := dt - ddt;

  rescod := fvdt_locate_kAnterior_dt(kAntIni, adtv, dt_ini);
  if rescod <> 0 then
  begin
    kAntFin := kAntIni;
    if rescod = -1 then
      valor := C_VALOR_NO_HAY_MEDIDAS
    else if adtv[kAntIni].calidad >= 0 then
      valor := adtv[kAntIni].v
    else
      valor := C_VALOR_FALLO_CALIDAD;
    Result := rescod;
    exit;
  end;

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

  if kAntIni = kAntFin then
  begin
    if adtv[kAntFin].calidad >= 0 then
      valor := adtv[kAntFin].v
    else
      valor := C_VALOR_FALLO_CALIDAD;
    Result := rescod;
    exit;
  end;

  delta_dt := adtv[kAntIni + 1].dt - dt_ini;
  if adtv[kAntIni].calidad >= 0 then
    acum_vxdt := adtv[kAntIni].v * delta_dt
  else
    goto lbl_falloCalidad;

  acum_dt := delta_dt;

  for k := kAntIni + 1 to kAntFin - 1 do
  begin
    if adtv[k].calidad >= 0 then
    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
    else
      goto lbl_falloCalidad;
  end;

  delta_dt := dt - adtv[kAntFin].dt;
  if adtv[kAntFin].calidad >= 0 then
  begin
    acum_vxdt := acum_vxdt + adtv[kAntFin].v * delta_dt;
    acum_dt := acum_dt + delta_dt;
  end
  else
    goto lbl_falloCalidad;

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

  Result := rescod;
  exit;

  lbl_falloCalidad:
    valor := C_VALOR_FALLO_CALIDAD;
  Result := rescod;

end;


// 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
var
  rescod: integer;
  k: integer;
  acum_vxdt, acum_vydt, acum_dt: double;
  delta_dt: double;
  dt_ini: TDateTime;

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

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

  if kAntIni = kAntFin then
  begin
    if adtv[kAntFin].calidad >= 0 then
      valor := adtv[kAntFin].v
    else
      valor := C_VALOR_FALLO_CALIDAD;
    Result := rescod;
    exit;
  end;

  if adtv[kAntIni].calidad <> 0 then
    goto lbl_FalloCalidad;
  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
    if adtv[k].calidad <> 0 then
      goto lbl_FalloCalidad;
    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;

  if adtv[kAntFin].calidad <> 0 then
    goto lbl_FalloCalidad;
  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
  if adtv[kAntIni].calidad >= 0 then
    valor := adtv[kAntIni].v
  else
    goto lbl_FalloCalidad;

  Result := rescod;
  exit;

  lbl_FalloCalidad:
    valor := C_VALOR_FALLO_CALIDAD;
  Result := rescod;

end;




constructor TVentanaDiaria.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 TVentanaDiaria.CrearHijo(kPrimera, kUltima: integer): TVentanaDiaria;
var
  res: TVentanaDiaria;
  xadtv: TDAOfRecMedida;
begin
  xadtv := copy(adtv, kPrimera, kUltima - kPrimera + 1);
  res := TVentanaDiaria.Create(medida, dia, xadtv, dt_Ultima_Actualizacion,
    dt_Ultimo_Dump);
  Result := res;
end;

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

begin
  inherited Create;
  flg_modificado := False;
  f.Read(menos_version{%H-}, 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{%H-}, 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 TVentanaDiaria.Incorpore_Incrustando(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));
    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];
  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 TVentanaDiaria.Incorpore(const adtvx: TDAOfRecMedida;
  kPrimera, kUltima: integer);
var
  kIni, rescodIni: integer;
  // kFin: integer;
  // rescodFin: integer;
  res: TDAOfRecMedida;
  NDatos: integer;
  NDatosx: integer;
  k: integer;
begin
  if adtvx = nil then
    exit;
  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);

  case resCodIni of
    -1:
    begin
      setlength(res, NDatosx);
      for k := kPrimera to kUltima do
        res[k - kPrimera] := adtvx[k];
    end;
    0:
    begin
      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;
    1:
    begin
      setlength(res, NDatos + NDatosX);
      for k := 0 to NDatos - 1 do
        res[k] := adtv[k];
      for k := kPrimera to kUltima do
        res[NDatos + k - kPrimera] := adtvx[k];
    end;
  end;
  setlength(adtv, 0);
  adtv := res;
end;


procedure TVentanaDiaria.WriteAppendToArchi(db: TDBPQCon; post_str: string = '');
var
  avh: TVentanaDiaria;
  archi: string;
  {$IFDEF ACTUALIZAR_DB}
  n: integer;
  {$ENDIF}
begin
  {$IFDEF ACTUALIZAR_DB}
  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;
  {$ENDIF}
  archi := ArchiDestino(medida, dia, post_str, True);
  if fileexists(archi) then
  begin
    avh := TVentanaDiaria.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 TVentanaDiaria.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;

procedure TVentanaDiaria.WriteToArchi_XLT(archi: string);
var
  f: textfile;
  k: integer;
  archis: string;
begin
  archis := ChangeFileExt(archi, '.xlt');
  assignfile(f, archis);
  rewrite(f);
  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 TVentanaDiaria.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 TVentanaDiaria.UpdateInsert_tsdbDiarios(db: TDBPQCon);
{$IFDEF ACTUALIZAR_DB}
var
  n: integer;
{$ENDIF}
begin
  {$IFDEF ACTUALIZAR_DB}
  if db <> nil 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) + ''' ); ');
  end;
  {$ENDIF}
end;

procedure TVentanaDiaria.WriteToStream(db: TDBPQCon; f: TStream);
var
  n: integer;
begin
  //  if not flg_modificado then
  //    exit;
  dt_Ultima_Actualizacion := now;
  //// <<-- FB: Para mi aca va la hora del servidor posgres
  n := -VERSION_TVH;
  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;
  UpdateInsert_tsdbDiarios(db);
end;

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

function TVentanaDiaria.dt_UltimaMedida: TDateTime;
begin
  Result := adtv[high(adtv)].dt;
end;

function TVentanaDiaria.ultimo_valor: double;
begin
  Result := adtv[high(adtv)].v;
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; flg_Suavizar: boolean; seriesAngulo: TSetOfByte;
  kSeriteVelParaTurbulencia: integer);
var
  delta_dt: TDateTime;
  cs: array of TCursorScada;
  kDato: integer;
  jMedida: integer;
  dt: TDateTime;
  val: double;
  sal: textfile;
  NSeries: integer;
  NPuntos: integer;
  flg_Terminar: boolean;
  anio, mes, dia, hora, minuto, segundo, mili_segundo: word;
  resCod: integer;
  dir: string;
  buff: array[1..1024 * 1024] of byte;
  vel: double;

begin
  SetFormatoFechaUruguay;

  dir := ExtractFileDir(archi);

  if (dir <> '') and not DirectoryExists(dir) then
    ForceDirectories(dir);
  NSeries := length(nidMedida);
  setlength(cs, NSeries + 1);
  assignfile(sal, archi);
  rewrite(sal);
  SetTextBuf(sal, buff{%H-});
  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 + 1, #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]);
  Write(sal, #9, 'Var(vel)');
  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, C_VALOR_SIN_DATO, jMedida in seriesAngulo)
    else
      cs[jMedida] := nil;

  cs[NSeries] := TCursorScada.Create(nidMedida[kSeriteVelParaTurbulencia],
    trunc(dt_Desde) - 1, trunc(dt_Hasta) + 1, C_VALOR_SIN_DATO, False, True);

  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 do
    begin
      if cs[jMedida] <> nil then
      begin
        if metodo = 0 then
          val := cs[jMedida].getval(dt, resCod)
        else
        if kDato = 0 then
        begin
          writeln('Inicializando Muestreador jMedida: ', jMedida);

          val := cs[jMedida].Muestreador_Iniciar(dt_Desde, dt_Hasta,
            dt_10m, resCod, flg_Suavizar);
        end
        else
        if metodo = 1 then
          val := cs[jMedida].Muestreador_NextSimple(dt, ResCod)
        else
          val := cs[jMedida].Muestreador_NextPromedio(dt, ResCod);

        if rescod >= 0 then
        begin
          if jMedida = kSeriteVelParaTurbulencia then
            vel := val;
          if jMedida = NSeries then
            val := sqrt(abs(val - sqr(vel)));
          Write(sal, #9, val);
        end
        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 do
    if cs[jMedida] <> nil then
      cs[jMedida].Free;

  setlength(cs, 0);
end;



// Genera archivo con series 10minutales compatible con AnalisisSerial. VERSIÓN PARA uadme_data_dnc
procedure GenSeries10m_sas_admedata(const archi: string;
  const nidMedida: array of integer; const nombresSeries: array of string;
  dt_Desde, dt_Hasta: TDateTime; metodo: integer; flg_Suavizar: boolean;
  seriesAngulo: TDAOfBoolean; kSeriteVelParaTurbulencia: integer);
var
  delta_dt: TDateTime;
  cs: array of TCursorScada;
  kDato: integer;
  jMedida: integer;
  dt: TDateTime;
  val: double;
  sal: textfile;
  NSeries: integer;
  NPuntos: integer;
  flg_Terminar: boolean;
  anio, mes, dia, hora, minuto, segundo, mili_segundo: word;
  resCod: integer;
  dir: string;
  buff: array[1..1024 * 1024] of byte;
  vel: double;

begin
  SetFormatoFechaUruguay;

  dir := ExtractFileDir(archi);
  if not DirectoryExists(dir) then
    ForceDirectories(dir);
  NSeries := length(nidMedida);
  setlength(cs, NSeries + 1);
  assignfile(sal, archi);
  rewrite(sal);
  SetTextBuf(sal, buff{%H-});
  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 + 1, #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]);
  Write(sal, #9, 'Var(vel)');
  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, C_VALOR_SIN_DATO, seriesAngulo[jmedida])
    else
      cs[jMedida] := nil;

  cs[NSeries] := TCursorScada.Create(nidMedida[kSeriteVelParaTurbulencia],
    trunc(dt_Desde) - 1, trunc(dt_Hasta) + 1, C_VALOR_SIN_DATO, False, True);

  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 do
    begin
      if cs[jMedida] <> nil then
      begin
        if metodo = 0 then
          val := cs[jMedida].getval(dt, resCod)
        else
        if kDato = 0 then
        begin
          writeln('Inicializando Muestreador jMediada: ', jMedida);

          val := cs[jMedida].Muestreador_Iniciar(dt_Desde, dt_Hasta,
            dt_10m, resCod, flg_Suavizar);
        end
        else
        if metodo = 1 then
          val := cs[jMedida].Muestreador_NextSimple(dt, ResCod)
        else
          val := cs[jMedida].Muestreador_NextPromedio(dt, ResCod);

        if rescod >= 0 then
        begin
          if jMedida = kSeriteVelParaTurbulencia then
            vel := val;
          if jMedida = NSeries then
            val := sqrt(abs(val - sqr(vel)));
          Write(sal, #9, val);
        end
        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 do
    if cs[jMedida] <> nil then
      cs[jMedida].Free;

  setlength(cs, 0);
end;


procedure GenSeries_sas(const archi: string; const nidMedida: array of integer;
  const nombresSeries: array of string; dt_Desde, dt_Hasta: TDateTime;
  dt_EntreMuestras: TDateTime; metodo: integer; flg_Suavizar: boolean;
  seriesAngulo: TSetOfByte; kSeriteVelParaTurbulencia: integer);
var
  delta_dt: TDateTime;
  cs: array of TCursorScada;
  kDato: integer;
  jMedida: integer;
  dt: TDateTime;
  val: double;
  sal: textfile;
  NSeries: integer;
  NPuntos: integer;
  flg_Terminar: boolean;
  anio, mes, dia, hora, minuto, segundo, mili_segundo: word;
  resCod: integer;
  dir: string;
  buff: array[1..1024 * 1024] of byte;
  vel: double;

begin
  SetFormatoFechaUruguay;

  dir := ExtractFileDir(archi);

  if (dir <> '') and not DirectoryExists(dir) then
    ForceDirectories(dir);
  NSeries := length(nidMedida);
  setlength(cs, NSeries + 1);
  assignfile(sal, archi);
  rewrite(sal);
  SetTextBuf(sal, buff{%H-});
  decodedate(dt_Desde, anio, mes, dia);
  decodetime(dt_Desde, hora, minuto, segundo, mili_segundo);

  NPuntos := trunc((dt_Hasta - dt_Desde) / dt_EntreMuestras);
  writeln(sal, NSeries + 1, #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, dt_EntreMuestras * 24, #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]);
  Write(sal, #9, 'Var(vel)');
  writeln(sal);

  kDato := 0;
  dt := dt_Desde;
  delta_dt := dt_EntreMuestras;
  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, C_VALOR_SIN_DATO, jMedida in seriesAngulo)
    else
      cs[jMedida] := nil;

  cs[NSeries] := TCursorScada.Create(nidMedida[kSeriteVelParaTurbulencia],
    trunc(dt_Desde) - 1, trunc(dt_Hasta) + 1, C_VALOR_SIN_DATO, False, True);

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

    for jMedida := 0 to NSeries do
    begin
      if cs[jMedida] <> nil then
      begin
        if metodo = 0 then
          val := cs[jMedida].getval(dt, resCod)
        else
        if kDato = 0 then
        begin
          //writeln('Inicializando Muestreador jMediada: ', jMedida);

          val := cs[jMedida].Muestreador_Iniciar(dt_Desde, dt_Hasta,
            dt_EntreMuestras, resCod, flg_Suavizar);
        end
        else
        if metodo = 1 then
          val := cs[jMedida].Muestreador_NextSimple(dt, ResCod)
        else
          val := cs[jMedida].Muestreador_NextPromedio(dt, ResCod);

        if rescod >= 0 then
        begin
          if jMedida = kSeriteVelParaTurbulencia then
            vel := val;
          if jMedida = NSeries then
            val := sqrt(abs(val - sqr(vel)));
          Write(sal, #9, val);
        end
        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 do
    if cs[jMedida] <> nil then
      cs[jMedida].Free;

  setlength(cs, 0);
end;


procedure GenSeries_sas_admedata(const archi: string;
  const nidMedida: array of integer; const nombresSeries: array of string;
  dt_Desde, dt_Hasta: TDateTime; dt_EntreMuestras: TDateTime;
  metodo: integer; flg_Suavizar: boolean; seriesAngulo: TDAOfBoolean;
  kSeriteVelParaTurbulencia: integer);
var
  delta_dt: TDateTime;
  cs: array of TCursorScada;
  kDato: integer;
  jMedida: integer;
  dt: TDateTime;
  val: double;
  sal: textfile;
  NSeries: integer;
  NPuntos: integer;
  flg_Terminar: boolean;
  anio, mes, dia, hora, minuto, segundo, mili_segundo: word;
  resCod: integer;
  dir: string;
  buff: array[1..1024 * 1024] of byte;
  vel: double;

begin
  SetFormatoFechaUruguay;

  dir := ExtractFileDir(archi);
  if not DirectoryExists(dir) then
    ForceDirectories(dir);
  NSeries := length(nidMedida);
  setlength(cs, NSeries + 1);
  assignfile(sal, archi);
  rewrite(sal);
  SetTextBuf(sal, buff{%H-});
  decodedate(dt_Desde, anio, mes, dia);
  decodetime(dt_Desde, hora, minuto, segundo, mili_segundo);

  NPuntos := trunc((dt_Hasta - dt_Desde) / dt_EntreMuestras);
  writeln(sal, NSeries + 1, #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, dt_EntreMuestras * 24, #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]);
  Write(sal, #9, 'Var(vel)');
  writeln(sal);

  kDato := 0;
  dt := dt_Desde;
  delta_dt := dt_EntreMuestras;
  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, C_VALOR_SIN_DATO, seriesAngulo[jMedida])
    else
      cs[jMedida] := nil;

  cs[NSeries] := TCursorScada.Create(nidMedida[kSeriteVelParaTurbulencia],
    trunc(dt_Desde) - 1, trunc(dt_Hasta) + 1, C_VALOR_SIN_DATO, False, True);

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

    for jMedida := 0 to NSeries do
    begin
      if cs[jMedida] <> nil then
      begin
        if metodo = 0 then
          val := cs[jMedida].getval(dt, resCod)
        else
        if kDato = 0 then
        begin
          writeln('Inicializando Muestreador jMediada: ', jMedida);

          val := cs[jMedida].Muestreador_Iniciar(dt_Desde, dt_Hasta,
            dt_EntreMuestras, resCod, flg_Suavizar);
        end
        else
        if metodo = 1 then
          val := cs[jMedida].Muestreador_NextSimple(dt, ResCod)
        else
          val := cs[jMedida].Muestreador_NextPromedio(dt, ResCod);

        if rescod >= 0 then
        begin
          if jMedida = kSeriteVelParaTurbulencia then
            vel := val;
          if jMedida = NSeries then
            val := sqrt(abs(val - sqr(vel)));
          Write(sal, #9, val);
        end
        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 do
    if cs[jMedida] <> nil then
      cs[jMedida].Free;

  setlength(cs, 0);
end;




function getSerie_VectR(nidMedida: integer; dt_PrimeraMuestra: TDateTime;
  dt_EntreMuestras: TDateTime; NMuestras: integer; metodo: integer;
  flg_Suavizar: boolean; flg_SerieAnguloDEG: boolean): TVectR;
var
  cs: TCursorScada;
  kDato: integer;
  dt, dt_Hasta: TDateTime;
  val: double;
  resCod: integer;
  res: TVectR;

begin
  kDato := 0;
  dt := dt_PrimeraMuestra;
  dt_Hasta := dt_PrimeraMuestra + NMuestras * dt_EntreMuestras;
  res := TVectR.Create_init(NMuestras);
  cs := TCursorScada.Create(nidMedida, trunc(dt_PrimeraMuestra) -
    1, trunc(dt_Hasta) + 1, C_VALOR_SIN_DATO, flg_SerieAnguloDEG);

  for kDato := 1 to NMuestras do
  begin
    dt := dt_PrimeraMuestra + (kDato - 1) * dt_EntreMuestras;
    if metodo = 0 then
      val := cs.getval(dt, resCod)
    else
    if kDato = 1 then
    begin
      val := cs.Muestreador_Iniciar(dt_PrimeraMuestra, dt_Hasta,
        dt_EntreMuestras, resCod, flg_Suavizar);
    end
    else
    if metodo = 1 then
      val := cs.Muestreador_NextSimple(dt, ResCod)
    else
      val := cs.Muestreador_NextPromedio(dt, ResCod);

    if rescod >= 0 then
      res.pon_e(kdato, val)
    else
      res.pon_e(kdato, -121111);
  end;

  cs.Free;
  Result := res;
end;




function GetRemote_VentanaDiaria(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 := 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;
  DefVal_SinDato: NReal; xflg_Angulo: boolean; xflg_Cuadrado: boolean);
begin
  inherited Create;

  self.NidMedida := nidMedida;
  self.kDiaDesde := kDiaDesde;
  self.kDiahasta := kDiaHasta;
  self.defVal_SinDato := defVal_SinDato;
  self.flg_FiltrarPorCalidad := flg_FiltrarPorCalidad;

  vh := TVentanaDiaria.CreateFromArchi(nidMedida, kDiaDesde);
  kMuestra := 0;
  vh_ant := nil;

  flg_Angulo := xflg_Angulo;
  flg_Cuadrado := xflg_Cuadrado;
end;

function TCursorScada.getval(dt: TDateTime; out ResCod: integer): NReal;
var
  dtDia: integer;
  v: NReal;
  xResCod: integer;
  buscando: boolean;
  flg_Actualizar_vh_ant: boolean;


  procedure search_vh_ant;
  begin
    if vh_ant <> nil then
    begin
      vh_ant.Free;
      vh_ant := nil;
    end;
    if dtDia > kDiaDesde then
    begin
      vh_ant := TVentanaDiaria.CreateFromArchi(nidMedida, dtDia - 1);
      if vh_ant.adtv = nil then
      begin
        buscando := True;
        Dec(dtDia);
        while buscando and (dtDia > kDiaDesde) do
        begin
          vh_ant := TVentanaDiaria.CreateFromArchi(nidMedida, dtDia - 1);
          if vh_ant.adtv <> nil then
            buscando := False
          else
            Dec(dtDia);
        end;
        if buscando then
        begin
          vh_ant.Free;
          vh_ant := nil;
        end;
      end;
    end;
  end;

begin
  dtDia := trunc(dt);

  if dtDia < kDiaDesde then
  begin
    resCod := -1;
    Result := defVal_SinDato;
    exit;
  end;
  if dtDia > kDiaHasta then
  begin
    resCod := 1;
    Result := defVal_SinDato;
    exit;
  end;

  flg_Actualizar_vh_ant := False;

  if vh = nil then
  begin
    // NO hay ventan activa , intenta con la ventana correspondiente al día
    // de dt.
    vh := TVentanaDiaria.CreateFromArchi(nidMedida, dtDia);
    kMuestra := 0;
    flg_Actualizar_vh_ant := True;
  end
  else if dtDia <> vh.dia then
  begin
    vh.Free;
    vh := TVentanaDiaria.CreateFromArchi(nidMedida, dtDia);
    kMuestra := 0;
    flg_Actualizar_vh_ant := True;
  end;

  if (vh.adtv = nil) then
  begin  // si no hay muestras tengo que retroceder
    buscando := True;
    Dec(dtDia);
    flg_Actualizar_vh_ant := True;
    while buscando and (dtDia >= kDiaDesde) do
    begin
      vh := TVentanaDiaria.CreateFromArchi(nidMedida, dtDia);
      kMuestra := 0;
      if vh.adtv <> nil then
        buscando := False
      else
        Dec(dtDia);
    end;
    if buscando then
    begin
      resCod := -3;
      Result := defVal_SinDato;
      exit;
    end;
  end;

  if flg_Actualizar_vh_ant then
    search_vh_ant;

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

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

  rescod := 0;
  if flg_Cuadrado then
    Result := v * v
  else
    Result := v;
end;



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

// Mientras que las muestras estén en rango retorna la siguiente.
// retorna NIL si ya recorrió todo el rango.
// Si es necesario avanza vh y vh_ant de forma de que ambas tenga muestras
function TCursorScada.Siguiente(dtFin: TDateTime): PRecMedida;
var
  kDia: integer;
  buscando: boolean;
begin
  if (vh = nil) then
  begin
    Result := nil;
    exit;
  end;
  if ((length(vh.adtv) = 0) or (kMuestra = high(vh.adtv))) then
  begin  // si la ventana actual no tiene muestras o estamos en la última intentamos avanzar
    buscando := True;
    while buscando and (vh.dia < dtFin) do
    begin
      kDia := vh.dia;
      if length(vh.adtv) > 0 then
      begin  // si la ventana actual tiene datos entonces pasará a ser la anterior.
        if vh_ant <> nil then
          vh_ant.Free;
        vh_ant := vh;
      end
      else
        vh.Free;
      // Cargamos nueva mentana actual.
      vh := TVentanaDiaria.CreateFromArchi(nidMedida, kDia + 1);
      if (length(vh.adtv) > 0) then
        buscando := False;
    end;

    if buscando then
    begin
      // llegamos al final sin encontrar una nueva ventana con datos.
      Result := nil;
      exit;
    end;
    kMuestra := 0;
    // si llegamos aquí tenemos nueva ventqan actual
  end
  else
    // a la ventana
    Inc(kMuestra);

  (*if vh.adtv[kMuestra].dt <= dtFin then
      Result := @vh.adtv[kMuestra]
    else

    Result := nil;
          DV&XC@201705191704*)
  Result := @vh.adtv[kMuestra];
end;



function TCursorScada.Muestreador_Iniciar(dtInicio, dtFin, dtDelta: TDateTime;
  out 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;
    Muestreador_Medida.v := defVal_SinDato;
    Muestreador_Medida.calidad := C_CALIDAD_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
    if ResCod = 0 then
      ResCod := 1 // no hay más muestras
    else
      ResCod := -2;
    Muestreador_Medida_sig.dt := -1;
    Muestreador_Medida_sig.calidad := C_CALIDAD_SINDATO;
  end
  else
    Muestreador_MEdida_sig := xprm^;
  Result := Muestreador_Medida.v;
end;

function TCursorScada.Muestreador_NextSimple( out dt: TDateTime;
  out 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.
    if Muestreador_Medida.calidad >= 0 then
      Result := Muestreador_Medida.v
    else
      Result := C_VALOR_FALLO_CALIDAD;
    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^;
    if Muestreador_Medida.calidad >= 0 then
      Result := Muestreador_Medida.v
    else
      Result := C_VALOR_FALLO_CALIDAD;
    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;
  flg_ConCalidad: boolean;

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;

  flg_ConCalidad := True;

  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, flg_ConCalidad)
    else
    begin
      if Muestreador_Medida.calidad >= 0 then
      begin
        if flg_Cuadrado then
          Result := sqr(Muestreador_Medida.v)
        else
          Result := Muestreador_Medida.v;
      end
      else
        Result := C_VALOR_FALLO_CALIDAD;
    end;
    resCod := 0;
    exit;
  end;


  flg_ConCalidad := True;
  Acum := 0;
  dtAnt := dt - Muestreador_dtDelta;

  // avanzamos si podemos.
  xprm := @Muestreador_Medida_sig;
  while (xprm <> nil) and (xprm^.dt <= dt) do
    //mientras la fecha de la medida sea menor a la fecha que solicito
  begin
    if flg_ConCalidad then
    begin
      if Muestreador_Suavizar then
        valor := InterPolLinealEntreMuestras(Muestreador_Medida,
          Muestreador_Medida_sig, (xprm^.dt + dtAnt) / 2.0, resCod, flg_ConCalidad)
      else
      if Muestreador_Medida.calidad >= 0 then
        valor := Muestreador_Medida.v
      else
        flg_ConCalidad := False;

      if flg_ConCalidad then
      begin
        if flg_Cuadrado then
          valor := sqr(valor);
        acum := acum + valor * (xprm^.dt - dtAnt);
      end;
    end;
    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 flg_ConCalidad then
    begin
      if Muestreador_Suavizar then
        valor := InterPolLinealEntreMuestras(Muestreador_Medida,
          Muestreador_Medida_sig, (dt + dtAnt) / 2.0, resCod, flg_ConCalidad)
      else
      if Muestreador_Medida.calidad >= 0 then
        valor := Muestreador_Medida.v
      else
        flg_ConCalidad := False;
    end;

    if flg_ConCalidad then
    begin
      if flg_Cuadrado then
        valor := sqr(valor);
      acum := acum + valor * (dt - dtAnt);
      Result := acum / Muestreador_dtDelta;
    end
    else
      Result := C_VALOR_FALLO_CALIDAD;

    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;
  flg_ConCalidad: boolean;

begin
  flg_ConCalidad := True;
  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, flg_ConCalidad)
    else
    begin
      if Muestreador_Medida.calidad >= 0 then
        Result := Muestreador_Medida.v
      else
        Result := C_VALOR_FALLO_CALIDAD;
    end;
    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 flg_ConCalidad then
    begin
      if Muestreador_Suavizar then
        valor := InterPolLinealEntreMuestras_AngDeg(Muestreador_Medida,
          Muestreador_Medida_sig, (xprm^.dt + dtAnt) / 2.0, resCod, flg_ConCalidad)
      else
      if Muestreador_Medida.calidad >= 0 then
        valor := Muestreador_Medida.v
      else
        flg_ConCalidad := False;
    end;

    if flg_ConCalidad then
    begin
      acum_x := acum_x + CosDeg(valor) * (xprm^.dt - dtAnt);
      acum_y := acum_y + SinDeg(valor) * (xprm^.dt - dtAnt);
    end;

    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 flg_ConCalidad then
      if Muestreador_Suavizar then
        valor := InterPolLinealEntreMuestras_AngDeg(Muestreador_Medida,
          Muestreador_Medida_sig, (dt + dtAnt) / 2.0, resCod, flg_ConCalidad)
      else
      if Muestreador_Medida.calidad >= 0 then
        valor := Muestreador_Medida.v
      else
        flg_ConCalidad := False;

    if flg_ConCalidad then
    begin
      acum_x := acum_x + CosDeg(valor) * (dt - dtAnt);
      acum_y := acum_y + SinDeg(valor) * (dt - dtAnt);
      Result := FaseDeg(acum_x, acum_y);
    end
    else
      Result := C_VALOR_FALLO_CALIDAD;
    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;



// Retorna TRUE si logra información de la medida en todo el rango.
function BajarLoNecesario(db: TDBPQCon;
  medida, kDia_Desde, kDia_Hasta: integer): boolean;
var
  sql: string;
  ds: TSQLQuery;
  dia: integer;
  vh: TVentanaDiaria;
  download: boolean;
  archivo: string;
  fecha_ultima_actualizacion: TDateTime;
  //  fecha_ultimo_dump: TDateTime;
  minutos: double;

begin
  Result := True;

  if not DirectoryExists(utsdb_diario.carpeta_dump_scada) then
    mkdir(utsdb_diario.carpeta_dump_scada);

  kDia_Desde := kDia_Desde - 1;

  sql := 'SELECT dia, dt_ultima_actualizacion, dt_ultimo_dump FROM tsdb_diarios WHERE medida = '
    + IntToStr(medida) + ' AND dia BETWEEN ' + IntToStr(kDia_Desde) +
    ' AND ' + IntToStr(kDia_Hasta) + ' ORDER BY dia ';
  ds := DB.query(sql);

  while not ds.EOF do
  begin
    download := False;
    dia := ds.Fields[0].AsInteger;
    archivo := utsdb_diario.ArchiDestino(medida, dia);
    fecha_ultima_actualizacion := ds.Fields[1].AsDateTime;
    //  fecha_ultimo_dump := ds.Fields[2].AsDateTime;

    if not fileexists(archivo) then
      download := True
    else
    begin
      vh := TVentanaDiaria.CreateFromArchi(medida, dia);
      minutos := (fecha_ultima_actualizacion - vh.dt_Ultima_Actualizacion) * 24 * 60;
      download := minutos > 1;
      vh.Free;
    end;

    if download then
    begin
      if not utsdb_diario.GetRemote_VentanaDiaria('aaa', medida, dia) then
        raise Exception.Create('No fue posible bajar el archivo Medida: ' +
          IntToStr(medida) + ' kDia: ' + IntToStr(dia));
      Result := False;
    end;
    ds.Next();
  end;
end;




initialization

{$IFDEF LINUX}
  carpeta_basura := CrearSiNoExiste(GetUserDir + 'basura');
 {$IFDEF PRONOS}//Pronos iba a buscar el scada a una carpeta equivocada, porque bajarlonecesario corre el upronos.
  if GetUserDir = '/tmp/' then
  begin
    carpeta_dump_scada := CrearSiNoExiste('/home/upronos/' + 'datos_scada');
  end
  else
   {$ENDIF}
    carpeta_dump_scada := CrearSiNoExiste(GetUserDir + 'datos_scada');

  carpeta_log_scada := CrearSiNoExiste(GetUserDir + 'log');
  carpeta_series10m := CrearSiNoExiste(GetUserDir + 'series10m');

{$ELSE}
  CrearSiNoExiste('c:\simsee');
  carpeta_basura := CrearSiNoExiste('c:\simsee\basura');
  CrearSiNoExiste('c:\simsee\adme_data');
  carpeta_log_scada := CrearSiNoExiste('c:\simsee\adme_data\log');
  carpeta_dump_scada := CrearSiNoExiste('c:\simsee\adme_data\datos_scada');
  carpeta_series10m := CrearSiNoExiste('c:\simsee\series10m');
{$ENDIF}

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

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

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

*)
