commit inicial
This commit is contained in:
16
src/actualizarToken.js
Normal file
16
src/actualizarToken.js
Normal file
@@ -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);
|
||||
}
|
||||
});
|
||||
37
src/config.js
Normal file
37
src/config.js
Normal file
@@ -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,
|
||||
|
||||
};
|
||||
23
src/database/config/db.models.js
Normal file
23
src/database/config/db.models.js
Normal file
@@ -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,
|
||||
}
|
||||
);
|
||||
79
src/database/models/interfazWs.js
Normal file
79
src/database/models/interfazWs.js
Normal file
@@ -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;
|
||||
72
src/database/models/tbCredencialesIntegracion.js
Normal file
72
src/database/models/tbCredencialesIntegracion.js
Normal file
@@ -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;
|
||||
18
src/envioDocumentos.js
Normal file
18
src/envioDocumentos.js
Normal file
@@ -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")
|
||||
|
||||
});
|
||||
31
src/envioPrecios.js
Normal file
31
src/envioPrecios.js
Normal file
@@ -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");
|
||||
// });
|
||||
22
src/envioStock.js
Normal file
22
src/envioStock.js
Normal file
@@ -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")
|
||||
|
||||
});
|
||||
97
src/procesos/actualizacionStock.js
Normal file
97
src/procesos/actualizacionStock.js
Normal file
@@ -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);
|
||||
}
|
||||
};
|
||||
72
src/procesos/actualizarPrecios.js
Normal file
72
src/procesos/actualizarPrecios.js
Normal file
@@ -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);
|
||||
}
|
||||
};
|
||||
85
src/procesos/autenticacion.js
Normal file
85
src/procesos/autenticacion.js
Normal file
@@ -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;
|
||||
}
|
||||
};
|
||||
178
src/procesos/envioBoleta.js
Normal file
178
src/procesos/envioBoleta.js
Normal file
@@ -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}`;
|
||||
};
|
||||
|
||||
26
src/utils/utils.js
Normal file
26
src/utils/utils.js
Normal file
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user