first commit

This commit is contained in:
Eidan T.
2026-05-09 14:00:04 -04:00
commit e0ec11977c
42 changed files with 4596 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
import pytz
from datetime import datetime, timedelta, time
class ConfigDatabase:
db_host= 'sql.glmanalytics.com'
db_user = 'vmglm001'
db_password = "A16n)J@^fj<]G('{"
class ConfigFTP:
ftp_server = '000oqta.rcomhost.com'
ftp_user = "ftp3592287"
ftp_password = 'mpFFMRW4W9w@SuK'
ftp_port = 21
ftp_path = "/www/TranferData_APP_PYTHON/"
class ConfigAPI:
pass
class GeneralConfig:
#DataControl
numDays = 3
zoneTime = pytz.timezone("America/El_Salvador")
zoneTimeCP = pytz.timezone("America/Santo_Domingo")
cutTime = time(17, 30, 00)
current_time = datetime.now(zoneTime)
current_hour = current_time.hour
nowTime = current_time.strftime("%Y-%m-%d")
nowTimeFormat = current_time.strftime("%Y%m%d")
execution_time = datetime.now(zoneTime)
#time = datetime.now(zoneTime)
#Para emails
fromEmail = 'notifications.noreply@gomezleemarketing.com'
smtp_server="smtp.gmail.com"
smtp_port=587
passwordEmail = 'GLM123456new1'
class ConfigNotifications:
webhookURL_wps_notifcation = "https://automation.glmanalytics.com/webhook/wsp-notifcation"
webhookURL = "https://automation.glmanalytics.com/webhook/205ae0d5-fc9c-44da-951c-84c033236579" # Ajusta con tu URL real
+9
View File
@@ -0,0 +1,9 @@
#from .GeneralConfig import *
#from .corripioConfig import *
#from .corripioConfig2 import *
#from .dipoConfig import *
#from .pgConfig import *
#from .feduroConfig import *
#from .cimbertonConfig import *
#from .echamorroConfig import *
#from .ftp_service import *
+42
View File
@@ -0,0 +1,42 @@
import os
class FeduroConfig:
userSharepoint = 'feddatadeployment.im@pg.com' #os.getenv('sharepointPG_user')
passwordSharepoint = 'Feduro_PG_2025$' #= os.getenv('sharepointPG_password')
siteSharepoint = 'https://pgone.sharepoint.com/sites/feduroinfinitystores' #os.getenv('sharepointPG_site')
database = 'IS001'
databaseID = '00010007'
areaID = '0007'
project_name = 'FEDURO'
classID = '0003'
reportsID = {'Cobertura':'0001',
'NegociadasIS001':'0002',
'Promociones':'0003',
#'Precios':'0004',
'Fmot':'0005',
'QualityDisplay':'0006',
'Checkout':'0007',
'QualityShelf':'0008',
'SOSFeduro':'0009',
#'OsaTorre':'0010',
#'OsaPsmt':'0011',
#'OsaFarmacia':'0012',
'OsaFotografico':'0013',
#'DataAvailability':'0014',
'OsaAnalogo':'0015',
'ModuloTareas':'0016',
#'SOSAPDO_IS001':'0017',
#'SOSBabyCare_IS001':'0018',
#'SOSFabricCare_IS001':'0019',
#'SOSFamilyCare_IS001':'0020',
#'SOSFemCare_IS001':'0012',
#'SOSHairCare_IS001':'0022',
#'SOSHomeCare_IS001':'0023',
#'SOSOralCare_IS001':'0024',
#'SOSPHC_IS001':'0025'
}
+115
View File
@@ -0,0 +1,115 @@
import os
from App.Config.GeneralConfig import GeneralConfig
from App.Config.GeneralConfig import ConfigFTP
from App.Utils.db_utils import DatabaseManager
from App.Utils.ftp_utils import FTPManager
from App.Utils.sftp_utils import SFTPManager
class FeduroConfig:
def __init__(self):
#Inicializando la base de datos
self.databaseCode = 'IS001'
self.databaseID = '00010007'
self.database = DatabaseManager(self.databaseCode, self.databaseID)
#Inicialzando el FTP
self.ftpdatabase = FTPManager(ConfigFTP.ftp_server, ConfigFTP.ftp_user, ConfigFTP.ftp_password, ConfigFTP.ftp_port)
#Inicializando el SFTP
self.sftp_host = 'c6a3nxpth6mf4.eastus2.azurecontainer.io'
self.sftp_port = 22
self.sftp_username = 'eLeader01'
self.sftp_password = 'c8gNBhJE@zt$ZkT&'
self.sftpdatabase = SFTPManager(self.sftp_host, self.sftp_port, self.sftp_username, self.sftp_password)
#Inicializando SharePoint
self.userSharepoint = 'feddatadeployment.im@pg.com' #os.getenv('sharepointPG_user')
self.passwordSharepoint = 'Feduro_PG_2025$' #= os.getenv('sharepointPG_password')
self.siteSharepoint = 'https://pgone.sharepoint.com/sites/feduroinfinitystores' #os.getenv('sharepointPG_site')
#General configuration feduro
self.areaID = '0007'
self.project_name = 'FEDURO'
self.classID = '0003'
self.reports = self.database.get_execute_query(f"select * from TransferData where active = 1")
self.reports_sftp = {'Cobertura':'/upload/FEDURO/DataCollection/Cobertura/CoberturaCheckIn-Out/',
'NegociadasIS001':'/upload/FEDURO/DataCollection/Negociadas/AC Ejecutado/',
'Promociones':'/upload/FEDURO/DataCollection/Promociones/PROMO EJECUTADA/',
'Precios':'',
'Fmot':'/upload/FEDURO/DataCollection/Fmot/fmot Data/',
'QualityDisplay':'/upload/FEDURO/DataCollection/Quality Display/',
'Checkout':'/upload/FEDURO/DataCollection/CheckOut/Data/',
'QualityShelf':'/upload/FEDURO/DataCollection/QualityShelf/QualityShelf_Data/',
'SOSFeduro':'/upload/FEDURO/DataCollection/SOS/Share of Share Ejecutado/',
'OsaTorre':'',
'OsaPsmt':'',
'OsaFarmacia':'',
'OsaFotografico':'/upload/FEDURO/DataCollection/OSA/OSA_Fotos_Categoria/',
'DataAvailability':'',
'OsaAnalogo': '/upload/FEDURO/DataCollection/OSA/Data/',
'ModuloTareas': '/upload/FEDURO/DataCollection/ModuloTareas/Data/',
'SOSAPDO_IS001':'/upload/FEDURO/DataCollection/SOS/Share of Share Ejecutado/',
'SOSBabyCare_IS001':'/upload/FEDURO/DataCollection/SOS/Share of Share Ejecutado/',
'SOSFabricCare_IS001':'/upload/FEDURO/DataCollection/SOS/Share of Share Ejecutado/',
'SOSFamilyCare_IS001':'/upload/FEDURO/DataCollection/SOS/Share of Share Ejecutado/',
'SOSFemCare_IS001':'/upload/FEDURO/DataCollection/SOS/Share of Share Ejecutado/',
'SOSHairCare_IS001':'/upload/FEDURO/DataCollection/SOS/Share of Share Ejecutado/',
'SOSHomeCare_IS001':'/upload/FEDURO/DataCollection/SOS/Share of Share Ejecutado/',
'SOSOralCare_IS001':'/upload/FEDURO/DataCollection/SOS/Share of Share Ejecutado/',
'SOSPHC_IS001':'/upload/FEDURO/DataCollection/SOS/Share of Share Ejecutado/'
}
self.reportsID = {'Cobertura':'0001',
'NegociadasIS001':'0002',
'Promociones':'0003',
#'Precios':'0004',
'Fmot':'0005',
'QualityDisplay':'0006',
'Checkout':'0007',
'QualityShelf':'0008',
'SOSFeduro':'0009',
#'OsaTorre':'0010',
#'OsaPsmt':'0011',
#'OsaFarmacia':'0012',
'OsaFotografico':'0013',
#'DataAvailability':'0014',
'OsaAnalogo':'0015',
'ModuloTareas':'0016',
#'SOSAPDO_IS001':'0017',
#'SOSBabyCare_IS001':'0018',
#'SOSFabricCare_IS001':'0019',
#'SOSFamilyCare_IS001':'0020',
#'SOSFemCare_IS001':'0012',
#'SOSHairCare_IS001':'0022',
#'SOSHomeCare_IS001':'0023',
#'SOSOralCare_IS001':'0024',
#'SOSPHC_IS001':'0025'
}
self.steps = {
"sftp": True,
"ftp": True,
"sharepoint_python": True,
"sharepoint_N8N": False,
}
+79
View File
@@ -0,0 +1,79 @@
import os
from App.Config.GeneralConfig import GeneralConfig
from App.Config.GeneralConfig import ConfigFTP
from App.Utils.db_utils import DatabaseManager
from App.Utils.ftp_utils import FTPManager
from App.Utils.sftp_utils import SFTPManager
from App.Utils.sharepoint_utils import SharepointManager
class PGConfig:
def __init__(self):
#Inicializando la base de datos
self.databaseCode = 'glm_pg'
self.databaseID = '00080001'
self.database = DatabaseManager(self.databaseCode, self.databaseID)
#Inicializando SharePoint
self.userSharepoint = 'gldatadeployment.im@pg.com'
self.passwordSharepoint = 'Glm-infinity-2025'
self.siteSharepoint = 'https://pgone.sharepoint.com/sites/eLeaderDataSharing'
data = self.database.get_execute_query("select reportID, parameter from TransferDataParameter where category = 'Sharepoint'")
self.sharepointPath = {
row['reportID']: row['parameter']
for row in data
}
self.sharepoint = SharepointManager(self.siteSharepoint, self.userSharepoint, self.passwordSharepoint)
#Inicialzando el FTP
self.ftpdatabase = FTPManager(ConfigFTP.ftp_server, ConfigFTP.ftp_user, ConfigFTP.ftp_password, ConfigFTP.ftp_port)
#Inicializando el SFTP
#self.sftp_host = 'c6a3nxpth6mf4.eastus2.azurecontainer.io'
#self.sftp_port = 22
#self.sftp_username = 'eLeader01'
#self.sftp_password = 'c8gNBhJE@zt$ZkT&'
#self.sftpdatabase = SFTPManager(self.sftp_host, self.sftp_port, self.sftp_username, self.sftp_password)
#General configuration PG
self.areaID = '0001'
self.project_name = 'PG'
self.classID = '0003'
self.reports = self.database.get_execute_query(f"select * from TransferData where active = 1")
self.reportsID = {'Cobertura':'0001',
'Negociadas':'0002',
'Promociones':'0003',
#'Precios':'0004',
#'Fmot':'0005',
'QualityDisplay':'0006',
'Checkout':'0007',
'QualityShelf':'0008',
'SOS':'0009',
#'OsaTorre':'0010',
#'OsaPsmt':'0011',
'OsaFarmacia':'0012',
'OsaMassy':'0013',
'OsaPenny':'0014',
'OsaFotografico':'0015',
'DataAvailability':'0016'
}
+3
View File
@@ -0,0 +1,3 @@
APP_NAME = "PYTHON_TRANSFERDATA_APP"
VERSION = "2.0.0"
AUTHOR = "eurbina"
+2
View File
@@ -0,0 +1,2 @@
# Modelos de datos (ej. esquemas de tablas si usas ORM)
# Por ahora vacío; agrega clases si usas SQLAlchemy
+1
View File
@@ -0,0 +1 @@
#from .etl_service import *
+23
View File
@@ -0,0 +1,23 @@
# Servicio API: endpoints GET para descarga
from flask import Flask, request, jsonify
from App.Services.logic_service import run_etl # O importa db_utils directamente
import pandas as pd
import logging
app = Flask(__name__)
logging.basicConfig(filename='Logs/api.log', level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
@app.route('/api/download', methods=['GET'])
def download_data():
try:
project = request.args.get('project')
task = request.args.get('task')
# Lógica: query DB y devolver JSON/CSV
conn = get_db_connection() # Importa de Utils
query = f"SELECT * FROM {task} WHERE project = '{project}'"
df = pd.read_sql(query, conn)
conn.close()
return jsonify(df.to_dict(orient='records'))
except Exception as e:
logging.error(f"API falló: {str(e)}")
return jsonify({"error": "Error interno del servidor"}), 500 # Devuelve error, pero Flask sigue vivo
+339
View File
@@ -0,0 +1,339 @@
import sys
import os
from typeguard import config
# subir 2 niveles hasta la raíz (donde está App)
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
from App.Utils.db_utils import DatabaseManager # Reutilizado de db_utils.py
from App.Config.GeneralConfig import GeneralConfig # Reutilizado de GeneralConfig.py
from App.Config.pgConfig import PGConfig # Reutilizado de PGConfig.py
from App.Config.version import APP_NAME, VERSION, AUTHOR # Reutilizado de version.py
from App.Utils.report_utils import ReportGenerator # Nueva utilidad para generar reportes (ej. Excel)
import logging
from datetime import datetime
# Configura logging
logging.basicConfig(filename='Logs/etl.log', level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
def run_etl(config, data_report, target_date_report):
'''Función principal para ejecutar ETL.
Lee los datos desde la base de datos, los transforma en un excel y los sube a SharePoint.
Args:
config: Configuración general de la aplicación.
database: Instancia de la clase DatabaseManager.
data_report: Diccionario con la información del reporte a generar.
target_date_report: Fecha objetivo para filtrar datos (str, formato 'YYYY-MM-DD')
Returns:
Tuple[bool, str]: Una tupla con el resultado de la operación y un mensaje informativo.
'''
date_execution = datetime.now(GeneralConfig.zoneTime)
target_date_report_format = target_date_report.strftime('%Y-%m-%d')
result = {
"errors": []
} # Lista para almacenar errores y mensajes informativos
################################## INICIANDO REPORTE ##################################
config.database.log_execution(procedure_id=str(config.database.db_databaseID)+str('0003')+str('0189')+str(data_report['reportID']),
procedure_name='Trasmision Datos '+str(data_report['project_name'])+' '+str(data_report['report_name']),
status='STARTING',
message='Inicializando la trasmision de reporte: '+str(data_report['report_name']),
executed_by=str(config.database.db_user),
source= f'{APP_NAME}_{VERSION}',
extra_info=None,
target_date=target_date_report_format,
date=str(date_execution))
################################ OBTENIENDO DATOS DE LA BASE DE DATOS ##################################
try:
query = f"SELECT * FROM {data_report['Table_Name']} WHERE Fecha = '{target_date_report_format}'"
print(query)
data_sql_report = config.database.get_execute_query(query)
if not data_sql_report:
print(f"No se encontraron datos para el reporte {data_report['report_name']} en la fecha {target_date_report_format}")
config.database.log_execution(procedure_id=f'{config.database.db_databaseID}00030189{data_report["reportID"]}',
procedure_name=f'Trasmision Datos {data_report["project_name"]} {data_report["report_name"]}',
status=f'WARNING',
message=f"No se encontraron datos para el reporte {data_report['report_name']} en la fecha {target_date_report_format}",
executed_by=f'{config.database.db_user}',
source= f'{APP_NAME}_{VERSION}',
extra_info=None,
target_date=target_date_report_format,
date=f'{date_execution}')
return {
"status": "WARNING",
"report": data_report["report_name"],
"message": "Sin datos en la base de datos"
}
config.database.log_execution(procedure_id=f'{config.database.db_databaseID}00030189{data_report["reportID"]}',
procedure_name=f'Trasmision Datos {data_report["project_name"]} {data_report["report_name"]}',
status=f'SUCCESS',
message=f'Lectura de la base datos para crear reporte :{data_report["report_name"]}',
executed_by=f'{config.database.db_user}',
source= f'{APP_NAME}_{VERSION}',
extra_info=None,
target_date=target_date_report_format,
date=f'{date_execution}')
except Exception as e:
config.database.log_execution(procedure_id=f'{config.database.db_databaseID}00030189{data_report["reportID"]}',
procedure_name=f'Trasmision Datos {data_report["project_name"]} {data_report["report_name"]}',
status=f'FAILED',
message=f'Error en la lectura de la base datos para crear reporte :{data_report["report_name"]}',
executed_by=f'{config.database.db_user}',
source= f'{APP_NAME}_{VERSION}',
extra_info=str(e),
target_date=target_date_report_format,
date=f'{date_execution}')
return {
"status": "FAILED",
"report": data_report["report_name"],
"message": f"Error lectura BD {str(e)}"
}
################################ CREANDO REPORTE EN EXCEL ##################################
try:
doc_report = ReportGenerator(target_date_report,**data_report)
doc_report.createReportData(data_sql_report)
config.database.log_execution(procedure_id=f'{config.database.db_databaseID}00030189{data_report["reportID"]}',
procedure_name=f'Trasmision Datos {data_report["project_name"]} {data_report["report_name"]}',
status=f'SUCCESS',
message=f'Creación del reporte :{data_report["report_name"] } en formato Excel',
executed_by=f'{config.database.db_user}',
source= f'{APP_NAME}_{VERSION}',
extra_info=None,
target_date=target_date_report_format,
date=f'{date_execution}')
except Exception as e:
config.database.log_execution(procedure_id=f'{config.database.db_databaseID}00030189{data_report["reportID"]}',
procedure_name=f'Trasmision Datos {data_report["project_name"]} {data_report["report_name"]}',
status=f'FAILED',
message=f'Error en la creación del reporte :{data_report["report_name"] } en formato Excel',
executed_by=f'{config.database.db_user}',
source= f'{APP_NAME}_{VERSION}',
extra_info=str(e),
target_date=target_date_report_format,
date=f'{date_execution}')
return {
"status": "FAILED",
"report": data_report["report_name"],
"message": f"Error creación del reporte {str(e)}"
}
################################ SUBIR REPORTE A FTP ###############################
if data_report.get('FTP', False):
try:
ftp_path = f"/www/TranferData_APP_PYTHON/{config.project_name}/{data_report['report_name']}/"
#print(ftp_path)
config.ftpdatabase.uploadFile(ftp_path, doc_report.docsend_Path) # Asegúrate de que la configuración FTP esté presente en el config general
config.database.log_execution(procedure_id=f'{config.database.db_databaseID}00030189{data_report["reportID"]}',
procedure_name=f'Trasmision Datos {data_report["project_name"]} {data_report["report_name"]}',
status=f'SUCCESS',
message=f'Subida del reporte :{data_report["report_name"] } a FTP',
executed_by=f'{config.database.db_user}',
source= f'{APP_NAME}_{VERSION}',
extra_info=None,
target_date=target_date_report_format,
date=f'{date_execution}')
except Exception as e:
config.database.log_execution(procedure_id=f'{config.database.db_databaseID}00030189{data_report["reportID"]}',
procedure_name=f'Trasmision Datos {data_report["project_name"]} {data_report["report_name"]}',
status=f'FAILED',
message=f'Error en la subida del reporte :{data_report["report_name"] } a FTP',
executed_by=f'{config.database.db_user}',
source= f'{APP_NAME}_{VERSION}',
extra_info=str(e),
target_date=target_date_report_format,
date=f'{date_execution}')
result["errors"].append(f"Error subir reporte a FTP {str(e)}")
#return {
# "status": "FAILED",
# "report": data_report["report_name"],
# "message": f"Error subir reporte a FTP {str(e)}"
#}
############################### SUBIR REPORTA A SFTP ##################################
if data_report.get('SFTP', False):
try:
# Lógica para subir a SFTP (puedes usar paramiko o alguna librería similar)
config.database.log_execution(procedure_id=f'{config.database.db_databaseID}00030189{data_report["reportID"]}',
procedure_name=f'Trasmision Datos {data_report["project_name"]} {data_report["report_name"]}',
status=f'SUCCESS',
message=f'Subida del reporte :{data_report["report_name"] } a SFTP',
executed_by=f'{config.database.db_user}',
source= f'{APP_NAME}_{VERSION}',
extra_info=None,
target_date=target_date_report_format,
date=f'{date_execution}')
except Exception as e:
config.database.log_execution(procedure_id=f'{config.database.db_databaseID}00030189{data_report["reportID"]}',
procedure_name=f'Trasmision Datos {data_report["project_name"]} {data_report["report_name"]}',
status=f'FAILED',
message=f'Error en la subida del reporte :{data_report["report_name"] } a SFTP',
executed_by=f'{config.database.db_user}',
source= f'{APP_NAME}_{VERSION}',
extra_info=str(e),
target_date=target_date_report_format,
date=f'{date_execution}')
result["errors"].append(f"Error subir reporte a SFTP {str(e)}")
#return {
# "status": "FAILED",
# "report": data_report["report_name"],
# "message": f"Error subir reporte a SFTP {str(e)}"
#}
################################ SUBIR REPORTE A SHAREPOINT PYTHON ##################################
if data_report.get('SharePoint', False):
try:
sharepoint_folder = config.sharepointPath.get(data_report['reportID'])
if not sharepoint_folder:
raise Exception(f"No existe ruta SharePoint para reportID {data_report['reportID']}")
config.sharepoint.uploadFile(doc_report, sharepoint_folder)
config.database.log_execution(procedure_id=f'{config.database.db_databaseID}00030189{data_report["reportID"]}',
procedure_name=f'Trasmision Datos {data_report["project_name"]} {data_report["report_name"]}',
status=f'SUCCESS',
message=f'Subida del reporte :{data_report["report_name"] } a SharePoint usando Python',
executed_by=f'{config.database.db_user}',
source= f'{APP_NAME}_{VERSION}',
extra_info=None,
target_date=target_date_report_format,
date=f'{date_execution}')
except Exception as e:
config.database.log_execution(procedure_id=f'{config.database.db_databaseID}00030189{data_report["reportID"]}',
procedure_name=f'Trasmision Datos {data_report["project_name"]} {data_report["report_name"]}',
status=f'FAILED',
message=f'Error en la subida del reporte :{data_report["report_name"] } a SharePoint usando Python',
executed_by=f'{config.database.db_user}',
source= f'{APP_NAME}_{VERSION}',
extra_info=str(e),
target_date=target_date_report_format,
date=f'{date_execution}')
result["errors"].append(f"Error subir reporte a SharePoint usando Python {str(e)}" )
#return {
# "status": "FAILED",
# "report": data_report["report_name"],
# "message": f"Error subir reporte a SharePoint usando Python {str(e)}"
#}
################################ SUBIR REPORTE A SHAREPOINT N8N ##################################
if data_report.get('N8N', False):
try:
config.database.log_execution(procedure_id=f'{config.database.db_databaseID}00030189{data_report["reportID"]}',
procedure_name=f'Trasmision Datos {data_report["project_name"]} {data_report["report_name"]}',
status=f'SUCCESS',
message=f'Subida del reporte :{data_report["report_name"] } a SharePoint usando N8N',
executed_by=f'{config.database.db_user}',
source= f'{APP_NAME}_{VERSION}',
extra_info=None,
target_date=target_date_report_format,
date=f'{date_execution}')
except Exception as e:
config.database.log_execution(procedure_id=f'{config.database.db_databaseID}00030189{data_report["reportID"]}',
procedure_name=f'Trasmision Datos {data_report["project_name"]} {data_report["report_name"]}',
status=f'FAILED',
message=f'Error en la subida del reporte :{data_report["report_name"] } a SharePoint usando N8N',
executed_by=f'{config.database.db_user}',
source= f'{APP_NAME}_{VERSION}',
extra_info=str(e),
target_date=target_date_report_format,
date=f'{date_execution}')
result["errors"].append(f"Error subir reporte a SharePoint usando N8N {str(e)}")
#return {
# "status": "FAILED",
# "report": data_report["report_name"],
# "message": f"Error subir reporte a SharePoint usando N8N {str(e)}"
#}
#################################################################################################
if result["errors"]:
return {
"status": "COMPLETED_WITH_ERRORS",
"report": data_report["report_name"],
"message": " | ".join(result["errors"])
}
else:
return {
"status": "SUCCESS",
"report": data_report["report_name"],
"message": "ETL ejecutado exitosamente"
}
# Aquí va la lógica de ETL específica para cada proyecto y reporte
# Por simplicidad, loggea que se ejecutó
#logging.info(f"ETL ejecutado para proyecto: {project}, reporte: {report_name}, usando config: {config.project_name}")
"""
pg_config = PGConfig()
database = DatabaseManager(pg_config.database, pg_config.databaseID)
data_report = {
'project_name': 'PG',
'report_name': 'Cobertura',
'Table_Name': 'ReportCobertura',
'nowTime': GeneralConfig.nowTime,
'nowTimeFormat': GeneralConfig.nowTimeFormat,
'project_name': pg_config.project_name,
'current_time': GeneralConfig.execution_time,
'reportID': '0001'
}
run_etl(pg_config, database, data_report, target_date_report='2026-03-26')"""
+13
View File
@@ -0,0 +1,13 @@
# Servicio UI: manejo de forms web
from flask import render_template, request
from App.Services.logic_service import run_etl
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
project = request.form['project']
task = request.form['task']
# Trigger ETL manual
run_etl(project, task)
return "ETL ejecutado"
return render_template('index.html')
+3
View File
@@ -0,0 +1,3 @@
/* Estilos básicos para UI */
body { font-family: Arial; }
form { max-width: 400px; }
+14
View File
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head><title>DataTransfer UI</title></head>
<body>
<h1>Actualizar Datos</h1>
<form method="POST">
Proyecto: <select name="project"><option>Feduro</option><option>Comberton</option></select><br>
Tarea: <select name="task"><option>negociadas</option><option>promociones</option></select><br>
Fecha inicio: <input type="date" name="start_date"><br>
Fecha fin: <input type="date" name="end_date"><br>
<button type="submit">Actualizar</button>
</form>
</body>
</html>
+111
View File
@@ -0,0 +1,111 @@
SELECT TOP (1000) [ID]
,[ExecutionDate]
,[ReportName]
,[ReportID]
,[Active]
,[TableName]
FROM [glm_pg].[dbo].[TransferDataParameters]
CREATE TABLE TransferData (
ID INT IDENTITY(1,1) PRIMARY KEY,
ExportDate DATETIME DEFAULT GETDATE(),
ReportID AS RIGHT('0000' + CAST(ID AS VARCHAR), 4) PERSISTED, -- 👈 automático
ReportName VARCHAR(100) NOT NULL,
TableName VARCHAR(200) NOT NULL, -- 👈 AQUÍ lo importante
Active BIT NOT NULL DEFAULT 1,
FTP BIT NOT NULL DEFAULT 1,
FTPS BIT NOT NULL DEFAULT 1,
SharePoint BIT NOT NULL DEFAULT 1,
N8N BIT NOT NULL DEFAULT 0
);
INSERT INTO TransferData (ReportName, TableName, Active, FTP, FTPS, SharePoint, N8N)
VALUES
-- 0001
('Cobertura','ReportCobertura',1,1,1,1,0),
-- 0002
('NegociadasIS001','IS001.dbo.AcuerdosComerciales_Report',1,1,1,1,0),
-- 0003
('Promociones','PromocionesAC_Report',1,1,1,1,0),
-- 0004 (desactivado)
('Precios','s/d',0,1,1,1,0),
-- 0005
('Fmot','Fmot_Report',1,1,1,1,0),
-- 0006
('QualityDisplay','dbo.QualityDisplay_Report',1,1,1,1,0),
-- 0007
('Checkout','IS001.dbo.Checkout_Report',1,1,1,1,0),
-- 0008
('QualityShelf','QualityShelf_Report',1,1,1,1,0),
-- 0009
('SOSFeduro','View_Medicion_SOS_PG',1,1,1,1,0),
-- 0010 (desactivado)
('OsaTorre','s/d',0,1,1,1,0),
-- 0011 (desactivado)
('OsaPsmt','s/d',0,1,1,1,0),
-- 0012 (desactivado)
('OsaFarmacia','s/d',0,1,1,1,0),
-- 0013
('OsaFotografico','OsaManualFotos_Report',1,1,1,1,0),
-- 0014 (desactivado)
('DataAvailability','s/d',0,1,1,1,0),
-- 0015
('OsaAnalogo','OsaManualAnalogo_Report',1,1,1,1,0),
-- 0016
('ModuloTareas','ModuloTareas_Report',1,1,1,1,0),
-- 0017 (desactivado)
('SOSAPDO_IS001','s/d',0,1,1,1,0),
-- 0018 (desactivado)
('SOSBabyCare_IS001','s/d',0,1,1,1,0),
-- 0019 (desactivado)
('SOSFabricCare_IS001','s/d',0,1,1,1,0),
-- 0020 (desactivado)
('SOSFamilyCare_IS001','s/d',0,1,1,1,0),
-- 0021 (desactivado)
('SOSFemCare_IS001','s/d',0,1,1,1,0),
-- 0022 (desactivado)
('SOSHairCare_IS001','s/d',0,1,1,1,0),
-- 0023 (desactivado)
('SOSHomeCare_IS001','s/d',0,1,1,1,0),
-- 0024 (desactivado)
('SOSOralCare_IS001','s/d',0,1,1,1,0),
-- 0025 (desactivado)
('SOSPHC_IS001','s/d',0,1,1,1,0);
select * from TransferData
select * from TransferData where active = 1
UPDATE TransferData
SET Active = 0
WHERE ReportID = '0005'
--drop table TransferData
+5
View File
@@ -0,0 +1,5 @@
#from .db_utils import *
#from .report_utils import *
#from .sharepoint_utils import *
#from .ftp_utils import *
#from .sftp_utils import *
+134
View File
@@ -0,0 +1,134 @@
# Utilidades para conexión a BD - Reutilizado y ajustado de database.py v1
import pyodbc
from App.Config.GeneralConfig import ConfigDatabase # Ajusta si GeneralConfig tiene otra estructura
class DatabaseManager:
def __init__(self, database, databaseID):
self.db_database = database
self.db_user = ConfigDatabase.db_user
self.db_password = ConfigDatabase.db_password
self.db_host = ConfigDatabase.db_host
self.db_databaseID = databaseID
def _connect(self):
try:
self.conn_str = (
'DRIVER={ODBC Driver 18 for SQL Server};'
f'SERVER={self.db_host};'
f'DATABASE={self.db_database};'
f'UID={self.db_user};'
f'PWD={self.db_password};'
f'Encrypt=yes;'
f'TrustServerCertificate=yes;'
)
#print(self.conn_str)
self.connection = pyodbc.connect(self.conn_str)
except pyodbc.Error as e:
print(f"Error al conectar a la base de datos: {e}")
self.connection = None
def _disconnect(self):
if self.connection:
self.connection.close()
# Función para ejecutar queries que retornan valores
def get_execute_query(self, query):
self._connect()
if not self.connection:
return None, "Error de conexión"
self.cursor = self.connection.cursor()
self.cursor.execute(query)
columns = [col[0] for col in self.cursor.description]
data = self.cursor.fetchall()
results = [dict(zip(columns, row)) for row in data]
self.cursor.close()
self._disconnect()
return results
# Función para ejecutar queries que retornan valores
def get_execute_query2(self, query):
self._connect()
if not self.connection:
return None, "Error de conexión"
try:
self.cursor = self.connection.cursor()
self.cursor.execute(query)
columns = [col[0] for col in self.cursor.description]
data = self.cursor.fetchall()
results = [dict(zip(columns, row)) for row in data]
self.cursor.close()
return results, None
except Exception as e:
print(f"Error al ejecutar la consulta SELECT: {e} \n {query}")
return None, str(e)
finally:
self._disconnect()
# Función para ejecutar queries que no retornan valores
def set_execute_query(self, query):
self._connect()
if not self.connection:
return False
try:
cursor = self.connection.cursor()
cursor.execute(query)
self.connection.commit()
cursor.close()
return True
except Exception as e:
print(f"Error al ejecutar la consulta de modificación: {e}")
return False
finally:
self._disconnect()
def log_execution(self, procedure_id, procedure_name, status, message, executed_by, extra_info, target_date, date,
procedure_type='PYTHON_APP', source='PYTHON_TRANSFERDATA_APP'):
self._connect()
if not self.connection:
return
self.cursor = self.connection.cursor()
sql = """
DECLARE @ExecID VARCHAR(50), @FieldID VARCHAR(50);
EXEC dbo.InsertExecutionLog
@ProcedureID = ?,
@ProcedureName = ?,
@ProcedureType = ?,
@Date = ?,
@TargetDate = ?,
@Status = ?,
@ExecutedBy = ?,
@MessageDescription = ?,
@Source = ?,
@ExtraInfo = ?,
@ExecutionIDGenerated = @ExecID OUTPUT,
@FieldIDGenerated = @FieldID OUTPUT;
SELECT @ExecID AS ExecutionID, @FieldID AS FieldID;
"""
try:
self.cursor.execute(sql, (
procedure_id, procedure_name, procedure_type,
date, target_date, status,
executed_by, message, source, extra_info
))
row = self.cursor.fetchone()
self.connection.commit()
except Exception as e:
print(f"Error en log_execution: {e}")
finally:
self._disconnect()
# Función simple para compatibilidad (usa DatabaseManager internamente)
def get_db_connection(database='tu_db', databaseID='default'):
manager = DatabaseManager(database, databaseID)
manager._connect()
return manager.connection # Devuelve la conexión cruda para pandas, etc.
+133
View File
@@ -0,0 +1,133 @@
from ftplib import FTP
import os
import time
class FTPManager:
def __init__(self, siteFTP, userFTP, passwordFTP, postFTP):
self.server = siteFTP
self.user = userFTP
self.password = passwordFTP
self.port = postFTP # Puerto por defecto: 21
self.ftp_client = None
self.list_failed_uploads = []
#def __init__(self, **kwargs):
# self.server = kwargs.get('server')
# self.user = kwargs.get('user')
# self.password = kwargs.get('password')
# self.port = kwargs.get('port', 21) # Puerto por defecto: 21
# self.ftp_client = None
# self.list_failed_uploads = []
def connect(self):
"""Conecta al servidor FTP"""
try:
self.ftp_client = FTP()
self.ftp_client.connect(self.server, self.port)
self.ftp_client.login(self.user, self.password)
self.ftp_client.encoding = 'latin-1'
#print(f"Conexión exitosa al servidor FTP: {self.server}")
except Exception as e:
#print(f"Error al conectar al servidor FTP: {e}")
raise
def disconnect(self):
"""Cierra la conexión FTP"""
if self.ftp_client:
self.ftp_client.quit()
#print("Conexión cerrada con éxito")
def upload_photos(self, local_folder, remote_folder, batch_size=100):
"""
Sube archivos en lotes de 100. Si hay un error, omite el archivo y lo guarda para intentarlo al final.
"""
try:
self.ftp_client.cwd(remote_folder)
except:
# if not exit create folder
self.ftp_client.mkd(remote_folder)
self.ftp_client.cwd(remote_folder)
files = [f for f in os.listdir(local_folder) if os.path.isfile(os.path.join(local_folder, f))]
total_files = len(files)
print(f"Total de archivos a subir: {total_files}")
for i in range(0, total_files, batch_size):
batch = files[i:i+batch_size]
print(f"Subiendo lote {i//batch_size + 1}: {len(batch)} archivos")
for file in batch:
local_path = os.path.join(local_folder, file)
try:
with open(local_path, 'rb') as local_file:
self.ftp_client.storbinary(f"STOR {file}", local_file)
print(f"Subido: {file}")
except Exception as e:
print(f"Error al subir {file}: {e}")
self.list_failed_uploads.append(file)
time.sleep(2) # Pequeña pausa entre lotes para estabilidad
# Intentar subir nuevamente los fallidos
if self.list_failed_uploads:
print("Reintentando archivos fallidos...")
for file in self.list_failed_uploads:
local_path = os.path.join(local_folder, file)
try:
with open(local_path, 'rb') as local_file:
self.ftp_client.storbinary(f"STOR {file}", local_file)
print(f"Subido en reintento: {file}")
except Exception as e:
print(f"Fallo definitivo en {file}: {e}")
print("Proceso de subida finalizado.")
def uploadFile(self, remote_folder, local_file_path):
self.connect()
try:
self.ftp_client.cwd("/") # 👈 IMPORTANTE
self.ensure_remote_path(remote_folder)
with open(local_file_path, "rb") as f:
file_name = os.path.basename(local_file_path)
self.ftp_client.storbinary(f"STOR {file_name}", f)
finally:
self.disconnect()
def ensure_remote_path(self, remote_folder):
folders = remote_folder.strip("/").split("/")
# SIEMPRE empezar desde raíz
self.ftp_client.cwd("/")
for folder in folders:
if folder == "":
continue
try:
self.ftp_client.cwd(folder)
except:
self.ftp_client.mkd(folder)
#print(f"Creando/navegando carpeta: {folder}")
self.ftp_client.cwd(folder)
# Ejemplo de uso
if __name__ == "__main__":
config = {
"server": "glmservice.com",
"user": "ftpeleaderuser",
"password": "AdAw#BaAjwQdc4B",
"port": 21
}
ftp_manager = FTPManager(**config)
try:
ftp_manager.connect()
ftp_manager.upload_photos("C:\\Users\\emili\\Downloads\\sfotos", "/imglm/test/glmpg01/")
finally:
ftp_manager.disconnect()
+50
View File
@@ -0,0 +1,50 @@
import requests
from datetime import datetime, timedelta
from App.Config.GeneralConfig import ConfigNotifications
def send_whatsapp(message):
url = ConfigNotifications.webhookURL_wps_notifcation # URL del webhook de WhatsApp en WPS
payload = {
"message": message
}
requests.post(url, json=payload)
def build_message(project_name, summary, duration):
status = "FINALIZADO" if not summary["failed"] else "FINALIZADO CON ERRORES ❌"
msg = f"""PROYECTO: {project_name}
Fecha: {datetime.now().strftime('%Y-%m-%d')}
Estado: {status}
Resumen:
- Reportes procesados: {summary['total']}
- Exitosos: {summary['success']}
- Con errores: {len(summary['failed'])}
"""
if summary["failed"]:
msg += "❌ Errores críticos:\n"
for e in summary["failed"]:
msg += f"- {e}\n"
if summary["errors"]:
msg += "\n🟠 Errores no bloqueantes:\n"
for e in summary["errors"]:
msg += f"- {e}\n"
if summary["warnings"]:
msg += "\n⚠️ Advertencias:\n"
for w in summary["warnings"]:
msg += f"- {w}\n"
msg += f"\n🕐 Duración: {str(duration).split('.')[0]}"
return msg
+53
View File
@@ -0,0 +1,53 @@
# Utilidades para generación de reportes - Reutilizado y ajustado de report.py v1
import os
import pandas as pd
import logging
from datetime import datetime
# Configura logging
logging.basicConfig(filename='Logs/reports.log', level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
class ReportGenerator:
"""Clase para generar y exportar reportes en Excel"""
def __init__(self, target_date_report, **kwargs ):
"""
Inicializa generador de reportes
Args:
name: Nombre del reporte
nowTime: Fecha actual (datetime)
nowTimeFormat: Formato de fecha (str)
project_name: Nombre del proyecto
current_time: Tiempo de ejecución (datetime)
reportID: ID del reporte (str)
"""
self.name = kwargs.get('report_name',None)
#self.date = kwargs.get('nowTime',None)
self.dateFormat = target_date_report.strftime("%Y%m%d")
self.project_name = kwargs.get('project_name',None)
#self.current_time = kwargs.get('current_time',None)
#self.reportID = kwargs.get('reportID',None)
self.actual_path = os.path.dirname(os.path.abspath(__file__))
#self.download_dir = os.path.join(self.actual_path, '..', '..','Reportes', str(self.project_name), str(self.name), 'download')
self.docsend_dir = os.path.join(self.actual_path, '..', '..','Reportes', str(self.project_name), str(self.name))
#os.makedirs(self.download_dir, exist_ok=True)
os.makedirs(self.docsend_dir, exist_ok=True)
#self.dowload_path = os.path.normpath(self.download_dir)
self.docsend_Path = os.path.normpath(os.path.join(self.docsend_dir, f"{self.name} - {self.dateFormat}.xlsx"))
self.report_name = f"{self.name} - {self.dateFormat}.xlsx"
print(self.report_name)
def createReportData(self,data_sql_report):
self.data_sql_report = data_sql_report
self.df = pd.DataFrame(self.data_sql_report)
self.df.to_excel(self.docsend_Path,index=False)
#return False
#print(self.docsend_Path)
+44
View File
@@ -0,0 +1,44 @@
import paramiko
import os
class SFTPManager():
def __init__(self, sftp_host, sftp_port, sftp_username, sftp_password):
self.sftp_host = sftp_host
self.sftp_username = sftp_username
self.sftp_password = sftp_password
self.sftp_port = sftp_port or 22 # Puerto por defecto: 22
self.ssh_client = None
self.sftp_client = None
def _SFTPConect(self):
self.ssh_client = paramiko.SSHClient()
self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # Acepta automáticamente la clave del host
self.ssh_client.connect(self.sftp_host, self.sftp_port, self.sftp_username, self.sftp_password)
self.sftp_client = self.ssh_client.open_sftp()
def _SFTPDisconnect(self):
if self.sftp_client:
self.sftp_client.close()
#print('SFTP desconectado.')
if self.ssh_client:
self.ssh_client.close()
#print('SSH desconectado.')
def uploadFile(self, task, sftp_path):
self._SFTPConect()
file_name = os.path.basename(task.docsend_Path)
pathFTP = os.path.join(sftp_path, file_name)
self.sftp_client.put(task.docsend_Path, pathFTP)
#print(f'Archivo {task.docsendPath} subido exitosamente a {pathFTP}')
self._SFTPDisconnect()
+109
View File
@@ -0,0 +1,109 @@
import requests
import os
import msal
from office365.sharepoint.client_context import ClientContext
from office365.runtime.auth.user_credential import UserCredential
from office365.runtime.auth.client_credential import ClientCredential
class SharepointManager:
def __init__(self, site, userSharepoint, passwordSharepoint):#,client_id,tenant_id,client_secret):
self.site = site
self.userSharepoint = userSharepoint
self.passwordSharepoint = passwordSharepoint
#self.client_id = client_id
#self.tenant_id = tenant_id
#self.client_secret = client_secret
#print(self.client_secret)
def uploadFile2(self, task, sharepoint_path):
self._connect2()
file_name = os.path.basename(task.docsend_Path)
with open(task.docsend_Path, "rb") as file_content:
folder = self.ctx.web.get_folder_by_server_relative_url(sharepoint_path)
upload = folder.upload_file(file_name, file_content.read()) # solo prepara la query
self.ctx.execute_query() # ejecuta en el contexto
print(f"✅ Archivo {file_name} subido a {upload.serverRelativeUrl}")
self._disconnect()
def _connect2(self):
#"""Conexión a SharePoint con access_token"""
#token = self._get_token()
#self.ctx = ClientContext(self.site).with_access_token(token)
#self.ctx.load(self.ctx.web)
#self.ctx.execute_query()
self.ctx = ClientContext(self.site).with_credentials(ClientCredential(self.client_id, self.client_secret))
self.ctx.load(self.ctx.web)
self.ctx.execute_query()
def _connect(self):
# 👇 Nada de AuthenticationContext aquí
self.ctx = ClientContext(self.site).with_credentials(
UserCredential(self.userSharepoint, self.passwordSharepoint)
)
self.ctx.load(self.ctx.web)
self.ctx.execute_query()
def _disconnect(self):
self.ctx = None
def uploadFile(self, task, sharepoint_path):
self._connect()
file_name = os.path.basename(task.docsend_Path)
with open(task.docsend_Path, "rb") as file_content:
response = (
self.ctx.web.get_folder_by_server_relative_url(sharepoint_path)
.upload_file(file_name, file_content.read())
.execute_query()
)
print(f"Archivo {file_name} subido a {response.serverRelativeUrl}")
self._disconnect()
def uploadFile2(self, task, sharepoint_path):
"""Subida con MSAL (App Registration)"""
self._connect2()
file_name = os.path.basename(task.docsend_Path)
with open(task.docsend_Path, "rb") as file_content:
folder = self.ctx.web.get_folder_by_server_relative_url(sharepoint_path)
upload = folder.upload_file(file_name, file_content.read())
self.ctx.execute_query()
print(f"✅ Archivo {file_name} subido a {upload.serverRelativeUrl}")
self._disconnect()
def uploadFileTemp(self, task, data):
self._connect()
file_name = os.path.basename(task.docsend_Path)
with open(task.docsend_Path, "rb") as file_content:
response = (
self.ctx.web.get_folder_by_server_relative_url(data[6])
.upload_file(file_name, file_content.read())
.execute_query()
)
print(f"Archivo {file_name} subido a {response.serverRelativeUrl}")
self._disconnect()
def dropFilesFolder(self, data):
self._connect()
folder = (
self.ctx.web.get_folder_by_server_relative_url(data[6])
.expand(["Files"])
.get()
.execute_query()
)
for file in folder.files:
print(f"Eliminando archivo: {file.name}")
file.delete_object()
self.ctx.execute_query()
self._disconnect()
+3
View File
@@ -0,0 +1,3 @@
from .Utils import *
from .Config import *
from .Services import *