feat: galeria muestra foto impresa + estimacion por placa en detalle

- Galeria: cards muestran imagen de referencia si existe, fallback a thumbnail 3D
- Detalle: nueva seccion 'Placas de impresion' con estimacion individual por parte
- Endpoint /estimate ahora acepta file_id para calcular por archivo
- Visualizacion de placa 220x220mm con proporcion de pieza y totales acumulados
This commit is contained in:
Consultoria AS
2026-05-01 08:23:03 +00:00
parent 7a66cc1d6e
commit 0764be4945
4 changed files with 129 additions and 5 deletions

View File

@@ -900,6 +900,101 @@ async function runEstimation() {
}
}
// ====== PLATE ESTIMATION ======
async function runPlateEstimation() {
const container = document.getElementById('plates-result');
if (!container || !currentModel) return;
const parts = (currentModel.files || []).filter(f => f.file_type === 'stl' || f.file_type === '3mf');
if (parts.length === 0) {
container.innerHTML = '<p class="text-sm text-slate-500">No hay partes para estimar.</p>';
return;
}
container.innerHTML = '<p class="text-sm text-slate-400">Calculando placas...</p>';
const PLATE_SIZE = 220; // mm (standard bed size)
let totalCost = 0;
let totalTime = 0;
let totalGrams = 0;
const results = [];
for (const part of parts) {
try {
const data = await apiGet(`/models/${getModelId()}/estimate?file_id=${part.id}`);
totalCost += data.cost;
totalTime += data.estimated_seconds;
totalGrams += data.grams;
// Determine fit color
const maxDim = Math.max(data.width_mm || 0, data.depth_mm || 0);
const fitColor = maxDim > PLATE_SIZE ? 'bg-red-500' : maxDim > PLATE_SIZE * 0.8 ? 'bg-yellow-500' : 'bg-cyan-500';
const fitText = maxDim > PLATE_SIZE ? 'No cabe en placa estandar' : 'Cabe en placa';
// Scale for visual (200px = 220mm)
const scale = 200 / PLATE_SIZE;
const rectW = Math.min((data.width_mm || 10) * scale, 200);
const rectH = Math.min((data.depth_mm || 10) * scale, 200);
results.push({
...data,
part,
fitColor,
fitText,
rectW,
rectH,
});
} catch (err) {
results.push({ part, error: err.message });
}
}
const totalHours = Math.floor(totalTime / 3600);
const totalMins = Math.floor((totalTime % 3600) / 60);
container.innerHTML = `
<div class="space-y-4">
${results.map((r, i) => r.error ? `
<div class="p-3 rounded-xl bg-red-500/10 border border-red-500/20 text-sm text-red-400">
${r.part.filename}: Error al calcular
</div>
` : `
<div class="p-4 rounded-xl bg-slate-900/50 border border-white/5">
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium">${r.part_name}</span>
<span class="text-[10px] px-2 py-0.5 rounded-full ${r.fitColor} text-white">${r.fitText}</span>
</div>
<div class="flex gap-4">
<div class="shrink-0">
<div class="relative w-[200px] h-[200px] border border-slate-600 rounded bg-slate-800/50" style="background-image: linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px); background-size: 18.18px 18.18px;">
<div class="absolute rounded bg-cyan-500/30 border border-cyan-400/60 flex items-center justify-center text-[10px] text-cyan-300" style="width:${r.rectW}px; height:${r.rectH}px; left:4px; top:4px;">
${r.width_mm}x${r.depth_mm}
</div>
<span class="absolute bottom-1 right-1 text-[9px] text-slate-500">220x220mm</span>
</div>
</div>
<div class="flex-1 grid grid-cols-2 gap-x-4 gap-y-1 text-xs">
<div><span class="text-slate-500">Volumen:</span> <span class="text-slate-200">${r.volume_cm3} cm³</span></div>
<div><span class="text-slate-500">Peso:</span> <span class="text-slate-200">${r.grams} g</span></div>
<div><span class="text-slate-500">Alto:</span> <span class="text-slate-200">${r.height_mm} mm</span></div>
<div><span class="text-slate-500">Costo:</span> <span class="text-cyan-400 font-bold">$${r.cost}</span></div>
<div class="col-span-2"><span class="text-slate-500">Tiempo:</span> <span class="text-slate-200">${r.estimated_time}</span></div>
</div>
</div>
</div>
`).join('')}
<div class="p-4 rounded-xl bg-cyan-500/10 border border-cyan-500/20">
<p class="text-sm font-bold text-cyan-400 mb-1">Total estimado (${results.length} placa${results.length > 1 ? 's' : ''})</p>
<div class="grid grid-cols-3 gap-3 text-xs">
<div><span class="text-slate-500">Peso:</span> <span class="text-slate-200">${totalGrams.toFixed(2)} g</span></div>
<div><span class="text-slate-500">Costo:</span> <span class="text-cyan-400 font-bold">$${totalCost.toFixed(2)}</span></div>
<div><span class="text-slate-500">Tiempo:</span> <span class="text-slate-200">${totalHours}h ${totalMins}m</span></div>
</div>
</div>
</div>
`;
}
// ====== COMPARISON ======
async function openCompareModal() {
const modal = document.getElementById('compare-modal');