From 053f1ea7f6a14eadc63180731e2f318a05e57b27 Mon Sep 17 00:00:00 2001 From: dmunozv Date: Wed, 10 Jun 2026 14:54:48 -0400 Subject: [PATCH] commit inicial --- .dockerignore | 28 + .gitignore | 27 + Dockerfile | 40 ++ package.json | 22 + src/actualizarToken.js | 16 + src/config.js | 37 ++ src/database/config/db.models.js | 23 + src/database/models/interfazWs.js | 79 +++ .../models/tbCredencialesIntegracion.js | 72 +++ src/envioDocumentos.js | 18 + src/envioPrecios.js | 31 + src/envioStock.js | 22 + src/procesos/actualizacionStock.js | 97 +++ src/procesos/actualizarPrecios.js | 72 +++ src/procesos/autenticacion.js | 85 +++ src/procesos/envioBoleta.js | 178 ++++++ src/utils/utils.js | 26 + yarn.lock | 588 ++++++++++++++++++ 18 files changed, 1461 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 package.json create mode 100644 src/actualizarToken.js create mode 100644 src/config.js create mode 100644 src/database/config/db.models.js create mode 100644 src/database/models/interfazWs.js create mode 100644 src/database/models/tbCredencialesIntegracion.js create mode 100644 src/envioDocumentos.js create mode 100644 src/envioPrecios.js create mode 100644 src/envioStock.js create mode 100644 src/procesos/actualizacionStock.js create mode 100644 src/procesos/actualizarPrecios.js create mode 100644 src/procesos/autenticacion.js create mode 100644 src/procesos/envioBoleta.js create mode 100644 src/utils/utils.js create mode 100644 yarn.lock diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f94af32 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,28 @@ +# Dependency directories +node_modules + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Version control +.git +.gitignore + +# Docker configuration files +Dockerfile +.dockerignore + +# Environment files +.env +.env.* + +# IDE / editor artifacts +.idea/ +.vscode/*.swp + +# Miscellaneous +*.md +.DS_Store +Thumbs.db diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fd581a --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Node.js / JavaScript +node_modules/ + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Environment files +.env +.env.* + +# Build output +dist/ +build/ + +# OS generated files +.DS_Store +Thumbs.db + +# IDE / editor folders +.idea/ +.vscode/ + +# Temporary files +*.tmp +*.swp diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..724f2a7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +# syntax=docker/dockerfile:1 + +# ---------- Build stage ---------- +FROM node:20-alpine AS builder + +WORKDIR /app + +# Install dependencies (cache layer) +COPY package.json . +COPY yarn.lock . +RUN yarn install --frozen-lockfile --production=false + +# Copy source code +COPY . . + +# Prune dev dependencies for production image +RUN yarn install --frozen-lockfile --production && \ + rm -rf **/*.test.js + +# ---------- Runtime stage ---------- +FROM node:20-alpine AS runtime + +RUN addgroup -S appgroup && adduser -S appuser -G appgroup + +WORKDIR /app + +COPY --from=builder /app/package.json . +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/src ./src + +ENV NODE_ENV=production + +USER appuser + +EXPOSE 3000 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=5s \ + CMD node -e "require('http').get('http://localhost:3000/health', () => process.exit(0)).on('error', () => process.exit(1))" + +CMD ["node", "src/index.js"] diff --git a/package.json b/package.json new file mode 100644 index 0000000..7d1dd3d --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "IntegracionMultivende", + "version": "1.0.0", + "main": "app.js", + "license": "MIT", + "type": "module", + "scripts": { + "dev": "nodemon src/index.js" + }, + "dependencies": { + "axios": "^1.8.4", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "form-data": "^4.0.3", + "mysql2": "^3.14.0", + "node-cron": "^3.0.3", + "sequelize": "^6.37.6" + }, + "devDependencies": { + "nodemon": "^3.1.9" + } +} diff --git a/src/actualizarToken.js b/src/actualizarToken.js new file mode 100644 index 0000000..655ea4e --- /dev/null +++ b/src/actualizarToken.js @@ -0,0 +1,16 @@ +import cron from 'node-cron'; + +import { actualizarToken } from './procesos/autenticacion.js'; +import { multivendeConfig } from './config.js'; + + +console.log("ACTUALIZACIÓN DE ACCESS TOKEN MULTIVENDE") +await actualizarToken(); + +cron.schedule('0 */6 * * *', async () => { + try { + await actualizarToken(); + } catch (error) { + console.log("Error", error); + } +}); diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..0e49ce3 --- /dev/null +++ b/src/config.js @@ -0,0 +1,37 @@ +import dotenv from 'dotenv'; +import { dirname, resolve } from 'path'; +import { fileURLToPath } from 'url'; + +// Resolver ruta absoluta del archivo actual +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Cargar .env desde 2 niveles arriba +dotenv.config({ path: resolve(__dirname, '../.env') }); + +export const dbFlexiCentralConfig = { + host: process.env.DB_FLEXI_CENTRAL_HOST, + port: process.env.DB_FLEXI_CENTRAL_PORT, + user: process.env.DB_FLEXI_CENTRAL_USER, + password: process.env.DB_FLEXI_CENTRAL_PASS, + database: process.env.DB_FLEXI_CENTRAL_NAME, +}; + +export const dbFlexiEcommerceConfig = { + host: process.env.DB_FLEXI_CENTRAL_HOST, + port: process.env.DB_FLEXI_CENTRAL_PORT, + user: process.env.DB_FLEXI_CENTRAL_USER, + password: process.env.DB_FLEXI_CENTRAL_PASS, + database: process.env.DB_FLEXI_ECOMMERCE_NAME, +}; + +export const multivendeConfig = { + clientId: process.env.MULTIVENDE_APP_CLIENT_ID, + clientSecret: process.env.MULTIVENDE_APP_CLIENT_SECRET, + urlBase: process.env.MULTIVENDE_URL_BASE, + warehouseId: process.env.MULTIVENDE_WAREHOUSE, + priceListId: process.env.MULTIVENDE_PRICE_LIST_ID, + provider: process.env.MULTIVENDE_PROVIDER, + emitterId: process.env.MULTIVENDE_EMITTER_ID, + +}; \ No newline at end of file diff --git a/src/database/config/db.models.js b/src/database/config/db.models.js new file mode 100644 index 0000000..b92e63c --- /dev/null +++ b/src/database/config/db.models.js @@ -0,0 +1,23 @@ +import Sequelize from "sequelize"; +import { dbFlexiEcommerceConfig, dbFlexiCentralConfig } from "../../config.js"; + +export const dbEcommerceFlexi = new Sequelize(dbFlexiEcommerceConfig.database, dbFlexiEcommerceConfig.user, dbFlexiEcommerceConfig.password, { + host: dbFlexiEcommerceConfig.host, + dialect: 'mysql', + port: dbFlexiEcommerceConfig.port, + define: { + freezeTableName: true + } + +}); + +export const dbCentralFlexi = new Sequelize(dbFlexiCentralConfig.database, dbFlexiCentralConfig.user, dbFlexiCentralConfig.password, { + host: dbFlexiCentralConfig.host, + port: dbFlexiCentralConfig.port, + dialect: 'mysql', + define: { + freezeTableName: true, + }, + // logging: false, +} +); \ No newline at end of file diff --git a/src/database/models/interfazWs.js b/src/database/models/interfazWs.js new file mode 100644 index 0000000..5bafb09 --- /dev/null +++ b/src/database/models/interfazWs.js @@ -0,0 +1,79 @@ +import { DataTypes } from 'sequelize'; +import { dbEcommerceFlexi } from '../config/db.models.js'; + +const InterfazWS = dbEcommerceFlexi.define('interfaz_ws', { + folio: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + tipo: { + type: DataTypes.TINYINT.UNSIGNED, + }, + estado: { + type: DataTypes.TINYINT.UNSIGNED, + }, + fechacarga: { + type: DataTypes.DATE, + }, + fechaproceso: { + type: DataTypes.DATE, + }, + sitio: { + type: DataTypes.INTEGER.UNSIGNED, + }, + documentoventa: { + type: DataTypes.INTEGER.UNSIGNED, + }, + fechadocumento: { + type: DataTypes.DATEONLY, + }, + numlocal: { + type: DataTypes.INTEGER.UNSIGNED, + }, + numbodega: { + type: DataTypes.INTEGER.UNSIGNED, + }, + ordenecommerce: { + type: DataTypes.CHAR(25), + }, + sku: { + type: DataTypes.CHAR(30), + }, + precio: { + type: DataTypes.DOUBLE(10, 2), + }, + saldo: { + type: DataTypes.INTEGER, + }, + proceso_origen: { + type: DataTypes.STRING(500), + }, + vtex: { + type: DataTypes.STRING(20), + }, + rappi: { + type: DataTypes.CHAR(1), + }, + centry: { + type: DataTypes.CHAR(1), + }, + fechaCentry: { + type: DataTypes.DATE, + }, + Multivende: { + type: DataTypes.CHAR(1), + }, + fechaMultivende: { + type: DataTypes.DATE, + }, + fecha_actualizacion: { + type: DataTypes.DATE, + }, +}, { + timestamps: false, +}); + +InterfazWS.removeAttribute('id'); + +export default InterfazWS; diff --git a/src/database/models/tbCredencialesIntegracion.js b/src/database/models/tbCredencialesIntegracion.js new file mode 100644 index 0000000..4718d19 --- /dev/null +++ b/src/database/models/tbCredencialesIntegracion.js @@ -0,0 +1,72 @@ +// models/credencialIntegracion.model.js +import { DataTypes } from "sequelize"; +import { dbEcommerceFlexi } from "../config/db.models.js"; // ajusta según tu archivo de conexión + +const tbCredencialesIntegracion = dbEcommerceFlexi.define("tb_credenciales_integracion", { + idsitio: { + type: DataTypes.INTEGER, + primaryKey: true, + allowNull: false, + }, + nombre: { + type: DataTypes.STRING(50), + allowNull: true, + }, + idcliente: { + type: DataTypes.STRING(100), + allowNull: true, + }, + client_secret: { + type: DataTypes.STRING(100), + allowNull: true, + }, + opcinal1: { + type: DataTypes.STRING(100), + allowNull: true, + }, + opcinal2: { + type: DataTypes.STRING(100), + allowNull: true, + }, + url: { + type: DataTypes.STRING(100), + allowNull: true, + }, + token: { + type: DataTypes.STRING(2000), + allowNull: true, + }, + estado: { + type: DataTypes.CHAR(1), + allowNull: true, + }, + fecha: { + type: DataTypes.DATE, + allowNull: true, + }, + token_D: { + type: DataTypes.STRING(100), + allowNull: true, + }, + token_type_D: { + type: DataTypes.STRING(100), + allowNull: true, + }, + expires_token_D: { + type: DataTypes.INTEGER, + allowNull: true, + }, + refresh_token_D: { + type: DataTypes.STRING(100), + allowNull: true, + }, + fecha_D: { + type: DataTypes.DATE, + allowNull: true, + }, +}, { + timestamps: false, + freezeTableName: true, +}); + +export default tbCredencialesIntegracion; diff --git a/src/envioDocumentos.js b/src/envioDocumentos.js new file mode 100644 index 0000000..a71ff21 --- /dev/null +++ b/src/envioDocumentos.js @@ -0,0 +1,18 @@ +import cron from 'node-cron'; + +import { procesarDocumentos } from './procesos/envioBoleta.js'; + +console.log("ENVÍO DE DOCUMENTOS MULTIVENDE") + +cron.schedule('*/15 * * * *', async () => { + console.log("Se inicia ENVÍO DE DOCUMENTOS") + try { + + await procesarDocumentos(); + + } catch (error) { + console.error("Error en ENVIO:", error); + } + console.log("Se finaliza ENVÍO DE DOCUMENTOS") + +}); \ No newline at end of file diff --git a/src/envioPrecios.js b/src/envioPrecios.js new file mode 100644 index 0000000..a3e8efb --- /dev/null +++ b/src/envioPrecios.js @@ -0,0 +1,31 @@ +import cron from 'node-cron'; + +import { multivendeConfig } from './config.js'; +import { actualizarStock } from './procesos/actualizacionStock.js'; +import { actualizarPrecios } from './procesos/actualizarPrecios.js'; + +console.log("ENVIO PRECIOS MULTIVENDE") + +cron.schedule('*/15 * * * *', async () => { + console.log("Se inicia actualización de PRECIOS"); + + try { + await actualizarPrecios(); + } catch (error) { + console.error("Error en ACTUALIZAR PRECIOS:", error); + } + + console.log("Finalizó actualización de PRECIOS"); +}); + +// cron.schedule('01 22 * * *', async () => { +// console.log("Se inicia actualización de PRECIOS"); + +// try { +// await actualizarPrecios(); +// } catch (error) { +// console.error("Error en ACTUALIZAR PRECIOS:", error); +// } + +// console.log("Finalizó actualización de PRECIOS"); +// }); \ No newline at end of file diff --git a/src/envioStock.js b/src/envioStock.js new file mode 100644 index 0000000..85f618d --- /dev/null +++ b/src/envioStock.js @@ -0,0 +1,22 @@ +import cron from 'node-cron'; + +import { multivendeConfig } from './config.js'; +import { actualizarStock } from './procesos/actualizacionStock.js'; +import { actualizarPrecios } from './procesos/actualizarPrecios.js'; + +console.log("ENVÍO DE STOCK MULTIVENDE") + + + +cron.schedule('*/2 * * * *', async () => { + console.log("Se inicia actualización de STOCK") + try { + + await actualizarStock(); + + } catch (error) { + console.error("Error en ACTUALIZAR STOCK:", error); + } + console.log("Se finaliza actualización de STOCK") + +}); \ No newline at end of file diff --git a/src/procesos/actualizacionStock.js b/src/procesos/actualizacionStock.js new file mode 100644 index 0000000..bd54af5 --- /dev/null +++ b/src/procesos/actualizacionStock.js @@ -0,0 +1,97 @@ +import axios from "axios"; +import { multivendeConfig } from "../config.js"; +import { dbEcommerceFlexi } from "../database/config/db.models.js"; +import InterfazWS from "../database/models/interfazWs.js"; +import { obtenerToken, tokenExpirado } from "../utils/utils.js"; + +const urlBase = multivendeConfig.urlBase; +const warehouseId = multivendeConfig.warehouseId; + +// Función auxiliar para dividir en lotes +const dividirEnLotes = (array, tamaño) => { + const lotes = []; + for (let i = 0; i < array.length; i += tamaño) { + lotes.push(array.slice(i, i + tamaño)); + } + return lotes; +}; + +export const actualizarStock = async (idsitio = 8) => { + try { + + + const [results] = await dbEcommerceFlexi.query(` + SELECT * FROM w_stock_multivende + `); + + if (!results.length) { + console.log("No hay datos para actualizar."); + return; + } + + const { accessToken, expiresAt } = await obtenerToken(idsitio); + + const stockData = results.map(({ sku, saldo, folio }) => ({ + code: sku, + amount: saldo, + folio, + })); + + const lotes = dividirEnLotes(stockData, 500); + const foliosExitosos = []; + + for (const lote of lotes) { + let isExpired = await tokenExpirado(expiresAt); + + if (isExpired) { + ({ accessToken, expiresAt } = await obtenerToken(idsitio)); + } + + const payload = lote.map(({ code, amount }) => ({ code, amount })); + + const response = await axios.post( + `${urlBase}/api/product-stocks/stores-and-warehouses/${warehouseId}/bulk-set`, + payload, + { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + } + ); + + const dataRespuesta = response.data; + + const codigosExitosos = dataRespuesta + .filter((item) => item.success === true) + .map((item) => item.code); + + const foliosLoteExitosos = lote + .filter(({ code }) => codigosExitosos.includes(code)) + .map(({ folio }) => folio); + + foliosExitosos.push(...foliosLoteExitosos); + } + + if (foliosExitosos.length > 0) { + + await InterfazWS.update( + { + Multivende: "1", + fechaMultivende: new Date(), + }, + { + where: { + folio: foliosExitosos, + }, + } + ); + + console.log(`✅ Se actualizaron ${foliosExitosos.length} registros exitosamente.`); + } else { + console.log("⚠️ No hubo SKUs con éxito en la respuesta."); + } + } catch (error) { + console.error("❌ Error actualizando stock:", error.message); + } +}; \ No newline at end of file diff --git a/src/procesos/actualizarPrecios.js b/src/procesos/actualizarPrecios.js new file mode 100644 index 0000000..116289d --- /dev/null +++ b/src/procesos/actualizarPrecios.js @@ -0,0 +1,72 @@ +import axios from "axios"; +import { multivendeConfig } from "../config.js"; +import { dbEcommerceFlexi } from "../database/config/db.models.js"; +import InterfazWS from "../database/models/interfazWs.js"; +import { obtenerToken, tokenExpirado } from "../utils/utils.js"; + +const urlBase = multivendeConfig.urlBase; +const priceListId = multivendeConfig.priceListId; + + +export const actualizarPrecios = async (idsitio = 8) => { + try { + let { accessToken, expiresAt } = await obtenerToken(idsitio); + + const [precios] = await dbEcommerceFlexi.query( + `SELECT * FROM w_precio_multivende`, + { raw: true } + ); + + for (const precio of precios) { + const { folio, sku, precio: valor } = precio; + + const isExpired = await tokenExpirado(expiresAt); + if (isExpired) { + ({ accessToken, expiresAt } = await obtenerToken(idsitio)); + } + + try { + await axios.post( + `${urlBase}/api/product-price-lists/${priceListId}/product-versions/${sku}/set`, + { + net: valor, + gross: valor, + tax: 0, + priceWithDiscount: 0, + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + } + ); + + await InterfazWS.update( + { + Multivende: 1, + fechaMultivende: new Date(), + }, + { where: { folio } } + ); + + console.log(`✅ Precio actualizado para SKU: ${sku}, folio: ${folio}`); + } catch (error) { + const status = error.response?.status; + const mensaje = error.response?.data || error.message; + + if (status === 404) { + await InterfazWS.update( + { Multivende: 3 }, + { where: { folio } } + ); + console.warn(`⚠️ SKU no encontrado (404), multivende actualizado a 3 para folio: ${folio}`); + } else { + console.error(`❌ Error actualizando PRECIO, SKU: ${sku}, folio: ${folio}`, mensaje); + } + } + } + } catch (error) { + console.error("Error en actualizarPrecios:", error); + } +}; diff --git a/src/procesos/autenticacion.js b/src/procesos/autenticacion.js new file mode 100644 index 0000000..14fb55b --- /dev/null +++ b/src/procesos/autenticacion.js @@ -0,0 +1,85 @@ +import axios from "axios"; + +import { multivendeConfig } from "../config.js"; +// import SkuSitios from "../database/models/skuSitios.js" +import tbCredencialesIntegracion from "../database/models/tbCredencialesIntegracion.js"; + +const clientId = multivendeConfig.clientId; +const clientSecret = multivendeConfig.clientSecret; +const urlBase = multivendeConfig.urlBase; + +/** + * + * Cuando la función actualizarToken no alcance a actualizar el token o falle, + * hay que seguir los siguientes pasos: + * 1.- Generar un autorization code en la cuenta de multivende del cliente. Usar la cuenta de Patricio Muñoz + * asociada a Flexi. Ir a aplicaciones y buscar la app Advicom. Clickear el botón generar token. + * 2.- Generar un nuevo access token y refresh token y actualizarlos en la tabla tb_credenciales_integracion. + * Usar el siguiente curl: + * curl --location -g '{{base_url}}/oauth/access-token' \ +--header 'cache-control: no-cache' \ +--header 'Content-Type: application/json' \ +--data '{ + "client_id": 00000000000, + "client_secret": "NMV6TupxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxpR3YNW", + "grant_type": "authorization_code", + "code": "ac-xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx" +} +' + + */ + +export const actualizarToken = async (idsitio = 8) => { + try { + const dataCredenciales = await tbCredencialesIntegracion.findOne({ + where: { idsitio }, + }); + + if (!dataCredenciales || !dataCredenciales.refresh_token_D) { + throw new Error(`No hay refresh_token disponible para idsitio ${idsitio}`); + } + + const data = { + client_id: clientId, + client_secret: clientSecret, + grant_type: "refresh_token", + refresh_token: dataCredenciales.refresh_token_D, + }; + + const response = await axios.post(`${urlBase}/oauth/access-token`, data, { + headers: { + "Content-Type": "application/json", + "cache-control": "no-cache", + }, + }); + console.log(response); + + + const { + token: access_token, + refreshToken: refresh_token, + token_type, + expiresAt, + } = response.data; + + const fechaExpiracion = new Date(expiresAt); + const ahora = new Date(); + const expires_in = Math.floor((fechaExpiracion - ahora) / 1000); + + await tbCredencialesIntegracion.update({ + token: access_token, + refresh_token_D: refresh_token, + token_type_D: token_type || "bearer", + expires_token_D: expires_in, + fecha_D: ahora, + }, { + where: { idsitio }, + }); + + console.log(`🔄 Token actualizado correctamente para idsitio ${idsitio}`); + return access_token; + } catch (error) { + console.error("❌ Error al renovar el token:", error.response?.data || error.message); + throw error; + } +}; \ No newline at end of file diff --git a/src/procesos/envioBoleta.js b/src/procesos/envioBoleta.js new file mode 100644 index 0000000..fd9918b --- /dev/null +++ b/src/procesos/envioBoleta.js @@ -0,0 +1,178 @@ +import fs from 'fs'; +import path from 'path'; +import axios from 'axios'; +import FormData from 'form-data'; +import { fileURLToPath } from 'url'; +import { Sequelize } from 'sequelize'; +import { dbEcommerceFlexi, dbCentralFlexi } from '../database/config/db.models.js'; +import { multivendeConfig } from "../config.js"; +import { obtenerToken, tokenExpirado } from "../utils/utils.js"; + +const urlBase = multivendeConfig.urlBase; +const idsitio = 8; +const emitterId = multivendeConfig.emitterId; +const provider = multivendeConfig.provider; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// 📁 Crear carpeta temporal si no existe +const tempDir = path.join(__dirname, 'tempdoc'); +if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir, { recursive: true }); +} + + + +// 🏁 Función principal +export const procesarDocumentos = async () => { + try { + const datos = await dbCentralFlexi.query( + `SELECT * from w_estado_doc_multivende`, + { type: Sequelize.QueryTypes.SELECT } + ); + + // console.log(datos) + + let { accessToken, expiresAt } = await obtenerToken(idsitio); + if (!accessToken) throw new Error('No se encontró token'); + + for (const row of datos) { + if (tokenExpirado(expiresAt)) { + ({ accessToken, expiresAt } = await obtenerToken(idsitio)); + if (!accessToken) throw new Error('No se pudo renovar token'); + } + + const nombreDoc = `${row.tipodoc}${row.NRODOC_LV}`; + const succli = row.succli_ph; + const base64 = row.pdfbase64; + const rutaTemporal = path.join(tempDir, `${nombreDoc}.pdf`); + fs.writeFileSync(rutaTemporal, Buffer.from(base64, 'base64')); + + try { + const { clientId, idcentry: checkoutId } = await extraerOrderIdyClientId(succli); + const fechaEmision = formatearFecha(row.FECMOV_LV); + + const response = await enviarDocumentoUploadCreate({ + checkoutId, + accessToken, + filePath: rutaTemporal, + clientId, + numFolio: row.NRODOC_LV.toString(), + emissionDate: fechaEmision, + }); + + if (response && (response.status === 'created' || response.statusCode === 200 || response.ok === true)) { + await dbCentralFlexi.query( + `UPDATE docpdf SET estado = 1 WHERE tipodoc = ? AND numerodoc = ?`, + { + replacements: [row.tipodoc, row.NRODOC_LV], + type: Sequelize.QueryTypes.UPDATE, + } + ); + console.log(`✅ Documento ${nombreDoc} enviado y actualizado`); + } else { + console.warn(`⚠️ Documento ${nombreDoc} no fue aceptado por la API. Respuesta:`, response); + } + } catch (err) { + console.error(`❌ Error al enviar ${nombreDoc}:`, err.message); + } finally { + // 🔥 Borrar archivo temporal siempre, haya éxito o fallo + try { + if (fs.existsSync(rutaTemporal)) fs.unlinkSync(rutaTemporal); + } catch (e) { + console.warn(`⚠️ No se pudo borrar ${rutaTemporal}:`, e.message); + } + } + } + } catch (err) { + console.error('❌ Error general:', err.message); + } +}; + + +// 🧩 Funciones auxiliares +const extraerOrderIdyClientId = async (succli) => { + const result = await dbEcommerceFlexi.query( + `SELECT json, idcentry FROM log_ordenes_marketplace WHERE NROPED = ? LIMIT 1`, + { + replacements: [succli], + type: Sequelize.QueryTypes.SELECT, + } + ); + + + if (!result.length) return { clientId: null, idcentry: null }; + + try { + const parsed = JSON.parse(result[0].json); + const clientId = parsed?.Client?._id || null; + const idcentry = result[0].idcentry || null; + + return { clientId, idcentry }; + } catch (err) { + console.error(`❌ Error al parsear JSON de log_ordenes_marketplace:`, err.message); + return { clientId: null, idcentry: result[0].idcentry || null }; + } +}; + +export const enviarDocumentoUploadCreate = async ({ + checkoutId, + accessToken, + filePath, + clientId, + numFolio, + emissionDate, + documentType +}) => { + if (!fs.existsSync(filePath)) { + throw new Error(`El archivo no existe: ${filePath}`); + } + /** + * electronic_billing_electronic_invoice (Factura Electrónica) + electronic_billing_not_taxed_electronic_invoice (Factura Electrónica Exenta) + electronic_billing_electronic_bill (Boleta Electrónica) + * + * + */ + let type = "electronic_billing_electronic_bill"; + if (documentType == "FV") { + type = "electronic_billing_electronic_invoice " + } + + try { + const url = `${urlBase}/api/checkouts/${checkoutId}/electronic-billing-documents/upload-create`; + + const form = new FormData(); + form.append('file', fs.createReadStream(filePath)); + form.append('ClientId', clientId); + form.append('id', numFolio); + form.append('emissionDate', emissionDate); + form.append('type', type); + form.append('provider', provider); + form.append('ElectronicBillingDocumentEmitterId', emitterId); + + const response = await axios.post(url, form, { + headers: { + Authorization: `Bearer ${accessToken}`, + ...form.getHeaders(), // ✅ Esto funciona ahora porque usas la librería correcta + }, + timeout: 60000, + }); + + console.log(`✅ Documento enviado para checkout ${checkoutId}`); + return response.data; + } catch (error) { + console.error(`❌ Error en upload-create:`, error.response?.data || error.message); + throw error; + } +}; + +const formatearFecha = (fecha) => { + if (!fecha || typeof fecha !== 'string') return ''; + const año = fecha.slice(0, 4); + const mes = fecha.slice(4, 6); + const dia = fecha.slice(6, 8); + return `${año}-${mes}-${dia}`; +}; + diff --git a/src/utils/utils.js b/src/utils/utils.js new file mode 100644 index 0000000..ccc30f9 --- /dev/null +++ b/src/utils/utils.js @@ -0,0 +1,26 @@ +import tbCredencialesIntegracion from "../database/models/tbCredencialesIntegracion.js"; + +/** + * Obtiene el token de acceso desde la tabla tb_credenciales_integracion. + */ +export const obtenerToken = async (idsitio = 8) => { + const credencial = await tbCredencialesIntegracion.findOne({ + where: { idsitio }, + }); + + if (!credencial || !credencial.token) { + throw new Error(`No hay token disponible para idsitio ${idsitio}`); + } + + return { + accessToken: credencial.token, + expiresAt: credencial.expires_at + }; +}; + +export const tokenExpirado = async (expiresAt) => { + const fechaExpiracion = new Date(expiresAt); + const ahora = new Date(); + + return fechaExpiracion <= ahora; +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..e27b05f --- /dev/null +++ b/yarn.lock @@ -0,0 +1,588 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/debug@^4.1.8": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + +"@types/node@*": + version "22.15.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.17.tgz#355ccec95f705b664e4332bb64a7f07db30b7055" + integrity sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw== + dependencies: + undici-types "~6.21.0" + +"@types/validator@^13.7.17": + version "13.15.0" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.15.0.tgz#d4643730536900190bb476a1dda0a4897c8881e2" + integrity sha512-nh7nrWhLr6CBq9ldtw0wx+z9wKnnv/uTVLA9g/3/TcOYxbpOSZE+MhKPmWqU+K0NvThjhv12uD8MuqijB0WzEA== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +aws-ssl-profiles@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz#157dd77e9f19b1d123678e93f120e6f193022641" + integrity sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g== + +axios@^1.8.4: + version "1.9.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.9.0.tgz#25534e3b72b54540077d33046f77e3b8d7081901" + integrity sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +chokidar@^3.5.2: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +debug@^4, debug@^4.3.4: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + +dotenv@^16.4.7: + version "16.5.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.5.0.tgz#092b49f25f808f020050051d1ff258e404c78692" + integrity sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg== + +dottie@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/dottie/-/dottie-2.0.6.tgz#34564ebfc6ec5e5772272d466424ad5b696484d4" + integrity sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +form-data@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c" + integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + mime-types "^2.1.12" + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +generate-function@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" + integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + dependencies: + is-property "^1.0.2" + +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + +inflection@^1.13.4: + version "1.13.4" + resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.13.4.tgz#65aa696c4e2da6225b148d7a154c449366633a32" + integrity sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-property@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +long@^5.2.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + +lru-cache@^7.14.1: + version "7.18.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" + integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== + +lru.min@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/lru.min/-/lru.min-1.1.2.tgz#01ce1d72cc50c7faf8bd1f809ebf05d4331021eb" + integrity sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg== + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +moment-timezone@^0.5.43: + version "0.5.48" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.48.tgz#111727bb274734a518ae154b5ca589283f058967" + integrity sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw== + dependencies: + moment "^2.29.4" + +moment@^2.29.4: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mysql2@^3.14.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.14.1.tgz#7786160abf086fd279e0253e16e34c05b4ab3b3e" + integrity sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w== + dependencies: + aws-ssl-profiles "^1.1.1" + denque "^2.1.0" + generate-function "^2.3.1" + iconv-lite "^0.6.3" + long "^5.2.1" + lru.min "^1.0.0" + named-placeholders "^1.1.3" + seq-queue "^0.0.5" + sqlstring "^2.3.2" + +named-placeholders@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351" + integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w== + dependencies: + lru-cache "^7.14.1" + +node-cron@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/node-cron/-/node-cron-3.0.3.tgz#c4bc7173dd96d96c50bdb51122c64415458caff2" + integrity sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A== + dependencies: + uuid "8.3.2" + +nodemon@^3.1.9: + version "3.1.10" + resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.10.tgz#5015c5eb4fffcb24d98cf9454df14f4fecec9bc1" + integrity sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw== + dependencies: + chokidar "^3.5.2" + debug "^4" + ignore-by-default "^1.0.1" + minimatch "^3.1.2" + pstree.remy "^1.1.8" + semver "^7.5.3" + simple-update-notifier "^2.0.0" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.5" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +object-assign@^4: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +pg-connection-string@^2.6.1: + version "2.8.5" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.8.5.tgz#82cefd0269cb64a09603342d9b69e8392e6eb6cd" + integrity sha512-Ni8FuZ8yAF+sWZzojvtLE2b03cqjO5jNULcHFfM9ZZ0/JXrgom5pBREbtnAw7oxsxJqHw9Nz/XWORUEL3/IFow== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +retry-as-promised@^7.0.4: + version "7.1.1" + resolved "https://registry.yarnpkg.com/retry-as-promised/-/retry-as-promised-7.1.1.tgz#3626246f04c1941ff10cebcfa3df0577fd8ab2d7" + integrity sha512-hMD7odLOt3LkTjcif8aRZqi/hybjpLNgSk5oF5FCowfCjok6LukpN2bDX7R5wDmbgBQFn7YoBxSagmtXHaJYJw== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^7.5.3, semver@^7.5.4: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +seq-queue@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" + integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q== + +sequelize-pool@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/sequelize-pool/-/sequelize-pool-7.1.0.tgz#210b391af4002762f823188fd6ecfc7413020768" + integrity sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg== + +sequelize@^6.37.6: + version "6.37.7" + resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-6.37.7.tgz#55a6f8555ae76c1fbd4bce76b2ac5fcc0a1e6eb6" + integrity sha512-mCnh83zuz7kQxxJirtFD7q6Huy6liPanI67BSlbzSYgVNl5eXVdE2CN1FuAeZwG1SNpGsNRCV+bJAVVnykZAFA== + dependencies: + "@types/debug" "^4.1.8" + "@types/validator" "^13.7.17" + debug "^4.3.4" + dottie "^2.0.6" + inflection "^1.13.4" + lodash "^4.17.21" + moment "^2.29.4" + moment-timezone "^0.5.43" + pg-connection-string "^2.6.1" + retry-as-promised "^7.0.4" + semver "^7.5.4" + sequelize-pool "^7.1.0" + toposort-class "^1.0.1" + uuid "^8.3.2" + validator "^13.9.0" + wkx "^0.5.0" + +simple-update-notifier@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== + dependencies: + semver "^7.5.3" + +sqlstring@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.3.tgz#2ddc21f03bce2c387ed60680e739922c65751d0c" + integrity sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg== + +supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toposort-class@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988" + integrity sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg== + +touch@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" + integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== + +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +uuid@8.3.2, uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +validator@^13.9.0: + version "13.15.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.0.tgz#2dc7ce057e7513a55585109eec29b2c8e8c1aefd" + integrity sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA== + +vary@^1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +wkx@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/wkx/-/wkx-0.5.0.tgz#c6c37019acf40e517cc6b94657a25a3d4aa33e8c" + integrity sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg== + dependencies: + "@types/node" "*"