Estimare Cost Constructie

Calculator deviz general — estimeaza costul fiecarei etape de constructie a casei tale

🏗️ Instrument complet pentru planificarea bugetului. Calculeaza costurile estimate pentru fiecare etapa de constructie, genereaza devize detaliate si obtine recomandari AI personalizate. Toate preturile sunt orientative si pot varia in functie de zona si materiale alese.

Informatii Proiect

Introduceti localitate/judet pentru acuratete mai buna a estimarilor.

Adaugare Etape Proiect

Selectati etapele de constructie si introduceti datele necesare.

Asistent Preturi AI

Utilizati AI pentru a cauta preturi estimative pentru materialele si manoperele din proiect.

Sectiune Element

Vizualizarea sectiunii transversale a elementului selectat.

Lista Etape Adaugate

Click pe rand pentru vizualizare. Folositi bifele pentru stergere multipla.

Categorie Tip Lucrare Detalii Cant. Actiuni
Nicio etapa adaugata.

Deviz General Estimativ

Aplicati un factor de pierderi si generati lista detaliata de materiale/manopere.

Deviz General Estimativ

Localitate: ${loc}

Factor Pierderi: ${cont}

Data: ${new Date().toLocaleDateString('ro-RO')}

${csHtml}${dHtml}
`; const w = window.open('','_blank'); if(w){w.document.write(rHtml);w.document.close();w.focus();} } // ================================================================================= // CANVAS // ================================================================================= function updateSectionCanvas() { const canvas = document.getElementById('section-canvas'); if(!canvas) return; const ctx = canvas.getContext('2d'); if(!ctx) return; const sel = elements.find(e => e.id === selectedElementId); if (sel && (sel.category === 'structura' || sel.type === 'sarpantaClasica')) { drawSection(ctx, sel.type, sel.dimensions); } else { ctx.clearRect(0,0,canvas.width,canvas.height); ctx.textAlign='center'; ctx.fillStyle='rgba(255,255,255,0.4)'; ctx.font='14px Inter'; ctx.fillText(sel ? 'Vizualizare disponibila doar pentru structura.' : 'Selectati un element din tabel.', canvas.width/2, canvas.height/2); } } function drawSection(ctx, type, dimensions) { ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height); ctx.strokeStyle='#ffb400'; ctx.lineWidth=1.5; ctx.font='12px Inter'; ctx.fillStyle='rgba(255,255,255,0.8)'; const w=ctx.canvas.width, h=ctx.canvas.height, margin=40, offset=30; const drawRectAndLabels = (lat, alt, color, labelX, labelY) => { const scale=Math.min((w-2*margin)/lat,(h-2*margin)/alt); const rW=lat*scale,rH=alt*scale,x=(w-rW)/2,y=(h-rH)/2; ctx.fillStyle=color; ctx.fillRect(x,y,rW,rH); ctx.strokeRect(x,y,rW,rH); ctx.fillStyle='rgba(255,255,255,0.8)'; ctx.textAlign='center'; ctx.fillText(labelX,x+rW/2,y+rH+20); ctx.save(); ctx.translate(x-offset,y+rH/2); ctx.rotate(-Math.PI/2); ctx.textAlign='center'; ctx.fillText(labelY,0,0); ctx.restore(); }; const v = dimensions; switch(type) { case 'fundatieIzolata': { const mL=Math.max(v.lbloc,v.lcuzinet),tH=v.hbloc+v.hcuzinet,sc=Math.min((w-2*margin)/mL,(h-2*margin)/tH); const bL=v.lbloc*sc,bH=v.hbloc*sc,cL=v.lcuzinet*sc,cH=v.hcuzinet*sc,totalH=bH+cH; const xB=(w-bL)/2,yB=(h-totalH)/2+cH,xC=xB+(bL-cL)/2,yC=yB-cH; ctx.fillStyle='rgba(255,180,0,0.2)'; ctx.fillRect(xB,yB,bL,bH); ctx.strokeRect(xB,yB,bL,bH); ctx.fillStyle='rgba(255,180,0,0.35)'; ctx.fillRect(xC,yC,cL,cH); ctx.strokeRect(xC,yC,cL,cH); ctx.fillStyle='rgba(255,255,255,0.8)'; ctx.textAlign='center'; ctx.fillText(`${v.lbloc.toFixed(2)} m`,xB+bL/2,yB+bH+20); ctx.fillText(`${v.lcuzinet.toFixed(2)} m`,xC+cL/2,yC-10); ctx.save(); ctx.translate(xB-offset,yB+bH/2); ctx.rotate(-Math.PI/2); ctx.textAlign='center'; ctx.fillText(`${v.hbloc.toFixed(2)} m`,0,0); ctx.restore(); ctx.save(); ctx.translate(xC-offset,yC+cH/2); ctx.rotate(-Math.PI/2); ctx.textAlign='center'; ctx.fillText(`${v.hcuzinet.toFixed(2)} m`,0,0); ctx.restore(); break; } case 'stalpRectangular': drawRectAndLabels(v.latime,v.lungime,'rgba(255,180,0,0.25)',`${v.latime.toFixed(2)} m`,`${v.lungime.toFixed(2)} m`); break; case 'stalpCircular': { const sc=(Math.min(w,h)-2*margin)/v.diametru, r=(v.diametru*sc)/2, cx=w/2, cy=h/2; ctx.fillStyle='rgba(255,180,0,0.25)'; ctx.beginPath(); ctx.arc(cx,cy,r,0,2*Math.PI); ctx.fill(); ctx.stroke(); ctx.fillStyle='rgba(255,255,255,0.8)'; ctx.textAlign='center'; ctx.fillText(`Ø ${v.diametru.toFixed(2)} m`,cx,cy+r+20); break; } case 'placa': case 'placaCota0': case 'placaPesteSubsol': case 'radierGeneral': case 'planseuDala': drawRectAndLabels(2.0,v.grosime,'rgba(255,180,0,0.2)','Sectiune Placa (1m latime)',`h = ${v.grosime.toFixed(2)} m`); break; case 'sarpantaClasica': { const pR=v.panta*(Math.PI/180), base=w-2*margin, ht=(base/2)*Math.tan(pR), sc=Math.min(1,(h-2*margin)/ht); const sB=base*sc, sH=ht*sc, x=(w-sB)/2, y=(h-sH)/2; ctx.fillStyle='rgba(255,180,0,0.15)'; ctx.beginPath(); ctx.moveTo(x,y+sH); ctx.lineTo(x+sB,y+sH); ctx.lineTo(x+sB/2,y); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.fillStyle='rgba(255,255,255,0.8)'; ctx.textAlign='center'; ctx.fillText(`${v.latimeCladire.toFixed(2)} m`,w/2,y+sH+20); ctx.fillText(`${v.panta.toFixed(2)}°`,w/2,y+sH/2); break; } default: { const lat=v.latime, alt=(type==='grindaSuprastructura')?v.inaltimeSubPlaca:v.inaltime; if(lat&&alt) drawRectAndLabels(lat,alt,'rgba(255,180,0,0.25)',`${lat.toFixed(2)} m`,`${alt.toFixed(2)} m`); } } } // ================================================================================= // AI PRICE SEARCH // ================================================================================= async function searchPricesWithAI() { if (!isAIReady()) { showAIError("AI nu este initializat. Verificati conexiunea."); return; } if (elements.length === 0) { showAIError("Adaugati mai intai elemente."); return; } const loc = document.getElementById('location').value || 'Romania'; setAILoadingState(true); hideAIResults(); try { const allMats = elements.flatMap(getMaterialBreakdown); const um = new Map(); allMats.forEach(m => { if(!um.has(m.name)) um.set(m.name, m); }); const uml = Array.from(um.values()); const BS = 15, TB = Math.ceil(uml.length/BS); let allPrices = {}, allTexts = []; for (let i = 0; i < TB; i++) { updateAIProgress(i+1, TB); const batch = uml.slice(i*BS, (i+1)*BS); const mp = batch.map(m => `"${m.name}" (unitate: ${m.unit})`).join(', '); const at = await callGoogleAI(`Ca expert in constructii din Romania, ofera o scurta analiza a preturilor estimate pentru zona "${loc}" pentru: ${mp}`); if (at) allTexts.push(at); if (!at.trim()) continue; try { const jt = await callGoogleAI(`Pe baza textului, extrage preturile. Returneaza array JSON cu "name","min","max" in RON. Materiale: ${mp}\nText: "${at}"\nRaspunsul: array JSON valid fara text suplimentar.`); const cj = jt.trim().replace(/```json\n?|\n?```/g, ''); const pa = JSON.parse(cj); if (Array.isArray(pa)) pa.forEach(p => { if(p.name && um.has(p.name)) allPrices[p.name] = {min:p.min<0?0:Number(p.min),max:p.max<0?0:Number(p.max)}; }); } catch(e) { console.warn('JSON parse fail batch', i+1, e); } } pricesData = allPrices; showAIResults(allTexts.length > 0 ? allTexts.join('\n\n---\n\n') : "Nu s-a putut genera analiza.", []); if (document.getElementById('report-container').style.display === 'block') generateReportWithPrices(); } catch(e) { console.error('AI Error:', e); showAIError(`Eroare: ${e.message}. Incercati din nou.`); } finally { setAILoadingState(false); } } function setAILoadingState(loading) { const b=document.getElementById('search-prices-btn'),s=document.getElementById('ai-spinner'),i=document.getElementById('ai-icon'),t=document.getElementById('search-btn-text'); if(loading){b.disabled=true;s.style.display='block';i.style.display='none';t.textContent='Cauta...';} else{b.disabled=elements.length===0;s.style.display='none';i.style.display='block';t.textContent='Cauta preturi';} } function updateAIProgress(c,t){document.getElementById('search-btn-text').textContent=`Cauta... (${c}/${t})`;} function showAIError(msg, type='error') { const e=document.getElementById('ai-error'),r=document.getElementById('ai-results'); e.textContent=msg; e.className = type==='success' ? 'est-ai-success' : 'est-ai-error'; e.style.display='block'; r.style.display='block'; document.getElementById('ai-analysis').style.display='none'; document.getElementById('ai-sources').style.display='none'; } function hideAIResults(){['ai-results','ai-error','ai-analysis','ai-sources'].forEach(id=>document.getElementById(id).style.display='none');} function showAIResults(text, sources) { const r=document.getElementById('ai-results'),a=document.getElementById('ai-analysis'),s=document.getElementById('ai-sources'); document.getElementById('ai-analysis-text').textContent=text; a.style.display='block'; if(sources&&sources.length>0){const sl=document.getElementById('ai-sources-list');sl.innerHTML='';sources.forEach(src=>{if(src.web){const li=document.createElement('li'),an=document.createElement('a');an.href=src.web.uri;an.target='_blank';an.style.color='#ffb400';an.textContent=src.web.title||src.web.uri;li.appendChild(an);sl.appendChild(li);}});s.style.display='block';} r.style.display='block'; document.getElementById('ai-error').style.display='none'; } function exportAISources() { const sl=document.getElementById('ai-sources-list'); if(!sl) return; const loc=document.getElementById('location').value||'N/A'; const links=Array.from(sl.querySelectorAll('a')); const content=`Surse consultate\nProiect: ${loc}\nData: ${new Date().toLocaleDateString('ro-RO')}\n\n---\n\n${links.map(l=>`${l.textContent}\n${l.href}`).join('\n\n')}`; const blob=new Blob([content],{type:'text/plain;charset=utf-8'}),url=URL.createObjectURL(blob),a=document.createElement('a'); a.href=url;a.download='surse_preturi_deviz.txt';document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(url); } function updateReportAndAIStatus(){document.getElementById('generate-report-btn').disabled=elements.length===0;document.getElementById('search-prices-btn').disabled=elements.length===0;} // ================================================================================= // MODAL // ================================================================================= function openQuantityModal(el){editingElement=el;document.getElementById('modal-element-name').textContent=el.typeText;document.getElementById('new-quantity').value=el.quantity;document.getElementById('quantity-modal').style.display='flex';document.getElementById('new-quantity').focus();} function closeQuantityModal(){editingElement=null;document.getElementById('quantity-modal').style.display='none';} function saveQuantityChange(){if(!editingElement)return;const nq=parseInt(document.getElementById('new-quantity').value);if(!isNaN(nq)&&nq>0){editingElement.quantity=nq;updateElementsTable();updateReportStatus();closeQuantityModal();}} // ================================================================================= // INIT // ================================================================================= function initEstimareApp() { initializeAI(); populateCategoryDropdown(); setTimeout(()=>updateAIButtonState(),2000); document.getElementById('category').addEventListener('change',e=>{populateTypeDropdown(e.target.value);document.getElementById('dimensions-container').style.display='none';document.getElementById('add-button-container').style.display='none';}); document.getElementById('element-type').addEventListener('change',e=>{createDimensionInputs(document.getElementById('category').value,e.target.value);}); document.getElementById('add-element-btn').addEventListener('click',addElement); document.getElementById('select-all').addEventListener('change',e=>{document.querySelectorAll('tbody input[type="checkbox"]').forEach(cb=>cb.checked=e.target.checked);updateSelectedCount();}); document.getElementById('delete-selected-btn').addEventListener('click',deleteSelectedElements); document.getElementById('generate-report-btn').addEventListener('click',generateReport); document.getElementById('export-report-btn').addEventListener('click',exportReport); document.getElementById('contingency-factor').addEventListener('input',()=>{if(document.getElementById('report-container').style.display==='block')generateReport();}); document.getElementById('search-prices-btn').addEventListener('click',searchPricesWithAI); document.getElementById('export-sources-btn').addEventListener('click',exportAISources); document.getElementById('modal-cancel-btn').addEventListener('click',closeQuantityModal); document.getElementById('modal-save-btn').addEventListener('click',saveQuantityChange); document.getElementById('quantity-modal').addEventListener('click',e=>{if(e.target.id==='quantity-modal')closeQuantityModal();}); updateReportStatus(); } if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',initEstimareApp);else initEstimareApp();