diff --git a/apps/backend/src/lib/telegram.ts b/apps/backend/src/lib/telegram.ts index edcfea2..5129976 100644 --- a/apps/backend/src/lib/telegram.ts +++ b/apps/backend/src/lib/telegram.ts @@ -1,6 +1,12 @@ import { Bot } from "grammy"; +import { + formatPrice, + formatTime, + getBeloQuoteStatus, + isChatValidated, + validateCode, +} from "../modules/telegram/telegram.service"; import { logger } from "./logger"; -import { validateCode } from "../modules/telegram/telegram.service"; const token = process.env.BOT_TOKEN; @@ -30,6 +36,38 @@ export async function startBot(): Promise { ); }); + bot.command("belo", async (ctx) => { + const validated = await isChatValidated(BigInt(ctx.chat.id)); + if (!validated) { + await ctx.reply( + "🔒 Tu cuenta no está conectada. Enviá /start para ver cómo conectarla.", + ); + return; + } + + const status = await getBeloQuoteStatus(); + if (!status) { + await ctx.reply("❌ No hay datos de BELO disponibles todavía."); + return; + } + + const progress = Math.round(status.progressPercentage ?? 0); + const message = [ + "💰 BELO · USDC/ARS", + "", + `Compra: $${formatPrice(status.buy)}`, + `Venta: $${formatPrice(status.sell)}`, + "", + `Mínimo del día: $${formatPrice(status.minBuy)}`, + `Máximo del día: $${formatPrice(status.maxBuy)}`, + `Avance del día: ${progress}%`, + "", + `🕐 ${formatTime(status.timeStamp)}`, + ].join("\n"); + + await ctx.reply(message, { parse_mode: "HTML" }); + }); + bot.on("message:text", async (ctx) => { const text = ctx.message.text.trim(); if (text.startsWith("/")) return; diff --git a/apps/backend/src/modules/telegram/telegram.service.ts b/apps/backend/src/modules/telegram/telegram.service.ts index 9e5a235..000ac4f 100644 --- a/apps/backend/src/modules/telegram/telegram.service.ts +++ b/apps/backend/src/modules/telegram/telegram.service.ts @@ -13,7 +13,7 @@ function generateRandomCode(): string { return code; } -function formatPrice(price: number): string { +export function formatPrice(price: number): string { return price.toLocaleString("es-AR", { minimumFractionDigits: 2, maximumFractionDigits: 2, @@ -72,7 +72,7 @@ export async function getTelegramStatus(): Promise<{ }; } -function formatTime(date: Date): string { +export function formatTime(date: Date): string { const artOffset = -3; const artMs = date.getTime() + artOffset * 3600000; const artDate = new Date(artMs); @@ -208,3 +208,75 @@ export async function checkAndNotifyBelo(): Promise { todayStart, }); } + +export interface BeloQuoteStatus { + buy: number; + sell: number; + timeStamp: Date; + minBuy: number; + maxBuy: number; + minSell: number; + maxSell: number; + progressPercentage: number | null; +} + +export async function getBeloQuoteStatus(): Promise { + const currentQuote = await prisma.quote.findFirst({ + where: { type: "BELO" }, + orderBy: { timeStamp: "desc" }, + }); + if (!currentQuote) return null; + + const now = new Date(); + const artOffset = -3; + const artMs = now.getTime() + artOffset * 3600000; + const artDate = new Date(artMs); + const todayStart = new Date( + Date.UTC( + artDate.getUTCFullYear(), + artDate.getUTCMonth(), + artDate.getUTCDate(), + ), + ); + + const agg = await prisma.quoteHistory.aggregate({ + where: { + type: "BELO", + timeStamp: { gte: todayStart }, + }, + _min: { buy: true, sell: true }, + _max: { buy: true, sell: true }, + }); + + const currentBuy = Number(currentQuote.buy); + const minBuy = Number(agg._min.buy ?? currentQuote.buy); + const maxBuy = Number(agg._max.buy ?? currentQuote.buy); + + let progressPercentage: number | null = null; + if (maxBuy > minBuy) { + progressPercentage = Math.min( + 100, + Math.max(0, ((currentBuy - minBuy) / (maxBuy - minBuy)) * 100), + ); + } else { + progressPercentage = 100; + } + + return { + buy: currentBuy, + sell: Number(currentQuote.sell), + timeStamp: currentQuote.timeStamp, + minBuy, + maxBuy, + minSell: Number(agg._min.sell ?? currentQuote.sell), + maxSell: Number(agg._max.sell ?? currentQuote.sell), + progressPercentage, + }; +} + +export async function isChatValidated(chatId: bigint): Promise { + const chat = await prisma.telegramChat.findFirst({ + where: { chatId, validatedAt: { not: null } }, + }); + return chat !== null; +}