@@ -120,7 +129,10 @@
|
|
|
- |
+ |
+ |
+ |
+ |
|
|
|
|
- |
+ |
+ |
+ |
+ |
|
|
@@ -153,7 +168,7 @@
-
+
@@ -170,12 +185,26 @@
+
+
+
+
+
Valor venta
@@ -187,6 +216,19 @@
x-text="fmtAdena(totalesGlobal().ganancia)">
+
+
Distribución del costo
+
+
+ Cristales
+ Ore
+ Fee
+
+
@@ -378,7 +420,10 @@
Shots |
Cristales |
Ore nec. |
- Costo |
+ C. Cristales |
+ C. Ore |
+ C. Fee |
+ Costo total |
Venta |
Ganancia |
@@ -391,7 +436,10 @@
|
|
|
- |
+ |
+ |
+ |
+ |
|
-
-
-
- | Tipo |
- Grado |
- Crafteos |
- Shots |
- Cristales |
- Ore nec. |
- Costo |
- Venta |
- Ganancia |
+
+
+
+ | Tipo |
+ Grado |
+ Crafteos |
+ Shots |
+ Cristales |
+ Ore nec. |
+ C. Cristales |
+ C. Ore |
+ C. Fee |
+ Costo total |
+ Venta |
+ Ganancia |
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
-
-
-
-
- |
- |
- |
- |
- |
- |
- |
- |
- |
-
-
-
-
+
+
+
@@ -484,40 +538,46 @@
-
-
-
- | Tipo |
- Grado |
- % |
- Crafteos |
- Shots |
- Cristales |
- Ore nec. |
- Costo |
- Venta |
- Ganancia |
+
+
+
+ | Tipo |
+ Grado |
+ % |
+ Crafteos |
+ Shots |
+ Cristales |
+ Ore nec. |
+ C. Cristales |
+ C. Ore |
+ C. Fee |
+ Costo total |
+ Venta |
+ Ganancia |
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
-
-
-
-
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
-
-
-
-
+
+
+
@@ -600,7 +660,10 @@
Crafteos nec. |
Cristales nec. |
Ore nec. |
- Costo |
+ C. Cristales |
+ C. Ore |
+ C. Fee |
+ Costo total |
Venta |
Ganancia |
@@ -618,7 +681,10 @@
|
|
|
- |
+ |
+ |
+ |
+ |
|
+
+
+
+
+
+
+ Grado del cristal
+
+
+
+
+
+
+
+
+
+ Tipo:
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Tipo |
+ Grado |
+ Crafteos |
+ Shots |
+ Cristales usados |
+ Ore nec. |
+ C. Cristales |
+ C. Ore |
+ C. Fee |
+ Costo total |
+ Venta |
+ Ganancia |
+
+
+
+
+
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+
+
+
+
+
+
+
+
+
+ Cristales sobrantes
+
+
+
+
+
+
+
+
+
+
+ Ninguna receta rentable para cristales grado con los filtros actuales.
+
+
+
+
+
+ Ingresá una cantidad de cristales para ver el plan.
+
+
+
@@ -734,6 +924,8 @@
presupuestoFiltroGrado: [],
presupuestoMaximizar: false,
presupuestoMezclar: false,
+ cristalesPresupuestoGrado: 'D',
+ cristalesPresupuestoCantidad: 0,
presupuestoMix: { D: 20, C: 20, B: 20, A: 20, S: 20 },
presupuestoMixTipo: { 'Soulshot': 34, 'Spiritshot': 33, 'Blessed Spiritshot': 33 },
rankingFiltro: null,
@@ -765,6 +957,21 @@
return window.calc.sumarTotales(this.tablas().flatMap(t => t.calculos));
},
+ pctC() {
+ const t = this.totalesGlobal();
+ return t.costoTotal > 0 ? (t.costoCristales * 100.0) / t.costoTotal : 0;
+ },
+
+ pctO() {
+ const t = this.totalesGlobal();
+ return t.costoTotal > 0 ? (t.costoOre * 100.0) / t.costoTotal : 0;
+ },
+
+ pctF() {
+ const t = this.totalesGlobal();
+ return t.costoTotal > 0 ? (t.costoDwarven * 100.0) / t.costoTotal : 0;
+ },
+
canSave() {
return this.totalesGlobal().cristalesUsados > 0 && this.saveStatus !== 'saving';
},
@@ -894,14 +1101,31 @@
},
objetivoCosto(f) {
+ const s = this.store().state;
+ if (!s) return 0;
+ return this.objetivoCostoCristales(f) + this.objetivoCostoOre(f) + this.objetivoCostoDwarven(f);
+ },
+
+ objetivoCostoCristales(f) {
const s = this.store().state;
if (!s) return 0;
const cristales = this.objetivoCristales(f);
- const ore = this.objetivoOre(f);
const precioCristal = s.insumos.cristales[f.grado] || 0;
+ return cristales * precioCristal;
+ },
+
+ objetivoCostoOre(f) {
+ const s = this.store().state;
+ if (!s) return 0;
+ const ore = this.objetivoOre(f);
const precioOre = f.soulOreReq != null ? s.insumos.soulOre : s.insumos.spiritOre;
- const fee = this.objetivoCrafteos(f) * (s.insumos.dwarvenFeePerCraft || 0);
- return cristales * precioCristal + ore * precioOre + fee;
+ return ore * precioOre;
+ },
+
+ objetivoCostoDwarven(f) {
+ const s = this.store().state;
+ if (!s) return 0;
+ return this.objetivoCrafteos(f) * (s.insumos.dwarvenFeePerCraft || 0);
},
objetivoVenta(f) {
@@ -941,6 +1165,9 @@
const precioOre = f.soulOreReq != null ? s.insumos.soulOre : s.insumos.spiritOre;
const orePorCraft = f.soulOreReq != null ? f.soulOreReq : f.spiritOreReq != null ? f.spiritOreReq : 0;
const costoPorBatch = f.cristalesReq * precioCristal + orePorCraft * precioOre + (s.insumos.dwarvenFeePerCraft || 0);
+ const costoCristalesPorBatch = f.cristalesReq * precioCristal;
+ const costoOrePorBatch = orePorCraft * precioOre;
+ const costoDwarvenPorBatch = (s.insumos.dwarvenFeePerCraft || 0);
const ventaPorBatch = f.shotsObtenidos * (s.insumos.venta[f.tipo]?.[f.grado] || 0);
const gananciaPorBatch = ventaPorBatch - costoPorBatch;
const roi = f.cristalesReq > 0 ? Math.round(gananciaPorBatch / f.cristalesReq) : 0;
@@ -954,6 +1181,9 @@
return {
id: f.id, tipo: f.tipo, grado: f.grado,
crafteos, cristales, ore, shots, costo, venta, ganancia, roi,
+ costoCristales: crafteos * costoCristalesPorBatch,
+ costoOre: crafteos * costoOrePorBatch,
+ costoDwarven: crafteos * costoDwarvenPorBatch,
};
});
},
@@ -992,9 +1222,15 @@
const shots = crafteos * f.shotsObtenidos;
const venta = shots * (s.insumos.venta[f.tipo]?.[f.grado] || 0);
const ganancia = venta - costo;
+ const precioCristal = s.insumos.cristales[f.grado] || 0;
+ const precioOre = f.soulOreReq != null ? s.insumos.soulOre : s.insumos.spiritOre;
+ const costoCristales = crafteos * f.cristalesReq * precioCristal;
+ const costoOre = crafteos * orePorCraft * precioOre;
+ const costoDwarven = crafteos * (s.insumos.dwarvenFeePerCraft || 0);
plan.push({
id: f.id, tipo: f.tipo, grado: f.grado,
crafteos, cristales, ore, shots, costo, venta, ganancia,
+ costoCristales, costoOre, costoDwarven,
});
restante -= costo;
}
@@ -1047,9 +1283,13 @@
const shots = crafteos * f.shotsObtenidos;
const venta = crafteos * ventaPorBatch;
const ganancia = venta - costo;
+ const costoCristales = crafteos * f.cristalesReq * precioCristal;
+ const costoOre = crafteos * orePorCraft * precioOre;
+ const costoDwarven = crafteos * (s.insumos.dwarvenFeePerCraft || 0);
plan.push({
id: f.id, tipo: f.tipo, grado: f.grado,
crafteos, cristales, ore, shots, costo, venta, ganancia,
+ costoCristales, costoOre, costoDwarven,
pctTipo: tipoPct, pctGrado: gradoPct, pctAsignado: Math.round(jointFraction * 1000) / 10,
});
}
@@ -1152,7 +1392,80 @@
costo: acc.costo + r.costo,
venta: acc.venta + r.venta,
ganancia: acc.ganancia + r.ganancia,
- }), { crafteos: 0, shots: 0, cristales: 0, ore: 0, costo: 0, venta: 0, ganancia: 0 });
+ costoCristales: acc.costoCristales + (r.costoCristales || 0),
+ costoOre: acc.costoOre + (r.costoOre || 0),
+ costoDwarven: acc.costoDwarven + (r.costoDwarven || 0),
+ }), { crafteos: 0, shots: 0, cristales: 0, ore: 0, costo: 0, venta: 0, ganancia: 0,
+ costoCristales: 0, costoOre: 0, costoDwarven: 0 });
+ },
+
+ planPorCristales() {
+ const s = this.store().state;
+ if (!s || this.cristalesPresupuestoCantidad <= 0) return [];
+ const grado = this.cristalesPresupuestoGrado;
+ const formulas = [...s.formulas]
+ .filter(f => f.grado === grado && f.cristalesReq > 0 && f.shotsObtenidos > 0)
+ .filter(f => this.presupuestoFiltroTipo.length === 0 || this.presupuestoFiltroTipo.includes(f.tipo));
+ if (formulas.length === 0) return [];
+
+ const ranked = formulas
+ .map(f => {
+ const precioCristal = s.insumos.cristales[f.grado] || 0;
+ const precioOre = f.soulOreReq != null ? s.insumos.soulOre : s.insumos.spiritOre;
+ const orePorCraft = f.soulOreReq != null ? f.soulOreReq : f.spiritOreReq != null ? f.spiritOreReq : 0;
+ const costoPorBatch = f.cristalesReq * precioCristal + orePorCraft * precioOre + (s.insumos.dwarvenFeePerCraft || 0);
+ const ventaPorBatch = f.shotsObtenidos * (s.insumos.venta[f.tipo]?.[f.grado] || 0);
+ const gananciaPorBatch = ventaPorBatch - costoPorBatch;
+ const roi = f.cristalesReq > 0 ? Math.round(gananciaPorBatch / f.cristalesReq) : 0;
+ return { f, costoPorBatch, gananciaPorBatch, roi };
+ })
+ .filter(x => x.gananciaPorBatch > 0)
+ .sort((a, b) => b.roi - a.roi);
+ if (ranked.length === 0) return [];
+
+ let restantes = this.cristalesPresupuestoCantidad;
+ const plan = [];
+ for (const { f, costoPorBatch } of ranked) {
+ if (restantes <= 0) break;
+ const crafteos = Math.floor(restantes / f.cristalesReq);
+ if (crafteos <= 0) continue;
+ const cristales = crafteos * f.cristalesReq;
+ const orePorCraft = f.soulOreReq != null ? f.soulOreReq : f.spiritOreReq != null ? f.spiritOreReq : 0;
+ const ore = crafteos * orePorCraft;
+ const shots = crafteos * f.shotsObtenidos;
+ const precioCristal = s.insumos.cristales[f.grado] || 0;
+ const precioOre = f.soulOreReq != null ? s.insumos.soulOre : s.insumos.spiritOre;
+ const costoCristales = cristales * precioCristal;
+ const costoOre = ore * precioOre;
+ const costoDwarven = crafteos * (s.insumos.dwarvenFeePerCraft || 0);
+ const costo = crafteos * costoPorBatch;
+ const venta = shots * (s.insumos.venta[f.tipo]?.[f.grado] || 0);
+ const ganancia = venta - costo;
+ plan.push({
+ id: f.id, tipo: f.tipo, grado: f.grado,
+ crafteos, cristales, ore, shots, costo, venta, ganancia,
+ costoCristales, costoOre, costoDwarven,
+ });
+ restantes -= cristales;
+ }
+ return plan;
+ },
+
+ planTotalesCristales() {
+ const p = this.planPorCristales();
+ return p.reduce((acc, r) => ({
+ cristales: acc.cristales + r.cristales,
+ crafteos: acc.crafteos + r.crafteos,
+ ore: acc.ore + r.ore,
+ shots: acc.shots + r.shots,
+ costo: acc.costo + r.costo,
+ venta: acc.venta + r.venta,
+ ganancia: acc.ganancia + r.ganancia,
+ costoCristales: acc.costoCristales + r.costoCristales,
+ costoOre: acc.costoOre + r.costoOre,
+ costoDwarven: acc.costoDwarven + r.costoDwarven,
+ }), { cristales: 0, crafteos: 0, ore: 0, shots: 0, costo: 0, venta: 0, ganancia: 0,
+ costoCristales: 0, costoOre: 0, costoDwarven: 0 });
},
toggleFiltroTipo(tipo) {
@@ -1221,6 +1534,9 @@
oreNecesario: c.oreNecesario,
crafteosPosibles: c.crafteosPosibles,
shotsObtenidos: c.shotsObtenidos,
+ costoCristales: c.costoCristales,
+ costoOre: c.costoOre,
+ costoDwarven: c.costoDwarven,
costoTotal: c.costoTotal,
valorVenta: c.valorVenta,
ganancia: c.ganancia,
@@ -1238,6 +1554,9 @@
totalShots: tg.shotsObtenidos,
totalCristalesUsed: tg.cristalesUsados,
totalOreUsed: tg.oreNecesario,
+ totalCostCristales: tg.costoCristales,
+ totalCostOre: tg.costoOre,
+ totalCostDwarven: tg.costoDwarven,
items,
snapshot: { insumos: s.insumos, formulas: s.formulas },
});
diff --git a/src/main/resources/templates/PageResource/historial.html b/src/main/resources/templates/PageResource/historial.html
index 7c0037e..ec59379 100644
--- a/src/main/resources/templates/PageResource/historial.html
+++ b/src/main/resources/templates/PageResource/historial.html
@@ -73,6 +73,43 @@
+
+
+
+
+
+ Total invertido
+
+ costo acumulado
+
+
+
+
+ Distribución global del costo
+
+
+ Cristales
+ Ore
+ Fee
+
+
+
Tendencia reciente
@@ -284,6 +321,37 @@
+
+
+
+
+
+ Costo total
+
+ esta corrida
+
+
+
+
+
@@ -352,6 +420,9 @@
| Ore |
Crafteos |
Shots |
+ C. Cristales |
+ C. Ore |
+ C. Fee |
Costo |
Venta |
Ganancia |
@@ -368,6 +439,9 @@
|
|
|
+ |
+ |
+ |
|
|
s + (i.crafteosPosibles || 0), 0);
},
+ costCristalesTotal() {
+ if (!this.modalDetails?.items) return 0;
+ return this.modalDetails.items.reduce((s, i) => s + (i.costoCristales || 0), 0);
+ },
+
+ costOreTotal() {
+ if (!this.modalDetails?.items) return 0;
+ return this.modalDetails.items.reduce((s, i) => s + (i.costoOre || 0), 0);
+ },
+
+ costDwarvenTotal() {
+ if (!this.modalDetails?.items) return 0;
+ return this.modalDetails.items.reduce((s, i) => s + (i.costoDwarven || 0), 0);
+ },
+
+ costCristalesPct() {
+ const total = this.modalDetails?.totalCost || 0;
+ return total > 0 ? Math.round((this.costCristalesTotal() * 1000) / total) / 10 : 0;
+ },
+
+ costOrePct() {
+ const total = this.modalDetails?.totalCost || 0;
+ return total > 0 ? Math.round((this.costOreTotal() * 1000) / total) / 10 : 0;
+ },
+
+ costDwarvenPct() {
+ const total = this.modalDetails?.totalCost || 0;
+ return total > 0 ? Math.round((this.costDwarvenTotal() * 1000) / total) / 10 : 0;
+ },
+
snapshotCristalesUsed() {
const items = this.modalDetails?.items || [];
return new Set(items.map(i => i.grado));
@@ -676,7 +780,7 @@
},
exportCSV() {
- const headers = ['Fecha', 'Label', 'Costo', 'Venta', 'Ganancia', 'Margen%', 'Costo/shot', 'Shots', 'Cristales', 'Ore', 'Items'];
+ const headers = ['Fecha', 'Label', 'Costo', 'Venta', 'Ganancia', 'Margen%', 'Costo/shot', 'Shots', 'Cristales', 'Ore', 'C. Cristales', 'C. Ore', 'C. Fee', 'Items'];
const rows = this.runs.map(r => [
this.fmtDateTime(r.createdAt),
r.label || '',
@@ -688,6 +792,9 @@
r.totalShots,
r.totalCristalesUsed,
r.totalOreUsed,
+ r.costCristales,
+ r.costOre,
+ r.costDwarven,
r.itemCount,
]);
const csv = [headers, ...rows].map(row => row.join(',')).join('\n');
| | |