UNPKG

bot-functions

Version:
1,664 lines 61.4 kB
require('dotenv').config();
const os = require('os');
const fs = require('fs');
const axios = require('axios');
const FormData = require('form-data');
const concat = require("concat-stream");
const glob = require("glob");
const AdmZip = require('adm-zip');
const log = require('lambda-log');
const moment = require('moment');

const imaps = require('imap-simple');
const _ = require('lodash');
const { simpleParser } = require('mailparser');
const xpath = require('xpath');
const { JSDOM } = require('jsdom');

const { S3Client } = require("@aws-sdk/client-s3");
const { Upload } = require("@aws-sdk/lib-storage");

const separador = (os.type() === "Windows_NT") ? `\\` : "/";

async function obtenerCorreos(email, password, subject) {
  const config = {
    imap: {
      user: email,
      password: password,
      host: 'imap.gmail.com', // Cambia esto si no estás usando Gmail
      port: 993,
      tls: true,
      tlsOptions: { rejectUnauthorized: false }, // Permitir certificados autofirmados
      authTimeout: 3000
    }
  };
  try {
    log.info("==> Conexion a Correos");
    const connection = await imaps.connect(config);
    await connection.openBox('INBOX');
    const searchCriteria = ['UNSEEN'];
    const fetchOptions = {
      bodies: ['HEADER', 'TEXT', ''],
      struct: true,
      markSeen: false,
      limit: 5 
    };
    log.info("==> Busqueda de correos");
    const messages = await connection.search(searchCriteria, fetchOptions);
    log.info("Exito al conectar");
    const latestMessage = messages[messages.length - 1];
    const parts = latestMessage.parts.filter(part => part.which === 'TEXT' || part.which === '');
    const allParts = parts.map(part => part.body).join('');
    const parsed = await simpleParser(allParts);
    let cleanText = parsed.text ? parsed.text.replace(/\n/g, '') : '';
    const keywords = ['client', 'Please', 'access', 'rights', 'either', 'owners', 'podido', 'prueba', 'dentro', 'enlace', 'google', 'enviar', 'accept'];
    const regex = new RegExp(keywords.join('|'), 'gi');
    cleanText = cleanText.replace(regex, '');
    const codeMatches = cleanText.match(/\b\w{6}\b/g);
    const codes = codeMatches ? codeMatches : [];
    const parsedMessage = {
      from: parsed.from ? parsed.from.text : '',
      subject: parsed.subject || '',
      date: parsed.date || '',
      text: cleanText,
      html: parsed.html || '',
      codes: codes
    };
    if (subject == parsedMessage.subject) {
      log.info("==> Se elimina el correo");
      await connection.addFlags(latestMessage.attributes.uid, '\\Deleted');
      await connection.closeBox(true);
      await connection.end();
      return parsedMessage;
    }
    return ['']
  } catch (err) {
    console.error('Error al obtener el correo:', err);
  }
}

async function obtenerCodigo(subject) {
  const codes = await axios.post(`${process.env.API_CORREOS}`, { subject });
  log.info(codes.data);
  return codes.data;
}

async function obtenerCodigoNuevo(subject) {
  const codes = await axios.post(`${process.env.API_NEW_CORREOS}`, { subject });
  log.info(codes.data);
  return codes.data;
}

class EmailManager {
  constructor() {
    this.connection = null;
  }

  async connectEmail(config) {
    const emailConfig = {
      imap: {
        user: config.mail,
        password: config.pass,
        host: config.host,
        port: config.port,
        tls: config.tls,
        tlsOptions: { rejectUnauthorized: config.tlsOptions.rejectUnauthorized },
        authTimeout: 10000
      }
    };
    try {
      this.connection = await imaps.connect(emailConfig);
      await this.connection.openBox('INBOX');
      log.info("Successfully connected to email account");
      return { success: true, message: 'Connected successfully' };
    } catch (error) {
      log.error("Error connecting to email:", error);
      throw new Error(`Failed to connect to email: ${error.message}`);
    }
  }

  extractTextWithXPath(htmlContent, xpathExpression) {
    if (!htmlContent || !xpathExpression) {
      log.warn("HTML content or XPath expression missing");
      return [];
    }
    try {
      const dom = new JSDOM(htmlContent);
      const document = dom.window.document;
      const extractedTexts = new Set();
      const xpathResult = document.evaluate(
        xpathExpression,
        document,
        null,
        dom.window.XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
        null
      );
      for (let i = 0; i < xpathResult.snapshotLength; i++) {
        const node = xpathResult.snapshotItem(i);
        this._addNodeContent(extractedTexts, node);
      }
      if (extractedTexts.size === 0) {
        try {
          const nodes = xpath.select(xpathExpression, document);
          this._processNodeList(extractedTexts, nodes);
        } catch (selectError) {
          this._tryCssFallback(extractedTexts, xpathExpression, document);
        }
      }
      return Array.from(extractedTexts).filter(text => text.trim().length > 0);
    } catch (error) {
      log.error("XPath extraction failed completely:", error);
      return [];
    }
  }

  _addNodeContent(collection, node) {
    if (!node) return;
    let content = '';
    switch (node.nodeType) {
      case 1:
        content = node.textContent?.trim();
        break;
      case 2:
        content = node.value?.trim();
        break;
      case 3:
        content = node.data?.trim();
        break;
    }
    if (content) collection.add(content);
}

  _processNodeList(collection, nodes) {
    if (Array.isArray(nodes)) {
      nodes.forEach(node => this._addNodeContent(collection, node));
    } else if (nodes) {
      if (typeof nodes === 'string') {
        collection.add(nodes.trim());
      } else {
        this._addNodeContent(collection, nodes);
      }
    }
  }

  _tryCssFallback(collection, xpathExpression, document) {
    try {
      const cssSelector = this._xpathToCssSelector(xpathExpression);
      if (!cssSelector) return;
      document.querySelectorAll(cssSelector).forEach(element => {
        const text = element.textContent?.trim();
        if (text) collection.add(text);
      });
    } catch (cssError) {
      log.debug("CSS fallback also failed:", cssError);
    }
  }

  _xpathToCssSelector(xpath) {
    if (!xpath || typeof xpath !== 'string') return null;
    const conversions = [
      { pattern: /^\/\/(\w+)$/, replacement: '$1' },
      { pattern: /^\/\/(\w+)\[@class="([^"]+)"\]$/, replacement: '$1.$2' },
      { pattern: /^\/\/(\w+)\[@id="([^"]+)"\]$/, replacement: '$1#$2' },
      { pattern: /^\/\/\*\[@class="([^"]+)"\]$/, replacement: '.$1' },
      { pattern: /^\/\/(\w+)\/text\(\)$/, replacement: '$1' }
    ];
    for (const conv of conversions) {
      if (conv.pattern.test(xpath)) {
        return xpath.replace(conv.pattern, conv.replacement);
      }
    }
    try {
      let css = xpath.replace(/\/\//g, '/').replace(/^\//, '');
      const parts = css.split('/').filter(p => p && p.trim().length);
      const mapped = parts.map(part => {
        part = part.replace(/\[@id=['"]([^'"]+)['"]\]/, '#$1');
        part = part.replace(/\[@class=['"]([^'"]+)['"]\]/, (_, cls) => '.' + cls.trim().split(/\s+/).join('.'));
        part = part.replace(/\[@(\w+)=['"]([^'"]+)['"]\]/, '[$1="$2"]');
        part = part.replace(/\/?text\(\)/g, '');
        return part.trim();
      }).filter(Boolean);
      const selector = mapped.join(' ');
      return selector || null;
    } catch (e) {
      return null;
    }
  }

  _buildOrFromEmails(emails) {
    if (emails.length === 1) {
      return ['FROM', emails[0]];
    }
    return emails.reduce((acc, email) => {
      if (!acc) return ['FROM', email];
      return ['OR', acc, ['FROM', email]];
    }, null);
  }

  async searchEmails(searchOptions = {}) {
    if (!this.connection) {
      throw new Error('Not connected to email account.');
    }
    const { subject, fromEmail, unreadOnly } = searchOptions;
    log.info("searchEmails criteria", { subject, fromEmail, unreadOnly });
    try {
      const searchCriteria = [];
      if (unreadOnly) searchCriteria.push('UNSEEN');
      if (subject) searchCriteria.push(['SUBJECT', subject]);
      const fromEmails = fromEmail.split(';').map(s => s.trim());
      searchCriteria.push(this._buildOrFromEmails(fromEmails));
      const sinceDate = new Date(Date.now() - 2 * 60 * 1000);
      searchCriteria.push(['SINCE', sinceDate]);
      const fetchOptions = {
        bodies: ['HEADER', 'TEXT', ''],
        struct: true,
        markSeen: false
      };
      const messages = await this.connection.search(searchCriteria, fetchOptions);
      log.info(`Found ${messages.length} matching emails before limiting`);
      let limitedMessages = messages;
      try {
        limitedMessages = [...messages]
          .sort((a, b) => {
            const ad = new Date(a?.attributes?.date || 0).getTime();
            const bd = new Date(b?.attributes?.date || 0).getTime();
            if (ad && bd) return ad - bd;
            return (a?.attributes?.uid || 0) - (b?.attributes?.uid || 0);
          });
          limitedMessages = limitedMessages.slice(-1);
      } catch (e) {
        log.warn("Failed to sort/limit messages, proceeding with all", { error: e.message });
      }
      log.info(`Processing ${limitedMessages.length} email(s) after limiting`);
      const parsedMessages = [];
      for (const message of limitedMessages) {
        try {
          const headerPart = message.parts.find(part => part.which === 'HEADER');
          const rawMessage = message.parts.find(part => part.which === '');
          const headers = headerPart?.body || {};
          let htmlContent = '';
          let textContent = '';
          if (rawMessage?.body) {
            try {
              const parsed = await simpleParser(rawMessage.body);
              htmlContent = parsed.html || '';
            } catch (parseError) {
              log.error('Error parsing raw message:', parseError);
            }
          }
          const parsedMessage = {
            uid: message.attributes.uid,
            from: headers.from ? headers.from[0] : '',
            subject: headers.subject ? headers.subject[0] : '',
            html: htmlContent,
            text: textContent,
            date: headers.date ? headers.date[0] : '',
            flags: message.attributes.flags || []
          };
          parsedMessages.push(parsedMessage);
        } catch (error) {
          log.error('Error processing message:', error);
        }
      }
      return parsedMessages;
    } catch (error) {
      log.error("Error searching emails:", error);
      throw new Error(`Failed to search emails: ${error.message}`);
    }
  }

  async searchAndExtract(searchOptions) {
    const emails = await this.searchEmails(searchOptions);
    const results = [];
    for (const email of emails) {
      let extractedData = [];
      if (email.html) {
        log.info("Extracting from HTML content");
        extractedData = this.extractTextWithXPath(email.html, searchOptions.xpathExpression);
      }
      results.push({
        ...email,
        extractedData
      });
    }
    return results;
  }
  
  async deleteEmails(uids) {
    if (!this.connection) {
      throw new Error('Not connected to email account');
    }
    
    const uidArray = Array.isArray(uids) ? uids : [uids];
    
    try {
      for (const uid of uidArray) {
        await this.connection.addFlags(uid, '\\Deleted');
      }
      await this.connection.imap.expunge();
      log.info(`Successfully deleted ${uidArray.length} email(s)`);
      
      return { 
        success: true, 
        deletedCount: uidArray.length, 
        action: 'deleted' 
      };
      
    } catch (error) {
      log.error("Error deleting emails:", error);
      throw new Error(`Failed to delete emails: ${error.message}`);
    } finally {
      if (this.connection) {
        try {
          await this.connection.end();
          this.connection = null;
        } catch (error) {
          log.error("Error disconnecting:", error);
        }
      }
    }
  }
}

async function manageEmails(config, searchOptions) {
  const emailManager = new EmailManager();
  try {
    await emailManager.connectEmail(config);
    let results;
    if (!searchOptions.xpathExpression) {
      throw new Error("xpathExpression is mandatory");
    }
    results = await emailManager.searchAndExtract(searchOptions);
    results.forEach( async (email) => {
      await emailManager.deleteEmails(email.uid);
    });
    return { success: true, emails: results };
  } catch (error) {
    log.error("Error in manageEmails:", error);
    return { success: false, error: error.message, emails: [] };
  }
}

async function* asyncGenerator(final) {
  var i = 0;
  while (i < final) {
    yield i++;
  }
}

const waitNtype = async (page, selector, text, timeout = 60000, visible = true) => {
  return new Promise(async function (resolve) {
    try {
      await sleep(1000);
      await page.waitForSelector(selector, { timeout: timeout, visible: visible });
      await page.type(selector, text);
      await sleep(3000);
      resolve(0);
    }
    catch (e4) { resolve(1) }
  });
}

const waitNclick = async (page, selector, timeout = 60000, visible = true) => {
  return new Promise(async function (resolve) {
    try {
      await sleep(1000);
      await page.waitForSelector(selector, { timeout: timeout, visible: visible });
      await (await page.$$(selector))[0].click().then(x => resolve(0));

    }
    catch (e4) { resolve(1) }
  });
}

const pupp_findSelector = async (page, selector, time = 60000, visible = true) => {
  return new Promise(async function (resolve) {
    try {
      await page.waitForSelector(selector, { timeout: time, visible: visible }).then(function () {
        resolve(true);
      });
    }
    catch (e1) {
      resolve(false);
    }
  });
};

const sleep = (waitTimeInMs) => new Promise(resolve => setTimeout(resolve, waitTimeInMs));

const waitFile = async (file, time = 500) => {
  return new Promise(async function (resolve) {
    let inc = 0;
    let found = [];
    while (!found.length && inc <= time) {
      glob(file, function (err, files) { if (err) { log.error(err.message) } else { found = files; } });
      await sleep(1000);
      inc++;
    }
    if (found.length) { log.info(`file available after ${inc} seconds`); resolve(found[0]) }
    else { log.error(`Time exceeded`); resolve(false) }
  });
}

const rename = async (inName, outName, ext) => {
  fs.rename(inName + ext, outName + ext, function (err) {
    if (err)
      log.error(err);
  });
};

const insertEvidence = async (rutaFile, rutaImg, db, idgfc, periodo, fecha, tipo) => {
  return new Promise(async function (resolve) {
    const strFile = (rutaFile != "") ? "rutaArchivo" : "rutaCaptura";
    const fileInsert = (rutaFile != "") ? process.env.DO_PATH + rutaFile : process.env.DO_PATH + rutaImg;
    const regs = await axios.post(process.env.HAS_EVIDENCE_PATH, { db, idgfc, periodo, fecha });
    if (regs.data.data) {
      await axios.post(process.env.UPDATE_EVIDENCE_PATH, { db, strFile, tipo, fileInsert, idgfc, periodo, fecha });
      resolve(true);
    } else {
      await axios.post(process.env.INSERT_EVIDENCE_PATH, { db, strFile, tipo, fileInsert, idgfc, periodo, fecha });
      resolve(true);
    }
  })
}

const insertEvidence2 = async (rutaFile, rutaImg, db, idgfc, periodo, fecha, tipo) => {
  return new Promise(async function (resolve) {
    const strFile = (rutaFile != "") ? "rutaArchivo" : "rutaCaptura";
    const fileInsert = (rutaFile != "") ? process.env.DO_PATH + rutaFile : process.env.DO_PATH + rutaImg;
    const regs = await axios.post(process.env.HAS_EVIDENCE_PATH2, { db, idgfc, periodo, fecha });
    if (regs.data.data) {
      await axios.post(process.env.UPDATE_EVIDENCE_PATH2, { db, strFile, tipo, fileInsert, idgfc, periodo, fecha });
      resolve(true);
    } else {
      await axios.post(process.env.INSERT_EVIDENCE_PATH2, { db, strFile, tipo, fileInsert, idgfc, periodo, fecha });
      resolve(true);
    }
  })
}

const insertEvidence3 = async (rutaFile, rutaImg, db, idgfc, periodo, fecha, tipo) => {
  return new Promise(async function (resolve) {
    const strFile = (rutaFile != "") ? "rutaArchivo" : "rutaCaptura";
    const fileInsert = (rutaFile != "") ? rutaFile : rutaImg;
    const regs = await axios.post(process.env.HAS_EVIDENCE_PATH, { db, idgfc, periodo, fecha });
    if (regs.data.data) {
      await axios.post(process.env.UPDATE_EVIDENCE_PATH, { db, strFile, tipo, fileInsert, idgfc, periodo, fecha });
      resolve(true);
    } else {
      await axios.post(process.env.INSERT_EVIDENCE_PATH, { db, strFile, tipo, fileInsert, idgfc, periodo, fecha });
      resolve(true);
    }
  })
}

const insertEvidence4 = async (rutaFile, rutaImg, db, idgfc, periodo, fecha, tipo) => {
  return new Promise(async function (resolve) {
    const strFile = (rutaFile != "") ? "rutaArchivo" : "rutaCaptura";
    const fileInsert = (rutaFile != "") ? rutaFile : rutaImg;
    const regs = await axios.post(process.env.HAS_EVIDENCE_PATH2, { db, idgfc, periodo, fecha });
    if (regs.data.data) {
      await axios.post(process.env.UPDATE_EVIDENCE_PATH2, { db, strFile, tipo, fileInsert, idgfc, periodo, fecha });
      resolve(true);
    } else {
      await axios.post(process.env.INSERT_EVIDENCE_PATH2, { db, strFile, tipo, fileInsert, idgfc, periodo, fecha });
      resolve(true);
    }
  })
}

// inserEvidence original, para que las evidencias se inserten por ejecucion
const insertEvidenceCore = async (rutaFile, rutaImg, db, idgfc, periodo, fecha, tipo) => {
  return new Promise(async function (resolve) {
    const strFile = (rutaFile != "") ? "rutaArchivo" : "rutaCaptura";
    const fileInsert = (rutaFile != "") ? rutaFile : rutaImg;
    await axios.post(process.env.INSERT_EVIDENCE_CORE, { db, strFile, tipo, fileInsert, idgfc, periodo, fecha });
    resolve(true);
  });
}

const insertLog = async (params, codigo) => {
  return new Promise(async function (resolve) {
    try {
      if (params.reporte.period == 0) rutaLog = process.env.INSERT_LOG_PATH2;
      else rutaLog = process.env.INSERT_LOG_PATH;

      const insertLogResponse = await axios.post(rutaLog, { codigo, params });

      return resolve(insertLogResponse.data.data)
    } catch (e) {

      resolve();
    }
  });
};

const insertLog2 = async (params, codigo) => {
  return new Promise(async function (resolve) {
    try {
      const insertLogResponse = await axios.post(process.env.INSERT_LOG_PATH2, { codigo, params });

      return resolve(insertLogResponse.data.data)
    } catch (e) {

      resolve();
    }
  });
};

const insertLogExec = async (params, datoActual, datoAnterior) => {
  return new Promise(async function (resolve) {
    try {
      const insertLogExecResponse = await axios.post(process.env.INSERT_LOG_EXEC, { datoActual, datoAnterior, params });
      return resolve(insertLogExecResponse.data.data)
    } catch (e) {

      resolve();
    }
  });
};

const checkData = async (params) => {
  return new Promise(async (resolve) => {
    try {
      actualFunction = "checkData";
      let campoC;
      const campo = (['v', 't'].includes(params.reporte.type.toLowerCase())) ? "ventasUnidades" : "existenciasUnidades";
      if (params.reporte.dateStr == "fecha") {
        campoC = params.reporte.date1;
      } else {
        campoC = params.reporte.yearWeekOrMonth;
      }
      const result = await axios.post(process.env.HAS_DATA_PATH, { campo, table: `${params.robot.baseHyperCube}.${params.reporte.store}`, idRobot: params.robot.idPC, field: params.reporte.dateStr, value: campoC });
      const res = (parseInt(result.data.data) === 0 || result.data.data === null) ? 1 : 0;
      resolve(res);
    } catch (error) {
      log.error(error.message)
      throw new Error("No se pudo verificar existencia de información");
    }
  });
}

const checkData2 = async (params, tipo = '') => {
  return new Promise(async (resolve) => {
    try {
      actualFunction = "checkData2";
      let campoC, nameTable;
      const campo = (['v', 't'].includes(params.reporte.type.toLowerCase())) ? "ventasUnidades" : "existenciasUnidades";
      if (tipo === 'ohcal1') campoC = params.reporte.intervalB[2];
      else if (tipo === 'ohcal2') campoC = params.reporte.intervalB[0];
      else if (params.reporte.dateStr == "fecha") campoC = params.reporte.date1;
      else campoC = params.reporte.yearWeekOrMonth;
      switch (params.robot.cadena.toLowerCase()) {
        case 'walmart':
          nameTable = 'supercenter';
          break;
        case 'walmart tp':
          nameTable = 'supercentertp';
          break;
        default:
          nameTable = params.robot.cadena.replace(/\s+/g, '').toLowerCase()
          break;
      }
      if (params.robot.proyecto.toLowerCase() == 'spinmaster2') {
        switch (params.robot.cadena.toLowerCase()) {
          case 'soriana':
            nameTable = 'sorianahipermercado';
            break;
          case 'soriana tp':
            nameTable = 'sorianahipermercadotp';
            break;
          default:
            nameTable = params.robot.cadena.replace(/\s+/g, '').toLowerCase()
            break;
        }
      }
      if (params.robot.proyecto.toLowerCase() == 'hanes') {
        switch (params.robot.cadena.toLowerCase()) {
          case 'sears mf':
            nameTable = 'searsmf';
            break;
          case 'sears uw':
            nameTable = 'searsuw';
            break;
          case 'sears pt':
            nameTable = 'searspt';
            break;
          case 'cityfresko d':
            nameTable = 'cityfreskod';
            break;
          case 'cityfresko uw':
            nameTable = 'cityfreskouw';
            break;
          case 'coppel uw':
            nameTable = 'coppeluw';
            break;
          case 'coppel d':
            nameTable = 'coppeld';
            break;
          case 'coppel n':
            nameTable = 'coppeln';
            break;
          default:
            nameTable = params.robot.cadena.replace(/\s+/g, '').toLowerCase()
            break;
        }
      }
      const table = `concentrado${nameTable}`;
      const result = await axios.post(process.env.HAS_DATA_PATH2, { campo, table: `${params.robot.baseHyperCube}.${table}`, field: params.reporte.dateStr, value: campoC });
      const res = (parseInt(result.data.data) === 0 || result.data.data === null) ? 1 : 0;
      resolve(res);
    } catch (error) {
      log.error(error.message)
      throw new Error("No se pudo verificar existencia de información");
    }
  });
}

function hour() {
  const dLog = new Date();
  const hh = (dLog.getHours() < 10) ? "0" + dLog.getHours() : dLog.getHours();
  const mm = (dLog.getMinutes() < 10) ? "0" + dLog.getMinutes() : dLog.getMinutes();
  return hh + ":" + mm;
}

const getParams = async (ejecucion) => {
  return new Promise(async function (resolve, reject) {
    try {
      const sendParams = {
        "period": process.env.PERIODO,
        "date": process.env.DIA_SEMANA_MES,
        "type0": ejecucion,
        "urgent0": 0,
        "existFile0": "",
        "idPC": process.env.ID_PROYECTO_CADENA,
        "proyecto": process.env.NOMBRE_CLIENTE
      }
      log.info(sendParams);
      const paramsAPI = await axios.post(process.env.GET_PARAMS_PATH, sendParams);
      const params = paramsAPI.data.data;
      return resolve(params);
    } catch (e) {
      return reject(new Error(e.message));
    }
  })
}

const sendErrorMail = async (data) => {
  return new Promise(async function (resolve, reject) {
    try {
      const mailResponse = await axios.post(process.env.MAIL_FAILED_PATH, data);
      return resolve()
    } catch (e) {
      return reject(new Error(e.message));
    }
  });
}

const renameFile = async (pathDownloads, params) => {
  return new Promise(async (resolve, reject) => {
    actualFunction = "renameFile";
    try {
      const file = `${pathDownloads}${separador}${process.env.NAME_FILE}${process.env.EXTENSION_FILE}`;
      const fileExist = await waitFile(file, 600);
      await sleep(5000);
      if (fileExist) {
        await rename(fileExist.replace(process.env.EXTENSION_FILE, ""), `${pathDownloads}/${params.reporte.filename}`, process.env.EXTENSION_FILE);
        await waitFile(`${pathDownloads}/${params.reporte.filename}${process.env.EXTENSION_FILE}`, 10);
        await sleep(5000);
        resolve();
      } else {
        throw new Error(`Archivo ${file} no disponible tras la espera`);
      }
    } catch (error) {
      await insertLog(params, 207);
      return reject(new Error(error.message));
    }
  });
}

const sendFile = async (pathDownloads, params) => {
  return new Promise(async function (resolve, reject) {
    actualFunction = "sendFile";
    try {
      const zip = new AdmZip();
      zip.addLocalFile(`${pathDownloads}/${params.reporte.filename}${process.env.EXTENSION_FILE}`);
      zip.writeZip(`${pathDownloads}/${params.reporte.filename}.zip`);
      await sleep(5000);
      let formData = new FormData();
      formData.append("upload", fs.createReadStream(`${pathDownloads}/${params.reporte.filename}.zip`));
      const regs = await axios.post(process.env.AUTH_PATH, { email: process.env.EMAIL_API });
      let headers = formData.getHeaders();
      headers["Authorization"] = regs.data.data;
      headers["bucket"] = params.reporte.pathDOcean;
      const uploadFileResponse = await axios.post(process.env.UPLOAD_DO_PATH, formData, { headers })
      if (uploadFileResponse.status === 200) {
        await insertEvidence(`${params.reporte.pathDOcean}/${params.reporte.filename}.zip`, "",
          params.robot.baseHyperCube, params.robot.idgfc, process.env.PERIODO, params.reporte.prefix, params.reporte.type)
        await insertLog(params, 305);
        resolve();
      }
    } catch (error) {
      await insertLog(params, 108);
      return reject(new Error(error.message));
    }
  });
}

const sendFile2 = async (pathDownloads, params) => {
  return new Promise(async function (resolve, reject) {
    actualFunction = "sendFile2";
    try {
      const zip = new AdmZip();
      zip.addLocalFile(`${pathDownloads}/${params.reporte.filename}${process.env.EXTENSION_FILE}`);
      zip.writeZip(`${pathDownloads}/${params.reporte.filename}.zip`);
      await sleep(5000);
      let formData = new FormData();
      formData.append("upload", fs.createReadStream(`${pathDownloads}/${params.reporte.filename}.zip`));
      const regs = await axios.post(process.env.AUTH_PATH, { email: process.env.EMAIL_API });
      let headers = formData.getHeaders();
      headers["Authorization"] = regs.data.data;
      headers["bucket"] = params.reporte.pathDOcean;
      const uploadFileResponse = await axios.post(process.env.UPLOAD_DO_PATH, formData, { headers })
      if (uploadFileResponse.status === 200) {
        await insertEvidence2(`${params.reporte.pathDOcean}/${params.reporte.filename}.zip`, "",
          params.robot.baseHyperCube, params.robot.idgfc, process.env.PERIODO, params.reporte.prefix, params.reporte.type)
        await insertLog2(params, 305);
        resolve();
      }
    } catch (error) {
      await insertLog2(params, 108);
      return reject(new Error(error.message));
    }
  });
}

const sendImage = async (pathDownloads, params) => {
  return new Promise(async function (resolve, reject) {
    actualFunction = "sendImage";
    try {
      let formData = new FormData();
      formData.append("upload", fs.createReadStream(`${pathDownloads}/${params.reporte.filename}${process.env.EXTENSION_IMG}`));
      formData.pipe(concat(async (data) => {
        const regs = await axios.post(process.env.AUTH_PATH, { email: process.env.EMAIL_API });
        let headers = formData.getHeaders();
        headers["Authorization"] = regs.data.data;
        headers["bucket"] = params.reporte.pathDOcean;
        const uploadFileResponse = await axios.post(process.env.UPLOAD_DO_PATH, data, { headers })
        if (uploadFileResponse.status === 200) {
          await insertEvidence('', `${params.reporte.pathDOcean}/${params.reporte.filename}${process.env.EXTENSION_IMG}`,
            params.robot.baseHyperCube, params.robot.idgfc, process.env.PERIODO, params.reporte.prefix, params.reporte.type)
          await insertLog(params, 305);
          resolve();
        }
      }))
    } catch (error) {
      await insertLog(params, 108);
      return reject(new Error(error.message));
    }
  });
}

const sendImage2 = async (pathDownloads, params) => {
  return new Promise(async function (resolve, reject) {
    actualFunction = "sendImage2";
    try {
      let formData = new FormData();
      formData.append("upload", fs.createReadStream(`${pathDownloads}/${params.reporte.filename}${process.env.EXTENSION_IMG}`));
      formData.pipe(concat(async (data) => {
        const regs = await axios.post(process.env.AUTH_PATH, { email: process.env.EMAIL_API });
        let headers = formData.getHeaders();
        headers["Authorization"] = regs.data.data;
        headers["bucket"] = params.reporte.pathDOcean;
        const uploadFileResponse = await axios.post(process.env.UPLOAD_DO_PATH, data, { headers })
        if (uploadFileResponse.status === 200) {
          await insertEvidence2('', `${params.reporte.pathDOcean}/${params.reporte.filename}${process.env.EXTENSION_IMG}`,
            params.robot.baseHyperCube, params.robot.idgfc, process.env.PERIODO, params.reporte.prefix, params.reporte.type)
          await insertLog2(params, 305);
          resolve();
        }
      }))
    } catch (error) {
      await insertLog2(params, 108);
      return reject(new Error(error.message));
    }
  });
}

const sendData = async (params, dataJSON) => {
  return new Promise(async function (resolve, reject) {
    actualFunction = "sendData";
    try {
      const batchPromises = [];
      const originalKeywork = params.reporte.keywork;
      const batchSize = Math.ceil(dataJSON.length / 5000);
      for await (let pos of asyncGenerator(batchSize)) {
        const batchData = dataJSON.slice((pos * 5000), (5000 * (pos + 1)));
        params.reporte.keywork = originalKeywork + pos;
        const response = await axios({
          method: 'post', url: process.env.CREATE_PACKAGE_PATH, data: { params, dataJSON: batchData },
          maxBodyLength: Infinity, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }
        });
        batchPromises.push(response.status)
      }
      batchPromises.map(reg => log.info('Insert status', reg));
      if (batchPromises.every(reg => reg == 200)) {
        resolve({ status: "Packages inserted successfully", exit: 1 });
      } else {
        return reject(new Error('No se insertaron todos los paquetes'));
      }
    } catch (error) {
      await insertLog(params, 107);
      return reject(new Error(error.message));
    }
  });
}

const sendData2 = async (params, dataJSON) => {
  return new Promise(async function (resolve, reject) {
    actualFunction = "sendData2";
    try {
      const batchPromises = [];
      const originalKeywork = params.reporte.keywork;
      const batchSize = Math.ceil(dataJSON.length / 5000);
      for await (let pos of asyncGenerator(batchSize)) {
        const batchData = dataJSON.slice((pos * 5000), (5000 * (pos + 1)));
        params.reporte.keywork = originalKeywork + pos;
        const response = await axios({
          method: 'post', url: process.env.CREATE_PACKAGE_PATH2, data: { params, dataJSON: batchData },
          maxBodyLength: Infinity, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }
        });
        batchPromises.push(response.status)
      }
      batchPromises.map(reg => log.info('Insert status', reg));
      if (batchPromises.every(reg => reg == 200)) {
        resolve({ status: "Packages inserted successfully", exit: 1 });
      } else {
        return reject(new Error('No se insertaron todos los paquetes'));
      }
    } catch (error) {
      await insertLog(params, 107);
      return reject(new Error(error.message));
    }
  });
}

const sendDataCH = async (params, dataJSON) => {
  return new Promise(async function (resolve, reject) {
    actualFunction = "sendDataCH";
    try {
      const batchPromises = [];
      const stringData = JSON.stringify(dataJSON);
      const response = await axios({
        method: 'post', url: process.env.INSERT_CH, data: {jsonContent: stringData },
        maxBodyLength: Infinity, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }
      });
      batchPromises.push(response.status);
      batchPromises.map(reg => log.info('Insert status', reg));
      if (batchPromises.every(reg => reg == 200)) {
        resolve({ status: "Packages inserted successfully", exit: 1 });
      } else {
        return reject(new Error('No se insertaron todos los paquetes'));
      }
    } catch (error) {
      await insertLog(params, 107);
      return reject(new Error(error.message));
    }
  });
}

const sendDataClickHouse = async (params, dataJSON) => {
  return new Promise(async function (resolve, reject) {
    actualFunction = "sendDataClickHouse";
    try {
      payload = {
        params,
        data: dataJSON
      }
      const batchPromises = [];
      const stringData = JSON.stringify(dataJSON);
      const response = await axios({
        method: 'post', 
        url: process.env.INSERT_CLICKHOUSE, 
        data: payload,
        maxBodyLength: Infinity, 
        headers: { 'Accept': 'application/json', 'Content-Type': 'application/json'}
      });
      batchPromises.push(response.status);
      batchPromises.map(reg => log.info('Insert status', reg));
      if (batchPromises.every(reg => reg == 200)) {
        resolve({ status: "Packages inserted successfully", exit: 1 });
      } else {
        return reject(new Error('No se insertaron todos los paquetes'));
      }
    } catch (error) {
      await insertLog(params, 107);
      return reject(new Error(error.message));
    }
  });
}

const waitFrame = (page, frame, framename, info, timeout, parent = null) => {
  return new Promise(async function (resolve, reject) {
    let counter = 0;
    while (!frame && counter < timeout) {
      frame = await page.frames().find(f => f.name() === framename && (parent ? f.parentFrame().name() == parent : true));
      await sleep(1000);
      counter++;
    }
    if (frame) {
      resolve(frame);
    } else {
      return reject(new Error("Not frame (waitFrame)"));
    }
  });
}

const waitlastWindow = async (browser, info, numws, pos) => {
  return new Promise(async function (resolve) {
    let len = 0;
    info.counter = 0;
    while (len != numws && info.counter < 30) {
      log.info("Waiting window changes ...")
      len = (await browser.pages()).length
      await sleep(1000);
      info.counter++;
    }
    const thispage = await (await browser.pages())[pos - 1]
    resolve(thispage)
  })
}

const evalCom = async (info, casse, resp, com, type) => {
  if (info.current == casse) {
    if ((resp.expect ? resp.counterExp < resp.expect.length && Object.keys(resp.expect[resp.counterExp])[0] == type && resp.expect[resp.counterExp][type].includes(com.url()) : false)) {
      resp.counterExp++;
      if (resp.counterExp == resp.expect.length) { resp.step = true; }
    }
    else if ((resp.regexpp ? resp.counterReg < resp.regexpp.length && Object.keys(resp.regexpp[resp.counterReg])[0] == type && com.url().match(resp.regexpp[resp.counterReg][type]).length : false)) {
      resp.counterReg++;
      if (resp.counterReg == resp.regexpp.length) { resp.step = true; resp.record.push(com.url()); }
    }
  }
}

const leftPad = (number, targetLength) => {
  let output = number + '';
  while (output.length < targetLength) {
    output = '0' + output;
  }
  return output;
}

const sumSalesInAPeriod = async (params, arrJSON) => {
  return new Promise(async function (resolve, reject) {
    try {
      let dataJson = {};
      let keyArr = '';
      let ventaU = 0;
      let ventaAntU = 0;
      let ventaP = 0;
      let ventaAntP = 0;
      arrJSON.forEach(e => {
        keyArr = `${e.cod}||${e.centro}`;
        if (!dataJson.hasOwnProperty(keyArr)) {
          dataJson[keyArr] = {
            proyecto: (params.robot.proyecto).toUpperCase(),
            gpocad: (params.robot.cadena).toUpperCase(),
            razonSocial: (params.robot.razonSocial).toUpperCase(),
            fecha: params.reporte.finalCY,
            tipoC: params.reporte.period,
            centro: e.centro,
            nomTienda: e.nomTienda,
            cod: e.cod,
            item: e.item,
            upc: e.upc,
            nomProd: e.nomProd,
            ventaU: e.ventaU,
            ventaP: e.ventaP
          }
        } else {
          ventaAntU = parseFloat(e.ventaU);
          ventaU = (ventaAntU + parseFloat(dataJson[keyArr].ventaU));
          dataJson[keyArr].ventaU = ventaU;
          ventaAntP = parseFloat(e.ventaP);
          ventaP = (ventaAntP + parseFloat(dataJson[keyArr].ventaP));
          dataJson[keyArr].ventaP = ventaP;
        }
      });
      return resolve(Object.values(dataJson));
    } catch (error) {
      console.error(error.message)
      throw new Error("Error en la función sumSalesInAPeriod");
    }
  });
}

const getStatus = async (db, cadena, fecha) => {
  return new Promise(async function (resolve, reject) {
    try {
      const status = await axios.get(`${process.env.GET_STATUS_PATH}${db}/${fecha}/${cadena.toLowerCase()}`);
      return resolve(status);
    } catch (e) {
      log.info(e);
      return reject(new Error(e.message));
    }
  })
}

const updateStatus = async (params, position, date) => {
  return new Promise(async function (resolve, reject) {
    try {
      await axios.post(process.env.SET_STATUS_PATH, { db: params.robot.baseHyperCube, position, cadena: params.robot.cadena.toLowerCase(), date });
      return resolve(true);
    } catch (e) {
      return reject(new Error(e.message));
    }
  })
}

async function* asyncGenerator(final) {
  var i = 0;
  while (i < final) {
    yield i++;
  }
}

const getParamsAndStatus = async (ejecucion) => {
  actualFunction = "getParamsAndStatus";
  const txtFecha = (process.env.DIA_SEMANA_MES == '' ? 'Sin Fecha' : 'Con Fecha');
  let initialParams = await getParams(ejecucion);
  if(txtFecha == 'Sin Fecha' && process.env.ID_PROYECTO_CADENA == 1419){
      const maxDate = await lastDateSales(initialParams);
      const nuevaFecha = moment(maxDate, "YYYYMMDD").add(8, 'days').format("YYYYMMDD");
      process.env.DIA_SEMANA_MES = nuevaFecha;
      initialParams = await getParams(ejecucion);
  }
  const status = await getStatus(initialParams.robot.baseHyperCube, initialParams.robot.cadena.toLowerCase(), initialParams.reporte.date1.replace(/-/gi, ''));
  return { initialParams, status: status.data.status };
};

function reformatDate(date, inf, outf) {
  let d;
  switch (inf) {
    case "yyyy-MM-dd":
      d = { year: date.split('-')[0], month: date.split('-')[1], day: date.split('-')[2] }
      break;
    case "dd/MM/yyyy":
      d = { year: date.split('/')[2], month: date.split('/')[1], day: date.split('/')[0] }
      break;
    case "MM/dd/yyyy":
      d = { year: date.split('/')[2], month: date.split('/')[0], day: date.split('/')[1] }
      break;
    case "yyyyMMdd":
      d = { year: date.substr(0, 4), month: date.substr(4, 2), day: date.substr(6, 2) };
      break;
  }
  switch (outf) {
    case "MM/dd/yyyy":
      return `${d.month}/${d.day}/${d.year}`
    case "dd/MM/yyyy":
      return `${d.day}/${d.month}/${d.year}`
    case "yyyy-MM-dd":
      return `${d.year}-${d.month}-${d.day}`
    case "MM.yyyy":
      return `${d.month}.${d.year}`
    case "MM-dd-yyyy":
      return `${d.month}-${d.day}-${d.year}`
    case "dd-MM-yyyy":
      return `${d.day}-${d.month}-${d.year}`
  }
}

const getIDReportSoriana = async (ejecucion) => {
  return new Promise(async function (resolve, reject) {
    try {
      const initialParams = await getParams(ejecucion);
      const idReport = await axios.get(`${process.env.GET_ID_REPORT_SORIANA}/${initialParams.robot.baseHyperCube}/${initialParams.reporte.finalCY}/${ejecucion}/${initialParams.reporte.period}`);
      return resolve(idReport.data.idReport);
    } catch (e) {
      return reject(new Error(e.message));
    }
  })
}

const setIDReportSoriana = async (params, idReport) => {
  return new Promise(async function (resolve, reject) {
    try {
      await axios.post(process.env.SET_ID_REPORT_SORIANA, { db: params.robot.baseHyperCube, date: params.reporte.finalCY, tipo: params.reporte.type, period: params.reporte.period, idReport });
      return resolve(true);
    } catch (e) {
      return reject(new Error(e.message));
    }
  })
}

const updateIDReportSoriana = async (params, idReport) => {
  return new Promise(async function (resolve, reject) {
    try {
      await axios.post(process.env.SET_ID_REPORT_SORIANA, { db: params.robot.baseHyperCube, date: params.reporte.finalCY, tipo: params.reporte.type, period: params.reporte.period, idReport });
      return resolve(true);
    } catch (e) {
      return reject(new Error(e.message));
    }
  })
}

const sendFile3 = async (pathDownloads, params) => {
  return new Promise(async function (resolve, reject) {
    actualFunction = "sendFile3";
    try {
      const zip = new AdmZip();
      zip.addLocalFile(`${pathDownloads}/${params.reporte.filename}${process.env.EXTENSION_FILE}`);
      zip.writeZip(`${pathDownloads}/${params.reporte.filename}.zip`);
      await sleep(5000);
      const key = params.reporte.pathS3 + '/' + params.reporte.filename + '.zip';
      const fileData = fs.readFileSync(`${pathDownloads}/${params.reporte.filename}.zip`);
      const regs = await axios.post(process.env.AUTH_PATH, { email: process.env.EMAIL_API });
      const body = { type: 1, bucketName: process.env.S3_BUCKET, file: fileData.toString('base64'), key };
      const uploadFileResponse = await axios.post(process.env.S3_UPLOAD_FILE, body,
        { maxBodyLength: Infinity, headers: { 'Authorization': regs.data.data, 'Content-Type': 'application/json' } }
      );
      if (uploadFileResponse.status === 200) {
        await insertEvidence3(uploadFileResponse.data.data, '', params.robot.baseHyperCube, params.robot.idgfc,
          process.env.PERIODO, params.reporte.prefix, params.reporte.type);
        await insertLog(params, 305);
        resolve();
      }
    } catch (error) {
      await insertLog(params, 108);
      return reject(new Error(error.message));
    }
  });
}

const sendFile4 = async (pathDownloads, params) => {
  return new Promise(async function (resolve, reject) {
    actualFunction = "sendFile4";
    try {
      const zip = new AdmZip();
      let periodo, fecha;
      zip.addLocalFile(`${pathDownloads}/${params.reporte.filename}${process.env.EXTENSION_FILE}`);
      zip.writeZip(`${pathDownloads}/${params.reporte.filename}.zip`);
      await sleep(5000);
      const key = params.reporte.pathS3 + '/' + params.reporte.filename + '.zip';
      const fileData = fs.readFileSync(`${pathDownloads}/${params.reporte.filename}.zip`);
      const regs = await axios.post(process.env.AUTH_PATH, { email: process.env.EMAIL_API });
      const body = { type: 1, bucketName: process.env.S3_BUCKET, file: fileData.toString('base64'), key };
      const uploadFileResponse = await axios.post(process.env.S3_UPLOAD_FILE, body,
        { maxBodyLength: Infinity, headers: { 'Authorization': regs.data.data, 'Content-Type': 'application/json' } }
      );
      if (uploadFileResponse.status === 200) {
        periodo = process.env.PERIODO
        fecha = params.reporte.prefix;
        //CARGA DE EVIDENCIAS DE BOTS SEMANALES Y MENSUALES
        if(params.robot.base_portal == 'wm'){
          periodo = 0;
          fecha = params.reporte.interval[params.reporte.interval.length - 1];
        }
        await insertEvidenceCore(uploadFileResponse.data.data, '', params.robot.baseHyperCube, params.robot.idgfc, periodo, fecha, params.reporte.type)
        await insertLog2(params, 305);
        resolve();
      }
    } catch (error) {
      await insertLog2(params, 108);
      return reject(new Error(error.message));
    }
  });
}

const sendImage3 = async (pathDownloads, params) => {
  return new Promise(async function (resolve, reject) {
    actualFunction = "sendFile3";
    try {
      const key = params.reporte.pathS3 + '/' + params.reporte.filename + process.env.EXTENSION_IMG;
      const fileData = fs.readFileSync(`${pathDownloads}/${params.reporte.filename}${process.env.EXTENSION_IMG}`);
      const regs = await axios.post(process.env.AUTH_PATH, { email: process.env.EMAIL_API });
      const body = { type: 1, bucketName: process.env.S3_BUCKET, file: fileData.toString('base64'), key };
      const uploadFileResponse = await axios.post(process.env.S3_UPLOAD_FILE, body,
        { maxBodyLength: Infinity, headers: { 'Authorization': regs.data.data, 'Content-Type': 'application/json' } }
      );
      if (uploadFileResponse.status === 200) {
        await insertEvidence3('', uploadFileResponse.data.data, params.robot.baseHyperCube, params.robot.idgfc,
          process.env.PERIODO, params.reporte.prefix, params.reporte.type);
        await insertLog(params, 305);
        resolve();
      }
    } catch (error) {
      await insertLog(params, 108);
      return reject(new Error(error.message));
    }
  });
}

const sendImage4 = async (pathDownloads, params) => {
  return new Promise(async function (resolve, reject) {
    actualFunction = "sendImage4";
    try {
      const key = params.reporte.pathS3 + '/' + params.reporte.filename + process.env.EXTENSION_IMG;
      const fileData = fs.readFileSync(`${pathDownloads}/${params.reporte.filename}${process.env.EXTENSION_IMG}`);
      const regs = await axios.post(process.env.AUTH_PATH, { email: process.env.EMAIL_API });
      const body = { type: 1, bucketName: process.env.S3_BUCKET, file: fileData.toString('base64'), key };
      const uploadFileResponse = await axios.post(process.env.S3_UPLOAD_FILE, body,
        { maxBodyLength: Infinity, headers: { 'Authorization': regs.data.data, 'Content-Type': 'application/json' } }
      );
      if (uploadFileResponse.status === 200) {
        await insertEvidence4('', uploadFileResponse.data.data, params.robot.baseHyperCube, params.robot.idgfc,
          process.env.PERIODO, params.reporte.prefix, params.reporte.type)
        await insertLog2(params, 305);
        resolve();
      }
    } catch (error) {
      await insertLog2(params, 108);
      return reject(new Error(error.message));
    }
  });
}


const sendChunks = async (pathDownloads, params) => {
  return new Promise(async function (resolve, reject) {
    actualFunction = "sendChunks";

    try {
      const zip = new AdmZip();
      zip.addLocalFile(`${pathDownloads}/${params.reporte.filename}${process.env.EXTENSION_FILE}`);
      zip.writeZip(`${pathDownloads}/${params.reporte.filename}.zip`);
      await sleep(5000);
      const key = params.reporte.pathS3 + '/' + params.reporte.filename + '.zip';
      const fileData = fs.readFileSync(`${pathDownloads}/${params.reporte.filename}.zip`);
      const regs = await axios.post(process.env.AUTH_PATH, { email: process.env.EMAIL_API });
      let data = fileData.toString('base64');
      const chunks = data.match(/.{1,5000000}/g);
      let numberChunk = 1;

      let uploadFileResponse
      for (let chunk of chunks) {
        const body = { type: 1, bucketName: process.env.S3_BUCKET, file: chunk, key, numberChunk, maxChunks: chunks.length };
        uploadFileResponse = await axios.post(process.env.S3_CHUNKS_EVIDENCE, body,
          { maxBodyLength: Infinity, headers: { 'Authorization': regs.data.data, 'Content-Type': 'application/json' } }
        );
        numberChunk++;
      }
      await insertEvidence3(uploadFileResponse.data.data, '', params.robot.baseHyperCube, params.robot.idgfc,
        process.env.PERIODO, params.reporte.prefix, params.reporte.type);
      await insertLog(params, 305);
      resolve();
    } catch (error) {
      await insertLog(params, 108);
      return reject(new Error(error.message));
    }
  });
}

const sendDataDaily = (params, data) => {
  return new Promise(async (resolve, reject) => {
    try {
      const hasData = await checkData2(params);
      if (!hasData) {
        log.info('Ya cuenta con data diaria')
        return resolve(0);
      }
      actualFunction = "sendDailyData";
      let dataDaily = await processDataDaily(params.reporte.interval, data);
      if (!dataDaily.length) { return resolve(0); };
      await sendData2(params, dataDaily);
      return resolve(1);
    } catch (error) {
      log.info(error.message)
      await insertLog(params, 108);
      return reject(new Error(error.message));
    }
  });
}

const processDataDaily = (intervalDays, dataD) => {
  return new Promise(async (resolve, reject) => {
    try {
      resolve(dataD.map(d => {
        return {
          proyecto: d.proyecto,
          gpocad: d.gpocad,
          razonSocial: d.razonSocial,
          fecha: (intervalDays[intervalDays.length - 1]),
          tipoC: 0,
          centro: d.centro,
          nomTienda: d.nomTienda,
          upc: d.upc,
          cod: d.cod,
          item: d.item,
          nomProd: d.nomProd,
          ventaU: d.ventaU,
          ventaP: d.ventaP,
          invU: d.invU,
          invP: d.invP,
          pzasCaja: d.pzasCaja
        }
      }));
    } catch (error) {
      return reject(new Error(error.message));
    }
  });
}

const checkDataVI = async (params) => {
  return new Promise(async (resolve) => {
    try {
      actualFunction = "checkDataVI";
      let campoC, nameTable;
      const campo = (['v', 't'].includes(params.reporte.type.toLowerCase())) ? "ventasUnidades" : "existenciasUnidades";
      if (params.reporte.dateStr == "fecha") campoC = params.reporte.date1;
      else campoC = params.reporte.yearWeekOrMonth;
      nameTable = params.robot.cadena.replace(/\s+/g, '').toLowerCase()
      const table = `concentrado${nameTable}`;
      const result = await axios.post(process.env.CHECKDATA_VTA_INV, { base: params.robot.baseHyperCube, campo, concentrado: table, fecha: campoC });
      resolve(result.data.data);
    } catch (error) {
      log.error(error.message)
      throw new Error("No se pudo verificar existencia de información");
    }
  });
}

const lastDateSales = async (params) => {
  return new Promise(async (resolve) => {
    try {
      actualFunction = "lastDateSales";
      let nameTable;
      nameTable = params.robot.cadena.replace(/\s+/g, '').toLowerCase()
      const table = `concentrado${nameTable}`;
      const result = await axios.post(process.env.GET_LAST_DATE_SALES, { base: params.robot.baseHyperCube, concentrado: table});
      resolve(result.data.data);
    } catch (error) {
      log.error(error.message)
      throw new Error("No se pudo verificar existencia de información");
    }
  });
}

function diferenciaHoras(tiempo1, tiempo2) {
  const [h1, m1] = tiempo1.split(":").map(Number);
  const [h2, m2] = tiempo2.split(":").map(Number);

  const diferenciaMinutos = (h1 * 60 + m1) - (h2 * 60 + m2);
  const horas = Math.floor(Math.abs(diferenciaMinutos) / 60);
  const minutos = Math.abs(diferenciaMinutos) % 60;

  return `${horas.toString().padStart(2, "0")}:${minutos.toString().padStart(2, "0")}`;
}

const insertCatspecs = async ({ db, area, cadena, nivel, fecha, val1, val5 }) => {
  try {
    const insertCatspecsResponse = await axios.post(process.env.POST_CATSPECS, {
      db, area, cadena, nivel, fecha, val1, val5
    });
    return insertCatspecsResponse.data;
  } catch (e) {
    log.error(e.message);
    throw new Error("No se pudo insertar la información de catspecs");
  }
}

const uploadImageToS3 = async (filePath, key) => {
  const fileData = fs.readFileSync(filePath);
  const regs = await axios.post(process.env.AUTH_PATH, { email: process.env.EMAIL_API });
  const body = { type: 1, bucketName: process.env.S3_BUCKET, file: fileData.toString('base64'), key };
  const uploadFileResponse = await axios.post(process.env.S3_UPLOAD_FILE, body,
    { maxBodyLength: Infinity, headers: { 'Authorization': regs.data.data, 'Content-Type': 'application/json' } }
  );
  return uploadFileResponse.data.data;
}

const sleepFile = (ms) => new Promise((r) => setTimeout(r, ms));

const validarZip = (zipPath) => {
  try {
    const zip = new AdmZip(zipPath);
    const entries = zip.getEntries();

    if (entries.length === 0)
      throw new Error("El zip está vacío, no contiene archivos.");

    // Verifica que cada entrada tenga contenido
    for (const entry of entries) {
      if (!entry.header || entry.header.size === 0)
        throw new Error(`La entrada "${entry.entryName}" está corrupta o vacía.`);
    }
    log.info(`[ZIP] Válido — ${entries.length} archivo(s) dentro.`);
    return true;
  } catch (error) {
    throw new Error(`[ZIP] Validación fallida: ${error.message}`);
  }
};

const uploadEvidence = async (pathDownloads, params, reintentos = 3) => {

  log.info(`[S3] Región: ${process.env.AWS_REGION_BUCKET} | Bucket: ${process.env.S3_BUCKET}`);
  const s3 = new S3Client({ region: process.env.AWS_REGION_BUCKET });

  const zipPath = `${pathDownloads}/${params.reporte.filename}.zip`;
  const key = `${params.reporte.pathS3}/${params.reporte.filename}.zip`;

  // Armar el zip
  const zip = new AdmZip();
  zip.addLocalFile(`${pathDownloads}/${params.reporte.filename}${process.env.EXTENSION_FILE}`);
  zip.writeZip(zipPath);

  // Validar antes de intentar subir
  validarZip(zipPath);

  let ultimoError;

  for (let intento = 1; intento <= reintentos; intento++) {
    try {
      log.info(`[S3] Intento ${intento}/${reintentos} — subiendo ${key}`);

      const upload = new Upload({
        client: s3,
        params: {
          Bucket: process.env.S3_BUCKET,
          Key: key,
          Body: fs.createReadStream(zipPath),
          ContentType: "application/zip",
          ACL: "public-read",
        },
        queueSize: 4,
        partSize: 10 * 1024 * 1024,
      });

      await upload.done();
      const url = `https://${process.env.S3_BUCKET}.s3.${process.env.AWS_REGION_BUCKET}.amazonaws.com/${key}`;
      log.info(`[S3] Upload exitoso en intento ${intento} — ${key}`);
      await insertEvidenceCore(url, '', params.robot.baseHyperCube, params.robot.idgfc, process.env.PERIODO, params.reporte.date1, params.reporte.type);
      await insertLog2(params, 305);
      return key;

    } catch (error) {
      ultimoError = error;
      log.error(`[S3] Intento ${intento} fallido: ${error.message}`);

      if (intento < reintentos) {
        const espera = 2000 * intento;
        log.info(`[S3] Reintentando en ${espera / 1000}s...`);
        await sleepFile(espera);
      }
    }
  }
  throw new Error(`[S3] Upload fallido tras ${reintentos} intentos: ${ultimoError.message}`);
};

const insertBotEvidence = async (rutaFile, params) => {
  return new Promise(async function (resolve) {
    
    await axios.post(process.env.INSERT_BOT_EVIDENCE, { 
      idgfc: params.robot.idgfc,
      idpc: params.robot.idPC,
      proyecto: params.robot.proyecto.toUpperCase(),
      grupo: params.robot.cadena.toUpperCase(),
      cadena: params.robot.cadena.toUpperCase(),
      fecha: params.reporte.date1,
      tipo: params.reporte.type,
      fileInsert: rutaFile
    });
    resolve(true);
  });
}

const uploadBotEvidence = async (pathDownloads, params, reintentos = 3) => {

  log.info(`[S3] Región: ${process.env.AWS_REGION_BUCKET} | Bucket: ${process.env.S3_BUCKET}`);
  const s3 = new S3Client({ region: process.env.AWS_REGION_BUCKET });

  const zipPath = `${pathDownloads}/${params.reporte.filename}.zip`;
  const key = `${params.reporte.pathS3}/${params.reporte.filename}.zip`;

  // Armar el zip
  const zip = new AdmZip();
  zip.addLocalFile(`${pathDownloads}/${params.reporte.filename}${process.env.EXTENSION_FILE}`);
  zip.writeZip(zipPath);

  // Validar antes de intentar subir
  validarZip(zipPath);

  let ultimoError;

  for (let intento = 1; intento <= reintentos; intento++) {
    try {
      log.info(`[S3] Intento ${intento}/${reintentos} — subiendo ${key}`);

      const upload = new Upload({
        client: s3,
        params: {
          Bucket: process.env.S3_BUCKET,
          Key: key,
          Body: fs.createReadStream(zipPath),
          ContentType: "application/zip",
          ACL: "public-read",
        },
        queueSize: 4,
        partSize: 10 * 1024 * 1024,
      });

      await upload.done();
      const url = `https://${process.env.S3_BUCKET}.s3.${process.env.AWS_REGION_BUCKET}.amazonaws.com/${key}`;
      log.info(`[S3] Upload exitoso en intento ${intento} — ${key}`);
      await insertBotEvidence(url, params);
      await insertLog2(params, 305);
      return key;

    } catch (error) {
      ultimoError = error;
      log.error(`[S3] Intento ${intento} fallido: ${error.message}`);

      if (intento < reintentos) {
        const espera = 2000 * intento;
        log.info(`[S3] Reintentando en ${espera / 1000}s...`);
        await sleepFile(espera);
      }
    }
  }
  throw new Error(`[S3] Upload fallido tras ${reintentos} intentos: ${ultimoError.message}`);
};

const rejectWithCodigo = (reject, message, codigo) => {
  const err = new Error(message);
  err.codigo = codigo;
  return reject(err);
};

const saveErrorScreenshot = async (params, error, actualFunction, codigo, screenshotPath) => {
  if (typeof codigo === 'number' && codigo >= 300) {
    log.info(`saveErrorScreenshot omitido: el código ${codigo} no es de error (catálogo caterrores: error solo <300)`);
    return;
  }
  let urlImage = '';
  try {
    if (screenshotPath && fs.existsSync(screenshotPath)) {
      const key = `${params?.reporte?.pathS3}/errors/${params?.reporte?.filename}.png`;
      urlImage = await uploadImageToS3(screenshotPath, key);
    }
  } catch (uploadError) {
    log.error(`No se pudo subir el screenshot de error: ${uploadError.message}`);
  }

  const payload = {
    keywork: params?.reporte?.keywork,
    idRobot: params?.robot?.idPC,
    proyecto: params?.robot?.proyecto?.toUpperCase(),
    cadena: params?.robot?.cadena?.toUpperCase(),
    fechaRobot: process.env.DIA_SEMANA_MES || params?.reporte?.date1,
    codigo: codigo || null,
    JSON: JSON.stringify({ message: error?.message, actualFunction, stack: error?.stack }).slice(0, 4000),
    rep: params?.reporte?.type,
    urlImage
  };

  try {
    await axios.post(process.env.INSERT_ERROR_SCREENSHOT, payload);
  } catch (postError) {
    log.error(`No se pudo guardar el log del error: ${postError.message}`);
  }
};

module.exports = {
  waitNtype,
  waitNclick,
  sleep,
  pupp_findSelector,
  waitFile,
  rename,
  insertEvidence,
  insertLog,
  checkData,
  getParams,
  sendErrorMail,
  hour,
  renameFile,
  sendFile,
  sendData,
  waitFrame,
  waitlastWindow,
  sendImage,
  evalCom,
  leftPad,
  sumSalesInAPeriod,
  asyncGenerator,
  getStatus,
  insertLog2,
  sendFile2,
  insertEvidence2,
  sendData2,
  checkData2,
  updateStatus,
  getParamsAndStatus,
  sendImage2,
  reformatDate,
  getIDReportSoriana,
  setIDReportSoriana,
  updateIDReportSoriana,
  sendFile3,
  sendFile4,
  sendImage3,
  sendImage4,
  sendChunks,
  sendDataDaily,
  obtenerCorreos,
  obtenerCodigo,
  obtenerCodigoNuevo,
  checkDataVI,
  diferenciaHoras,
  lastDateSales,
  EmailManager,
  manageEmails,
  insertLogExec,
  sendDataCH,
  processDataDaily,
  uploadImageToS3,
  insertCatspecs,
  uploadEvidence,
  sendDataClickHouse,
  uploadBotEvidence,
  rejectWithCodigo,
  saveErrorScreenshot
}