#!/usr/bin/env python3
# transporteaereo.py

"""
Descarga datos del transporte aereo: pasajeros, aeronaves y mercancías

Con regresiones de las tres series

Exporta ficheros excel y carga base de datos
    
"""

import logging
import sys
import pathlib
import time
import filecmp
import shutil                                   
from datetime import datetime
import pandas as pd
import numpy as np
import statsmodels.api as sm
import sqlite3



#++++++ RASTREO - LOGGING - DEGUG - DEPURACION +++++++++++++

logging.basicConfig(
    format="%(asctime)s %(levelname)s:%(name)s: %(message)s",
    level=logging.INFO,  # DEBUG
    datefmt="%H:%M:%S",
    stream=sys.stderr,
)
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.disabled = False


#++++++++++++++ DOWNLOAD URL ++++++++++++++++


def download_url_excel (lg): # Descarga ficheros de la web
    
    ''' 
    Descarga los datos a excel de aeronaves (salidas y llegadas), 
    pasajeros y mercancías (salidas y llegadas) de la web: 
    https://www.fomento.gob.es/BE/?nivel=2&orden=03000000
    '''
    
    logger = logging.getLogger('__' + sys._getframe().f_code.co_name)
    logger.disabled = lg
    
    urls = ['https://www.fomento.gob.es/BE/sedal/03010100.XLS', # Aeronaves
            'https://www.fomento.gob.es/BE/sedal/03010200.XLS', # Pasajeros
            'https://www.fomento.gob.es/BE/sedal/03010300.XLS' # Mercancías
            ]
    categorias =['Aeronaves', 'Pasajeros', 'Mercancias']
    salida = True
    try:
        logger.info('Initium ->')
        
        for url in urls:
            # Si existe el fichero hace una copia fichero_old
            fichero = 'd_' + categorias[urls.index(url)] + '.xlsx'
            fichero_old = 'd_' + categorias[urls.index(url)] + '_old.xlsx'
            path = pathlib.Path(fichero)
            existe = path.is_file()
            if existe:
                logger.info('{} EXISTE'.format(fichero))
                shutil.copy(fichero, fichero_old)
            # Baja a df y exporta a excell
            df = pd.read_excel(url, header=0)
            fichero = 'd_' + categorias[urls.index(url)] + '.xlsx'
            df.to_excel(fichero, index = False)
            logger.info(fichero + '<-'+ url)
            # Comprueba si son iguales   
            # False: Deep comparison -content-, True:metadatos 
            iguales = filecmp.cmp(fichero, fichero_old, shallow = True) 
            logger.info('¿Iguales?: {}'.format(iguales))
            if not iguales: salida = False
            print(salida)
            
        logger.info('<- Finita est')
        
    except:  # catch *all* exceptions
        logger.exception('Error: [{}]'.format(sys.exc_info()))
    return salida



#+++++++++++++++++++ LOAD - CARGA  BD +++++++++++++++++++++++++++++++++++++

def carga_df_bd (df, bd, tabla, lg= False):
    ''' Carga el dataframe en tabla de la bd.sqlite3 '''
    
    logger = logging.getLogger('__' + sys._getframe().f_code.co_name)
    logger.disabled = lg
    
    try:

        logger.info(tabla)
        con = sqlite3.connect(bd) # Conexión
        #cur = con.cursor() # Cursor
        df.to_sql(tabla, con, if_exists = "replace") # df to bd
        con.close() # Cierre de conexion

    except:  # catch *all* exceptions
        logger.exception('Error: [{}]'.format(sys.exc_info()))


#++++++++++++++ TRANSFORMERS - DATA COLLECTION ++++++++++++++++


def transform_file_df (f, lg): # Transforma fichero a serie temporal
    
    ''' 
    Transforma datos del fichero excel original
    '''
    
    logger = logging.getLogger('__' + sys._getframe().f_code.co_name)
    logger.disabled = lg

    try:
        logger.info('Initium ->')

        df = pd.read_excel(f, header=10)
        column_names = ['PERIODO', 'TOTAL', 'NACIONALTOTAL', 'NACIONALREGULAR', 'NACIONALNOREGULAR', 'INTERNACIONALTOTAL', 'INTERNACIONALREGULAR', 'INTERNACIONALNOREGULAR']
        df = df.set_axis(column_names, axis = 1) # Cambia el nombre de las columnas
        logger.info('File: ' + f)
        
        meses = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic']
        anos = []
        for i in range(1990, 2050,1): anos.append(str(i))
        df['ANO'] = df['PERIODO'].astype(str).str[:4]
        df['MES'] = df['PERIODO'].astype(str).str[-3:]
        for row in range(0,round(len(df)/1),1): 
            if not(df.loc[row,'MES'] in meses):
                df.at[row, 'MES'] = ''
            else:
                df.at[row, 'MES'] = '{:02}'.format(meses.index(df.loc[row,'MES'])+1)
            if not(df.loc[row,'ANO'] in anos):
                df.at[row, 'ANO'] = ''
            # Borra filas vacias y de comentarios
            if df.loc[row,'MES']=='' and df.loc[row,'ANO']=='':
                df = df.drop(row)
        df=df.reset_index(drop=True) # Reset index

        for row in range(0,round(len(df)/1),1):      
            if df.loc[row,'ANO']=='' and row > 0:
                df.at[row, 'ANO'] = df.loc[row-1,'ANO']
                
        df['FECHA'] = df['ANO'] + df['MES'] + '01'  
        
        # Datos anuales
        df_anual= df.loc[df['MES'] == ''].copy()
        df_anual.drop(['ANO', 'MES','FECHA'], inplace=True, axis=1)# Elimino la columna
        df_anual = df_anual.reset_index(drop=True) # Reset index
        df_anual.rename(columns={'PERIODO':'ANYO'},inplace=True)
        # Elimina indeterminaciones
        df_anual.replace([np.inf, -np.inf, '',np.nan], 0, inplace = True)
        

        # Datos mensuales
        df = df.loc[df['MES'] != ''].copy()
        # Elimina indeterminaciones
        df.replace([np.inf, -np.inf, '',np.nan], 0, inplace = True)
        df.drop('PERIODO', inplace=True, axis=1)# Elimino la columna
        df = df.reset_index(drop=True) # Reset index
        df['FECHA'] = pd.to_datetime(df['FECHA']).dt.normalize()
        df['PERIODO'] = df['FECHA'].apply(lambda x:datetime.strftime(x,'%YM%m'))
        df.rename(columns={'ANO':'ANYO'},inplace=True)
        ''' Quita duplicados y rellena si falta alguna fecha'''
        # Convierte la columna Fecha(string) to datatime types
        datetime_series = df['FECHA']
        # Creamos el datetime index pasando del datetime series
        datetime_index = pd.DatetimeIndex(datetime_series.values)
        df2 = df.set_index(datetime_index)
        # Borramos la columna Fecha
        df2.drop('FECHA', axis = 1, inplace = True)
        df2.sort_index(inplace=True) # Hay que ordenar para rellenar
        df2 = df2[~df2.index.duplicated(keep='first')] # Elimina duplicados
        # Rellena con el primero de mes y valores fila anterior la fecha que falta
        df2 = df2.asfreq('MS', method = 'bfill') 
        df2 = df2.reset_index() # Quita el indice y lo pasa a columna
        df2 = df2.rename({'index':'FECHA'}, axis = 1) # Renombra la columna index
          
        salida = df2, df_anual
        logger.info('<- Finita est')
        return salida
        
    except:  # catch *all* exceptions
        logger.exception('Error: [{}]'.format(sys.exc_info()))



    
def transform_files_dfvalor (export = False, lg = False): # Testing - prueba 
     
    '''
    Transforma los ficheros excel de Aeronaves, Mercancías y Pasajeros
    a dataframes anual  y mensual, anual con categorías (solo una columna de valor)
    y mensual con categorias.
    
    Exporta los 4 a excel
    
     
    Parameters
    ----------
    lg : TYPE: Boolean, False - activa el logging

    Returns
    -------
    Exporta 4 ficheros (Anual y mesual) con varias columnas de valores y otro con
    solo una columan de valor y categorias. 
    df anual y mensual con solo una columna de valor y categorías.
    
    '''
    logger = logging.getLogger('__' + sys._getframe().f_code.co_name)
    logger.disabled = lg
    
    try:
        logger.info('Initium ->')
        # Bajamos los ficheros excel de la url
        iguales = False
        
        '''
        iguales = download_url_excel (lg)
        print('Download-iguales: {}'.format(iguales))
        '''
        
        tipos = ['Aeronaves', 'Pasajeros', 'Mercancias']
        carga = 1
        df0_predict = pd.DataFrame()
        df0_forecast = pd.DataFrame()
        df0A = pd.DataFrame() # Anual
        df0M = pd.DataFrame() # Mensual
        i = 0
        ntipos = len(tipos) # número de categorías
        
        # Carga los ficheros originales y los transforma en un df mesual y otro anual
        if not iguales:
            for tip in tipos:
                i = i +1
                logger.info('Tipos {}/{}: {}'.format(i, ntipos, tip) )   
                fichero = 'd_{}.xlsx'.format(tip)  
                dfm, dfa = transform_file_df(fichero, lg)
                
                # ANUAL
                dfa['TIPO'] = tipos[tipos.index(tip)]
                frames = [df0A, dfa]  # Para concatenar todas las categorias anualmente
                df1= pd.concat(frames, join="outer").reset_index(drop=True)
                df0A = df1
                
                # MENSUAL
                dfm['TIPO'] = tipos[tipos.index(tip)]
                dfm['PERIODO'] = dfm['FECHA'].apply(lambda x:datetime.strftime(x,'%YM%m'))
                frames = [df0M, dfm]  # Para concatenar todas las tipos anualmente
                df1= pd.concat(frames, join="outer").reset_index(drop=True)
                df0M = df1
                

            ''' ANUAL - De columnas a filas con categorías '''
            column_names = ['ANYO', 'TOTAL', 'NACIONAL TOTAL', 'NACIONAL REGULAR', 'NACIONAL NO REGULAR',
                            'INTERNACIONAL TOTAL', 'INTERNACIONAL REGULAR', 'INTERNACIONAL NO REGULAR', 'TIPO']
            df0A1 = df0A.set_axis(column_names, axis = 1) # Cambia el nombre de las columnas                           
            df0A_fijo = df0A1.iloc[:, [0,8]] # Parte fija para concatenar
            df0A_valor = pd.DataFrame() 
            columnas = df0A1.columns # Nombre de las columnas
            for col in range(1,8):
                columna = columnas[col]
                df_valor = df0A1.iloc[:,col] # Valor de una columna
                df_join = df0A_fijo.join(df_valor) # Union con la parte fina
                df_join['CLASE'] = '{}'.format(columnas[col]) # Añado columna clase
                df_join['CATEGORIA'] = df_join['TIPO'] + ' - ' + df_join['CLASE'] # Añado columna categoría
                df_join.rename(columns={columna:'VALOR'},inplace=True) # Renombro la columana que añado
                frames = [df0A_valor, df_join]  # Para concatenar todas las tipos anualmente
                df_cat= pd.concat(frames, join="outer").reset_index(drop=True) # Concateno al final
                df0A_valor = df_cat # Añado al df la categoria


            ''' MENSUAL - De columnas a filas con categorías '''
            column_names = ['FECHA', 'TOTAL', 'NACIONAL TOTAL', 'NACIONAL REGULAR', 'NACIONAL NO REGULAR',
                            'INTERNACIONAL TOTAL', 'INTERNACIONAL REGULAR', 'INTERNACIONAL NO REGULAR', 
                            'ANYO', 'MES', 'PERIODO', 'TIPO']
            df0M1 = df0M.set_axis(column_names, axis = 1) # Cambia el nombre de las columnas                           
            df0M_fijo = df0M1.iloc[:, [0,11,10,8,9]] # Parte fija para concatenar
            df0M_valor = pd.DataFrame()
            columnas = df0M1.columns # Nombre de las columnas
            for col in range(1,8):
                columna = columnas[col]
                df_valor = df0M1.iloc[:,col] # Valor de una columna
                df_join = df0M_fijo.join(df_valor) # Union con la parte fina
                df_join['CLASE'] = '{}'.format(columnas[col]) # Añado columna clase
                df_join['CATEGORIA'] = df_join['TIPO'] + ' - ' + df_join['CLASE'] # Añado columna categoría
                df_join.rename(columns={columna:'VALOR'},inplace=True) # Renombro la columana que añado
                frames = [df0M_valor, df_join]  # Para concatenar todas las tipos anualmente
                df_cat= pd.concat(frames, join="outer").reset_index(drop=True) # Concateno al final
                df0M_valor = df_cat # Añado al df la categoria
            
            # Ordenamos los df
            df0A = df0A.sort_values(by = ['TIPO', 'ANYO'], ascending = [True, True]).reset_index(drop=True)
            df0A['PERIODO'] = df0A['ANYO'].apply(lambda x: str(x) + 'M12') # Añado periodo anual
            df0M = df0M.sort_values(by = ['TIPO','FECHA'], ascending = [True, True]).reset_index(drop=True)
            df0A_valor = df0A_valor.sort_values(by = ['CATEGORIA', 'ANYO'], ascending = [True, True]).reset_index(drop=True)
            df0A_valor['PERIODO'] = df0A_valor['ANYO'].apply(lambda x: str(x) + 'M12') # Añado periodo anual
            df0M_valor = df0M_valor.sort_values(by = ['CATEGORIA','FECHA'], ascending = [True, True]).reset_index(drop=True)
            
            # Info
            logger.info('Return df_ANUAL \n-> Filas =  {}  y campos: \n{}'.format(len(df0A), df0A.dtypes))
            logger.info('Return df_MENSUAL \n-> Filas =  {} y campos: \n{}'.format(len(df0M), df0M.dtypes))
            logger.info('Return df_ANUAL_valor \n-> Filas =  {} y campos: \n{}'.format(len(df0A_valor), df0A_valor.dtypes))
            logger.info('Return df_MENSUAL_valor \n-> Filas =  {} y campos: \n{}'.format(len(df0M_valor), df0M_valor.dtypes))
            # Exportamos los df a excel
            
            if export:
                fichero = 'd_taereo_anual.xlsx'
                logger.info(fichero) # Anual
                df0A.to_excel(fichero, index = False)
                fichero = 'd_taereo_mensual.xlsx'
                logger.info(fichero) # Forecast
                df0M.to_excel(fichero, index = False)
                fichero = 'd_taereo_anual_valor.xlsx'
                logger.info(fichero) # Anual
                df0A_valor.to_excel(fichero, index = False)
                fichero = 'd_taereo_mensual_valor.xlsx'
                logger.info(fichero) # Forecast
                df0M_valor.to_excel(fichero, index = False)
               
            salida = df0A_valor, df0M_valor
            
            return salida
        
        logger.info('<- Finita est')
    except:  # catch *all* exceptions
        logging.exception('Error: [{}]'.format(sys.exc_info()))    




def transform_df_serie (df, lg): # df a serie temporal
    '''
    Convierte un df a serie temporal: index + VALOR
    
    Parameters
    ----------
    local_log : TYPE: Boolean, False - activa el logging

    Returns
    -------
    df serie de tiempo
    st serie de tiempo

    '''

    logger = logging.getLogger('__' + sys._getframe().f_code.co_name)
    logger.disabled = lg
    
    try:
        
        logger.info('Initium ->')

        column_names = ['FECHA', 'VALOR']
        df = df.set_axis(column_names, axis = 1) # Cambia el nombre de las columnas
        df['FECHA'] = pd.to_datetime(df['FECHA']).apply(lambda x:datetime.strftime(x,'%Y-%m-%d'))
        #df = df.set_index('FECHA')
        df.set_index('FECHA', inplace=True)  # Serie de tiempo
        df.index = pd.to_datetime(df.index) # Indice de tiempo
        df.sort_index(ascending=True, inplace=True) # Ordena por fechas ascendente
        st = df.squeeze()
        #st = pd.Series(df['VALOR'], index = df.index) # Es lo mismo que squeeze
        #df0.index = df.index.to_period('M')
 
        #print(df.loc['2020':'2021'])
        #print(st.loc['2020'])
        logger.info('Dataframe-df0:\nlen: {}, type: {}'.format(len(df), type(df)))
        logger.info('Dataframe-Campos: \n{}'.format(df.dtypes))
        logger.info('Serie-st:\nlen: {}, type: {}'.format(len(st), type(st)))
        logger.info('Serie-Campos: \n{}'.format(st.dtypes))
        
        salida = [df, st]
        logger.info('<- Finita est')
        return salida

    except:  # catch *all* exceptions
        logging.exception('Error: [{}]'.format(sys.exc_info()))  
        
          

    
#+++++++++++++++++++++ REGRESIONES ++++++++++++++++++++++++++++++++++++


def hp_filter (df, lg = False): # Filtro de Hodrick-Prescott
    '''
    Separa la tendencia del ciclo y añade recesiones
    
    Parameters
    ----------
    local_log : TYPE: Boolean, False - activa el logging

    Returns
    -------
    df con  tendencia y recesiones

    '''

    logger = logging.getLogger('__' + sys._getframe().f_code.co_name)
    logger.disabled = lg
    
    try:
        
        logger.info('Initium ->')
        
        # Aplicamos logaritmo neperiano a los datos
        logger.info('log(df)')
        df['VALOR'] = np.log(df['VALOR'])  
        df.replace([np.inf, -np.inf,'',np.nan], 0, inplace = True)  # Pongo a O todas las singularidades    
        
        # Filtro de Hodrick-Prescott
        logger.info('Filtro de Hodrick-Prescott')
        hp_cycle, hp_trend = sm.tsa.filters.hpfilter(df, lamb=1500)  
        ''' Con 1600 filtra bien SS y TA
         lamb sugerido 1600 para datos trimestrales, 6.5 (1600/4^4) anuales y 129600 (1600*3*3^4) mensuales
         Cuanto más bajo es lamb mayor rizado en la tendencia
        '''
        hp_trend = np.e**hp_trend # tendencia en totales
        hp_trend = hp_trend.round(0) # Redondea a cero decimales
        
        df_trend = pd.DataFrame(hp_trend) #
        df_trend_rc = recessions_add(df_trend, lg)
        #df_trend_rc['FECHA'] = pd.to_datetime(df_trend_rc['FECHA']).apply(lambda x:datetime.strftime(x,'%YM%m'))
        #df_trend_rc['FECHA'] = df_trend_rc.index
        #x_trend = df_trend_rc['PERIODO'].tolist()                                                                                             
        #y_trend = df_trend_rc['RA'].tolist()
        #y_trend_rc  =  df_trend_rc['RC'].tolist()
        
        salida = [df_trend_rc]
        #salida = [x_trend, y_trend, y_trend_rc]
        logger.info('<- Finita est')
        return salida

    except:  # catch *all* exceptions
        logging.exception('Error: [{}]'.format(sys.exc_info()))    


def date_recessions(local_log): # Recesiones USA
    '''
    
    Parameters
    ----------
    local_log : logging local disabled

    Returns
    -------
    dfyrc : dataframe, serie temporal, años de recesion
    dfmrc : dataframe, serie temporal, meses de recesion

    '''
    
    logger = logging.getLogger('&date_recessions')
    logger.disabled = local_log
    dict_recessions = { 1961 : 'Superávit - Sube: Tipos de Interés',
                       1970 : 'Guerra Vietnan - Sube: Deficit, Infraccón y Tipo de Interés',
                       1974 : 'Petróleo - se cuadriplica, Estanflacción',
                       1975 : 'Petróleo - se cuadriplica, Estanflacción',
                       1980 : 'W Doble Inmersión - Sube: Tipos de Interés',
                       1981 : 'Revolución Iraní - Sube: Petroleo',
                       1982 : 'Revolución Iraní - Sube: Petroleo',
                       1990 : 'Tras una larga expansión - Sube: Inflacción y Tipos de Interés, pesimismo consumidores',
                       1991 : 'Tras una larga expansión - Sube: Inflacción y Tipos de Interés, pesimismo consumidores',
                       2001 : 'Burbuja Puntocom y 11 S',
                       2007 : 'Burbuja Inmobiliaria, hipotecas subprime',
                       2008 : 'Burbuja Inmobiliaria, hipotecas subprime',
                       2020 : 'Coronavirus'
                       }
    
    try:
        logger.info('Años recesión USA')
        # YEAR recessions
        yrc = [1961, 1970, 1974, 1975, 1980, 1981, 1982, 1990, 1991, 2001, 2007, 2008, 2020]
        dfyrc = pd.DataFrame(yrc, columns=['PERIODO'])
        dfyrc['RC'] = 1
        dfyrc['PERIODO'] = pd.to_datetime(dfyrc['PERIODO'], format="%Y")
        dfyrc.set_index('PERIODO', inplace=True)  # Serie de tiempo
        # print(dfyrc)
        # MONTHS recessions
        mrc = []
        for y in yrc:
            for m in range(1, 13):
                e = [(str(y) + '-' + str(m))]
                mrc = mrc + e
        dfmrc = pd.DataFrame(mrc, columns=['PERIODO'])
        dfmrc['RC'] = 1
        dfmrc['PERIODO'] = pd.to_datetime(dfmrc['PERIODO'], format="%Y-%m")
        dfmrc.set_index('PERIODO', inplace=True)  # Serie de tiempo
        # print(dfmrc)
        return dfyrc, dfmrc, dict_recessions
    except ValueError as e:
        logging.exception('ValueError:', e)
    except:  # catch *all* exceptions
        e = sys.exc_info()
        logging.exception("Error: [{}]".format(e))


def recessions_add(df, lg): # Añade recesiones USA a un df
    '''
    Añade una columna con recesiones. 
    Ojo, el df tiene que venir con indice y este llamarse PERIODO.
    '''
    
    logger = logging.getLogger('__' + sys._getframe().f_code.co_name)
    logger.disabled = lg
    
    try: 
        logger.info('Initium ->')
            
        column_names = ['RA']
        df = df.set_axis(column_names, axis = 1) # Cambia el nombre de las columnas
        
        # Años y meses de recesión en USA
        yrc, mrc, dict_recessions = date_recessions(lg)
        '''
        # Para imprimir las recesiones
        df_recessions = pd.DataFrame(list(dict_recessions.items()),
                        columns=['YEAR', 'RECESSION'])
        df_recessions.to_excel('RecesionesUSA.xlsx', index = False)
        '''

        # Añadimos la columna recesion RC al fichero index fecha, y solo los meses con recesion
        #df_rc = pd.merge(df, mrc, left_index = True, right_index = True) # Intersección
        
        df_rc = df.join(mrc) # Union
        
        # Relleno columna RC para que la pinte bien
        df_rc.fillna(0, inplace=True)  # Rellena los blancos con ceros
        # Rellena con el máximo valor y el mínimo para pintar bien las recesiones
        df_rc['RC'] = df_rc['RC'].map({1: df_rc['RA'].max(),
                                        0: df_rc['RA'].min()},
                                        na_action=None)        
        column_names = ['TENDENCIA', 'RECESION']
        df_rc = df_rc.set_axis(column_names, axis = 1) # Cambia el nombre de las columnas
        df_rc.sort_index(ascending=True, inplace=True) # Ordena por fechas ascendente
        
        logger.info('<- Finita est')
        return df_rc
    
    except:  # catch *all* exceptions
        logging.exception('Error: [{}]'.format(sys.exc_info()))    


def regression(df, lg = False): # Regresion UCARIMA
    '''
    Regresion por HPfilter, uc, uc-arima'

    Parameters
    ----------
    df : dataframe, serie tiempo

    Returns
    -------
    forecast = pronostico de 12 meses
    R2 : grado de ajuste
    hp_trend = tendencia de la serie

    '''
    
    logger = logging.getLogger('__' + sys._getframe().f_code.co_name)
    logger.disabled = lg
    
    try:
        logger.info('Initium ->')
                
        #Cambiamos los datos de 2020 ponderando con las medias anuales
        ratio_ponderacion = float(df.loc['2020'].mean()/df.loc['2019'].mean())
        df.loc['2020'] = df.loc['2019'].values * ratio_ponderacion
        
        # Aplicamos logaritmo neperiano a los datos
        logger.info('log(df)')
        df['VALOR'] = np.log(df['VALOR']) 
        # Pongo a O todas las singularidades e indeterminaciones 
        df.replace([np.inf, -np.inf,'',np.nan], 0, inplace = True)
             
        # Regresion con UCARIMA con 12 retardos
        logger.info('UCARIMA-12')
        mod_ucarima = sm.tsa.UnobservedComponents(df, 'rwalk', autoregressive=12) 
        ''' Con 4 da mejor R2 que con 4
         Here the powell method is used, since it achieves a
         higher loglikelihood than the default L-BFGS method
        '''
        
        res_ucarima = mod_ucarima.fit(method='powell', disp=False)
        #print(res_ucarima.summary())
        
        # Calculo de R2        
        logger.info('UCARIMA - R2')
        y = res_ucarima.fittedvalues[12:]+res_ucarima.resid[12:]
        R2_UCARIMA = 1 - np.sum(res_ucarima.resid[12:]**2)/np.sum((y-y.mean())**2)
        R2 = round(R2_UCARIMA * 100, 2)
        logger.info('R2 % ... UCARIMA...{}'.format(R2))
        
        # Prediction
        logger.info('PREDICT')
        predict_ucarima = res_ucarima.predict(steps=12)
        df_predict = pd.DataFrame(predict_ucarima)
           
        df_predict.rename(index = {0:'FECHA'}, inplace = True) # Indice fecha
        df_predict = np.e**(np.e**df_predict) # Predict  UCARIMA en totales 
        df_predict.rename(columns = {'predicted_mean':'PREDICT'}, inplace = True)
        df_predict = df_predict.round(0)
        r2 = [R2]*len(df_predict) # array con el valor de R2 n veces
        df_predict['R2'] = r2 # Añado el array como columna al df

               
        # Perform forecasting 12 meses ************
        logger.info('FORECAST')
        fcast_ucarima = res_ucarima.get_forecast(steps=12, alpha=0.05).summary_frame()
        df_forecast = pd.DataFrame(fcast_ucarima)
        df_forecast = np.e**(np.e**df_forecast) # Forecast  UCARIMA en totales
        df_forecast = df_forecast.round(0) # Redondea a cero decimales
        #df_forecast = df_forecast.drop(['mean_se'], axis = 1, inplace = True) # Quito columna
        
        # Saco periodo
        df_forecast['FECHA'] = df_forecast.index
        df_forecast['PERIODO'] = df_forecast['FECHA'].apply(lambda x:datetime.strftime(x,'%YM%m'))
        
        #x = df_forecast['PERIODO'].tolist()[-12:] # Valores de x
        yt = df_forecast['mean'].tolist() # Valores de  y valor en el tiempo t
        '''
        yti = df_forecast['mean_ci_lower'].tolist() # Valores de  y error inferior
        yts = df_forecast['mean_ci_upper'].tolist() # Valores de  y error superior
        '''
        
        # Estadistica del forecast
        logger.info('FORECAST-Estadística vs t-12')
        df = np.e**(np.e**df) # df original en valores absolutos
        df = df.round(0)
        yt_12 = df['VALOR'][-12:].tolist() # los útlimos 12 valores
        y_var12 = [] # Variación en % de 12 meses anteriores del forecast 
        for i in range(len(df_forecast)):
            var = round((yt[i]- yt_12[i])*100/yt_12[i], 2)
            y_var12.append(var)
        df_forecast['Vt_12'] = yt_12
        df_forecast['V_var12'] = y_var12
        
        df_fc = df_forecast[['FECHA', 'PERIODO', 'mean', 'mean_ci_lower', 'mean_ci_upper', 'Vt_12', 'V_var12']]
        column_names = ['FECHA','PERIODO','FCMEAN', 'FCMEAN_CI_LOWER', 'FCMEAN_CI_UPPER','VT_12', 'V_VAR12']
        df_fc = df_fc.set_axis(column_names, axis = 1) # Cambia el nombre de las columnas
        df_forecastsalida = df_fc[['FECHA','PERIODO','FCMEAN', 'FCMEAN_CI_LOWER', 'FCMEAN_CI_UPPER','VT_12', 'V_VAR12']]

        
        salida = [df_predict, df_forecastsalida]
        #salida = [x, yt, yt_12, y_var12, yti, yts, R2]
        logger.info('<- Finita est')
        return salida
        
    except ValueError as e:
        logging.exception('ValueError:', e)
    except:  # catch *all* exceptions
        e = sys.exc_info()
        logging.exception("Error: [{}]".format(e))


def regression_add (df, export = False, lg = False): # Añade estadisticos de regresion 
     
    '''
    Calcula las columnas predición, tendencia, recesion, forecast,
    vt-12 (desplazda 12 meses) y delta(vt - vt-12) para todas las categorias

    Parameters
    ----------
    df : Entrada de un df con categorías, fechas y valores.
    export : Si queremos que exporte ficheros excel (predicción y forecast)
    lg : TYPE: Boolean, False - activa el logging

    Returns
    -------
    df_predict 
    df_forecast
    
    '''
    logger = logging.getLogger('__' + sys._getframe().f_code.co_name)
    logger.disabled = lg
    
    try:
        logger.info('Initium ->')
        # Categorias únicas para procesar
        categorias = pd.unique(df['CATEGORIA']).tolist()
        #categorias = ['Pasajeros - TOTAL'] # Para pruebas
        ncategorias = len(categorias)
    
        df0_predict= pd.DataFrame()
        df0_forecast= pd.DataFrame()
        i = 0
        for cat in categorias:
            i= i + 1
            try:
                logger.info('Categoría {}/{}: {} ******'.format(i, ncategorias, cat) )
                df0 = df.loc[df['CATEGORIA'] == cat] # Filtro por categoria
                df0 = df0.reset_index(drop=True) # Reseteo el indice
                # Añado columna desplazando 12 periodos Vt-12
                df0['VT_12'] = df0['VALOR'].shift(12)
                # Añado columna con la Vt vs Vt-12 en %
                df0['V_VAR12'] = round((df0['VALOR']-df0['VT_12'])*100/df0['VT_12'],2)
                # Quita blancos, infinitos e indeterminaciones pasandolos a valor 0.
                df0.replace([np.inf, -np.inf, '',np.nan], 0, inplace = True)
                
                df01 = df0[['FECHA', 'VALOR']]
                dfst = transform_df_serie (df01, lg = True) # df a serie temporal
                dft = dfst[0].asfreq('MS', method = 'bfill') # Si falta algún periodo lo rellena
                trend_rc = hp_filter (dft, lg= True) # Tendencia y recesiones
                regre = regression(dft, lg = True) # Regresion
                
                # Predicción
                df_prerc = regre[0].join(trend_rc[0]) # Union prediccion-tendencia
                df_prerc = df_prerc.reset_index(drop=True) # Reseteo el indice
                df_pre = df0.join(df_prerc) # Union + trc:tendencia y recesion
                frames = [df0_predict, df_pre]  # Para concatenar todas las categorias
                df_cat = pd.concat(frames, join="outer").reset_index(drop=True) # Añade sin indice
                df0_predict = df_cat
                
                # Forecast
                df_forecast = regre[1].assign(CATEGORIA = cat) # Añade columna CATEGORIA
                frames = [df0_forecast, df_forecast]  # Para concatenar todas las categorias
                df_fcat = pd.concat(frames, join="outer").reset_index(drop=True) # Añade sin indice
                df0_forecast = df_fcat
                
            except:
                logging.exception('Error: [{}]'.format(sys.exc_info())) 
         
         
        # Ordenamos los df
        df0_predict = df0_predict.sort_values(by = ['CATEGORIA', 'FECHA'], ascending = [True, True]).reset_index(drop=True)
        df0_forecast = df0_forecast.sort_values(by = ['CATEGORIA','FECHA'], ascending = [True, True]).reset_index(drop=True)
        
        # Info
        logger.info('Return df_PREDICT \n {} filas  y campos: \n{}'.format(len(df0_predict), df0_predict.dtypes))
        logger.info('Return df_FORECAST \n {} filas  y campos: \n{}'.format(len(df0_forecast), df0_forecast.dtypes))
          
        # Exportamos los df a excel
            
        if export:
            fichero = 'd_taereo_m_predict.xlsx'
            logger.info(fichero) # Anual
            df0_predict.to_excel(fichero, index = False)
            fichero = 'd_taereo_m_forecast.xlsx'
            logger.info(fichero) # Forecast
            df0_forecast.to_excel(fichero, index = False)
            
               
        salida = df0_predict, df0_forecast
        logger.info('<- Finita est')
        return salida

    except:  # catch *all* exceptions
        logger.info('Categoría {}/{}: {} *** ERROR ***'.format(i, len(categorias), cat) ) 
        logging.exception('Error: [{}]'.format(sys.exc_info()))   
        
 

 
#++++++++++++++ FUNCIÓN TEST - PRUEBAS +++++++++++++++++++++++++++++++++++++
    
def test (lg = False): # Testing - prueba 
     
    '''
         
    Parameters
    ----------
    lg : TYPE: Boolean, False - activa el logging

    Returns
    -------
    None
    
    '''
    logger = logging.getLogger('__' + sys._getframe().f_code.co_name)
    logger.disabled = lg
    
    try:
        logger.info('Initium ->')
        export = False
        dfa, dfm = transform_files_dfvalor (export = export, lg = lg)
        dfm_predict, dfm_forecast = regression_add (dfm, export = export, lg = lg)
        print(dfm_predict)
        print(dfm_forecast)
        
        
        salida = '*** TEST, missa est ***'
        logger.info('<- Finita est')
        return salida
        
    except:  # catch *all* exceptions
        logging.exception('Error: [{}]'.format(sys.exc_info()))    



#++++++++++++++ FUNCIÓN PRINCIPAL +++++++++++++++++++++++++++++++++++++

def main (lg = False): # Main 
     
    '''
    Parameters
    ----------
    lg : TYPE: Boolean, False - activa el logging

    Returns
    -------
    None.

    '''
    logger = logging.getLogger('__' + sys._getframe().f_code.co_name)
    
    # Variables
    logger.disabled = lg # False activa el logging - rastreo
    export = True # True exporta ficheros excel
    #bd = '/var/www/html/lartica/db.sqlite3' # Path base de datos entorno producción
    bd = '/home/ja/Documentos/test/lartica/db.sqlite3' # Path base de datos entorno pruebas
       
    
    try:
        logger.info('Initium ->')
        
        # Bajamos los ficheros excel de la url
        iguales = download_url_excel (lg)
   
        # Carga los ficheros originales y los transforma en un df mesual y otro anual
        if not iguales: # Solo transformamos si no son iguales
           
            # Dataframes con solo una columna valor
            dfa, dfm = transform_files_dfvalor (export = export, lg = lg)
           
            # Dataframes con predicción y forecast - datos mensuales 
            dfm_predict, dfm_forecast = regression_add (dfm, export = export, lg = lg)
     
            # Carga dataframes en base de datos
            carga_df_bd (dfa, bd = bd, tabla = 'TaereoAnual', lg=lg)
            carga_df_bd (dfm_predict, bd = bd, tabla = 'TaereoPredict', lg=lg )
            carga_df_bd (dfm_forecast, bd = bd, tabla = 'TaereoForecast', lg=lg )
            
        salida = '*** MAIN, missa est ***'
        logger.info('<- Finita est')
        return salida

    except:  # catch *all* exceptions
        logging.exception('Error: [{}]'.format(sys.exc_info()))    

if __name__ == "__main__": # Main
    start = time.perf_counter()
    assert sys.version_info >= (3, 7),"Script requires Python 3.7+."
    here = pathlib.Path(__file__).parent
    logger.disabled = False
    logger.info(here)
    logger.info('Initium novum')
    # ********** INICIO **********
    
    # local log disabled
    lgd = False # False activa el logging - rastreo
    #salida = test(lgd) # Testing
    salida = main(lgd) # Producción
    logger.info(salida)
    
    
    # ********** FIN **********
    logger.info('Ave verum')
    elapsed = time.perf_counter() - start
    logger.info(f"Program completed in {elapsed:0.5f} seconds.")

