- Backend Node.js/Express con PostgreSQL - Frontend React 19 con Vite - Docker Compose para orquestacion - Documentacion completa en README.md - Scripts SQL para base de datos - Configuracion de ejemplo (.env.example)
651 lines
24 KiB
JavaScript
651 lines
24 KiB
JavaScript
const pool = require('../db/connection');
|
|
const transporter = require('../services/mailService');
|
|
|
|
const contractsNearToEnd = async (req, res) => {
|
|
try{
|
|
const countResult = await pool.query('SELECT contratcsneartoend() as total');
|
|
const contractsResult = await pool.query('SELECT * FROM contractsneartoenddata()');
|
|
const userEmails = await pool.query('SELECT * FROM getusersmailneartoend()');
|
|
const totalExpiring = countResult.rows[0].total;
|
|
const contracts = contractsResult.rows;
|
|
const users = userEmails.rows;
|
|
|
|
if(totalExpiring >= 1 && users.length > 0){
|
|
|
|
const recipients = users.map(u => u.email);
|
|
|
|
const mailOptions = {
|
|
from: 'soporte@horuxfin.com',
|
|
to: recipients,
|
|
subject: 'Contratos próximos a expirar',
|
|
html: `
|
|
<div style="max-width:600px;margin:0 auto;font-family:Arial,Helvetica,sans-serif;
|
|
background-color:#ffffff;border:1px solid #e5e5e5;border-radius:8px;
|
|
overflow:hidden;">
|
|
<!-- Header -->
|
|
<div style="background-color:#7a0c1e;padding:18px 22px;">
|
|
<h2 style="margin:0;color:#ffffff;font-size:20px;font-weight:600;">
|
|
Notificación de contratos
|
|
</h2>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div style="padding:22px;color:#333333;font-size:14px;line-height:1.6;">
|
|
<p style="margin-top:0;">
|
|
Actualmente hay <b>${totalExpiring}</b> contrato(s) por expirar.
|
|
</p>
|
|
<p style="margin-top:0;">
|
|
Los siguientes contratos son los que expiran:
|
|
</p>
|
|
<table width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;border:1px solid #ddd;">
|
|
<thead>
|
|
<tr style="background-color:#f4f4f4;">
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">ID contract</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">RFC</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Nombre</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Fecha inicio contrato</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Fecha fin contrato</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Salario diario</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${contracts.map(c => {
|
|
const inicio = formatDateDMY(c.start_contract);
|
|
const fin = formatDateDMY(c.end_contract);
|
|
return `
|
|
<tr>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.id_contract}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.rfc_emp}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.name_employee}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${inicio}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${fin}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.dailypay}</td>
|
|
</tr>
|
|
`;
|
|
}).join('')}
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- CTA -->
|
|
<div style="text-align:center;margin-top:32px;">
|
|
<a href="https://hacienda.consultoria-as.com/"
|
|
style="background-color:#7a0c1e;color:#ffffff;
|
|
text-decoration:none;font-weight:bold;
|
|
padding:14px 28px;border-radius:6px;
|
|
display:inline-block;">
|
|
Revisar en la web
|
|
</a>
|
|
</div>
|
|
|
|
<p style="margin-top:16px;font-size:12px;color:#777777;text-align:center;">
|
|
Por favor considere si seran recontratados o finalizados.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div style="background-color:#f6f6f6;padding:12px 20px;
|
|
font-size:12px;color:#888888;text-align:center;">
|
|
Sistema de gestión financiera · Consultoría AS
|
|
</div>
|
|
|
|
</div>
|
|
`
|
|
};
|
|
await transporter.sendMail(mailOptions);
|
|
res.json({
|
|
message: 'Correo enviado con contratos por expirar.',
|
|
data:totalExpiring
|
|
});
|
|
}else{
|
|
res.json({
|
|
message: 'No hay contratos por expirar',
|
|
data:totalExpiring
|
|
});
|
|
}
|
|
}catch(error){
|
|
console.error(error);
|
|
res.status(500).json({ status: 500, message: 'No se pudo mandar el correo con los contratos por expirar' });
|
|
}
|
|
};
|
|
const paymentDeadLine = async (req, res) => {
|
|
try{
|
|
const deadLineCount = await pool.query('SELECT countdelaypayments() as total');
|
|
const deadLineResult = await pool.query('SELECT * FROM getdelaypayments()');
|
|
const usersDeadLine = await pool.query('SELECT * FROM getusersmailpaymentdelay()');
|
|
const totalDeadline = deadLineCount.rows[0].total;
|
|
const deadLines = deadLineResult.rows;
|
|
const users = usersDeadLine.rows;
|
|
|
|
if(totalDeadline >= 1 && users.length >0){
|
|
const recipients = users.map(u => u.email);
|
|
const mailOptions = {
|
|
from: 'soporte@horuxfin.com',
|
|
to: recipients,
|
|
subject: 'WARNING: OVERDUE PAYMENTS',
|
|
html: `
|
|
<div style="max-width:600px;margin:0 auto;font-family:Arial,Helvetica,sans-serif;
|
|
background-color:#ffffff;border:1px solid #e5e5e5;border-radius:8px;
|
|
overflow:hidden;">
|
|
<!-- Header -->
|
|
<div style="background-color:#7a0c1e;padding:18px 22px;">
|
|
<h2 style="margin:0;color:#ffffff;font-size:20px;font-weight:600;">
|
|
Notification regarding overdue payments
|
|
</h2>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div style="padding:22px;color:#333333;font-size:14px;line-height:1.6;">
|
|
<p style="margin-top:0;">
|
|
Currently there are <b>${totalDeadline}</b> overdue payment(s).
|
|
</p>
|
|
<table width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;border:1px solid #ddd;">
|
|
<thead>
|
|
<tr style="background-color:#f4f4f4;">
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">ID</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Description</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Deadline</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Total</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${deadLines.map(c => {
|
|
const fecha = new Date(c.payment_deadline);
|
|
const fechaFormateada = fecha.toLocaleDateString('en-US', {
|
|
day: '2-digit',
|
|
month: 'long'
|
|
});
|
|
return `
|
|
<tr>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.id_expense}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.description}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${fechaFormateada}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.total || 'N/A'}</td>
|
|
</tr>
|
|
`;
|
|
}).join('')}
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- CTA -->
|
|
<div style="text-align:center;margin-top:32px;">
|
|
<a href="https://hacienda.consultoria-as.com/"
|
|
style="background-color:#7a0c1e;color:#ffffff;
|
|
text-decoration:none;font-weight:bold;
|
|
padding:14px 28px;border-radius:6px;
|
|
display:inline-block;">
|
|
Please review and pay as soon as possible.
|
|
</a>
|
|
</div>
|
|
|
|
<p style="margin-top:16px;font-size:12px;color:#777777;text-align:center;">
|
|
We recommend that review your overdue expenses as soon as possible.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div style="background-color:#f6f6f6;padding:12px 20px;
|
|
font-size:12px;color:#888888;text-align:center;">
|
|
Financial management system · Consultoría AS
|
|
</div>
|
|
|
|
</div>`
|
|
};
|
|
await transporter.sendMail(mailOptions);
|
|
res.json({
|
|
message: 'Correo enviado con pagos atrasados.',
|
|
data:totalDeadline
|
|
});
|
|
}else{
|
|
res.json({
|
|
message: 'No hay pagos con atraso',
|
|
data:totalDeadline
|
|
});
|
|
}
|
|
}catch(error){
|
|
console.error(error);
|
|
res.status(500).json({ status: 500, message: 'No se pudo mandar el correo con con los pagos atrasados' });
|
|
}
|
|
};
|
|
|
|
const expensesNearToDeadline = async (req, res) => {
|
|
try{
|
|
const deadLineCount = await pool.query('SELECT COUNT(*) FROM expensesneartodeadline()');
|
|
const deadLineResult = await pool.query('SELECT * FROM expensesneartodeadline()');
|
|
const usersDeadLine = await pool.query('SELECT * FROM getusersmailpaymentdelay()');
|
|
|
|
const totalDeadline = deadLineCount.rows[0].count;
|
|
const deadLines = deadLineResult.rows;
|
|
const users = usersDeadLine.rows;
|
|
console.log(totalDeadline);
|
|
if(totalDeadline >= 1 && users.length >0){
|
|
const recipients = users.map(u => u.email);
|
|
const mailOptions = {
|
|
from: 'soporte@horuxfin.com',
|
|
to: recipients,
|
|
subject: 'WARNING: PAYMENTS DUE SOON FOR LATE PAYMENTS',
|
|
html: `
|
|
<div style="max-width:600px;margin:0 auto;font-family:Arial,Helvetica,sans-serif;
|
|
background-color:#ffffff;border:1px solid #e5e5e5;border-radius:8px;
|
|
overflow:hidden;">
|
|
<!-- Header -->
|
|
<div style="background-color:#7a0c1e;padding:18px 22px;">
|
|
<h2 style="margin:0;color:#ffffff;font-size:20px;font-weight:600;">
|
|
Notification of payments approaching a deadline
|
|
</h2>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div style="padding:22px;color:#333333;font-size:14px;line-height:1.6;">
|
|
<p style="margin-top:0;">
|
|
Currently there are <b>${totalDeadline}</b> payment(s) close to deadline.
|
|
</p>
|
|
<table width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;border:1px solid #ddd;">
|
|
<thead>
|
|
<tr style="background-color:#f4f4f4;">
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">ID</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Description</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Deadline</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Total</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${deadLines.map(c => {
|
|
const fecha = new Date(c.payment_deadline);
|
|
const fechaFormateada = fecha.toLocaleDateString('en-US', {
|
|
day: '2-digit',
|
|
month: 'long'
|
|
});
|
|
return `
|
|
<tr>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.id_expense}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.description}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${fechaFormateada}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.total || 'N/A'}</td>
|
|
</tr>
|
|
`;
|
|
}).join('')}
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- CTA -->
|
|
<div style="text-align:center;margin-top:32px;">
|
|
<a href="https://hacienda.consultoria-as.com/"
|
|
style="background-color:#7a0c1e;color:#ffffff;
|
|
text-decoration:none;font-weight:bold;
|
|
padding:14px 28px;border-radius:6px;
|
|
display:inline-block;">
|
|
Review and pay spendings
|
|
</a>
|
|
</div>
|
|
|
|
<p style="margin-top:16px;font-size:12px;color:#777777;text-align:center;">
|
|
We recommend reviewing this application before the deadline.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div style="background-color:#f6f6f6;padding:12px 20px;
|
|
font-size:12px;color:#888888;text-align:center;">
|
|
Financial management system · Consultoría AS
|
|
</div>
|
|
|
|
</div>
|
|
`
|
|
};
|
|
await transporter.sendMail(mailOptions);
|
|
res.json({
|
|
message: 'Correo enviado con pagos por expirar.',
|
|
data:totalDeadline
|
|
});
|
|
}else{
|
|
res.json({
|
|
message: 'No hay pagos con atraso',
|
|
data:totalDeadline
|
|
});
|
|
}
|
|
}catch(error){
|
|
console.error(error);
|
|
res.status(500).json({ status: 500, message: 'No se pudo mandar el correo con los pagos prontos a fecha limite' });
|
|
}
|
|
};
|
|
|
|
const expensesspecial = async (req, res) => {
|
|
try{
|
|
const expensesCount = await pool.query('SELECT COUNT(*) AS total FROM getexpensesspecial()');
|
|
const expensesResult = await pool.query('SELECT * FROM getexpensesspecial()');
|
|
const totalExpenses = expensesCount.rows[0].total;
|
|
const expenses = expensesResult.rows;
|
|
|
|
if(totalExpenses >= 1){
|
|
const mailOptions = {
|
|
from: 'soporte@horuxfin.com',
|
|
to: 'soporte@horuxfin.com',
|
|
subject: 'Recordatorio: Hay gastos por aprobar',
|
|
html: `
|
|
<h2>Notificación de gastos por aprobar</h2>
|
|
<p>Actualmente hay <b>${totalExpenses}</b> gasto(s) por aprobar</p>
|
|
<ul>Los siguientes gastos faltan por aprobar</ul>
|
|
<ul>
|
|
${expenses.map(c => {
|
|
const fecha = new Date(c.payment_deadline);
|
|
const fechaFormateada = fecha.toLocaleDateString('es-MX');
|
|
const product = Array.isArray(c.products) && c.products.length > 0
|
|
? c.products.map(p => `${p.name_product} ($${p.total})`).join(', ')
|
|
: 'N/A';
|
|
return `<li>
|
|
ID:${c.id_expense} - Descripción: ${c.description}
|
|
- Productos: ${product} -
|
|
fecha limite de pago: ${fechaFormateada} -
|
|
total del gasto: ${c.total || 'N/A'}
|
|
</li>`;
|
|
}).join('')}
|
|
</ul>`
|
|
};
|
|
await transporter.sendMail(mailOptions);
|
|
res.json({
|
|
message: 'Correo enviado con gastos por aprobar',
|
|
data:totalExpenses
|
|
});
|
|
}else{
|
|
res.json({
|
|
message: 'No hay pagos por aprobar',
|
|
data:totalExpenses
|
|
});
|
|
}
|
|
}catch(error){
|
|
console.error(error);
|
|
res.status(500).json({ status: 500, message: 'No se pudo mandar el correo con los contratos por expirar' });
|
|
}
|
|
};
|
|
|
|
const emailbirthday = async (req, res) => {
|
|
try{
|
|
const endpointName = 'emailbirthday';
|
|
const current = new Date();
|
|
const mes = current.getMonth() + 1;
|
|
const anio = current.getFullYear();
|
|
const validateEndpoint = await pool.query('SELECT validateendpoint($1,$2::int,$3::int) AS total',[endpointName,mes,anio]);
|
|
|
|
if (validateEndpoint.rows[0].total > 0) {
|
|
return res.status(400).json({
|
|
message: `Este endpoint ya fue ejecutado en ${current.toLocaleString('es-MX', { month: 'long' })}.`
|
|
});
|
|
}else {
|
|
await pool.query(`INSERT INTO endpoint_logs (endpoint_name) VALUES ($1)`,[endpointName]);
|
|
const birthdayCount = await pool.query('SELECT COUNT(*) AS total FROM getbirthdaysemployees()');
|
|
const birthdayResult = await pool.query('SELECT * FROM getbirthdaysemployees()');
|
|
const userEmails = await pool.query('SELECT * FROM getusersmailbirthday()');
|
|
|
|
const totalBirthday = birthdayCount.rows[0].total;
|
|
const birthdays = birthdayResult.rows;
|
|
const users = userEmails.rows;
|
|
const currentMonth = new Date().toLocaleString('en-US', { month: 'long' });
|
|
|
|
if(totalBirthday >= 1 && users.length >0){
|
|
|
|
const recipients = users.map(u => u.email);
|
|
|
|
const mailOptions = {
|
|
from: 'soporte@horuxfin.com',
|
|
to: recipients,
|
|
subject: `Friendly reminder: Birthdays of the month of ${currentMonth}`,
|
|
html: `
|
|
<div style="max-width:600px;margin:0 auto;font-family:Arial,Helvetica,sans-serif;
|
|
background-color:#ffffff;border:1px solid #e5e5e5;border-radius:8px;
|
|
overflow:hidden;">
|
|
<!-- Header -->
|
|
<div style="background-color:#7a0c1e;padding:18px 22px;">
|
|
<h2 style="margin:0;color:#ffffff;font-size:20px;font-weight:600;">
|
|
In this month there are ${totalBirthday} birthday person(s)
|
|
</h2>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div style="padding:22px;color:#333333;font-size:14px;line-height:1.6;">
|
|
<p style="margin-top:0;">
|
|
The following employees are celebrating their birthdays this month:
|
|
</p>
|
|
|
|
<table width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;border:1px solid #ddd;">
|
|
<thead>
|
|
<tr style="background-color:#f4f4f4;">
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">RFC</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Name</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Birthday</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${birthdays.map(c => {
|
|
const fecha = new Date(c.birthday);
|
|
const fechaFormateada = fecha.toLocaleDateString('en-US', {
|
|
day: '2-digit',
|
|
month: 'long'
|
|
});
|
|
return `
|
|
<tr>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.rfc}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.fullname}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${fechaFormateada}</td>
|
|
</tr>
|
|
`;
|
|
}).join('')}
|
|
</tbody>
|
|
</table>
|
|
|
|
|
|
<p style="margin-top:16px;font-size:12px;color:#777777;text-align:center;">
|
|
Remember to congratulate them on their special day!
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div style="background-color:#f6f6f6;padding:12px 20px;
|
|
font-size:12px;color:#888888;text-align:center;">
|
|
Financial management system · Consultoría AS
|
|
</div>
|
|
|
|
</div>`};
|
|
await transporter.sendMail(mailOptions);
|
|
res.json({
|
|
message: 'Correo enviado con los cumpleaños',
|
|
data:totalBirthday
|
|
});
|
|
}else{
|
|
res.json({
|
|
message: 'No hay cumpleañeros este mes',
|
|
data:totalBirthday
|
|
});
|
|
}}
|
|
}catch(error){
|
|
console.error(error);
|
|
res.status(500).json({ status: 500, message: 'No se pudo enviar correo con los cumpleañeros' });
|
|
}
|
|
};
|
|
const expiredcontracts = async (req, res) => {
|
|
try{
|
|
const expiredCountTotal = await pool.query('SELECT expiredcontracts() as total');
|
|
const expiredResult = await pool.query('SELECT * FROM getexpirecontracts()');
|
|
const expiredCountDay = await pool.query('SELECT COUNT(*) AS total FROM getexpirecontracts()');
|
|
const totalExpired = expiredCountTotal.rows[0].total;
|
|
const totalDayExpired = expiredCountDay.rows[0].total;
|
|
const expiredContracts = expiredResult.rows;
|
|
|
|
if(totalExpired >= 1){
|
|
const mailOptions = {
|
|
from: 'soporte@horuxfin.com',
|
|
to: 'soporte@horuxfin.com',
|
|
subject: 'Contratos expirados el día de hoy',
|
|
html: `
|
|
<h2>Notificación contratos expirados el día de hoy</h2>
|
|
<p>Cantidad de contratos <b>${totalDayExpired}</b> que expiraron hoy.</p>
|
|
<ul>Los contratos expirados son</ul>
|
|
<ul>
|
|
${expiredContracts.map(c => {
|
|
const fecha = new Date(c.end_contract);
|
|
const fechaFormateada = fecha.toLocaleDateString('es-MX');
|
|
return `<li>
|
|
ID:${c.id_contract} - RFC: ${c.rfc_employee}
|
|
- Nombre: ${c.name_employee} -
|
|
fecha de expiración: ${fechaFormateada}
|
|
</li>`;
|
|
}).join('')}
|
|
</ul>
|
|
<p>Por favor considere si seran recontratados o no para que se inicie el proceso.</p>`
|
|
};
|
|
await transporter.sendMail(mailOptions);
|
|
res.json({
|
|
message: 'Correo enviado con contratos expirados hoy',
|
|
data:totalDayExpired
|
|
});
|
|
}else{
|
|
res.json({
|
|
message: 'Hoy no expiro ningun contrato',
|
|
data:totalDayExpired
|
|
});
|
|
}
|
|
}catch(error){
|
|
console.error(error);
|
|
res.status(500).json({ status: 500, message: 'No se pudo mandar el correo con los contratos por expirar' });
|
|
}
|
|
};
|
|
const expiredContractsMonth = async (req, res) => {
|
|
try{
|
|
// Verifica si hoy es el último día del mes
|
|
if (isLastDayOfMonth()) {
|
|
return res.status(200).json({
|
|
message: 'El proceso solo se ejecuta el último día del mes',
|
|
executed: false
|
|
});
|
|
}
|
|
const endpointName = 'expiredContractsMonth';
|
|
const current = new Date();
|
|
const mes = current.getMonth() + 1;
|
|
const anio = current.getFullYear();
|
|
const validateEndpoint = await pool.query('SELECT validateendpoint($1,$2::int,$3::int) AS total',[endpointName,mes,anio]);
|
|
if (validateEndpoint.rows[0].total > 0) {
|
|
return res.status(400).json({
|
|
message: `Este endpoint ya fue ejecutado en ${current.toLocaleString('es-MX', { month: 'long' })}.`
|
|
});
|
|
}else
|
|
{
|
|
await pool.query(`INSERT INTO endpoint_logs (endpoint_name) VALUES ($1)`,[endpointName]);
|
|
const expiredCountTotal = await pool.query('SELECT COUNT(*) AS total FROM getexpirecontractsmonth()');
|
|
const expiredResult = await pool.query('SELECT * FROM getexpirecontractsmonth()');
|
|
const userEmails = await pool.query('SELECT * FROM getusersmailneartoend()');
|
|
|
|
const totalExpired = expiredCountTotal.rows[0].total;
|
|
const expiredContracts = expiredResult.rows;
|
|
const currentMonth = new Date().toLocaleString('es-MX', { month: 'long' });
|
|
const users = userEmails.rows;
|
|
|
|
if(totalExpired >= 1 && users.length > 0){
|
|
const recipients = users.map(u => u.email);
|
|
const mailOptions = {
|
|
from: 'soporte@horuxfin.com',
|
|
to: recipients,
|
|
subject: `Contratos expirados en el mes ${currentMonth} `,
|
|
html: `
|
|
<div style="max-width:600px;margin:0 auto;font-family:Arial,Helvetica,sans-serif;
|
|
background-color:#ffffff;border:1px solid #e5e5e5;border-radius:8px;
|
|
overflow:hidden;">
|
|
<!-- Header -->
|
|
<div style="background-color:#7a0c1e;padding:18px 22px;">
|
|
<h2 style="margin:0;color:#ffffff;font-size:20px;font-weight:600;">
|
|
Notificación contratos expirados del mes de ${currentMonth}
|
|
</h2>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div style="padding:22px;color:#333333;font-size:14px;line-height:1.6;">
|
|
<p style="margin-top:0;">
|
|
En este mes expiraron <b>${totalExpired}.
|
|
</p>
|
|
<p style="margin-top:0;">
|
|
Los contratos expirados son en el mes fueron:
|
|
</p>
|
|
<table width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;border:1px solid #ddd;">
|
|
<thead>
|
|
<tr style="background-color:#f4f4f4;">
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">ID contract</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">RFC</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Nombre</th>
|
|
<th align="left" style="padding:8px;border:1px solid #ddd;">Fecha que expiro:</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${expiredContracts.map(c => {
|
|
const fin = formatDateDMY(c.end_contract);
|
|
return `
|
|
<tr>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.id_contract}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.rfc_employee}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${c.name_employee}</td>
|
|
<td style="padding:8px;border:1px solid #ddd;">${fin}</td>
|
|
</tr>
|
|
`;
|
|
}).join('')}
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- CTA -->
|
|
<div style="text-align:center;margin-top:32px;">
|
|
<a href="https://hacienda.consultoria-as.com/"
|
|
style="background-color:#7a0c1e;color:#ffffff;
|
|
text-decoration:none;font-weight:bold;
|
|
padding:14px 28px;border-radius:6px;
|
|
display:inline-block;">
|
|
Revisar en la web
|
|
</a>
|
|
</div>
|
|
|
|
<p style="margin-top:16px;font-size:12px;color:#777777;text-align:center;">
|
|
Le recomendamos generar el contrato en caso de recontratación.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div style="background-color:#f6f6f6;padding:12px 20px;
|
|
font-size:12px;color:#888888;text-align:center;">
|
|
Sistema de gestión financiera · Consultoría AS
|
|
</div>
|
|
|
|
</div>`
|
|
};
|
|
await transporter.sendMail(mailOptions);
|
|
res.json({
|
|
message: 'Correo enviado con contratos expirados en el mes',
|
|
data:totalExpired
|
|
});
|
|
}else{
|
|
res.json({
|
|
message: 'Este mes no expiro ningun contrato',
|
|
data:totalExpired
|
|
});
|
|
}}
|
|
}catch(error){
|
|
console.error(error);
|
|
res.status(500).json({ status: 500, message: 'No se pudo mandar el correo con los contratos por expirar' });
|
|
}
|
|
};
|
|
|
|
|
|
const isLastDayOfMonth = () => {
|
|
const today = new Date();
|
|
const tomorrow = new Date(today);
|
|
tomorrow.setDate(today.getDate() + 1);
|
|
return tomorrow.getDate() === 1;
|
|
};
|
|
const formatDateDMY = (value) => {
|
|
if (!value) return '';
|
|
const date = value instanceof Date ? value : new Date(value);
|
|
if (isNaN(date.getTime())) return '';
|
|
return date.toLocaleDateString('es-MX', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric'
|
|
});
|
|
};
|
|
|
|
module.exports = {contractsNearToEnd,paymentDeadLine,expensesspecial,
|
|
emailbirthday,expiredcontracts,expiredContractsMonth,expensesNearToDeadline};
|