Quarkus 3.20.1 monolith serving React 18 + TypeScript + Tailwind SPA. Features: - Three-tab calculator (Insumos, Fórmulas, Calculadora) for Soulshot, Spiritshot and Blessed Spiritshot crafting in Lineage 2 Interlude/Clásico with all 15 grades and pre-loaded recipes - Real-time profitability computation (cristales → ore → crafteos → shots → cost → sale → ganancia) - Multi-user auth with JWT in httpOnly cookie (bcrypt + RSA 2048) - H2 file-based persistence in ./data/shots.mv.db (file-based, H2) - Auto-save on state changes (debounced 500ms) - Production history with stats (total/avg/best/worst/last5avg) and per-run detail modal with snapshot of insumos+formulas Stack: - Backend: Quarkus REST + Hibernate ORM Panache + smallrye-jwt - Frontend: React 18 + TypeScript + Vite + Tailwind 3 - Build: Maven runs frontend-maven-plugin (Node 22 + npm ci) then copies dist to META-INF/resources for Quarkus to serve Verified: - 5 backend endpoints + 5 history endpoints with curl - 35/35 browser tests via Playwright + Chromium - All TS strict, all builds green
84 lines
2.4 KiB
TypeScript
84 lines
2.4 KiB
TypeScript
import type { Calculo, Formula, Insumos, OreType, Totales } from '../types'
|
|
|
|
export function calcularFila(
|
|
formula: Formula,
|
|
cristalesDisponibles: number,
|
|
insumos: Insumos,
|
|
): Calculo {
|
|
const orePerCraft = formula.soulOreReq ?? formula.spiritOreReq ?? 0
|
|
const oreLabel: OreType | null =
|
|
formula.soulOreReq != null ? 'Soul Ore' : formula.spiritOreReq != null ? 'Spirit Ore' : null
|
|
|
|
if (formula.cristalesReq <= 0) {
|
|
return {
|
|
tipo: formula.tipo,
|
|
grado: formula.grado,
|
|
cristalesDisponibles,
|
|
cristalesUsados: 0,
|
|
oreNecesario: 0,
|
|
oreLabel,
|
|
crafteosPosibles: 0,
|
|
shotsObtenidos: 0,
|
|
costoTotal: 0,
|
|
valorVenta: 0,
|
|
ganancia: 0,
|
|
warning: 'Completa la receta en la pestaña Fórmulas',
|
|
}
|
|
}
|
|
|
|
const crafteosPosibles = Math.floor(cristalesDisponibles / formula.cristalesReq)
|
|
const cristalesUsados = crafteosPosibles * formula.cristalesReq
|
|
const oreNecesario = crafteosPosibles * orePerCraft
|
|
const shotsObtenidos = crafteosPosibles * formula.shotsObtenidos
|
|
|
|
const precioCristal = insumos.cristales[formula.grado]
|
|
const precioOre = orePerCraft > 0
|
|
? formula.soulOreReq != null
|
|
? insumos.soulOre
|
|
: insumos.spiritOre
|
|
: 0
|
|
const precioVenta = insumos.venta[formula.tipo][formula.grado]
|
|
|
|
const costoTotal = cristalesUsados * precioCristal + oreNecesario * precioOre
|
|
const valorVenta = shotsObtenidos * precioVenta
|
|
const ganancia = valorVenta - costoTotal
|
|
|
|
return {
|
|
tipo: formula.tipo,
|
|
grado: formula.grado,
|
|
cristalesDisponibles,
|
|
cristalesUsados,
|
|
oreNecesario,
|
|
oreLabel,
|
|
crafteosPosibles,
|
|
shotsObtenidos,
|
|
costoTotal,
|
|
valorVenta,
|
|
ganancia,
|
|
warning: null,
|
|
}
|
|
}
|
|
|
|
export function sumarTotales(calculos: Calculo[]): Totales {
|
|
return calculos.reduce<Totales>(
|
|
(acc, c) => ({
|
|
cristalesUsados: acc.cristalesUsados + c.cristalesUsados,
|
|
oreNecesario: acc.oreNecesario + c.oreNecesario,
|
|
crafteosPosibles: acc.crafteosPosibles + c.crafteosPosibles,
|
|
shotsObtenidos: acc.shotsObtenidos + c.shotsObtenidos,
|
|
costoTotal: acc.costoTotal + c.costoTotal,
|
|
valorVenta: acc.valorVenta + c.valorVenta,
|
|
ganancia: acc.ganancia + c.ganancia,
|
|
}),
|
|
{
|
|
cristalesUsados: 0,
|
|
oreNecesario: 0,
|
|
crafteosPosibles: 0,
|
|
shotsObtenidos: 0,
|
|
costoTotal: 0,
|
|
valorVenta: 0,
|
|
ganancia: 0,
|
|
},
|
|
)
|
|
}
|