🧾 Cotización

SubtotalS/ 0.00
DescuentoS/ 0.00
TOTALS/ 0.00
🤖 Asesor de cotizaciones✕
Dicta o escribe lo que pide el cliente y te armo la cotización.
Ej: "4 cámaras dahua 4mp exterior, nvr de 8 canales, disco 2tb, 305m de cable, instalación 350"
2BRAIND
Tecnología y Seguridad Electrónica
WhatsApp: +51 939 316 437
oroliquido.online/camaras
Arequipa, Perú

COTIZACIÓN

Cliente: ${cliente}    Fecha: ${fecha}

${filas}${filaMo}
Cant.DescripciónP. Unit.Total

${montoDesc > 0 ? `Descuento ${descuento}%: -${S(montoDesc)}
` : ""} TOTAL: ${S(total)}

${CONFIG.pie}

Fotos de equipos: GSMArena.com
`; } // ============ GENERADOR DOCX REAL (vanilla JS, sin librerías) ============ // ZIP almacenado (sin compresión) + WordprocessingML mínimo → .docx que // abre en cualquier Word (escritorio, móvil, Google Docs, LibreOffice). function crc32Tabla() { const t = new Uint32Array(256); for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); t[n] = c >>> 0; } return t; } const CRC_T = crc32Tabla(); function crc32(u8) { let c = 0xFFFFFFFF; for (let i = 0; i < u8.length; i++) c = CRC_T[(c ^ u8[i]) & 0xFF] ^ (c >>> 8); return ((c ^ 0xFFFFFFFF) >>> 0); } const TE = new TextEncoder(); function zipDocx(files) { const partes = [], central = []; let offset = 0; const dt = new Date(); const tiempo = ((dt.getHours() & 31) << 11) | ((dt.getMinutes() & 63) << 5) | ((dt.getSeconds() / 2) & 31); const fecha = (((dt.getFullYear() - 1980) & 127) << 9) | (((dt.getMonth() + 1) & 15) << 5) | (dt.getDate() & 31); for (const [nombre, contenido] of Object.entries(files)) { const nameU8 = TE.encode(nombre); const data = typeof contenido === "string" ? TE.encode(contenido) : contenido; const crc = crc32(data); const local = new Uint8Array(30 + nameU8.length + data.length); const dv = new DataView(local.buffer); dv.setUint32(0, 0x04034b50, true); dv.setUint16(4, 20, true); dv.setUint16(6, 0x0800, true); dv.setUint16(8, 0, true); dv.setUint16(10, tiempo, true); dv.setUint16(12, fecha, true); dv.setUint32(14, crc, true); dv.setUint32(18, data.length, true); dv.setUint32(22, data.length, true); dv.setUint16(26, nameU8.length, true); dv.setUint16(28, 0, true); local.set(nameU8, 30); local.set(data, 30 + nameU8.length); partes.push(local); const cen = new Uint8Array(46 + nameU8.length); const cv = new DataView(cen.buffer); cv.setUint32(0, 0x02014b50, true); cv.setUint16(4, 20, true); cv.setUint16(6, 20, true); cv.setUint16(8, 0x0800, true); cv.setUint16(10, 0, true); cv.setUint16(12, tiempo, true); cv.setUint16(14, fecha, true); cv.setUint32(16, crc, true); cv.setUint32(20, data.length, true); cv.setUint32(24, data.length, true); cv.setUint16(28, nameU8.length, true); cv.setUint32(42, offset, true); cen.set(nameU8, 46); central.push(cen); offset += local.length; } const centralSize = central.reduce((s, c) => s + c.length, 0); const eocd = new Uint8Array(22); const ev = new DataView(eocd.buffer); ev.setUint32(0, 0x06054b50, true); ev.setUint16(8, central.length, true); ev.setUint16(10, central.length, true); ev.setUint32(12, centralSize, true); ev.setUint32(16, offset, true); const total = new Uint8Array(offset + centralSize + 22); let pos = 0; for (const p of partes) { total.set(p, pos); pos += p.length; } for (const c of central) { total.set(c, pos); pos += c.length; } total.set(eocd, pos); return total; } function xmlEsc(s) { return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function construirDocx(datos) { // datos: {cliente, fecha, filas:[{cant, desc, unit, total}], manoObra, descuento, montoDesc, total, pie} const p = (t, opts) => `${opts ? `${opts}` : ""}${xmlEsc(t)}`; const celda = (t, ancho, alinear) => `${alinear ? `` : ""}${xmlEsc(t)}`; const filasXml = datos.filas.map(f => `${celda(f.cant, 800, "center")}${celda(f.desc, 5600)}${celda(f.unit, 1600, "right")}${celda(f.total, 1600, "right")}`).join(""); const filaMo = datos.manoObra > 0 ? `${celda("1", 800, "center")}${celda("Mano de obra / instalación", 5600)}${celda(datos.moneda + " " + datos.manoObra.toFixed(2), 1600, "right")}${celda(datos.moneda + " " + datos.manoObra.toFixed(2), 1600, "right")}` : ""; const docXml = ` ${p("2BRAIND — Tecnología y Seguridad Electrónica", '')} ${p("WhatsApp: +51 939 316 437 · oroliquido.online/camaras · Arequipa, Perú", '')} ${p("COTIZACIÓN", '')} ${p("Cliente: " + datos.cliente + " Fecha: " + datos.fecha)} ${celda("Cant.", 800, "center")}${celda("Descripción", 5600)}${celda("P. Unit.", 1600, "right")}${celda("Total", 1600, "right")} ${filasXml}${filaMo} ${datos.montoDesc > 0 ? p("Descuento " + datos.descuento + "%: -" + datos.moneda + " " + datos.montoDesc.toFixed(2), '') : ""} ${p("TOTAL: " + datos.moneda + " " + datos.total.toFixed(2), '')} ${p(datos.pie)} `; return zipDocx({ "[Content_Types].xml": '', "_rels/.rels": '', "word/document.xml": docXml }); } function descargarWord() { if (!carrito.length) { alert("Agrega productos a la cotización primero."); return; } const cliente = (document.getElementById("cliente").value || "cliente").replace(/[^a-z0-9áéíóúñ\s-]/gi, "").trim().replace(/\s+/g, "-").toLowerCase(); const fecha = new Date().toLocaleDateString("es-PE", { day: "2-digit", month: "long", year: "numeric" }); const descuento = Math.min(100, Math.max(0, parseFloat(document.getElementById("descuento").value) || 0)); let subtotal = carrito.reduce((s, x) => s + x.precio * x.cantidad, 0); const moModo = document.getElementById("moModo").value; const moValor = parseFloat(document.getElementById("moValor").value) || 0; const nCams = carrito.filter(x => x.seccion === "CAMARAS").reduce((s, x) => s + x.cantidad, 0); let manoObra = 0; if (moModo === "por_camara") manoObra = moValor * nCams; else if (moModo === "fijo") manoObra = moValor; const montoDesc = subtotal * descuento / 100; const total = Math.max(0, subtotal + manoObra - montoDesc); const datos = { cliente: document.getElementById("cliente").value || "—", fecha, descuento, montoDesc, total, manoObra, moneda: "S/", pie: CONFIG.pie, filas: carrito.map(x => ({ cant: String(x.cantidad), desc: x.nombre, unit: x.precio.toFixed(2), total: (x.precio * x.cantidad).toFixed(2) })) }; const docx = construirDocx(datos); const blob = new Blob([docx], { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "Cotizacion-2Braind-" + (cliente !== "cliente" ? cliente : new Date().toISOString().slice(0, 10)) + ".docx"; try { const snap = snapshotCotizacion("Word"); const a2 = histLeer(); a2.unshift(snap); histGuardar(a2); empujarNube(snap); } catch(e) {} document.body.appendChild(a); a.click(); a.remove(); } document.getElementById("pPie").textContent = CONFIG.pie; function ficha(id) { const p = CATALOGO.find(x => x.id === id); if (!p) return; const previa = document.querySelector(".ficha"); if (previa) previa.remove(); const img = p.foto || ""; const chips = [ p.mp ? p.mp + "MP" : "", p.tecnologia, p.ambiente ? (p.ambiente === "Exterior" ? "Exterior IP66+" : p.ambiente) : "", p.marca && p.marca !== "GENÉRICA" ? p.marca : "", ].filter(Boolean).map(c => `${c}`).join(""); const div = document.createElement("div"); div.className = "ficha"; div.onclick = (e) => { if (e.target === div) div.remove(); }; div.innerHTML = `
${p.bestseller ? '⭐ MÁS VENDIDA' : ""} ${img ? `` : ""}
${(p.tipo || p.seccion || "").toUpperCase()}

${p.nombre}

${chips}
${p.descripcion ? `

${p.descripcion}

` : ""} ${p.incluye ? `
Incluye / qué trae:\n${p.incluye}
` : ""}
`; document.body.appendChild(div); } function enviarWhatsApp() { try { if (carrito.length) { const sn = snapshotCotizacion('WhatsApp'); const aa = histLeer(); aa.unshift(sn); histGuardar(aa); empujarNube(sn); } } catch(e) {} if (!carrito.length) { alert("Agrega productos a la cotización primero."); return; } const cliente = document.getElementById("cliente").value || ""; const descuento = Math.min(100, Math.max(0, parseFloat(document.getElementById("descuento").value) || 0)); const subtotal = carrito.reduce((s, x) => s + x.precio * x.cantidad, 0); const moModo = document.getElementById("moModo").value; const moValor = parseFloat(document.getElementById("moValor").value) || 0; const nCams = carrito.filter(x => x.seccion === "CAMARAS").reduce((s, x) => s + x.cantidad, 0); let manoObra = 0; if (moModo === "por_camara") manoObra = moValor * nCams; else if (moModo === "fijo") manoObra = moValor; const montoDesc = subtotal * descuento / 100; const total = Math.max(0, subtotal + manoObra - montoDesc); const lineas = carrito.map(x => `• ${x.cantidad}× ${x.nombre} — ${x.precio ? S(x.precio * x.cantidad) : "consultar"}`).join("\n"); let txt = `*COTIZACIÓN DE SEGURIDAD*\n${cliente ? "Cliente: " + cliente + "\n" : ""}\n${lineas}`; if (manoObra > 0) txt += `\n• Mano de obra / instalación — ${S(manoObra)}`; if (montoDesc > 0) txt += `\n• Descuento ${descuento}% — -${S(montoDesc)}`; txt += `\n\n*TOTAL: ${total ? S(total) : "por confirmar"}*`; window.open(`https://wa.me/${CONFIG.whatsapp}?text=${encodeURIComponent(txt)}`, "_blank"); } function llenarPrint() { const cliente = document.getElementById("cliente").value || "—"; document.getElementById("pCliente").textContent = "Cliente: " + cliente; document.getElementById("pFecha").textContent = new Date().toLocaleDateString("es-PE", { day: "2-digit", month: "long", year: "numeric" }); const tb = document.getElementById("pLineas"); tb.innerHTML = ""; carrito.forEach(x => { const tr = document.createElement("tr"); const fotoCell = x.foto ? `` : ""; tr.innerHTML = `${x.cantidad}${fotoCell}${x.nombre}${S(x.precio)}${S(x.precio * x.cantidad)}`; tb.appendChild(tr); }); const descuento = Math.min(100, Math.max(0, parseFloat(document.getElementById("descuento").value) || 0)); let subtotal = carrito.reduce((s, x) => s + x.precio * x.cantidad, 0); const moModo = document.getElementById("moModo").value; const moValor = parseFloat(document.getElementById("moValor").value) || 0; const nCams = carrito.filter(x => x.seccion === "CAMARAS").reduce((s, x) => s + x.cantidad, 0); let manoObra = 0; if (moModo === "por_camara") manoObra = moValor * nCams; else if (moModo === "fijo") manoObra = moValor; if (manoObra > 0) { const trMo = document.createElement("tr"); trMo.innerHTML = `1Mano de obra / instalación${S(manoObra)}${S(manoObra)}`; tb.appendChild(trMo); } const montoDesc = subtotal * descuento / 100; const total = Math.max(0, subtotal + manoObra - montoDesc); const pTot = document.getElementById("pTotal"); pTot.textContent = ""; if (montoDesc > 0) { const dLin = document.createElement("div"); dLin.style.cssText = "font-size:11px; color:#666; text-align:right; margin-bottom:2px;"; dLin.textContent = `Descuento ${descuento}%: -S/ ${montoDesc.toFixed(2)}`; pTot.appendChild(dLin); } const tLin = document.createElement("div"); tLin.style.cssText = "font-size:16px; font-weight:bold; text-align:right;"; tLin.textContent = `TOTAL: S/ ${total.toFixed(2)}`; pTot.appendChild(tLin); }
Fotos de equipos: GSMArena.com