| | |
| | |
| |
|
| | |
| | var productosSheet = "Productos"; |
| | var pedidosSheet = "Pedidos"; |
| | var productosData = []; |
| | var stlFiles = {}; |
| | var currentColor = "#000000"; |
| | var currentModel = null; |
| |
|
| | |
| | function onOpen() { |
| | var ui = SpreadsheetApp.getUi(); |
| | ui.createMenu('3D Viewer') |
| | .addItem('Agregar al carrito', 'addToCart') |
| | .addSeparator() |
| | .addItem('Limpiar carrito', 'clearCart') |
| | .addToUi(); |
| | |
| | |
| | loadProducts(); |
| | |
| | |
| | init3DViewer(); |
| | } |
| |
|
| | |
| | function loadProducts() { |
| | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(productosSheet); |
| | if (!sheet) return; |
| | |
| | var data = sheet.getDataRange().getValues(); |
| | productosData = []; |
| | |
| | for (var i = 1; i < data.length; i++) { |
| | if (data[i][0] && data[i][1]) { |
| | productosData.push({ |
| | nombre: data[i][0], |
| | precio: data[i][1], |
| | vida: data[i][2], |
| | inventario: data[i][3], |
| | id: data[i][4] |
| | }); |
| | } |
| | } |
| | |
| | Logger.log('Productos cargados: ' + productosData.length); |
| | } |
| |
|
| | |
| | function init3DViewer() { |
| | |
| | var html = HtmlService.createHtmlOutputFromFile('viewer') |
| | .setWidth(400) |
| | .setHeight(400); |
| | |
| | SpreadsheetApp.getUi().showModelessDialog(html, 'Visor 3D'); |
| | } |
| |
|
| | |
| | function addToCart(productName, quantity, client) { |
| | |
| | var product = productosData.find(p => |
| | p.nombre.toLowerCase() === productName.toLowerCase() |
| | ); |
| | |
| | if (!product) { |
| | SpreadsheetApp.getUi().alert('Producto no encontrado: ' + productName); |
| | return; |
| | } |
| | |
| | if (product.inventario <= 0) { |
| | SpreadsheetApp.getUi().alert('Producto sin inventario: ' + productName); |
| | return; |
| | } |
| | |
| | |
| | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(pedidosSheet); |
| | if (!sheet) { |
| | SpreadsheetApp.getUi().alert('Hoja de pedidos no encontrada'); |
| | return; |
| | } |
| | |
| | sheet.appendRow([ |
| | product.nombre, |
| | quantity, |
| | client, |
| | product.inventario, |
| | product.id |
| | ]); |
| | |
| | |
| | updateInventory(product.nombre, -quantity); |
| | |
| | SpreadsheetApp.getUi().alert('Producto agregado al carrito: ' + product.nombre); |
| | } |
| |
|
| | |
| | function updateInventory(productName, quantity) { |
| | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(productosSheet); |
| | if (!sheet) return; |
| | |
| | var data = sheet.getDataRange().getValues(); |
| | |
| | for (var i = 1; i < data.length; i++) { |
| | if (data[i][0] && data[i][0].toLowerCase() === productName.toLowerCase()) { |
| | var currentInv = parseInt(data[i][3]) || 0; |
| | var newInv = currentInv + quantity; |
| | sheet.getRange(i+1, 4).setValue(newInv); |
| | break; |
| | } |
| | } |
| | } |
| |
|
| | |
| | function clearCart() { |
| | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(pedidosSheet); |
| | if (!sheet) return; |
| | |
| | var lastRow = sheet.getLastRow(); |
| | if (lastRow > 1) { |
| | sheet.deleteRows(2, lastRow - 1); |
| | SpreadsheetApp.getUi().alert('Carrito limpiado'); |
| | } |
| | } |
| |
|
| | |
| | function loadSTLFiles() { |
| | |
| | |
| | stlFiles = { |
| | 'greca': 'data/stl/greca.stl', |
| | 'minimalista': 'data/stl/minimalista.stl', |
| | 'cl谩sico': 'data/stl/cl谩sico.stl', |
| | 'moderno': 'data/stl/moderno.stl' |
| | }; |
| | |
| | |
| | |
| | stlIdMapping = { |
| | |
| | 1: 'greca', |
| | 2: 'mapa_rd', |
| | 3: 'arete_greca', |
| | 4: 'arete_greca', |
| | 5: 'arete_mapa_rd', |
| | 6: 'arete_mapa_rd', |
| | 7: 'arete_bandera_rd', |
| | 8: 'arete_bandera_rd', |
| | 9: 'llavero_greca', |
| | 10: 'llavero_mapa_rd', |
| | 11: 'figura_catalogo', |
| | 12: 'llavero_bandera_rd', |
| | 13: 'llavero_bandera_rd', |
| | 14: 'cortador_game', |
| | 15: 'poley_grande_steam', |
| | 16: 'poley_peq_steam', |
| | 17: 'love_jesus', |
| | 18: 'posa_vasos', |
| | 19: 'keychain_mom', |
| | 20: 'llavero_tambor', |
| | 21: 'arete_tambor', |
| | 22: 'gato_flexible', |
| | 23: 'gancho_llaves', |
| | 24: 'gancho_llaves' |
| | }; |
| | |
| | |
| | stlFilenameToDisplayName = { |
| | 'greca': 'Llavero estilo Greca Grab.stl', |
| | 'mapa_rd': 'Llavero estilo Mapa RD Grab.stl', |
| | 'arete_greca': 'Arete estilo Greca peq.stl', |
| | 'arete_mapa_rd': 'Arete estilo Mapa RD.stl', |
| | 'arete_bandera_rd': 'Arete estilo Bandera RD peq.stl', |
| | 'llavero_bandera_rd': 'Llavero estilo Bandera RD Grab.stl', |
| | 'cortador_game': 'CortadorGalletasGameControl.stl', |
| | 'poley_grande_steam': 'Poleygrande STEAM.stl', |
| | 'poley_peq_steam': 'Poleypeq STEAM.stl', |
| | 'love_jesus': 'Love=Jesus.stl', |
| | 'posa_vasos': 'Posa Vasos.stl', |
| | 'keychain_mom': 'KeychainMom.stl', |
| | 'llavero_tambor': 'llavero estilo tambor.STL', |
| | 'arete_tambor': 'Arete estilo tambor.stl', |
| | 'gato_flexible': 'Gato+flexible.stl', |
| | 'gancho_llaves': 'Gancho para llaves sweet home.STL' |
| | }; |
| | |
| | |
| | displayNameToStlFilename = { |
| | 'Llavero estilo Greca Grab.stl': 'greca', |
| | 'Llavero estilo Mapa RD Grab.stl': 'mapa_rd', |
| | 'Arete estilo Greca peq.stl': 'arete_greca', |
| | 'Arete estilo Mapa RD.stl': 'arete_mapa_rd', |
| | 'Arete estilo Bandera RD peq.stl': 'arete_bandera_rd', |
| | 'Llavero estilo Bandera RD Grab.stl': 'llavero_bandera_rd', |
| | 'CortadorGalletasGameControl.stl': 'cortador_game', |
| | 'Poleygrande STEAM.stl': 'poley_grande_steam', |
| | 'Poleypeq STEAM.stl': 'poley_peq_steam', |
| | 'Love=Jesus.stl': 'love_jesus', |
| | 'Posa Vasos.stl': 'posa_vasos', |
| | 'KeychainMom.stl': 'keychain_mom', |
| | 'llavero estilo tambor.STL': 'llavero_tambor', |
| | 'Arete estilo tambor.stl': 'arete_tambor', |
| | 'Gato+flexible.stl': 'gato_flexible', |
| | 'Gancho para llaves sweet home.STL': 'gancho_llaves' |
| | }; |
| | |
| | |
| | baseNameToStlFilename = { |
| | 'llavero estilo greca': 'llavero_greca', |
| | 'llavero estilo mapa rd': 'llavero_mapa_rd', |
| | 'arete estilo greca': 'arete_greca', |
| | 'arete estilo mapa rd': 'arete_mapa_rd', |
| | 'arete estilo bandera rd': 'arete_bandera_rd', |
| | 'llavero estilo bandera rd': 'llavero_bandera_rd', |
| | 'cortador de galletas game': 'cortador_game', |
| | 'poleygrande steam': 'poley_grande_steam', |
| | 'poleypeq steam': 'poley_peq_steam', |
| | 'llavero love=jesus': 'love_jesus', |
| | 'posa vasos': 'posa_vasos', |
| | 'keychainmom': 'keychain_mom', |
| | 'llavero estilo tambor': 'llavero_tambor', |
| | 'arete estilo tambor': 'arete_tambor', |
| | 'gato flexible': 'gato_flexible', |
| | 'gancho para llaves': 'gancho_llaves' |
| | }; |
| | |
| | Logger.log('STL files cargados: ' + Object.keys(stlFiles).length); |
| | } |
| |
|
| | |
| | function changeColor(color) { |
| | currentColor = color; |
| | |
| | var app = UiApp.getActiveApplication(); |
| | if (app) { |
| | app.getElementById('modelColor').setStyleAttribute('color', color); |
| | } |
| | |
| | SpreadsheetApp.getUi().showModelessDialog( |
| | HtmlService.createHtmlOutput( |
| | '<div id="modelColor" style="color:' + color + '">Color actualizado</div>' |
| | ).setWidth(200).setHeight(100), |
| | 'Color' |
| | ); |
| | } |
| |
|
| | |
| | function loadModel(modelName) { |
| | currentModel = modelName; |
| | |
| | Logger.log('Cargando modelo: ' + modelName); |
| | } |
| |
|
| | |
| | function getViewerHTML() { |
| | return ` |
| | <!DOCTYPE html> |
| | <html> |
| | <head> |
| | <title>Visor 3D Interactivo</title> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> |
| | <style> |
| | body { |
| | margin: 0; |
| | padding: 10px; |
| | background: #f0f0f0; |
| | font-family: Arial, sans-serif; |
| | } |
| | #viewer { |
| | width: 100%; |
| | height: 300px; |
| | border: 1px solid #ccc; |
| | background: #ffffff; |
| | } |
| | .color-picker { |
| | display: flex; |
| | gap: 5px; |
| | margin-top: 10px; |
| | } |
| | .color-btn { |
| | width: 30px; |
| | height: 30px; |
| | border: none; |
| | border-radius: 50%; |
| | cursor: pointer; |
| | } |
| | </style> |
| | </head> |
| | <body> |
| | <h3>Visor 3D - Selecciona un modelo</h3> |
| | <div id="viewer"></div> |
| | <div class="color-picker"> |
| | <button class="color-btn" style="background: #000000" onclick="selectColor('#000000')"></button> |
| | <button class="color-btn" style="background: #ff0000" onclick="selectColor('#ff0000')"></button> |
| | <button class="color-btn" style="background: #00ff00" onclick="selectColor('#00ff00')"></button> |
| | <button class="color-btn" style="background: #0000ff" onclick="selectColor('#0000ff')"></button> |
| | <button class="color-btn" style="background: #ffff00" onclick="selectColor('#ffff00')"></button> |
| | <button class="color-btn" style="background: #ff00ff" onclick="selectColor('#ff00ff')"></button> |
| | <button class="color-btn" style="background: #00ffff" onclick="selectColor('#00ffff')"></button> |
| | </div> |
| | |
| | <script> |
| | let scene, camera, renderer, model; |
| | let currentColor = '#000000'; |
| | |
| | function init() { |
| | scene = new THREE.Scene(); |
| | camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000); |
| | camera.position.z = 5; |
| | |
| | renderer = new THREE.WebGLRenderer({ antialias: true }); |
| | renderer.setSize(380, 280); |
| | document.getElementById('viewer').appendChild(renderer.domElement); |
| | |
| | |
| | const gridHelper = new THREE.GridHelper(10, 10); |
| | scene.add(gridHelper); |
| | |
| | animate(); |
| | } |
| | |
| | function animate() { |
| | requestAnimationFrame(animate); |
| | if (model) { |
| | model.rotation.y += 0.01; |
| | } |
| | renderer.render(scene, camera); |
| | } |
| | |
| | function selectColor(color) { |
| | currentColor = color; |
| | if (model) { |
| | model.traverse((child) => { |
| | if (child.isMesh) { |
| | child.material.color.set(color); |
| | } |
| | }); |
| | } |
| | google.script.run.withSuccessHandler(function() {}).changeColor(color); |
| | } |
| | |
| | function loadModel(modelName) { |
| | |
| | if (model) { |
| | scene.remove(model); |
| | } |
| | |
| | |
| | const geometry = new THREE.BoxGeometry(2, 2, 2); |
| | const material = new THREE.MeshPhongMaterial({ |
| | color: currentColor, |
| | shininess: 100 |
| | }); |
| | model = new THREE.Mesh(geometry, material); |
| | scene.add(model); |
| | |
| | |
| | const ambientLight = new THREE.AmbientLight(0x404040); |
| | scene.add(ambientLight); |
| | |
| | const pointLight = new THREE.PointLight(0xffffff, 1, 100); |
| | pointLight.position.set(10, 10, 10); |
| | scene.add(pointLight); |
| | |
| | google.script.run.withSuccessHandler(function() {}).loadModel(modelName); |
| | } |
| | |
| | |
| | window.onload = function() { |
| | init(); |
| | |
| | loadModel('greca'); |
| | }; |
| | </script> |
| | </body> |
| | </html> |
| | `; |
| | } |
| |
|
| | |
| | function showViewer() { |
| | var html = HtmlService.createHtmlOutput(getViewerHTML()) |
| | .setWidth(420) |
| | .setHeight(450); |
| | |
| | SpreadsheetApp.getUi().showModelessDialog(html, 'Visor 3D'); |
| | } |
| |
|
| | |
| | function doGet(e) { |
| | var action = e.parameter.action; |
| | |
| | if (action === 'products') { |
| | return getProducts(); |
| | } |
| | |
| | if (action === 'order') { |
| | return saveOrder(e); |
| | } |
| | |
| | return ContentService.createTextOutput(JSON.stringify({ error: 'Acci贸n no v谩lida' })) |
| | .setMimeType(ContentService.MimeType.JSON); |
| | } |
| |
|
| | |
| | function getProducts() { |
| | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(productosSheet); |
| | if (!sheet) { |
| | return ContentService.createTextOutput(JSON.stringify({ values: [] })) |
| | .setMimeType(ContentService.MimeType.JSON); |
| | } |
| | |
| | var data = sheet.getDataRange().getValues(); |
| | var result = []; |
| | |
| | |
| | result.push(['id_producto', 'Productos', 'Precio_Unitario_con_costos', 'Precio unitario', 'vida promedio', 'INVENTARIO']); |
| | |
| | |
| | for (var i = 1; i < data.length; i++) { |
| | result.push([ |
| | data[i][0], |
| | data[i][1], |
| | data[i][2], |
| | data[i][3], |
| | data[i][4], |
| | data[i][5] |
| | ]); |
| | } |
| | |
| | return ContentService.createTextOutput(JSON.stringify({ values: result })) |
| | .setMimeType(ContentService.MimeType.JSON); |
| | } |
| |
|
| | |
| | function saveOrder(e) { |
| | var product = e.parameter.product; |
| | var quantity = parseInt(e.parameter.quantity); |
| | var client = e.parameter.client; |
| | var phone = e.parameter.phone; |
| | var latitude = e.parameter.latitude || ''; |
| | var longitude = e.parameter.longitude || ''; |
| | var fecha = e.parameter.fecha || new Date().toISOString(); |
| | |
| | if (!product || !quantity || !client) { |
| | return ContentService.createTextOutput(JSON.stringify({ error: 'Faltan datos requeridos' })) |
| | .setMimeType(ContentService.MimeType.JSON); |
| | } |
| | |
| | try { |
| | var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(pedidosSheet); |
| | if (!sheet) { |
| | return ContentService.createTextOutput(JSON.stringify({ error: 'Hoja de pedidos no encontrada' })) |
| | .setMimeType(ContentService.MimeType.JSON); |
| | } |
| | |
| | |
| | var productosSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Productos'); |
| | var productosData = productosSheet.getDataRange().getValues(); |
| | var productId = ''; |
| | var productPrice = 0; |
| | |
| | |
| | for (var i = 1; i < productosData.length; i++) { |
| | if (productosData[i][0] && productosData[i][0].toString() === productId) { |
| | productId = productosData[i][0]; |
| | productPrice = productosData[i][2]; |
| | break; |
| | } |
| | } |
| | |
| | |
| | if (!productId && product) { |
| | for (var i = 1; i < productosData.length; i++) { |
| | if (productosData[i][1] && productosData[i][1].toString().toLowerCase() === product.toLowerCase()) { |
| | productId = productosData[i][0]; |
| | productPrice = productosData[i][2]; |
| | break; |
| | } |
| | } |
| | } |
| | |
| | |
| | var pedidoId = 'PED-' + new Date().getTime(); |
| | |
| | |
| | sheet.appendRow([ |
| | product, // Productos |
| | quantity, // Cantidad |
| | client, // Cliente |
| | '', // INVENTARIO (dejar vac铆o) |
| | productId, |
| | pedidoId, |
| | productPrice * quantity, |
| | productId, |
| | latitude, |
| | longitude, |
| | phone, |
| | fecha |
| | ]); |
| | |
| | |
| | updateInventory(product, -quantity); |
| | |
| | return ContentService.createTextOutput(JSON.stringify({ |
| | success: true, |
| | pedidoId: pedidoId, |
| | price: productPrice, |
| | total: productPrice * quantity |
| | }) |
| | .setMimeType(ContentService.MimeType.JSON); |
| | |
| | } catch (error) { |
| | return ContentService.createTextOutput(JSON.stringify({ error: error.toString() })) |
| | .setMimeType(ContentService.MimeType.JSON); |
| | } |
| | } |