First upload

This commit is contained in:
2026-03-17 14:12:37 +01:00
commit c857900860
46 changed files with 3343 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
// import mariadb from 'mariadb';
import * as mariadb from 'mariadb';
// Fix para BigInt en la serialización JSON (necesario para IDs grandes en MariaDB)
BigInt.prototype.toJSON = function () {
return this.toString();
};
// Configuración del Pool
export const pool = mariadb.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: 'Reale',
connectionLimit: 20,
timezone: 'Z',
insertIdAsNumber: true
});
// Helper para escapar identificadores (tablas/columnas)
const b = (id) => `\`${id.replace(/`/g, '``')}\``;
export class Database {
constructor(connector) {
/** @type {mariadb.Pool | mariadb.PoolConnection} */
this.connector = connector;
}
async insert(table, data) {
const rows = Array.isArray(data) ? data : [data];
if (!rows.length) return null;
const fields = Object.keys(rows[0]);
const query = `INSERT INTO ${b(table)} (${fields.map(b).join(',')}) VALUES (${fields.map(() => '?').join(',')})`;
return this.connector.batch(query, rows.map(r => fields.map(f => r[f])));
}
async update(table, data, where) {
const fields = Object.keys(data);
const wFields = Object.keys(where);
const query = `UPDATE ${b(table)} SET ${fields.map(f => `${b(f)}=?`).join(',')} WHERE ${wFields.map(f => `${b(f)}=?`).join(' AND ')}`;
return this.connector.query(query, [...fields.map(f => data[f]), ...wFields.map(f => where[f])]);
}
async upsert(table, data, ignore = false) {
const rows = Array.isArray(data) ? data : [data];
if (!rows.length) return null;
const fields = Object.keys(rows[0]);
const set = fields.filter(f => f !== 'id').map(f => `${b(f)}=VALUES(${b(f)})`).join(',');
const query = `INSERT ${ignore ? 'IGNORE' : ''} INTO ${b(table)} (${fields.map(b).join(',')}) VALUES (${fields.map(() => '?').join(',')}) ON DUPLICATE KEY UPDATE ${set}`;
return this.connector.batch(query, rows.map(r => fields.map(f => r[f])));
}
async delete(table, where) {
const keys = Object.keys(where);
if (!keys.length) throw new Error('Delete requiere condiciones');
return this.connector.query(`DELETE FROM ${b(table)} WHERE ${keys.map(k => `${b(k)}=?`).join(' AND ')}`, keys.map(k => where[k]));
}
async select(table, where = {}, opts = { limit: 5000, order: null }) {
const keys = Object.keys(where);
let sqlStr = `SELECT * FROM ${b(table)}`;
if (keys.length) sqlStr += ` WHERE ${keys.map(k => `${b(k)}=?`).join(' AND ')}`;
if (opts.order) sqlStr += ` ORDER BY ${b(opts.order)}`;
return this.connector.query(sqlStr + ` LIMIT ${opts.limit}`, keys.map(k => where[k]));
}
async one(table, where) {
const res = await this.select(table, where, { limit: 1 });
return res[0] || null;
}
async count(table, where = {}) {
const keys = Object.keys(where);
let sqlStr = `SELECT COUNT(*) as total FROM ${b(table)}`;
if (keys.length) sqlStr += ` WHERE ${keys.map(k => `${b(k)}=?`).join(' AND ')}`;
const res = await this.connector.query(sqlStr, keys.map(k => where[k]));
return Number(res[0].total);
}
async search(table, fields, term, limit = 1000) {
if (!fields.length || !term) return [];
const where = fields.map(f => `${b(f)} LIKE ?`).join(' OR ');
return this.connector.query(`SELECT * FROM ${b(table)} WHERE ${where} LIMIT ${limit}`, fields.map(() => `%${term}%`));
}
async unique(table, fields, where = {}) {
const f = Array.isArray(fields) ? fields.map(b).join(',') : b(fields);
const keys = Object.keys(where);
let sqlStr = `SELECT DISTINCT ${f} FROM ${b(table)}`;
if (keys.length) sqlStr += ` WHERE ${keys.map(k => `${b(k)}=?`).join(' AND ')}`;
const sort = Array.isArray(fields) ? b(fields[0]) : f;
return this.connector.query(sqlStr + ` ORDER BY ${sort} ASC`, keys.map(k => where[k]));
}
async raw(query, data = {}) {
const params = [];
const sqlStr = query.replace(/:(\w+)/g, (_, key) => {
params.push(data[key]);
return '?';
});
return this.connector.query(sqlStr, params);
}
}
/**
* Lógica de Transacciones
*/
export const trx = async (opts, actions) => {
const tasks = Array.isArray(opts) ? opts : [opts];
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
const transactionDb = new Database(conn);
const results = {};
for (const item of tasks) {
if (typeof actions[item.action] !== 'function') {
throw new Error(`Acción ${item.action} no existe`);
}
results[item.action] = await actions[item.action](transactionDb, item);
}
await conn.commit();
return results;
} catch (err) {
console.error('Error en la transacción:', err);
await conn.rollback();
throw err;
} finally {
if (conn) conn.release();
}
};
// Exportamos la instancia principal vinculada al Pool
export const db = new Database(pool);

View File

@@ -0,0 +1,47 @@
import nodemailer from 'nodemailer';
// Configuración del transportador
const transporter = nodemailer.createTransport({
host: process.env.db, // Nota: Asegúrate de que esta variable sea el host SMTP (ej. smtp.tuproveedor.com)
port: 587,
secure: false, // true para 465, false para otros puertos
auth: {
user: "natxocc",
pass: "Ncc629406731.",
},
});
/**
* Envía un correo electrónico
* @param {string} to - Destinatario
* @param {string} subject - Asunto
* @param {string} html - Cuerpo en HTML
* @param {Object|null} file - Opcional: { filename, content, contentType }
*/
export const send = async (to, subject, html, file = null) => {
const mailOptions = {
from: '"Natxocc" <natxocc@natxocc.com>',
to,
subject,
html,
};
// Si pasas un archivo (ej: buffer de Sharp), lo añadimos como attachment
if (file) {
mailOptions.attachments = [
{
filename: file.filename,
content: file.content,
contentType: file.contentType || 'image/jpeg'
}
];
}
try {
await transporter.sendMail(mailOptions);
} catch (error) {
console.error('Error enviando email:', error);
throw error; // Re-lanzamos para que la ruta de Hono capture el error
}
};

View File

@@ -0,0 +1,112 @@
import * as soap from 'soap';
import { HttpClient } from 'soap';
import { trx } from './db.service.js';
import { actions } from '../actions/soap.actions.js';
// --- Helpers de Fecha ---
const fmt = (d) => (!isNaN(d.getTime()) ? d.toLocaleDateString('sv-SE') : null);
const getFirstMonth = (m = 0) => {
const now = new Date();
return fmt(new Date(now.getFullYear(), now.getMonth() + m, 1));
};
const getDate = (d = 0) => {
const t = new Date();
t.setDate(t.getDate() + d);
return fmt(t);
};
const SOAP_SERVICES = {
DescargaPolizas: "https://lba.realeonline.net/Reale.B2b.Services.Multitarificadores.IisHost/DescargaPolizas.svc?wsdl",
LiquidacionMediador: "https://lba.realeonline.net/Reale.B2b.Services.Multitarificadores.IisHost/LiquidacionMediador.svc?wsdl",
DescargaSiniestros: "https://lba.realeonline.net/Reale.B2b.Services.Multitarificadores.IisHost/DescargaSiniestros.svc?wsdl",
DescargaCompletaRecibos: "https://lba.realeonline.net/Reale.B2b.Services.Multitarificadores.IisHost/DescargaCompletaRecibos.svc?wsdl",
DescargaCartera: "https://lba.realeonline.net/Reale.B2b.Services.Multitarificadores.IisHost/DescargaCartera.svc?wsdl",
ConsultaPolizas: "https://lba.realeonline.net/Reale.B2b.Services.Multitarificadores.IisHost/ConsultaPolizas.svc?wsdl",
ConsultaRecibos: "https://lba.realeonline.net/Reale.B2b.Services.Multitarificadores.IisHost/ConsultaRecibos.svc?wsdl",
ConsultaAgendaTramitacion: "https://lba.realeonline.net/Reale.B2b.Services.Multitarificadores.IisHost/ConsultaAgendaTramitacion.svc?wsdl",
TramitacionSiniestro: "https://lba.realeonline.net/Reale.B2b.Services.Multitarificadores.IisHost/TramitacionSiniestro.svc?wsdl",
};
export const soapCall = async (params, query, save = true) => {
const { CodigoMediador, service, method } = params;
const url = SOAP_SERVICES[service];
if (!url) throw new Error(`Servicio SOAP '${service}' no encontrado`);
// --- Identificador desde ENV o Query ---
const Identificador = query.Identificador || process.env[`SOAP_${CodigoMediador}`];
if (!Identificador) throw new Error(`Identificador no configurado para mediador ${CodigoMediador}`);
// --- Normalización de Argumentos ---
const Mediador = query.Mediador || CodigoMediador;
const CodigoPoliza = query.CodigoPoliza || "0";
const CodigoSiniestro = query.CodigoSiniestro || "0";
const CodigoExpediente = query.CodigoExpediente || "0";
const Observaciones = query.Observaciones || "";
const CodigoRecibo = query.CodigoRecibo || "0";
const CodigoSuplemento = query.CodigoSuplemento || "1000";
const Clave = query.Clave || "10000";
const Empresa = query.Empresa || "4";
const CodigoEmpresa = query.CodigoEmpresa || Empresa;
const Plataforma = query.Plataforma || "10000";
const CodigoRamo = query.CodigoRamo || "0";
const FechaFinal = query.FechaFinal || getDate();
const FechaRenovacion = query.FechaRenovacion || getFirstMonth(-1);
const FechaInicial = query.FechaInicial || getFirstMonth(-1);
const FechaLiquidacion = query.FechaLiquidacion || getDate();
const HEADER = `<int:Plataforma xmlns:int="http://reale.net/wcf/internalServices">${Plataforma}</int:Plataforma><int:Identificador xmlns:int="http://reale.net/wcf/internalServices">${Identificador}</int:Identificador>`;
// --- Diccionario de Configuración ---
const SOAP_CONFIG = {
DescargaPolizas: { Descargar: { args: { Clave, CodigoRamo, Empresa, FechaFinal, FechaInicial, Identificador, Plataforma, TipoSuplemento1: "NP", TipoSuplemento2: "AN", TipoSuplemento3: "RE", TipoSuplemento4: "SP" }, header: null } },
LiquidacionMediador: {
Consulta: { args: { CodigoMediador, Empresa, FechaLiquidacion }, header: HEADER },
ObtenerFechasLiquidacion: { args: { CodigoMediador, Empresa }, header: HEADER }
},
DescargaSiniestros: { Descargar: { args: { AceptarCicos: true, AceptarSdm: true, Clave, CodigoRamo, Empresa, FechaFinal, FechaInicial, Identificador, IncluirAperturas: true, IncluirAperturasCicos: true, IncluirAperturasSdm: true, IncluirPagosImdemnizacion: true, IncluirTerminados: true, Plataforma }, header: HEADER } },
DescargaCompletaRecibos: { DescargarNew: { args: { Clave, CodigoRamo, Empresa, FechaFinal, FechaInicial, Identificador, Plataforma, IncluirAnulados: true, IncluirCobrados: true, IncluirDevueltos: true, IncluirNuevos: true }, header: null } },
DescargaCartera: {
DescargarCartera: { args: { CodigoMediador, Empresa, FechaRenovacion, Identificador, Plataforma }, header: null },
ObtenerListaRamos: { args: { Empresa }, header: null },
ObtenerListaRenovaciones: { args: { Empresa, Mediador }, header: null }
},
ConsultaPolizas: {
ConsultarPoliza: { args: { CodigoPoliza, CodigoRamo, CodigoSuplemento, Empresa, Identificador, Plataforma }, header: null },
ObtenerListaPolizasMediador: { args: { CodigoMediador, Empresa, Identificador }, header: null },
ObtenerListaSuplementosPoliza: { args: { CodigoPoliza, CodigoRamo, Empresa, Identificador }, header: null }
},
ConsultaRecibos: {
ConsultarRecibo: { args: { CodigoPoliza, CodigoRamo, CodigoRecibo, CodigoSuplemento, Empresa, Identificador, Plataforma }, header: null },
ObtenerListaRecibosPoliza: { args: { CodigoPoliza, CodigoRamo, Empresa, Identificador }, header: null }
},
ConsultaAgendaTramitacion: { ConsultaTramitacion: { args: { Datos: { CodigoExpediente, CodigoPoliza, CodigoSiniestro, Empresa } }, header: HEADER } },
TramitacionSiniestro: { InsertarTramite: { args: { CodigoEmpresa, CodigoExpediente, CodigoSiniestro, Identificador, Observaciones, Plataforma }, header: null } }
};
const config = SOAP_CONFIG[service]?.[method];
if (!config) throw new Error(`Método '${method}' no configurado para el servicio '${service}'`);
try {
const client = await soap.createClientAsync(url, {
httpClient: new HttpClient(),
request: { timeout: 120000 }
});
if (config.header) client.addSoapHeader(config.header);
const methodName = `${method}Async`;
const [result] = await client[methodName](config.args);
if (save) {
await trx({ action: service, data: result }, actions);
}
return result;
} catch (error) {
console.error(`[SOAP ERROR] ${service}/${method}:`, error.message);
throw error;
}
};

View File

@@ -0,0 +1,151 @@
import { XMLParser } from "fast-xml-parser";
import { trx } from "./db.service.js";
import { actions } from "../actions/soap.actions.js";
const parser = new XMLParser({
removeNSPrefix: true,
ignoreAttributes: false,
parseTagValue: true,
trimValues: true,
});
const ALIAS_MAP = {
Polizas: { service: "DescargaPolizas", method: "Descargar" },
Recibos: { service: "DescargaCompletaRecibos", method: "Descargar" },
Siniestros: { service: "DescargaSiniestros", method: "Descargar" },
Cartera: { service: "DescargaCartera", method: "DescargarCartera" },
TramiteSiniestro: { service: "ConsultaAgendaTramitacion", method: "ConsultaTramitacion" },
Poliza: { service: "ConsultaPolizas", method: "ConsultarPoliza" },
Recibo: { service: "ConsultaRecibos", method: "ConsultarRecibo" },
Siniestro: { service: "ConsultaSiniestros", method: "ConsultarSiniestro" },
};
const cleanNil = (obj) => {
if (obj !== null && typeof obj === "object") {
if (obj["@_nil"] === "true" || obj["@_nil"] === true) return null;
if (Array.isArray(obj)) return obj.map(cleanNil);
const newObj = {};
for (const [key, value] of Object.entries(obj)) {
newObj[key] = cleanNil(value);
}
return newObj;
}
return obj;
};
const fmt = (d) => (!isNaN(d.getTime()) ? d.toLocaleDateString("sv-SE") : null);
const getFirstMonth = (m = 0) => {
const now = new Date();
return fmt(new Date(now.getFullYear(), now.getMonth() + m, 1));
};
const getDate = (d = 0) => {
const t = new Date();
t.setDate(t.getDate() + d);
return fmt(t);
};
const getXmlBody = (service, method, q) => {
const templates = {
DescargaPolizas: `<des:DescargarPolizasRequest><des:Clave>${q.Clave}</des:Clave><des:CodigoRamo>${q.CodigoRamo}</des:CodigoRamo><des:Empresa>${q.Empresa}</des:Empresa><des:FechaFinal>${q.FechaFinal}</des:FechaFinal><des:FechaInicial>${q.FechaInicial}</des:FechaInicial><des:Identificador>${q.Identificador}</des:Identificador><des:Plataforma>${q.Plataforma}</des:Plataforma><des:TipoSuplemento1>NP</des:TipoSuplemento1><des:TipoSuplemento2>AN</des:TipoSuplemento2><des:TipoSuplemento3>RE</des:TipoSuplemento3><des:TipoSuplemento4>SP</des:TipoSuplemento4></des:DescargarPolizasRequest>`,
DescargaSiniestros: `<des:DescargarSiniestrosRequest><des:AceptarCicos>true</des:AceptarCicos><des:AceptarSdm>true</des:AceptarSdm><des:Clave>${q.Clave}</des:Clave><des:CodigoRamo>${q.CodigoRamo}</des:CodigoRamo><des:Empresa>${q.Empresa}</des:Empresa><des:FechaFinal>${q.FechaFinal}</des:FechaFinal><des:FechaInicial>${q.FechaInicial}</des:FechaInicial><des:Identificador>${q.Identificador}</des:Identificador><des:IncluirAperturas>true</des:IncluirAperturas><des:Plataforma>${q.Plataforma}</des:Plataforma></des:DescargarSiniestrosRequest>`,
DescargaCompletaRecibos: `<des:DescargaCompletaRecibosRequest><des:Clave>${q.Clave}</des:Clave><des:Empresa>${q.Empresa}</des:Empresa><des:FechaFinal>${q.FechaFinal}</des:FechaFinal><des:FechaInicial>${q.FechaInicial}</des:FechaInicial><des:Identificador>${q.Identificador}</des:Identificador><des:IncluirAnulados>true</des:IncluirAnulados><des:IncluirCobrados>true</des:IncluirCobrados><des:IncluirDevueltos>true</des:IncluirDevueltos><des:IncluirNuevos>true</des:IncluirNuevos></des:DescargaCompletaRecibosRequest>`,
DescargaCartera: `<des:DescargarCarteraRequest><des:CodigoMediador>${q.CodigoMediador}</des:CodigoMediador><des:Empresa>${q.Empresa}</des:Empresa><des:FechaRenovacion>${q.FechaRenovacion}</des:FechaRenovacion><des:Identificador>${q.Identificador}</des:Identificador><des:Plataforma>${q.Plataforma}</des:Plataforma></des:DescargarCarteraRequest>`,
ConsultaAgendaTramitacion: `<int:ConsultaTramitacionRequest xmlns:int="http://reale.net/wcf/internalServices"><int:Datos><int:CodigoExpediente>${q.CodigoExpediente}</int:CodigoExpediente><int:CodigoPoliza>${q.CodigoPoliza}</int:CodigoPoliza><int:CodigoSiniestro>${q.CodigoSiniestro}</int:CodigoSiniestro><int:Empresa>${q.Empresa}</int:Empresa></int:Datos></int:ConsultaTramitacionRequest>`,
ConsultaPolizas: `<des:ConsultarPolizaRequest><des:CodigoPoliza>${q.CodigoPoliza}</des:CodigoPoliza><des:CodigoRamo>${q.CodigoRamo}</des:CodigoRamo><des:CodigoSuplemento>${q.CodigoSuplemento || 0}</des:CodigoSuplemento><des:Empresa>${q.Empresa}</des:Empresa><des:Identificador>${q.Identificador}</des:Identificador><des:Plataforma>${q.Plataforma}</des:Plataforma></des:ConsultarPolizaRequest>`,
ConsultaRecibos: `<des:ConsultarReciboRequest><des:CodigoPoliza>${q.CodigoPoliza}</des:CodigoPoliza><des:CodigoRamo>${q.CodigoRamo}</des:CodigoRamo><des:CodigoRecibo>${q.CodigoRecibo || 0}</des:CodigoRecibo><des:CodigoSuplemento>${q.CodigoSuplemento || 0}</des:CodigoSuplemento><des:Empresa>${q.Empresa}</des:Empresa><des:Identificador>${q.Identificador}</des:Identificador><des:Plataforma>${q.Plataforma}</des:Plataforma></des:ConsultarReciboRequest>`,
ConsultaSiniestros: `<des:ConsultarSiniestroRequest><des:CodigoPoliza>${q.CodigoPoliza}</des:CodigoPoliza><des:CodigoRamo>${q.CodigoRamo}</des:CodigoRamo><des:CodigoSiniestro>${q.CodigoSiniestro}</des:CodigoSiniestro><des:CodigoSuplemento>${q.CodigoSuplemento || 0}</des:CodigoSuplemento><des:Empresa>${q.Empresa}</des:Empresa><des:Identificador>${q.Identificador}</des:Identificador><des:Plataforma>${q.Plataforma}</des:Plataforma></des:ConsultarSiniestroRequest>`,
};
return templates[service] || `<des:${method}Request><des:Identificador>${q.Identificador}</des:Identificador></des:${method}Request>`;
};
export const soapCall = async (params, query, save = true) => {
const { CodigoMediador, alias } = params;
const target = ALIAS_MAP[alias] || { service: params.service, method: params.method };
const { service, method } = target;
if (!service) throw new Error(`Servicio o Alias '${alias || params.service}' no definido`);
const url = `https://lba.realeonline.net/Reale.B2b.Services.Multitarificadores.IisHost/${service}.svc`;
const Identificador = query.Identificador || process.env[`SOAP_${CodigoMediador}`];
if (!Identificador) throw new Error(`Identificador no configurado para ${CodigoMediador}`);
const q = {
...query,
Identificador,
CodigoMediador,
Clave: query.Clave || "10000",
Empresa: query.Empresa || "4",
Plataforma: query.Plataforma || "10000",
CodigoRamo: query.CodigoRamo || "0",
CodigoSuplemento: query.CodigoSuplemento || "0",
CodigoPoliza: query.CodigoPoliza || "0",
CodigoSiniestro: query.CodigoSiniestro || "0",
CodigoExpediente: query.CodigoExpediente || "0",
FechaFinal: query.FechaFinal || getDate(),
FechaInicial: query.FechaInicial || (alias === "Cartera" ? getFirstMonth(0) : getFirstMonth(-1)),
FechaRenovacion: query.FechaRenovacion || getFirstMonth(1),
};
let nsBase = "http://reale.net/B2b/Multitarificadores";
let nsName = service;
let contract = `I${service}`;
let actMethod = method;
let soapHeader = "";
if (service === "DescargaCartera") {
nsName = "DescargaFicheros";
contract = "IDescargaCartera";
}
else if (service === "ConsultaPolizas") {
nsName = "ConsultaPolizas";
contract = "IConsultaPolizas";
}
else if (service === "DescargaCompletaRecibos" && method === "Descargar") {
actMethod = "DescargarNew";
}
else if (service === "ConsultaAgendaTramitacion") {
nsBase = "http://reale.net/wcf/internalServices";
nsName = "";
contract = "IAgendaTramitacion";
actMethod = "ConsultaTramitacion";
soapHeader = `<int:Plataforma xmlns:int="http://reale.net/wcf/internalServices">${q.Plataforma}</int:Plataforma><int:Identificador xmlns:int="http://reale.net/wcf/internalServices">${q.Identificador}</int:Identificador>`;
}
const fullNs = nsName ? `${nsBase}/${nsName}` : nsBase;
const soapAction = `"${fullNs}/${contract}/${actMethod}"`.replace(/([^:])\/\//g, "$1/");
const soapEnvelope = `
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:des="http://reale.net/B2b/Multitarificadores/${nsName || "DescargaFicheros"}"
xmlns:int="http://reale.net/wcf/internalServices">
<soapenv:Header>${soapHeader}</soapenv:Header>
<soapenv:Body>${getXmlBody(service, method, q)}</soapenv:Body>
</soapenv:Envelope>`.trim();
try {
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "text/xml;charset=UTF-8", "SOAPAction": soapAction },
body: soapEnvelope,
});
const xmlData = await response.text();
const rawJson = parser.parse(xmlData);
if (!response.ok) {
const fault = rawJson.Envelope?.Body?.Fault?.faultstring;
throw new Error(typeof fault === "object" ? JSON.stringify(fault) : fault || `Error ${response.status}`);
}
const resultBody = rawJson.Envelope?.Body;
const responseName = Object.keys(resultBody || {})[0];
const result = cleanNil(resultBody?.[responseName] || {});
// CUIDADO DA ERROR CUANDO USAS /Poliza /Siniestro /Recibo porque no hay un action para ellos
if (save) await trx({ action: service, data: result }, actions);
return result;
} catch (error) {
console.error(`[SOAP ERROR] ${service}/${method}:`, error.message);
throw error;
}
};