瀏覽代碼

proyecto migradodesde Azure DevOps

Francisco huezo 6 年之前
當前提交
a34aeb9833

+ 104 - 0
.gitignore

@@ -0,0 +1,104 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/

+ 3 - 0
.vscode/settings.json

@@ -0,0 +1,3 @@
+{
+    "python.pythonPath": ".venv\\Scripts\\python.exe"
+}

+ 22 - 0
README.md

@@ -0,0 +1,22 @@
+# Bot Descarga Documentos Fiscales del CENACE
+
+## Instalación
+
+1. Crear virtual env 
+
+    ```python -m venv .venv ```
+
+2. Activate virtual env
+
+    ```.venv\Scripts\activate```
+
+3. Instalar dependencias de Python
+
+    ```pip install -r requirements.txt```
+
+## Ejecución
+
+Ejectuar el comando
+
+```python main.py```
+

二進制
cert/carlosmauriciojuarez-mexicanenergytrading.cer


二進制
cert/carlosmauriciojuarez-mexicanenergytrading.pfx


二進制
cert/carlosmauriciojuarez-mexicanenergytrading_user.key


+ 49 - 0
config/logging.yaml

@@ -0,0 +1,49 @@
+version: 1
+disable_existing_loggers: true
+
+
+
+formatters:
+    standard:
+        format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+    error:
+        format: "%(asctime)s %(levelname)s <PID %(process)d:%(processName)s> %(name)s.%(funcName)s(): %(message)s"
+
+handlers:
+    console:
+        class: logging.StreamHandler
+        level: DEBUG
+        formatter: standard
+        stream: ext://sys.stdout
+
+    file_handler:
+        class: logging.handlers.RotatingFileHandler
+        formatter: standard
+        filename: log.log
+        maxBytes: 10485760 # 10MB
+        backupCount: 5
+        encoding: utf8
+
+    error_file_handler:
+        class: logging.handlers.RotatingFileHandler
+        level: ERROR
+        formatter: error
+        filename: errors.log
+        maxBytes: 10485760 # 10MB
+        backupCount: 5
+        encoding: utf8
+
+root:
+    level: DEBUG
+    handlers: [console]
+
+loggers:
+    sim.memsim:
+        level: INFO
+        handlers: [file_handler, error_file_handler]
+        propogate: no
+
+#    sim.utils.common:
+#        level: DEBUG
+#        handlers: [console, error_file_handler]
+#        propogate: yes

二進制
drivers/chromedriver.exe


+ 8 - 0
main.py

@@ -0,0 +1,8 @@
+from sim.memsim import download_files
+from sim.filemanager import move_file_to_server
+
+if __name__ == '__main__':
+    ecds = download_files()
+    # for ecd in ecds:
+    #     move_file_to_server(ecd)
+

+ 16 - 0
requirements.txt

@@ -0,0 +1,16 @@
+asn1crypto==0.24.0
+certifi==2019.3.9
+cffi==1.12.3
+chardet==3.0.4
+cryptography==2.6.1
+idna==2.8
+pycparser==2.19
+pyOpenSSL==19.0.0
+PyYAML==5.1
+requests==2.22.0
+selenium==3.141.0
+six==1.12.0
+urllib3==1.25.2
+wincertstore==0.2
+python-dotenv==0.10.3
+pyodbc==4.0.26

+ 3 - 0
sim/__init__.py

@@ -0,0 +1,3 @@
+import os
+
+BASE_DIR = os.path.abspath(__file__ + "/../../")

+ 27 - 0
sim/config.py

@@ -0,0 +1,27 @@
+import os
+from sim import BASE_DIR
+
+
+class Configuration:
+
+    def __init__(self):
+
+        self.__basepath__ = BASE_DIR
+
+        self.DRIVER_PATH = os.path.join(self.__basepath__, 'drivers', 'chromedriver.exe')
+        self.DOWNLOAD_PATH = os.path.join(self.__basepath__, 'Downloads', 'df')
+        self.SERVER_PATH = r'\\192.168.98.134\metco_ecd\facturas'
+        self.CERT_PATH = os.path.join(self.__basepath__, 'cert')
+        self.PRIVATE_KEY = os.getenv('CENACE_PRIVATE_KEY')
+        self.USER_NAME = os.getenv('CENACE_USERNAME')
+        self.PASSWORD = os.getenv('CENACE_PASSWORD')
+        self.TEMPLATES_PATH = os.path.join(self.__basepath__, 'templates')
+        self.DBUSER = os.getenv('DBUSER')
+        self.DBPASS = os.getenv('DBPASS')
+        self.SENDER_ADDRESS=os.getenv("SENDER_ADDRESS")
+        self.SMTP_SERVER=os.getenv("SMTP_SERVER")
+        self.SMTP_PORT=os.getenv("SMTP_PORT")
+        self.AUTH_USER=os.getenv("AUTH_USER")
+        self.AUTH_PASS=os.getenv("AUTH_PASS")
+
+

+ 51 - 0
sim/filemanager.py

@@ -0,0 +1,51 @@
+import os
+import logging
+import shutil
+
+from sim.config import Configuration
+import sim.utils.logconfig
+
+def move_file_to_server(df_name):
+    _config = Configuration()
+    _downloadFilePath = _config.DOWNLOAD_PATH
+    _destinationBasePath = _config.SERVER_PATH
+
+    _exts = [".pdf", ".xml"]
+
+    _month = {
+        '01': 'Enero',
+        '02': 'Febrero',
+        '03': 'Marzo',
+        '04': 'Abril',
+        '05': 'Mayo',
+        '06': 'Junio',
+        '07': 'Julio',
+        '08': 'Agosto',
+        '09': 'Septiembre',
+        '10': 'Octubre',
+        '11': 'Noviembre',
+        '12': 'Diciembre'}
+
+    logger = logging.getLogger(__name__)
+    filepath = os.path.join(_downloadFilePath, df_name)
+    #id = ecd_name.split("-")[0]
+    #ecd_date = id.strip("SIN").strip("EC")
+    df_date = df_name[:8]
+
+    year = df_date[:4]
+    month = df_date[4:6] + ' ' + _month[df_date[4:6]]
+
+    for ext in _exts:
+        file = filepath + ext
+        dest_folder = os.path.join(_destinationBasePath, year, month, "Recibidas")
+        #dest_folder = os.path.join(_destinationBasePath, ext.replace(".", "") + '_test', year, month)
+
+        if not os.path.exists(dest_folder):
+            logger.info(r"Creando el directorio {0} porque no existe".format(dest_folder))
+            os.makedirs(dest_folder, exist_ok=True)
+
+        if not os.path.exists(file):
+            logger.debug("El archivo {0}{1} no existe".format(df_name, ext))
+        else:
+            logger.info("Copiando el archivo {0}{1} a su carpeta destino {2}".format(df_name, ext, dest_folder))
+            shutil.copy(file, dest_folder)

+ 230 - 0
sim/memsim.py

@@ -0,0 +1,230 @@
+import os
+import glob
+import sqlite3
+import time
+import logging
+from datetime import datetime
+
+from selenium import webdriver
+from selenium.webdriver.chrome.options import Options
+
+from sim.config import Configuration
+from sim.utils.unzipfile import unzipfile_and_remove
+from sim.utils.common import Validation
+from sim.utils.exceptions import CertificadosIncorrectos
+from sim.utils.mailsender import Emailer
+import sim.utils.logconfig
+from dotenv import load_dotenv
+import pyodbc
+from sim.filemanager import move_file_to_server
+
+def download_files():
+    load_dotenv()
+    rootLogger = logging.getLogger(__name__)
+    rootLogger.info("Iniciando proceso de descarga de Facturas")
+    start = time.time()
+
+    config = Configuration()
+    val = Validation()
+    mail = Emailer(config.SMTP_SERVER,config.SMTP_PORT,config.AUTH_USER,config.AUTH_PASS)
+
+    downloadFilePath = config.DOWNLOAD_PATH
+    chromedriver = config.DRIVER_PATH
+    exts = [".pdf", ".xml"]
+
+    url = "https://memsim.cenace.gob.mx/produccion/participantes/LOGIN/"
+
+    try:
+
+        options = Options()
+        options.add_argument("start-maximized")
+        prefs = {"download.default_directory": downloadFilePath,
+                 "download.prompt_for_download": False,
+                 "download.directory_upgrade": True,
+                 "safebrowsing.enabled":True,
+                 "plugins.always_open_pdf_externally": True}
+        options.add_experimental_option("prefs", prefs)
+
+        driver = webdriver.Chrome(executable_path=chromedriver, chrome_options=options)
+        #driver.implicitly_wait(10)
+
+        rootLogger.info("Ingresando a la url: {0}".format(url))
+
+        driver.get(url)
+        rootLogger.info("Iniciando proceso de certificación y login...")
+        val.validate_element_by_id(driver,"uploadCerfile0")
+        driver.find_element_by_id("uploadCerfile0").send_keys(
+            os.path.join(config.CERT_PATH,"carlosmauriciojuarez-mexicanenergytrading.cer"))
+
+        
+        val.validate_element_by_id(driver,"uploadKeyfile0")
+        driver.find_element_by_id("uploadKeyfile0").send_keys(
+            os.path.join(config.CERT_PATH,"carlosmauriciojuarez-mexicanenergytrading_user.key"))
+
+        val.validate_element_by_id(driver,"txtPrivateKey")
+        driver.find_element_by_id("txtPrivateKey").send_keys(config.PRIVATE_KEY)
+        time.sleep(1)
+        driver.find_element_by_id("btnEnviar").click()
+
+        if not val.check_element_by_id(driver, "txtUsuario"):
+            raise CertificadosIncorrectos(driver.find_element_by_id("lblCertificadoIncorrecto").text)
+        
+        driver.find_element_by_id("txtUsuario").send_keys(config.USER_NAME)
+        driver.find_element_by_id("txtPassword").send_keys(config.PASSWORD)
+        driver.find_element_by_id("Button1").click()
+        rootLogger.info("Inicio de sesión satisfactorio...")
+
+        rootLogger.info("Ingreso a la sección de Estados de Cuenta y Facturas...")
+
+        driver.find_element_by_css_selector(
+            "#ctl07 > ul:nth-child(1) > li:nth-child(4) > a:nth-child(1) > span:nth-child(1)").click()
+        
+        driver.find_element_by_css_selector("#FACT_Consultar > span:nth-child(1)").click()
+        
+        
+        val.validate_element_by_tag_name(driver, "iframe")
+        driver.switch_to.frame(driver.find_element_by_tag_name("iframe"))
+
+        driver.find_element_by_css_selector("li.rtLI:nth-child(2) > ul:nth-child(2) > li:nth-child(1) > div:nth-child(1) > span:nth-child(2)").click()
+
+        val.validate_element_not_present(driver, "RadAjaxLoadingPanel1RadAjaxPanel1")
+        val.validate_element_by_id(driver, "Div7")
+
+        ecd_div = driver.find_element_by_id("Div7")
+        trs = ecd_div.find_elements_by_tag_name("tr")
+
+        regs = []
+        
+        rootLogger.info("Leyendo listado de DF en la página web...")
+
+        for tr in trs:
+            tr_id = tr.get_property("id")
+            if tr_id != "":
+                tds = tr.find_elements_by_tag_name("td")
+                df_name = tds[0].get_attribute("innerHTML")
+                xml_id = tds[1].find_element_by_tag_name("input").get_property("id")
+                pdf_id = tds[2].find_element_by_tag_name("input").get_property("id")
+                update = tds[3].get_attribute("innerHTML")
+
+                rootLogger.debug(" DF: {0} | actualizacion: {1}".format(df_name, update))
+                ecd_info = [df_name, xml_id, pdf_id, update]
+
+                regs.append(ecd_info)
+
+        num_regs = len(regs)
+
+        rootLogger.info("Se encontraton {0} registros".format(num_regs))
+        i = 1
+        d = 0
+        e = 0
+        scroll=1
+        downloaded_dfs = []
+
+        for reg in regs:
+            df_name = reg[0]
+            rootLogger.info("{0} de {1} -- Descargando información del Estado de Cuenta: {2} ...".format(i, num_regs, df_name))
+            conn = pyodbc.connect('Driver={SQL Server};'
+                      'Server=192.168.98.207;'
+                      'Database=PRD_ORION;'
+                      'UID='+config.DBUSER+';'
+                      'PWD='+config.DBPASS+';')
+
+            cursor = conn.cursor()
+            rootLogger.info("Buscando en la base de datos...")
+            cursor.execute("SELECT * FROM MEXICO.DF_DOWNLOAD_LOG WHERE NOMBRE_FACTURA = '{0}'".format(df_name))
+            data = cursor.fetchall()
+
+            if len(data) == 0:
+                rootLogger.info("No se encontró en la base de datos... Se inicia la Descarga!")
+
+                filepath = downloadFilePath + "\\" + df_name
+                rootLogger.info("filepath: " + filepath)
+                try:
+                    driver.find_element_by_id(reg[1]).click()
+                except:
+                    driver.execute_script("document.getElementById(\"RadGrid6_GridData\").scroll(0,"+str(175*scroll)+")")
+                    scroll=scroll+1
+                    driver.find_element_by_id(reg[1]).click()
+                
+                val.validate_download(filepath+".xml")
+
+                try:
+                    driver.find_element_by_id(reg[2]).click()
+                except:
+                    driver.execute_script("document.getElementById(\"RadGrid6_GridData\").scroll(0,"+str(175*scroll)+")")
+                    driver.find_element_by_id(reg[2]).click()
+                
+                val.validate_download(filepath+".pdf")
+                
+                flag = True
+
+                for ext in exts:
+                    if not os.path.exists(filepath + ext):
+                        flag = False
+
+                        for filename in glob.glob(filepath + "*"):
+                            os.remove(filename)
+
+                        for filename in glob.glob(downloadFilePath + "*.tmp"):
+                            os.remove(filename)
+                        e = e + 1
+                        break
+
+                if flag:
+                    rootLogger.info("Archivos Descargados satisfactoriamente... df_name: "+df_name)
+                    downloaded_dfs.append(df_name)
+                    rootLogger.info("Moviendo archivos al servidor...")
+                    move_file_to_server(df_name)
+                    for filename in glob.glob(filepath + "*"):
+                        os.remove(filename)
+                    rootLogger.info("Guardando información en la base de datos...")
+                    cursor.execute(
+                        "INSERT INTO MEXICO.DF_DOWNLOAD_LOG(NOMBRE_FACTURA) values ('{0}')".format(df_name))
+
+                    d = d + 1
+
+            else:
+                rootLogger.info("Los archivos ya se descargaron...")
+
+            conn.commit()
+            conn.close()
+
+            i = i + 1
+
+        driver.switch_to.default_content()
+
+        driver.find_element_by_id("btnCerrarSesion").click()
+
+        driver.quit()
+
+        end = time.time()
+        total_time = end - start
+        mins, secs = divmod(total_time, 60)
+        hours, mins = divmod(mins, 60)
+
+        rootLogger.info("Se descargaron un total de {0} Documentos Fiscales".format(d))
+        rootLogger.info("Falló la descarga de {0} Documentos Fiscales".format(e))
+        rootLogger.info("El proceso finalizón en {0:.0f}:{1:.0f}:{2:.2f} ".format(hours, mins, secs))
+
+        
+        if d > 0:
+            mail.send_success_email('robot@mercadoselectricos.com.sv','francisco.huezo@mercadoselectricos.com.sv',
+                            "Proceso de Descarga finalizado","Descarga de archivos finalizada",
+                            type='success', report_count= d)
+
+        return downloaded_dfs
+
+    except Exception as ex:
+        template = "An exception of type {0} occurred. Arguments:\n{1!r}"
+        for filename in glob.glob(downloadFilePath+"\\" + "*"):
+            os.remove(filename)
+        message = template.format(type(ex).__name__, ex.args)
+        rootLogger.error(message, exc_info=True)
+        driver.quit()
+        mail.send_warning_email('robot@mercadoselectricos.com.sv', 'francisco.huezo@mercadoselectricos.com.sv',
+                        "Proceso de Descarga finalizado", "Error en el proceso de descarga de archivos")
+
+        return []
+
+if __name__ == "__main__":
+    download_files()

+ 0 - 0
sim/utils/__init__.py


+ 91 - 0
sim/utils/common.py

@@ -0,0 +1,91 @@
+from selenium.common.exceptions import NoSuchElementException
+import time
+import logging
+import sys, traceback
+import os
+from pathlib import Path
+
+class Validation:
+
+    def __init__(self):
+        self.logger = logging.getLogger(__name__)
+
+    def validate_element_by_id(self, browser, id):
+        self.logger.debug( "Buscando elemento: " + id)
+        ready = False
+        while (not ready):
+            try:
+                browser.find_element_by_id(id)
+                self.logger.debug("El elmento con id {0} esta listo".format(id))
+                ready = True
+            except:
+                self.logger.debug("El elmento con id {0} no esta listo".format(id))
+                ready = False
+            time.sleep(0.25)
+
+        return ready
+
+    def validate_element_by_tag_name(self, browser, tag):
+        self.logger.debug( "Buscando elemento: " + tag)
+        ready = False
+        while (not ready):
+            try:
+                browser.find_element_by_tag_name(tag)
+                self.logger.debug("El elmento con tag name {0} esta listo".format(tag))
+                ready = True
+            except:
+                self.logger.debug("El elmento con tag name {0} no esta listo".format(tag))
+                ready = False
+            time.sleep(0.25)
+
+        return ready
+
+
+    def validate_element_not_present(self, browser, id):
+        self.logger.debug("Buscando elemento: " + id)
+        ready = False
+        while not ready:
+            try:
+                browser.find_element_by_id(id)
+                self.logger.debug("El elmento con id {0} esta presente".format(id))
+                ready = False
+            except NoSuchElementException:
+                self.logger.debug("El elmento con id {0} no esta presente".format(id))
+                ready = True
+            time.sleep(0.25)
+
+        return ready
+
+    def check_element_by_id(self, driver, id):
+        try:
+            driver.find_element_by_id(id)
+        except NoSuchElementException:
+            return False
+        return True
+    
+    def validate_download(self, filename):
+        self.logger.debug( "Se descargara el archivo: " + filename.split("\\")[-1])
+        my_file = Path(filename)
+        cont = 0 #Controla la cantidad de intentos para descargar el archivo
+        result=False
+        listo = False
+        while (not listo):
+            try:
+                if my_file.exists():
+                    self.logger.debug("Archivo descargado.!")
+                    listo = True
+                    result=True
+                else:
+                    self.logger.debug("Descargando ...")
+                    listo = False
+            except:
+                self.logger.debug("Descargando ...")
+                listo = False
+                traceback.print_exc(file=sys.stdout)
+            if cont == 120:
+                self.logger.debug("Se agoto el tiempo de espera para validar la descargar.")
+                listo = True
+                result=False
+            cont = cont+1
+            time.sleep(0.5)
+        return result

+ 2 - 0
sim/utils/exceptions.py

@@ -0,0 +1,2 @@
+class CertificadosIncorrectos(Exception):
+    pass

+ 31 - 0
sim/utils/logconfig.py

@@ -0,0 +1,31 @@
+import os
+import logging
+import logging.config
+import yaml
+
+from sim import BASE_DIR
+
+_yaml_path = os.path.join(BASE_DIR,'config', 'logging.yaml')
+_logs_path = os.path.join(BASE_DIR,'log','sim-cenace.log')
+_error_path = os.path.join(BASE_DIR,'log','error-sim-cenace.log')
+_value = os.getenv('LOG_CFG', None)
+_default_level = logging.INFO
+
+if _value:
+    path = _value
+if os.path.exists(_yaml_path):
+    with open(_yaml_path, 'rt') as f:
+        try:
+            config = yaml.safe_load(f.read())
+
+            config['handlers']['file_handler']['filename'] = _logs_path
+            config['handlers']['error_file_handler']['filename'] = _error_path
+
+            logging.config.dictConfig(config)
+        except Exception as e:
+            print(e)
+            print('Error in Logging Configuration. Using default configs')
+            logging.basicConfig(level=_default_level)
+else:
+    logging.basicConfig(level=_default_level)
+    print('Failed to load configuration file. Using default configs')

+ 102 - 0
sim/utils/mailsender.py

@@ -0,0 +1,102 @@
+import datetime
+import smtplib
+import os
+import logging
+
+from sim.config import Configuration
+import ssl
+
+config = Configuration()
+
+
+class Emailer():
+
+    def __init__(self, hostname, port, auth_user, auth_pass):
+        self.hostname = hostname
+        self.port = port
+        self.auth_user = auth_user
+        self.auth_pass = auth_pass
+        self.logger = logging.getLogger(__name__)
+
+    def read_template(self, filename):
+        from string import Template
+
+        with open(filename, 'r', encoding='utf-8') as template_file:
+            template_file_content = template_file.read()
+        return Template(template_file_content)
+
+    def send_success_email(self, from_address, to_address, alert_message, subject, type=None, report_count=0):
+
+        from email.mime.multipart import MIMEMultipart
+        from email.mime.text import MIMEText
+
+        self.logger.info("Preparando para enviar correo electrónico")
+
+        head_color = "#6bab01"
+
+        message_template = self.read_template(os.path.join(config.TEMPLATES_PATH,'success-message-inline.html'))
+
+        message = message_template.substitute(fecha=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+                                              alert_message=alert_message,
+                                              cuenta_reportes=report_count,
+                                              head_color=head_color)
+
+        msg = MIMEMultipart()
+
+        msg['From'] = from_address
+        msg['To'] = to_address
+        msg['Subject'] = subject
+
+        msg.attach(MIMEText(message, 'html'))
+        self.logger.debug("Conectando con el servidor SMTP: {0}".format(self.hostname))
+        
+        context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+        server = smtplib.SMTP(self.hostname, self.port)
+        server.starttls(context=context)
+        server.login(self.auth_user,self.auth_pass)
+        
+        self.logger.debug("Enviando correo electrónico")
+        server.sendmail(msg['From'], msg['To'], msg.as_string())
+        self.logger.debug("Correo Electrónico enviado")
+        self.logger.debug("Desconectando del servidor SMTP")
+        server.quit()
+        self.logger.info("Correo electrónico enviado con éxito a la dirección {0}".format(to_address))
+
+    def send_warning_email(self, from_address, to_address, alert_message, subject):
+
+        from email.mime.multipart import MIMEMultipart
+        from email.mime.text import MIMEText
+
+        self.logger.info("Preparando para enviar correo electrónico")
+
+        head_color = "#ffaa00"
+
+        message_template = self.read_template(os.path.join(config.TEMPLATES_PATH, 'warning-message-inline.html'))
+
+        message = message_template.substitute(fecha=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+                                              alert_message=alert_message,
+                                              head_color=head_color)
+
+        msg = MIMEMultipart()
+
+        msg['From'] = from_address
+        msg['To'] = to_address
+        msg['Subject'] = subject
+
+        msg.attach(MIMEText(message, 'html'))
+        self.logger.debug("Conectando con el servidor SMTP: {0}".format(self.hostname))
+        context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+        server = smtplib.SMTP(self.hostname, self.port)
+        server.starttls(context=context)
+        server.login(self.auth_user,self.auth_pass)
+        self.logger.debug("Enviando correo electrónico")
+        server.sendmail(msg['From'], msg['To'], msg.as_string())
+        self.logger.debug("Correo Electrónico enviado")
+        self.logger.debug("Desconectando del servidor SMTP")
+        server.quit()
+        self.logger.info("Correo electrónico enviado con éxito a la dirección {0}".format(to_address))
+
+
+
+
+

+ 27 - 0
sim/utils/unzipfile.py

@@ -0,0 +1,27 @@
+import zipfile
+import os
+import time
+
+def unzipfile_and_remove(downloadFilePath, filename):
+
+    filepath = downloadFilePath + "\\" + filename + ".zip"
+
+    while not os.path.exists(filepath):
+        time.sleep(1)
+
+    if os.path.isfile(filepath):
+
+        zip_ref = zipfile.ZipFile(filepath, "r")
+        zip_ref.extractall(downloadFilePath)
+        zip_ref.close()
+
+        os.remove(filepath)
+    else:
+        raise ValueError("%s isn't a file!" % filepath)
+
+
+
+
+
+
+

+ 378 - 0
templates/message.html

@@ -0,0 +1,378 @@
+<!doctype html>
+<html>
+  <head>
+    <meta name="viewport" content="width=device-width" />
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <title>Simple Transactional Email</title>
+    <style>
+      /* -------------------------------------
+          GLOBAL RESETS
+      ------------------------------------- */
+
+      /*All the styling goes here*/
+
+      img {
+        border: none;
+        -ms-interpolation-mode: bicubic;
+        max-width: 100%;
+      }
+      body {
+        background-color: #f6f6f6;
+        font-family: sans-serif;
+        -webkit-font-smoothing: antialiased;
+        font-size: 14px;
+        line-height: 1.4;
+        margin: 0;
+        padding: 0;
+        -ms-text-size-adjust: 100%;
+        -webkit-text-size-adjust: 100%;
+      }
+      table {
+        border-collapse: separate;
+        mso-table-lspace: 0pt;
+        mso-table-rspace: 0pt;
+        width: 100%; }
+        table td {
+          font-family: sans-serif;
+          font-size: 14px;
+          vertical-align: top;
+      }
+      /* -------------------------------------
+          BODY & CONTAINER
+      ------------------------------------- */
+      .body {
+        background-color: #f6f6f6;
+        width: 100%;
+      }
+      /* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
+      .container {
+        display: block;
+        margin: 0 auto !important;
+        /* makes it centered */
+        max-width: 580px;
+        padding: 10px;
+        width: 580px;
+      }
+      /* This should also be a block element, so that it will fill 100% of the .container */
+      .content {
+        box-sizing: border-box;
+        display: block;
+        margin: 0 auto;
+        max-width: 580px;
+        padding: 10px;
+      }
+      /* -------------------------------------
+          HEADER, FOOTER, MAIN
+      ------------------------------------- */
+      .main {
+        background: #ffffff;
+        border-radius: 3px;
+        width: 100%;
+      }
+      .wrapper {
+        box-sizing: border-box;
+        padding: 20px;
+      }
+      .content-block {
+        padding-bottom: 10px;
+        padding-top: 10px;
+      }
+      .footer {
+        clear: both;
+        margin-top: 10px;
+        text-align: center;
+        width: 100%;
+      }
+        .footer td,
+        .footer p,
+        .footer span,
+        .footer a {
+          color: #999999;
+          font-size: 12px;
+          text-align: center;
+      }
+      /* -------------------------------------
+          TYPOGRAPHY
+      ------------------------------------- */
+      h1,
+      h2,
+      h3,
+      h4 {
+        color: #000000;
+        font-family: sans-serif;
+        font-weight: 400;
+        line-height: 1.4;
+        margin: 0;
+        margin-bottom: 30px;
+      }
+      h1 {
+        font-size: 35px;
+        font-weight: 300;
+        text-align: center;
+        text-transform: capitalize;
+      }
+      p,
+      ul,
+      ol {
+        font-family: sans-serif;
+        font-size: 14px;
+        font-weight: normal;
+        margin: 0;
+        margin-bottom: 15px;
+      }
+        p li,
+        ul li,
+        ol li {
+          list-style-position: inside;
+          margin-left: 5px;
+      }
+      a {
+        color: #3498db;
+        text-decoration: underline;
+      }
+      /* -------------------------------------
+          BUTTONS
+      ------------------------------------- */
+      .btn {
+        box-sizing: border-box;
+        width: 100%; }
+        .btn > tbody > tr > td {
+          padding-bottom: 15px; }
+        .btn table {
+          width: auto;
+      }
+        .btn table td {
+          background-color: #ffffff;
+          border-radius: 5px;
+          text-align: center;
+      }
+        .btn a {
+          background-color: #ffffff;
+          border: solid 1px #3498db;
+          border-radius: 5px;
+          box-sizing: border-box;
+          color: #3498db;
+          cursor: pointer;
+          display: inline-block;
+          font-size: 14px;
+          font-weight: bold;
+          margin: 0;
+          padding: 12px 25px;
+          text-decoration: none;
+      }
+      .btn-primary table td {
+        background-color: #3498db;
+      }
+      .btn-primary a {
+        background-color: #3498db;
+        border-color: #3498db;
+        color: #ffffff;
+      }
+      /* -------------------------------------
+          OTHER STYLES THAT MIGHT BE USEFUL
+      ------------------------------------- */
+      .last {
+        margin-bottom: 0;
+      }
+      .first {
+        margin-top: 0;
+      }
+      .align-center {
+        text-align: center;
+      }
+      .align-right {
+        text-align: right;
+      }
+      .align-left {
+        text-align: left;
+      }
+      .clear {
+        clear: both;
+      }
+      .mt0 {
+        margin-top: 0;
+      }
+      .mb0 {
+        margin-bottom: 0;
+      }
+      .preheader {
+        color: transparent;
+        display: none;
+        height: 0;
+        max-height: 0;
+        max-width: 0;
+        opacity: 0;
+        overflow: hidden;
+        mso-hide: all;
+        visibility: hidden;
+        width: 0;
+      }
+      .powered-by a {
+        text-decoration: none;
+      }
+      hr {
+        border: 0;
+        border-bottom: 1px solid #f6f6f6;
+        margin: 20px 0;
+      }
+      /* -------------------------------------
+          RESPONSIVE AND MOBILE FRIENDLY STYLES
+      ------------------------------------- */
+      @media only screen and (max-width: 620px) {
+        table[class=body] h1 {
+          font-size: 28px !important;
+          margin-bottom: 10px !important;
+        }
+        table[class=body] p,
+        table[class=body] ul,
+        table[class=body] ol,
+        table[class=body] td,
+        table[class=body] span,
+        table[class=body] a {
+          font-size: 16px !important;
+        }
+        table[class=body] .wrapper,
+        table[class=body] .article {
+          padding: 10px !important;
+        }
+        table[class=body] .content {
+          padding: 0 !important;
+        }
+        table[class=body] .container {
+          padding: 0 !important;
+          width: 100% !important;
+        }
+        table[class=body] .main {
+          border-left-width: 0 !important;
+          border-radius: 0 !important;
+          border-right-width: 0 !important;
+        }
+        table[class=body] .btn table {
+          width: 100% !important;
+        }
+        table[class=body] .btn a {
+          width: 100% !important;
+        }
+        table[class=body] .img-responsive {
+          height: auto !important;
+          max-width: 100% !important;
+          width: auto !important;
+        }
+      }
+      /* -------------------------------------
+          PRESERVE THESE STYLES IN THE HEAD
+      ------------------------------------- */
+      @media all {
+        .ExternalClass {
+          width: 100%;
+        }
+        .ExternalClass,
+        .ExternalClass p,
+        .ExternalClass span,
+        .ExternalClass font,
+        .ExternalClass td,
+        .ExternalClass div {
+          line-height: 100%;
+        }
+        .apple-link a {
+          color: inherit !important;
+          font-family: inherit !important;
+          font-size: inherit !important;
+          font-weight: inherit !important;
+          line-height: inherit !important;
+          text-decoration: none !important;
+        }
+        .btn-primary table td:hover {
+          background-color: #34495e !important;
+        }
+        .btn-primary a:hover {
+          background-color: #34495e !important;
+          border-color: #34495e !important;
+        }
+      }
+    </style>
+  </head>
+  <body class="">
+    <span class="preheader">Notificación Robot Descarga Documentos Fiscales CENACE</span>
+    <table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
+      <tr>
+        <td>&nbsp;</td>
+        <td class="container">
+          <div class="content">
+
+            <!-- START CENTERED WHITE CONTAINER -->
+            <table role="presentation" class="main">
+
+              <!-- START MAIN CONTENT AREA -->
+              <tr>
+                <td class="wrapper">
+                  <table role="presentation" border="0" cellpadding="0" cellspacing="0">
+                    <tr>
+                      <td>
+                        <p align="right">${fecha}</p>
+                        <p>La descarga de los archivos de Documentos Fiscales del Sistema de Información de Mercado del CENACE se realizó con el siguiente estado</p>
+                        <table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
+                          <tbody>
+                            <tr>
+                              <td align="center">
+                                <table role="presentation" border="0" cellpadding="0" cellspacing="0">
+                                  <tbody>
+                                    <tr>
+                                      <td> <a class="btn-primary">${estado}</a> </td>
+                                    </tr>
+                                  </tbody>
+                                </table>
+                              </td>
+                            </tr>
+                          </tbody>
+                        </table>
+                        <p>Puede ver los Documentos Fiscales en el siguiente enlace:</p>
+                        <table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary">
+                          <tbody>
+                            <tr>
+                              <td align="center">
+                                <table role="presentation" border="0" cellpadding="0" cellspacing="0">
+                                  <tbody>
+                                    <tr>
+                                      <td> <a class="btn-primary">http://ecd.metco.com</a> </td>
+                                    </tr>
+                                  </tbody>
+                                </table>
+                              </td>
+                            </tr>
+                          </tbody>
+                        </table>
+                      </td>
+                    </tr>
+                  </table>
+                </td>
+              </tr>
+
+            <!-- END MAIN CONTENT AREA -->
+            </table>
+            <!-- END CENTERED WHITE CONTAINER -->
+
+            <!-- START FOOTER -->
+            <div class="footer">
+              <table role="presentation" border="0" cellpadding="0" cellspacing="0">
+                <tr>
+                  <td class="content-block">
+                    <span class="apple-link">Mercados Eléctricos de Centroamérica</span>
+                  </td>
+                </tr>
+                <tr>
+                  <td class="content-block powered-by">
+                    Powered by Mercados BI Team
+                  </td>
+                </tr>
+              </table>
+            </div>
+            <!-- END FOOTER -->
+
+          </div>
+        </td>
+        <td>&nbsp;</td>
+      </tr>
+    </table>
+  </body>
+</html>

+ 149 - 0
templates/success-message-inline.html

@@ -0,0 +1,149 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+<head>
+    <meta name="viewport" content="width=device-width"/>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <title>Alerts e.g. approaching your limit</title>
+
+
+    <style type="text/css">
+img {
+max-width: 100%;
+}
+body {
+-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em;
+}
+body {
+background-color: #f6f6f6;
+}
+@media only screen and (max-width: 640px) {
+  body {
+    padding: 0 !important;
+  }
+  h1 {
+    font-weight: 800 !important; margin: 20px 0 5px !important;
+  }
+  h2 {
+    font-weight: 800 !important; margin: 20px 0 5px !important;
+  }
+  h3 {
+    font-weight: 800 !important; margin: 20px 0 5px !important;
+  }
+  h4 {
+    font-weight: 800 !important; margin: 20px 0 5px !important;
+  }
+  h1 {
+    font-size: 22px !important;
+  }
+  h2 {
+    font-size: 18px !important;
+  }
+  h3 {
+    font-size: 16px !important;
+  }
+  .container {
+    padding: 0 !important; width: 100% !important;
+  }
+  .content {
+    padding: 0 !important;
+  }
+  .content-wrap {
+    padding: 10px !important;
+  }
+  .invoice {
+    width: 100% !important;
+  }
+}
+
+    </style>
+</head>
+
+<body itemscope itemtype="http://schema.org/EmailMessage"
+      style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
+      bgcolor="#f6f6f6">
+
+<table class="body-wrap"
+       style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;"
+       bgcolor="#f6f6f6">
+    <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+        <td style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
+            valign="top"></td>
+        <td class="container" width="600"
+            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;"
+            valign="top">
+            <div class="content"
+                 style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;">
+                <table class="main" width="100%" cellpadding="0" cellspacing="0"
+                       style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;"
+                       bgcolor="#fff">
+                    <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                        <td class="alert alert-warning"
+                            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background-color: ${head_color}; margin: 0; padding: 20px;"
+                            align="center" bgcolor="${head_color}" valign="top">
+                            ${alert_message}
+                        </td>
+                    </tr>
+                    <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                        <td class="content-wrap"
+                            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;"
+                            valign="top">
+                            <table width="100%" cellpadding="0" cellspacing="0"
+                                   style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                    <td class="content-block"
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;"
+                                        valign="top">
+                                        Se descargaron un total de <strong
+                                            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">${cuenta_reportes}
+                                        reportes</strong> de la web del CENACE.
+                                    </td>
+                                </tr>
+                                <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                    <td class="content-block"
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;"
+                                        valign="top">
+                                        Para ver los Documentos Fiscales puede hacer click en el siguiente enlace
+                                    </td>
+                                </tr>
+                                <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                    <td class="content-block"
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;"
+                                        valign="top" align="center">
+                                        <a href="http://www.mailgun.com" class="btn-primary"
+                                           style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #348eda; margin: 0; border-color: #348eda; border-style: solid; border-width: 10px 20px;">Ver Documentos Fiscales</a>
+                                    </td>
+                                </tr>
+                                <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                    <td class="content-block"
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;"
+                                        valign="top">
+                                        Este mensaje es autogenerado. Favor no responder. <br>
+                                        Mensaje enviado el ${fecha}
+                                    </td>
+                                </tr>
+                            </table>
+                        </td>
+                    </tr>
+                </table>
+                <div class="footer"
+                     style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
+                    <table width="100%"
+                           style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                        <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                            <td class="aligncenter content-block"
+                                style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;"
+                                align="center" valign="top"><p>Mercados Eléctricos de Centroamérica | Market Intelligence Team</p>
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </td>
+        <td style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
+            valign="top"></td>
+    </tr>
+</table>
+</body>
+</html>

+ 139 - 0
templates/warning-message-inline.html

@@ -0,0 +1,139 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+<head>
+    <meta name="viewport" content="width=device-width"/>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <title>Falla en Descarga de Documentos Fiscales CENACE</title>
+
+
+    <style type="text/css">
+img {
+max-width: 100%;
+}
+body {
+-webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em;
+}
+body {
+background-color: #f6f6f6;
+}
+@media only screen and (max-width: 640px) {
+  body {
+    padding: 0 !important;
+  }
+  h1 {
+    font-weight: 800 !important; margin: 20px 0 5px !important;
+  }
+  h2 {
+    font-weight: 800 !important; margin: 20px 0 5px !important;
+  }
+  h3 {
+    font-weight: 800 !important; margin: 20px 0 5px !important;
+  }
+  h4 {
+    font-weight: 800 !important; margin: 20px 0 5px !important;
+  }
+  h1 {
+    font-size: 22px !important;
+  }
+  h2 {
+    font-size: 18px !important;
+  }
+  h3 {
+    font-size: 16px !important;
+  }
+  .container {
+    padding: 0 !important; width: 100% !important;
+  }
+  .content {
+    padding: 0 !important;
+  }
+  .content-wrap {
+    padding: 10px !important;
+  }
+  .invoice {
+    width: 100% !important;
+  }
+}
+
+    </style>
+</head>
+
+<body itemscope itemtype="http://schema.org/EmailMessage"
+      style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
+      bgcolor="#f6f6f6">
+
+<table class="body-wrap"
+       style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;"
+       bgcolor="#f6f6f6">
+    <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+        <td style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
+            valign="top"></td>
+        <td class="container" width="600"
+            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;"
+            valign="top">
+            <div class="content"
+                 style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;">
+                <table class="main" width="100%" cellpadding="0" cellspacing="0"
+                       style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;"
+                       bgcolor="#fff">
+                    <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                        <td class="alert alert-warning"
+                            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: bold; text-align: center; border-radius: 3px 3px 0 0; background-color: ${head_color}; margin: 0; padding: 20px;"
+                            align="center" bgcolor="${head_color}" valign="top">
+                            ${alert_message}
+                        </td>
+                    </tr>
+                    <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                        <td class="content-wrap"
+                            style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;"
+                            valign="top">
+                            <table width="100%" cellpadding="0" cellspacing="0"
+                                   style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                    <td class="content-block"
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;"
+                                        valign="top">
+                                        Error en la descarga de Documentos Fiscales desde el SIM del CENACE
+                                    </td>
+                                </tr>
+                                <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                    <td class="content-block"
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;"
+                                        valign="top">
+                                        Para mas detalle verifique los logs de la aplicación.
+                                    </td>
+                                </tr>
+                                <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                                    <td class="content-block"
+                                        style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;"
+                                        valign="top">
+                                        Este mensaje es autogenerado. Favor no responder. <br>
+                                        Mensaje enviado el ${fecha}
+                                    </td>
+                                </tr>
+                            </table>
+                        </td>
+                    </tr>
+                </table>
+                <div class="footer"
+                     style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
+                    <table width="100%"
+                           style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                        <tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
+                            <td class="aligncenter content-block"
+                                style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;"
+                                align="center" valign="top"><p>Mercados Eléctricos de Centroamérica | Market Intelligence Team</p>
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+            </div>
+        </td>
+        <td style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
+            valign="top"></td>
+    </tr>
+</table>
+</body>
+</html>