first commit

This commit is contained in:
2026-07-16 20:48:43 -03:00
commit 5696cee264
1111 changed files with 322270 additions and 0 deletions
@@ -0,0 +1,26 @@
import { Document, Model, Schema, model } from "mongoose";
export interface IAppointment {
companyId: string;
start: Date;
notificated?: boolean;
notificationIntents?: number;
}
export interface IAppointmentDocument extends Omit<IAppointment, "id">, Document {}
export class Appointments {
schema: Schema;
appointmentsList: Model<IAppointmentDocument>;
constructor() {
this.schema = new Schema({
companyId: { type: Schema.Types.ObjectId, required: true, ref: "Companie" },
start: { type: Date, required: true },
notificated: { type: Boolean, required: false },
notificationIntents: { type: Number, required: false },
});
this.appointmentsList = model<IAppointmentDocument>("Appointment", this.schema);
}
}
@@ -0,0 +1,25 @@
import mongoose, { Document, Model, Schema, model } from "mongoose";
export interface ICompany {
id?: string;
name: string;
ownerId: string;
automaticNotifications?: boolean;
}
export interface ICompanyDocument extends Omit<ICompany, "id">, Document {}
export class Companies {
schema: Schema;
companyList: Model<ICompanyDocument>;
constructor() {
this.schema = new Schema({
name: { type: String, required: true, unique: true },
ownerId: { type: Schema.Types.ObjectId, required: true, ref: "User" },
automaticNotifications: { type: Boolean, required: false },
});
this.companyList = model<ICompanyDocument>("Company", this.schema);
}
}
@@ -0,0 +1,168 @@
import dayjs from "dayjs";
import axios from "axios";
import { logDone, logError, logIntent } from "../../config/logger.js";
import { Appointments, IAppointment, IAppointmentDocument } from "../Appointments/Appointments.js";
import { Companies } from "../Companies/Companies.js";
import { PlanSuscriptions } from "../PlanSubscriptions/PlanSubscriptions.js";
export type ApiVoidResult = {
success: boolean;
message: string;
};
export type SendWapSystemNotificationParams = {
appointmentId: string;
systemToken: string;
};
export class Notifications {
private users: string[] = [];
private companies: string[] = [];
private appointments: IAppointmentDocument[] = [];
private appointmentsCollection: Appointments;
constructor() {
this.users = [];
this.companies = [];
this.appointments = [];
this.appointmentsCollection = new Appointments();
}
private async loadUsers() {
logIntent("Cargando lista de usuarios.");
const loadedPlanIds = `${process.env.PLAN_LIST}`;
if (loadedPlanIds === "") {
logError("No se ha encontrado una lista de planes valida.", "");
process.exit(1);
}
const PLAN_IDS = loadedPlanIds.split(",");
const subs = new PlanSuscriptions();
const result = await subs.planSuscriptionList.find({ planId: { $in: PLAN_IDS } }).exec();
this.users = result.map((x) => String(x.userId));
logDone("Lista de usuarios cargada correctamente.");
}
private async loadCompanies() {
logIntent("Cargando lista de organizaciones.");
const orgs = new Companies();
for (const user of this.users) {
const organizations = await orgs.companyList
.find({ ownerId: user, automaticNotifications: true })
.exec();
for (const orgItem of organizations) {
this.companies.push(orgItem.id);
}
}
logDone("Lista de organizaciones cargada correctamente.");
}
private async loadAppointments() {
try {
logIntent("Cargando nuevo lote de notificaciones");
this.appointments = [];
const dateFrom = dayjs().clone().startOf("day");
const dateTo = dayjs().clone().endOf("day");
for (const company of this.companies) {
const result = await this.appointmentsCollection.appointmentsList
.findOne({
start: { $gte: dateFrom.toDate(), $lte: dateTo.toDate() },
companyId: company,
$and: [
{ $or: [{ notificated: false }, { notificated: { $exists: false } }] },
{
$or: [
{ notificationIntents: { $lt: 3 } },
{ notificationIntents: { $exists: false } },
],
},
],
})
.exec();
if (!result) {
continue;
}
this.appointments.push(result);
}
logDone("Nuevo lote de notificaciones cargado correctamente.");
} catch (error) {
logError("Error cargando nuevo lote de notificaciones:", error);
}
}
public async start() {
await this.loadUsers();
await this.loadCompanies();
await this.loadAppointments();
this.next();
}
public async next() {
const getDelay = () => {
const timeBase = Number(process.env.TIME_BASE ? process.env.TIME_BASE : 8);
const timeExtra = Number(process.env.TIME_EXTRA ? process.env.TIME_EXTRA : 8);
return Math.floor(Math.random() * timeBase) + timeExtra;
};
const app = this.appointments.shift();
if (!app) {
logDone("Se ha finalizado el envío del lote de notificaciones.");
setTimeout(() => {
this.reload();
}, getDelay() * 1000);
return;
}
try {
await this.send(String(app.id));
app.notificated = true;
} catch (error) {
app.notificationIntents = app.notificationIntents ? app.notificationIntents + 1 : 1;
logError("Error enviando notificación:", error);
}
await app.save();
setTimeout(() => {
this.next();
}, getDelay() * 1000);
}
public async reload() {
await this.loadAppointments();
if (this.appointments.length < 1) {
this.end();
}
this.next();
}
public async end() {
process.exit(0);
}
private async send(appointmentId: string): Promise<void> {
logIntent(`Enviando notificación para la cita ${appointmentId}.`);
const dataApi: SendWapSystemNotificationParams = {
appointmentId: appointmentId,
systemToken: `${process.env.API_KEY}`,
};
const response = await axios.post<ApiVoidResult>(`${process.env.API_URL}`, dataApi, {
headers: {
"Content-Type": "application/json",
},
});
const result: ApiVoidResult = response.data;
if (!result.success) {
throw new Error(result.message);
}
logDone("Notificación enviada correctamente.");
}
}
@@ -0,0 +1,26 @@
import mongoose, { Document, Model, Schema, model } from "mongoose";
export interface IPlanSuscription {
userId: string;
planId: string;
startDate: Date;
endDate: Date;
}
export interface IPlanSuscriptionDocument extends IPlanSuscription, Document {}
export class PlanSuscriptions {
schema: Schema;
planSuscriptionList: Model<IPlanSuscriptionDocument>;
constructor() {
this.schema = new Schema({
userId: { type: Schema.Types.ObjectId, required: true },
planId: { type: Schema.Types.ObjectId, required: true },
startDate: { type: Date, required: true },
endDate: { type: Date, required: true },
});
this.planSuscriptionList = model<IPlanSuscriptionDocument>("PlanSuscription", this.schema);
}
}