| |
| |
|
|
| |
| 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); |
| } |
| } |