miinventario-backend-v2/src/modules/credits/credits.service.ts
rckrdmrd 5a1c966ed2 Migración desde miinventario/backend - Estándar multi-repo v2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 08:12:15 -06:00

212 lines
5.5 KiB
TypeScript

import {
Injectable,
NotFoundException,
BadRequestException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { CreditBalance } from './entities/credit-balance.entity';
import {
CreditTransaction,
TransactionType,
} from './entities/credit-transaction.entity';
import { CreditPackage } from './entities/credit-package.entity';
@Injectable()
export class CreditsService {
constructor(
@InjectRepository(CreditBalance)
private readonly balanceRepository: Repository<CreditBalance>,
@InjectRepository(CreditTransaction)
private readonly transactionRepository: Repository<CreditTransaction>,
@InjectRepository(CreditPackage)
private readonly packageRepository: Repository<CreditPackage>,
private readonly dataSource: DataSource,
) {}
async getBalance(userId: string): Promise<CreditBalance> {
let balance = await this.balanceRepository.findOne({ where: { userId } });
if (!balance) {
// Create initial balance for new users
balance = this.balanceRepository.create({
userId,
balance: 0,
totalPurchased: 0,
totalConsumed: 0,
totalFromReferrals: 0,
});
await this.balanceRepository.save(balance);
}
return balance;
}
async hasMinimumCredits(userId: string, minimum: number): Promise<boolean> {
const balance = await this.getBalance(userId);
return balance.balance >= minimum;
}
async addCredits(
userId: string,
amount: number,
type: TransactionType,
description: string,
referenceId?: string,
referenceType?: string,
): Promise<CreditTransaction> {
return this.dataSource.transaction(async (manager) => {
const balanceRepo = manager.getRepository(CreditBalance);
const transactionRepo = manager.getRepository(CreditTransaction);
// Get or create balance
let balance = await balanceRepo.findOne({ where: { userId } });
if (!balance) {
balance = balanceRepo.create({
userId,
balance: 0,
totalPurchased: 0,
totalConsumed: 0,
totalFromReferrals: 0,
});
}
// Update balance
balance.balance += amount;
if (type === TransactionType.PURCHASE) {
balance.totalPurchased += amount;
} else if (type === TransactionType.REFERRAL_BONUS) {
balance.totalFromReferrals += amount;
}
await balanceRepo.save(balance);
// Create transaction record
const transaction = transactionRepo.create({
userId,
type,
amount,
balanceAfter: balance.balance,
description,
referenceId,
referenceType,
});
return transactionRepo.save(transaction);
});
}
async consume(
userId: string,
amount: number,
description: string,
referenceId?: string,
referenceType?: string,
): Promise<CreditTransaction> {
return this.dataSource.transaction(async (manager) => {
const balanceRepo = manager.getRepository(CreditBalance);
const transactionRepo = manager.getRepository(CreditTransaction);
const balance = await balanceRepo.findOne({ where: { userId } });
if (!balance || balance.balance < amount) {
throw new BadRequestException('Creditos insuficientes');
}
// Deduct credits
balance.balance -= amount;
balance.totalConsumed += amount;
await balanceRepo.save(balance);
// Create transaction record
const transaction = transactionRepo.create({
userId,
type: TransactionType.CONSUMPTION,
amount: -amount,
balanceAfter: balance.balance,
description,
referenceId,
referenceType,
});
return transactionRepo.save(transaction);
});
}
async getTransactions(
userId: string,
page = 1,
limit = 20,
): Promise<{ transactions: CreditTransaction[]; total: number }> {
const [transactions, total] = await this.transactionRepository.findAndCount(
{
where: { userId },
order: { createdAt: 'DESC' },
skip: (page - 1) * limit,
take: limit,
},
);
return { transactions, total };
}
async getPackages(): Promise<CreditPackage[]> {
return this.packageRepository.find({
where: { isActive: true },
order: { sortOrder: 'ASC' },
});
}
async getPackageById(packageId: string): Promise<CreditPackage> {
const pkg = await this.packageRepository.findOne({
where: { id: packageId, isActive: true },
});
if (!pkg) {
throw new NotFoundException('Paquete no encontrado');
}
return pkg;
}
async grantWelcomeCredits(userId: string): Promise<void> {
const welcomeCredits = 50; // Free credits for new users
await this.addCredits(
userId,
welcomeCredits,
TransactionType.PROMO,
'Creditos de bienvenida',
);
}
async grantReferralBonus(
referrerId: string,
referredId: string,
referralId: string,
): Promise<void> {
const referrerBonus = 100;
const referredBonus = 50;
// Give bonus to referrer
await this.addCredits(
referrerId,
referrerBonus,
TransactionType.REFERRAL_BONUS,
'Bonus por referido',
referralId,
'referral',
);
// Give bonus to referred user
await this.addCredits(
referredId,
referredBonus,
TransactionType.REFERRAL_BONUS,
'Bonus por usar codigo de referido',
referralId,
'referral',
);
}
}