pedidos
This commit is contained in:
72
.cursor/debug.log
Normal file
72
.cursor/debug.log
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:entry","message":"Push entrada","data":{"wooProductId":"39326","categories":["Carnes > Vacuna"],"sellUnit":"kg"},"timestamp":1768774472072,"sessionId":"debug-session","hypothesisId":"A,B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:url","message":"URL construida","data":{"url":"https://piaf.floda.dev/wp-json/wc/v3/products/39326","base":"https://piaf.floda.dev/wp-json/wc/v3"},"timestamp":1768774472088,"sessionId":"debug-session","hypothesisId":"A"}
|
||||||
|
{"location":"wooSnapshot.js:fetchWooCategoriesByNames","message":"Categorias en Woo","data":{"searchingFor":["Carnes > Vacuna"],"normalizedNames":["carnes","vacuna"],"wooCategoriesCount":50,"wooCategories":[{"id":96,"name":"Aceites acetos y vinagres","slug":"aceites-acetos-y-vinagres"},{"id":99,"name":"Aceitesacetos y vinagres","slug":"aceitesacetos-y-vinagres"},{"id":95,"name":"Aceitunas","slug":"aceitunas"},{"id":110,"name":"Achuras","slug":"achuras"},{"id":85,"name":"Almacén","slug":"almacen"},{"id":87,"name":"Asado","slug":"asado"},{"id":97,"name":"Bebidas","slug":"bebidas"},{"id":138,"name":"Cabernet","slug":"cabernet"},{"id":134,"name":"Calchaquies","slug":"calchaquies"},{"id":109,"name":"Carnes","slug":"carnes"},{"id":111,"name":"Cerdo","slug":"cerdo"},{"id":126,"name":"Cerveza","slug":"cerveza"},{"id":98,"name":"Con alcohol","slug":"con-alcohol"},{"id":90,"name":"Condimentos","slug":"condimentos"},{"id":93,"name":"Conservas","slug":"conservas"},{"id":112,"name":"Cordero","slug":"cordero"},{"id":92,"name":"Delicatessen","slug":"delicatessen"},{"id":105,"name":"Dips","slug":"dips"},{"id":104,"name":"Dulces","slug":"dulces"},{"id":118,"name":"Embutidos","slug":"embutidos-rebozados"},{"id":113,"name":"Exóticas","slug":"exoticas"},{"id":117,"name":"Fiambreria y Charcuteria de autor","slug":"charcuteria"},{"id":121,"name":"Frutos secos","slug":"frutos-secos"},{"id":116,"name":"Hamburguesas","slug":"hamburguesas"},{"id":86,"name":"Huevos","slug":"huevos"},{"id":135,"name":"Low cost","slug":"low-cost"},{"id":127,"name":"Madurada","slug":"madurada"},{"id":136,"name":"Malbec","slug":"malbec"},{"id":132,"name":"Mendoza","slug":"mendoza"},{"id":103,"name":"Mermeladas","slug":"mermeladas"},{"id":100,"name":"Miel","slug":"miel"},{"id":88,"name":"Panes y Harinas","slug":"panes-y-harinas"},{"id":101,"name":"Pastas y arroces","slug":"pastas-y-arroces"},{"id":131,"name":"Patagonia","slug":"patagonia"},{"id":94,"name":"Pepinos","slug":"pepinos"},{"id":119,"name":"Pescados","slug":"pescados"},{"id":137,"name":"Pinot Noir","slug":"pinot-noir"},{"id":114,"name":"Pollo","slug":"pollo"},{"id":84,"name":"Proveeduría","slug":"proveeduria"},{"id":120,"name":"Quesos","slug":"quesos"},{"id":15,"name":"Rebozados","slug":"uncategorized"},{"id":89,"name":"Sal pimienta y especias","slug":"sal-pimienta-y-especias"},{"id":91,"name":"Salsas","slug":"salsas"},{"id":130,"name":"Selección Malbec","slug":"seleccion-malbec"},{"id":133,"name":"Selección Pinot","slug":"seleccion-pinot"},{"id":129,"name":"Seleccionados","slug":"seleccionados"},{"id":108,"name":"Sin alcohol","slug":"sin-alcohol"},{"id":102,"name":"Snacks","slug":"snacks"},{"id":115,"name":"Vacuna","slug":"vacuna"},{"id":125,"name":"Vinos","slug":"vinos"}]},"timestamp":1768774472886,"sessionId":"debug-session","hypothesisId":"B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:cats","message":"Categorias mapeadas","data":{"requested":["Carnes > Vacuna"],"foundCount":2,"found":[{"id":109,"name":"Carnes"},{"id":115,"name":"Vacuna"}]},"timestamp":1768774472887,"sessionId":"debug-session","hypothesisId":"B,C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:payload","message":"Payload final","data":{"updatePayload":{"categories":[{"id":109},{"id":115}],"meta_data":[{"key":"_sell_unit_override","value":"kg"}]},"isEmpty":false},"timestamp":1768774472888,"sessionId":"debug-session","hypothesisId":"C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:entry","message":"Push entrada","data":{"wooProductId":"39326","categories":[],"sellUnit":"kg"},"timestamp":1768774473232,"sessionId":"debug-session","hypothesisId":"A,B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:url","message":"URL construida","data":{"url":"https://piaf.floda.dev/wp-json/wc/v3/products/39326","base":"https://piaf.floda.dev/wp-json/wc/v3"},"timestamp":1768774473235,"sessionId":"debug-session","hypothesisId":"A"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:payload","message":"Payload final","data":{"updatePayload":{"meta_data":[{"key":"_sell_unit_override","value":"kg"}]},"isEmpty":false},"timestamp":1768774473236,"sessionId":"debug-session","hypothesisId":"C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:response","message":"Respuesta Woo","data":{"wooProductId":"39326","responseCategories":[{"id":109,"name":"Carnes"},{"id":115,"name":"Vacuna"}],"status":"success"},"timestamp":1768774473657,"sessionId":"debug-session","hypothesisId":"D,E"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:response","message":"Respuesta Woo","data":{"wooProductId":"39326","responseCategories":[{"id":109,"name":"Carnes"},{"id":115,"name":"Vacuna"}],"status":"success"},"timestamp":1768774473983,"sessionId":"debug-session","hypothesisId":"D,E"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:entry","message":"Push entrada","data":{"wooProductId":"39326","categories":[],"sellUnit":"kg"},"timestamp":1768774474525,"sessionId":"debug-session","hypothesisId":"A,B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:url","message":"URL construida","data":{"url":"https://piaf.floda.dev/wp-json/wc/v3/products/39326","base":"https://piaf.floda.dev/wp-json/wc/v3"},"timestamp":1768774474527,"sessionId":"debug-session","hypothesisId":"A"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:payload","message":"Payload final","data":{"updatePayload":{"meta_data":[{"key":"_sell_unit_override","value":"kg"}]},"isEmpty":false},"timestamp":1768774474527,"sessionId":"debug-session","hypothesisId":"C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:response","message":"Respuesta Woo","data":{"wooProductId":"39326","responseCategories":[{"id":109,"name":"Carnes"},{"id":115,"name":"Vacuna"}],"status":"success"},"timestamp":1768774475208,"sessionId":"debug-session","hypothesisId":"D,E"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:entry","message":"Push entrada","data":{"wooProductId":"39326","categories":[],"sellUnit":"kg"},"timestamp":1768774481413,"sessionId":"debug-session","hypothesisId":"A,B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:url","message":"URL construida","data":{"url":"https://piaf.floda.dev/wp-json/wc/v3/products/39326","base":"https://piaf.floda.dev/wp-json/wc/v3"},"timestamp":1768774481415,"sessionId":"debug-session","hypothesisId":"A"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:payload","message":"Payload final","data":{"updatePayload":{"meta_data":[{"key":"_sell_unit_override","value":"kg"}]},"isEmpty":false},"timestamp":1768774481415,"sessionId":"debug-session","hypothesisId":"C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:response","message":"Respuesta Woo","data":{"wooProductId":"39326","responseCategories":[{"id":109,"name":"Carnes"},{"id":115,"name":"Vacuna"}],"status":"success"},"timestamp":1768774482123,"sessionId":"debug-session","hypothesisId":"D,E"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:entry","message":"Push entrada","data":{"wooProductId":"3253","categories":["Quesos","Carnes > Achuras"],"sellUnit":"kg"},"timestamp":1768774662226,"sessionId":"debug-session","hypothesisId":"A,B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:url","message":"URL construida","data":{"url":"https://piaf.floda.dev/wp-json/wc/v3/products/3253","base":"https://piaf.floda.dev/wp-json/wc/v3"},"timestamp":1768774662231,"sessionId":"debug-session","hypothesisId":"A"}
|
||||||
|
{"location":"wooSnapshot.js:fetchWooCategoriesByNames","message":"Categorias en Woo","data":{"searchingFor":["Quesos","Carnes > Achuras"],"normalizedNames":["quesos","carnes","achuras"],"wooCategoriesCount":50,"wooCategories":[{"id":96,"name":"Aceites acetos y vinagres","slug":"aceites-acetos-y-vinagres"},{"id":99,"name":"Aceitesacetos y vinagres","slug":"aceitesacetos-y-vinagres"},{"id":95,"name":"Aceitunas","slug":"aceitunas"},{"id":110,"name":"Achuras","slug":"achuras"},{"id":85,"name":"Almacén","slug":"almacen"},{"id":87,"name":"Asado","slug":"asado"},{"id":97,"name":"Bebidas","slug":"bebidas"},{"id":138,"name":"Cabernet","slug":"cabernet"},{"id":134,"name":"Calchaquies","slug":"calchaquies"},{"id":109,"name":"Carnes","slug":"carnes"},{"id":111,"name":"Cerdo","slug":"cerdo"},{"id":126,"name":"Cerveza","slug":"cerveza"},{"id":98,"name":"Con alcohol","slug":"con-alcohol"},{"id":90,"name":"Condimentos","slug":"condimentos"},{"id":93,"name":"Conservas","slug":"conservas"},{"id":112,"name":"Cordero","slug":"cordero"},{"id":92,"name":"Delicatessen","slug":"delicatessen"},{"id":105,"name":"Dips","slug":"dips"},{"id":104,"name":"Dulces","slug":"dulces"},{"id":118,"name":"Embutidos","slug":"embutidos-rebozados"},{"id":113,"name":"Exóticas","slug":"exoticas"},{"id":117,"name":"Fiambreria y Charcuteria de autor","slug":"charcuteria"},{"id":121,"name":"Frutos secos","slug":"frutos-secos"},{"id":116,"name":"Hamburguesas","slug":"hamburguesas"},{"id":86,"name":"Huevos","slug":"huevos"},{"id":135,"name":"Low cost","slug":"low-cost"},{"id":127,"name":"Madurada","slug":"madurada"},{"id":136,"name":"Malbec","slug":"malbec"},{"id":132,"name":"Mendoza","slug":"mendoza"},{"id":103,"name":"Mermeladas","slug":"mermeladas"},{"id":100,"name":"Miel","slug":"miel"},{"id":88,"name":"Panes y Harinas","slug":"panes-y-harinas"},{"id":101,"name":"Pastas y arroces","slug":"pastas-y-arroces"},{"id":131,"name":"Patagonia","slug":"patagonia"},{"id":94,"name":"Pepinos","slug":"pepinos"},{"id":119,"name":"Pescados","slug":"pescados"},{"id":137,"name":"Pinot Noir","slug":"pinot-noir"},{"id":114,"name":"Pollo","slug":"pollo"},{"id":84,"name":"Proveeduría","slug":"proveeduria"},{"id":120,"name":"Quesos","slug":"quesos"},{"id":15,"name":"Rebozados","slug":"uncategorized"},{"id":89,"name":"Sal pimienta y especias","slug":"sal-pimienta-y-especias"},{"id":91,"name":"Salsas","slug":"salsas"},{"id":130,"name":"Selección Malbec","slug":"seleccion-malbec"},{"id":133,"name":"Selección Pinot","slug":"seleccion-pinot"},{"id":129,"name":"Seleccionados","slug":"seleccionados"},{"id":108,"name":"Sin alcohol","slug":"sin-alcohol"},{"id":102,"name":"Snacks","slug":"snacks"},{"id":115,"name":"Vacuna","slug":"vacuna"},{"id":125,"name":"Vinos","slug":"vinos"}]},"timestamp":1768774663022,"sessionId":"debug-session","hypothesisId":"B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:cats","message":"Categorias mapeadas","data":{"requested":["Quesos","Carnes > Achuras"],"foundCount":3,"found":[{"id":110,"name":"Achuras"},{"id":109,"name":"Carnes"},{"id":120,"name":"Quesos"}]},"timestamp":1768774663023,"sessionId":"debug-session","hypothesisId":"B,C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:payload","message":"Payload final","data":{"updatePayload":{"categories":[{"id":110},{"id":109},{"id":120}],"meta_data":[{"key":"_sell_unit_override","value":"kg"}]},"isEmpty":false},"timestamp":1768774663023,"sessionId":"debug-session","hypothesisId":"C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:response","message":"Respuesta Woo","data":{"wooProductId":"3253","responseCategories":[{"id":109,"name":"Carnes"},{"id":110,"name":"Achuras"},{"id":120,"name":"Quesos"}],"status":"success"},"timestamp":1768774663803,"sessionId":"debug-session","hypothesisId":"D,E"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:entry","message":"Push entrada","data":{"wooProductId":"3256","categories":["Quesos","Carnes > Achuras"],"sellUnit":"kg"},"timestamp":1768774663825,"sessionId":"debug-session","hypothesisId":"A,B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:url","message":"URL construida","data":{"url":"https://piaf.floda.dev/wp-json/wc/v3/products/3256","base":"https://piaf.floda.dev/wp-json/wc/v3"},"timestamp":1768774663827,"sessionId":"debug-session","hypothesisId":"A"}
|
||||||
|
{"location":"wooSnapshot.js:fetchWooCategoriesByNames","message":"Categorias en Woo","data":{"searchingFor":["Quesos","Carnes > Achuras"],"normalizedNames":["quesos","carnes","achuras"],"wooCategoriesCount":50,"wooCategories":[{"id":96,"name":"Aceites acetos y vinagres","slug":"aceites-acetos-y-vinagres"},{"id":99,"name":"Aceitesacetos y vinagres","slug":"aceitesacetos-y-vinagres"},{"id":95,"name":"Aceitunas","slug":"aceitunas"},{"id":110,"name":"Achuras","slug":"achuras"},{"id":85,"name":"Almacén","slug":"almacen"},{"id":87,"name":"Asado","slug":"asado"},{"id":97,"name":"Bebidas","slug":"bebidas"},{"id":138,"name":"Cabernet","slug":"cabernet"},{"id":134,"name":"Calchaquies","slug":"calchaquies"},{"id":109,"name":"Carnes","slug":"carnes"},{"id":111,"name":"Cerdo","slug":"cerdo"},{"id":126,"name":"Cerveza","slug":"cerveza"},{"id":98,"name":"Con alcohol","slug":"con-alcohol"},{"id":90,"name":"Condimentos","slug":"condimentos"},{"id":93,"name":"Conservas","slug":"conservas"},{"id":112,"name":"Cordero","slug":"cordero"},{"id":92,"name":"Delicatessen","slug":"delicatessen"},{"id":105,"name":"Dips","slug":"dips"},{"id":104,"name":"Dulces","slug":"dulces"},{"id":118,"name":"Embutidos","slug":"embutidos-rebozados"},{"id":113,"name":"Exóticas","slug":"exoticas"},{"id":117,"name":"Fiambreria y Charcuteria de autor","slug":"charcuteria"},{"id":121,"name":"Frutos secos","slug":"frutos-secos"},{"id":116,"name":"Hamburguesas","slug":"hamburguesas"},{"id":86,"name":"Huevos","slug":"huevos"},{"id":135,"name":"Low cost","slug":"low-cost"},{"id":127,"name":"Madurada","slug":"madurada"},{"id":136,"name":"Malbec","slug":"malbec"},{"id":132,"name":"Mendoza","slug":"mendoza"},{"id":103,"name":"Mermeladas","slug":"mermeladas"},{"id":100,"name":"Miel","slug":"miel"},{"id":88,"name":"Panes y Harinas","slug":"panes-y-harinas"},{"id":101,"name":"Pastas y arroces","slug":"pastas-y-arroces"},{"id":131,"name":"Patagonia","slug":"patagonia"},{"id":94,"name":"Pepinos","slug":"pepinos"},{"id":119,"name":"Pescados","slug":"pescados"},{"id":137,"name":"Pinot Noir","slug":"pinot-noir"},{"id":114,"name":"Pollo","slug":"pollo"},{"id":84,"name":"Proveeduría","slug":"proveeduria"},{"id":120,"name":"Quesos","slug":"quesos"},{"id":15,"name":"Rebozados","slug":"uncategorized"},{"id":89,"name":"Sal pimienta y especias","slug":"sal-pimienta-y-especias"},{"id":91,"name":"Salsas","slug":"salsas"},{"id":130,"name":"Selección Malbec","slug":"seleccion-malbec"},{"id":133,"name":"Selección Pinot","slug":"seleccion-pinot"},{"id":129,"name":"Seleccionados","slug":"seleccionados"},{"id":108,"name":"Sin alcohol","slug":"sin-alcohol"},{"id":102,"name":"Snacks","slug":"snacks"},{"id":115,"name":"Vacuna","slug":"vacuna"},{"id":125,"name":"Vinos","slug":"vinos"}]},"timestamp":1768774664616,"sessionId":"debug-session","hypothesisId":"B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:cats","message":"Categorias mapeadas","data":{"requested":["Quesos","Carnes > Achuras"],"foundCount":3,"found":[{"id":110,"name":"Achuras"},{"id":109,"name":"Carnes"},{"id":120,"name":"Quesos"}]},"timestamp":1768774664616,"sessionId":"debug-session","hypothesisId":"B,C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:payload","message":"Payload final","data":{"updatePayload":{"categories":[{"id":110},{"id":109},{"id":120}],"meta_data":[{"key":"_sell_unit_override","value":"kg"}]},"isEmpty":false},"timestamp":1768774664617,"sessionId":"debug-session","hypothesisId":"C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:response","message":"Respuesta Woo","data":{"wooProductId":"3256","responseCategories":[{"id":109,"name":"Carnes"},{"id":110,"name":"Achuras"},{"id":120,"name":"Quesos"}],"status":"success"},"timestamp":1768774665336,"sessionId":"debug-session","hypothesisId":"D,E"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:entry","message":"Push entrada","data":{"wooProductId":"3251","categories":["Fiambreria y Charcuteria de autor","Quesos","Carnes > Achuras"],"sellUnit":"kg"},"timestamp":1768774665354,"sessionId":"debug-session","hypothesisId":"A,B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:url","message":"URL construida","data":{"url":"https://piaf.floda.dev/wp-json/wc/v3/products/3251","base":"https://piaf.floda.dev/wp-json/wc/v3"},"timestamp":1768774665357,"sessionId":"debug-session","hypothesisId":"A"}
|
||||||
|
{"location":"wooSnapshot.js:fetchWooCategoriesByNames","message":"Categorias en Woo","data":{"searchingFor":["Fiambreria y Charcuteria de autor","Quesos","Carnes > Achuras"],"normalizedNames":["fiambreria y charcuteria de autor","quesos","carnes","achuras"],"wooCategoriesCount":50,"wooCategories":[{"id":96,"name":"Aceites acetos y vinagres","slug":"aceites-acetos-y-vinagres"},{"id":99,"name":"Aceitesacetos y vinagres","slug":"aceitesacetos-y-vinagres"},{"id":95,"name":"Aceitunas","slug":"aceitunas"},{"id":110,"name":"Achuras","slug":"achuras"},{"id":85,"name":"Almacén","slug":"almacen"},{"id":87,"name":"Asado","slug":"asado"},{"id":97,"name":"Bebidas","slug":"bebidas"},{"id":138,"name":"Cabernet","slug":"cabernet"},{"id":134,"name":"Calchaquies","slug":"calchaquies"},{"id":109,"name":"Carnes","slug":"carnes"},{"id":111,"name":"Cerdo","slug":"cerdo"},{"id":126,"name":"Cerveza","slug":"cerveza"},{"id":98,"name":"Con alcohol","slug":"con-alcohol"},{"id":90,"name":"Condimentos","slug":"condimentos"},{"id":93,"name":"Conservas","slug":"conservas"},{"id":112,"name":"Cordero","slug":"cordero"},{"id":92,"name":"Delicatessen","slug":"delicatessen"},{"id":105,"name":"Dips","slug":"dips"},{"id":104,"name":"Dulces","slug":"dulces"},{"id":118,"name":"Embutidos","slug":"embutidos-rebozados"},{"id":113,"name":"Exóticas","slug":"exoticas"},{"id":117,"name":"Fiambreria y Charcuteria de autor","slug":"charcuteria"},{"id":121,"name":"Frutos secos","slug":"frutos-secos"},{"id":116,"name":"Hamburguesas","slug":"hamburguesas"},{"id":86,"name":"Huevos","slug":"huevos"},{"id":135,"name":"Low cost","slug":"low-cost"},{"id":127,"name":"Madurada","slug":"madurada"},{"id":136,"name":"Malbec","slug":"malbec"},{"id":132,"name":"Mendoza","slug":"mendoza"},{"id":103,"name":"Mermeladas","slug":"mermeladas"},{"id":100,"name":"Miel","slug":"miel"},{"id":88,"name":"Panes y Harinas","slug":"panes-y-harinas"},{"id":101,"name":"Pastas y arroces","slug":"pastas-y-arroces"},{"id":131,"name":"Patagonia","slug":"patagonia"},{"id":94,"name":"Pepinos","slug":"pepinos"},{"id":119,"name":"Pescados","slug":"pescados"},{"id":137,"name":"Pinot Noir","slug":"pinot-noir"},{"id":114,"name":"Pollo","slug":"pollo"},{"id":84,"name":"Proveeduría","slug":"proveeduria"},{"id":120,"name":"Quesos","slug":"quesos"},{"id":15,"name":"Rebozados","slug":"uncategorized"},{"id":89,"name":"Sal pimienta y especias","slug":"sal-pimienta-y-especias"},{"id":91,"name":"Salsas","slug":"salsas"},{"id":130,"name":"Selección Malbec","slug":"seleccion-malbec"},{"id":133,"name":"Selección Pinot","slug":"seleccion-pinot"},{"id":129,"name":"Seleccionados","slug":"seleccionados"},{"id":108,"name":"Sin alcohol","slug":"sin-alcohol"},{"id":102,"name":"Snacks","slug":"snacks"},{"id":115,"name":"Vacuna","slug":"vacuna"},{"id":125,"name":"Vinos","slug":"vinos"}]},"timestamp":1768774666044,"sessionId":"debug-session","hypothesisId":"B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:cats","message":"Categorias mapeadas","data":{"requested":["Fiambreria y Charcuteria de autor","Quesos","Carnes > Achuras"],"foundCount":4,"found":[{"id":110,"name":"Achuras"},{"id":109,"name":"Carnes"},{"id":117,"name":"Fiambreria y Charcuteria de autor"},{"id":120,"name":"Quesos"}]},"timestamp":1768774666045,"sessionId":"debug-session","hypothesisId":"B,C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:payload","message":"Payload final","data":{"updatePayload":{"categories":[{"id":110},{"id":109},{"id":117},{"id":120}],"meta_data":[{"key":"_sell_unit_override","value":"kg"}]},"isEmpty":false},"timestamp":1768774666045,"sessionId":"debug-session","hypothesisId":"C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:response","message":"Respuesta Woo","data":{"wooProductId":"3251","responseCategories":[{"id":109,"name":"Carnes"},{"id":110,"name":"Achuras"},{"id":117,"name":"Fiambreria y Charcuteria de autor"},{"id":120,"name":"Quesos"}],"status":"success"},"timestamp":1768774666798,"sessionId":"debug-session","hypothesisId":"D,E"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:entry","message":"Push entrada","data":{"wooProductId":"3253","categories":["Quesos"],"sellUnit":"kg"},"timestamp":1768774718170,"sessionId":"debug-session","hypothesisId":"A,B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:url","message":"URL construida","data":{"url":"https://piaf.floda.dev/wp-json/wc/v3/products/3253","base":"https://piaf.floda.dev/wp-json/wc/v3"},"timestamp":1768774718173,"sessionId":"debug-session","hypothesisId":"A"}
|
||||||
|
{"location":"wooSnapshot.js:fetchWooCategoriesByNames","message":"Categorias en Woo","data":{"searchingFor":["Quesos"],"normalizedNames":["quesos"],"wooCategoriesCount":50,"wooCategories":[{"id":96,"name":"Aceites acetos y vinagres","slug":"aceites-acetos-y-vinagres"},{"id":99,"name":"Aceitesacetos y vinagres","slug":"aceitesacetos-y-vinagres"},{"id":95,"name":"Aceitunas","slug":"aceitunas"},{"id":110,"name":"Achuras","slug":"achuras"},{"id":85,"name":"Almacén","slug":"almacen"},{"id":87,"name":"Asado","slug":"asado"},{"id":97,"name":"Bebidas","slug":"bebidas"},{"id":138,"name":"Cabernet","slug":"cabernet"},{"id":134,"name":"Calchaquies","slug":"calchaquies"},{"id":109,"name":"Carnes","slug":"carnes"},{"id":111,"name":"Cerdo","slug":"cerdo"},{"id":126,"name":"Cerveza","slug":"cerveza"},{"id":98,"name":"Con alcohol","slug":"con-alcohol"},{"id":90,"name":"Condimentos","slug":"condimentos"},{"id":93,"name":"Conservas","slug":"conservas"},{"id":112,"name":"Cordero","slug":"cordero"},{"id":92,"name":"Delicatessen","slug":"delicatessen"},{"id":105,"name":"Dips","slug":"dips"},{"id":104,"name":"Dulces","slug":"dulces"},{"id":118,"name":"Embutidos","slug":"embutidos-rebozados"},{"id":113,"name":"Exóticas","slug":"exoticas"},{"id":117,"name":"Fiambreria y Charcuteria de autor","slug":"charcuteria"},{"id":121,"name":"Frutos secos","slug":"frutos-secos"},{"id":116,"name":"Hamburguesas","slug":"hamburguesas"},{"id":86,"name":"Huevos","slug":"huevos"},{"id":135,"name":"Low cost","slug":"low-cost"},{"id":127,"name":"Madurada","slug":"madurada"},{"id":136,"name":"Malbec","slug":"malbec"},{"id":132,"name":"Mendoza","slug":"mendoza"},{"id":103,"name":"Mermeladas","slug":"mermeladas"},{"id":100,"name":"Miel","slug":"miel"},{"id":88,"name":"Panes y Harinas","slug":"panes-y-harinas"},{"id":101,"name":"Pastas y arroces","slug":"pastas-y-arroces"},{"id":131,"name":"Patagonia","slug":"patagonia"},{"id":94,"name":"Pepinos","slug":"pepinos"},{"id":119,"name":"Pescados","slug":"pescados"},{"id":137,"name":"Pinot Noir","slug":"pinot-noir"},{"id":114,"name":"Pollo","slug":"pollo"},{"id":84,"name":"Proveeduría","slug":"proveeduria"},{"id":120,"name":"Quesos","slug":"quesos"},{"id":15,"name":"Rebozados","slug":"uncategorized"},{"id":89,"name":"Sal pimienta y especias","slug":"sal-pimienta-y-especias"},{"id":91,"name":"Salsas","slug":"salsas"},{"id":130,"name":"Selección Malbec","slug":"seleccion-malbec"},{"id":133,"name":"Selección Pinot","slug":"seleccion-pinot"},{"id":129,"name":"Seleccionados","slug":"seleccionados"},{"id":108,"name":"Sin alcohol","slug":"sin-alcohol"},{"id":102,"name":"Snacks","slug":"snacks"},{"id":115,"name":"Vacuna","slug":"vacuna"},{"id":125,"name":"Vinos","slug":"vinos"}]},"timestamp":1768774718887,"sessionId":"debug-session","hypothesisId":"B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:cats","message":"Categorias mapeadas","data":{"requested":["Quesos"],"foundCount":1,"found":[{"id":120,"name":"Quesos"}]},"timestamp":1768774718887,"sessionId":"debug-session","hypothesisId":"B,C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:payload","message":"Payload final","data":{"updatePayload":{"categories":[{"id":120}],"meta_data":[{"key":"_sell_unit_override","value":"kg"}]},"isEmpty":false},"timestamp":1768774718887,"sessionId":"debug-session","hypothesisId":"C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:response","message":"Respuesta Woo","data":{"wooProductId":"3253","responseCategories":[{"id":120,"name":"Quesos"}],"status":"success"},"timestamp":1768774719630,"sessionId":"debug-session","hypothesisId":"D,E"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:entry","message":"Push entrada","data":{"wooProductId":"3256","categories":["Quesos"],"sellUnit":"kg"},"timestamp":1768774722058,"sessionId":"debug-session","hypothesisId":"A,B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:url","message":"URL construida","data":{"url":"https://piaf.floda.dev/wp-json/wc/v3/products/3256","base":"https://piaf.floda.dev/wp-json/wc/v3"},"timestamp":1768774722061,"sessionId":"debug-session","hypothesisId":"A"}
|
||||||
|
{"location":"wooSnapshot.js:fetchWooCategoriesByNames","message":"Categorias en Woo","data":{"searchingFor":["Quesos"],"normalizedNames":["quesos"],"wooCategoriesCount":50,"wooCategories":[{"id":96,"name":"Aceites acetos y vinagres","slug":"aceites-acetos-y-vinagres"},{"id":99,"name":"Aceitesacetos y vinagres","slug":"aceitesacetos-y-vinagres"},{"id":95,"name":"Aceitunas","slug":"aceitunas"},{"id":110,"name":"Achuras","slug":"achuras"},{"id":85,"name":"Almacén","slug":"almacen"},{"id":87,"name":"Asado","slug":"asado"},{"id":97,"name":"Bebidas","slug":"bebidas"},{"id":138,"name":"Cabernet","slug":"cabernet"},{"id":134,"name":"Calchaquies","slug":"calchaquies"},{"id":109,"name":"Carnes","slug":"carnes"},{"id":111,"name":"Cerdo","slug":"cerdo"},{"id":126,"name":"Cerveza","slug":"cerveza"},{"id":98,"name":"Con alcohol","slug":"con-alcohol"},{"id":90,"name":"Condimentos","slug":"condimentos"},{"id":93,"name":"Conservas","slug":"conservas"},{"id":112,"name":"Cordero","slug":"cordero"},{"id":92,"name":"Delicatessen","slug":"delicatessen"},{"id":105,"name":"Dips","slug":"dips"},{"id":104,"name":"Dulces","slug":"dulces"},{"id":118,"name":"Embutidos","slug":"embutidos-rebozados"},{"id":113,"name":"Exóticas","slug":"exoticas"},{"id":117,"name":"Fiambreria y Charcuteria de autor","slug":"charcuteria"},{"id":121,"name":"Frutos secos","slug":"frutos-secos"},{"id":116,"name":"Hamburguesas","slug":"hamburguesas"},{"id":86,"name":"Huevos","slug":"huevos"},{"id":135,"name":"Low cost","slug":"low-cost"},{"id":127,"name":"Madurada","slug":"madurada"},{"id":136,"name":"Malbec","slug":"malbec"},{"id":132,"name":"Mendoza","slug":"mendoza"},{"id":103,"name":"Mermeladas","slug":"mermeladas"},{"id":100,"name":"Miel","slug":"miel"},{"id":88,"name":"Panes y Harinas","slug":"panes-y-harinas"},{"id":101,"name":"Pastas y arroces","slug":"pastas-y-arroces"},{"id":131,"name":"Patagonia","slug":"patagonia"},{"id":94,"name":"Pepinos","slug":"pepinos"},{"id":119,"name":"Pescados","slug":"pescados"},{"id":137,"name":"Pinot Noir","slug":"pinot-noir"},{"id":114,"name":"Pollo","slug":"pollo"},{"id":84,"name":"Proveeduría","slug":"proveeduria"},{"id":120,"name":"Quesos","slug":"quesos"},{"id":15,"name":"Rebozados","slug":"uncategorized"},{"id":89,"name":"Sal pimienta y especias","slug":"sal-pimienta-y-especias"},{"id":91,"name":"Salsas","slug":"salsas"},{"id":130,"name":"Selección Malbec","slug":"seleccion-malbec"},{"id":133,"name":"Selección Pinot","slug":"seleccion-pinot"},{"id":129,"name":"Seleccionados","slug":"seleccionados"},{"id":108,"name":"Sin alcohol","slug":"sin-alcohol"},{"id":102,"name":"Snacks","slug":"snacks"},{"id":115,"name":"Vacuna","slug":"vacuna"},{"id":125,"name":"Vinos","slug":"vinos"}]},"timestamp":1768774722779,"sessionId":"debug-session","hypothesisId":"B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:cats","message":"Categorias mapeadas","data":{"requested":["Quesos"],"foundCount":1,"found":[{"id":120,"name":"Quesos"}]},"timestamp":1768774722780,"sessionId":"debug-session","hypothesisId":"B,C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:payload","message":"Payload final","data":{"updatePayload":{"categories":[{"id":120}],"meta_data":[{"key":"_sell_unit_override","value":"kg"}]},"isEmpty":false},"timestamp":1768774722780,"sessionId":"debug-session","hypothesisId":"C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:entry","message":"Push entrada","data":{"wooProductId":"3256","categories":["Quesos"],"sellUnit":"kg"},"timestamp":1768774723054,"sessionId":"debug-session","hypothesisId":"A,B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:url","message":"URL construida","data":{"url":"https://piaf.floda.dev/wp-json/wc/v3/products/3256","base":"https://piaf.floda.dev/wp-json/wc/v3"},"timestamp":1768774723055,"sessionId":"debug-session","hypothesisId":"A"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:response","message":"Respuesta Woo","data":{"wooProductId":"3256","responseCategories":[{"id":120,"name":"Quesos"}],"status":"success"},"timestamp":1768774723469,"sessionId":"debug-session","hypothesisId":"D,E"}
|
||||||
|
{"location":"wooSnapshot.js:fetchWooCategoriesByNames","message":"Categorias en Woo","data":{"searchingFor":["Quesos"],"normalizedNames":["quesos"],"wooCategoriesCount":50,"wooCategories":[{"id":96,"name":"Aceites acetos y vinagres","slug":"aceites-acetos-y-vinagres"},{"id":99,"name":"Aceitesacetos y vinagres","slug":"aceitesacetos-y-vinagres"},{"id":95,"name":"Aceitunas","slug":"aceitunas"},{"id":110,"name":"Achuras","slug":"achuras"},{"id":85,"name":"Almacén","slug":"almacen"},{"id":87,"name":"Asado","slug":"asado"},{"id":97,"name":"Bebidas","slug":"bebidas"},{"id":138,"name":"Cabernet","slug":"cabernet"},{"id":134,"name":"Calchaquies","slug":"calchaquies"},{"id":109,"name":"Carnes","slug":"carnes"},{"id":111,"name":"Cerdo","slug":"cerdo"},{"id":126,"name":"Cerveza","slug":"cerveza"},{"id":98,"name":"Con alcohol","slug":"con-alcohol"},{"id":90,"name":"Condimentos","slug":"condimentos"},{"id":93,"name":"Conservas","slug":"conservas"},{"id":112,"name":"Cordero","slug":"cordero"},{"id":92,"name":"Delicatessen","slug":"delicatessen"},{"id":105,"name":"Dips","slug":"dips"},{"id":104,"name":"Dulces","slug":"dulces"},{"id":118,"name":"Embutidos","slug":"embutidos-rebozados"},{"id":113,"name":"Exóticas","slug":"exoticas"},{"id":117,"name":"Fiambreria y Charcuteria de autor","slug":"charcuteria"},{"id":121,"name":"Frutos secos","slug":"frutos-secos"},{"id":116,"name":"Hamburguesas","slug":"hamburguesas"},{"id":86,"name":"Huevos","slug":"huevos"},{"id":135,"name":"Low cost","slug":"low-cost"},{"id":127,"name":"Madurada","slug":"madurada"},{"id":136,"name":"Malbec","slug":"malbec"},{"id":132,"name":"Mendoza","slug":"mendoza"},{"id":103,"name":"Mermeladas","slug":"mermeladas"},{"id":100,"name":"Miel","slug":"miel"},{"id":88,"name":"Panes y Harinas","slug":"panes-y-harinas"},{"id":101,"name":"Pastas y arroces","slug":"pastas-y-arroces"},{"id":131,"name":"Patagonia","slug":"patagonia"},{"id":94,"name":"Pepinos","slug":"pepinos"},{"id":119,"name":"Pescados","slug":"pescados"},{"id":137,"name":"Pinot Noir","slug":"pinot-noir"},{"id":114,"name":"Pollo","slug":"pollo"},{"id":84,"name":"Proveeduría","slug":"proveeduria"},{"id":120,"name":"Quesos","slug":"quesos"},{"id":15,"name":"Rebozados","slug":"uncategorized"},{"id":89,"name":"Sal pimienta y especias","slug":"sal-pimienta-y-especias"},{"id":91,"name":"Salsas","slug":"salsas"},{"id":130,"name":"Selección Malbec","slug":"seleccion-malbec"},{"id":133,"name":"Selección Pinot","slug":"seleccion-pinot"},{"id":129,"name":"Seleccionados","slug":"seleccionados"},{"id":108,"name":"Sin alcohol","slug":"sin-alcohol"},{"id":102,"name":"Snacks","slug":"snacks"},{"id":115,"name":"Vacuna","slug":"vacuna"},{"id":125,"name":"Vinos","slug":"vinos"}]},"timestamp":1768774723821,"sessionId":"debug-session","hypothesisId":"B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:cats","message":"Categorias mapeadas","data":{"requested":["Quesos"],"foundCount":1,"found":[{"id":120,"name":"Quesos"}]},"timestamp":1768774723822,"sessionId":"debug-session","hypothesisId":"B,C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:payload","message":"Payload final","data":{"updatePayload":{"categories":[{"id":120}],"meta_data":[{"key":"_sell_unit_override","value":"kg"}]},"isEmpty":false},"timestamp":1768774723822,"sessionId":"debug-session","hypothesisId":"C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:response","message":"Respuesta Woo","data":{"wooProductId":"3256","responseCategories":[{"id":120,"name":"Quesos"}],"status":"success"},"timestamp":1768774724564,"sessionId":"debug-session","hypothesisId":"D,E"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:entry","message":"Push entrada","data":{"wooProductId":"3253","categories":["Quesos"],"sellUnit":"kg"},"timestamp":1768774728111,"sessionId":"debug-session","hypothesisId":"A,B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:url","message":"URL construida","data":{"url":"https://piaf.floda.dev/wp-json/wc/v3/products/3253","base":"https://piaf.floda.dev/wp-json/wc/v3"},"timestamp":1768774728113,"sessionId":"debug-session","hypothesisId":"A"}
|
||||||
|
{"location":"wooSnapshot.js:fetchWooCategoriesByNames","message":"Categorias en Woo","data":{"searchingFor":["Quesos"],"normalizedNames":["quesos"],"wooCategoriesCount":50,"wooCategories":[{"id":96,"name":"Aceites acetos y vinagres","slug":"aceites-acetos-y-vinagres"},{"id":99,"name":"Aceitesacetos y vinagres","slug":"aceitesacetos-y-vinagres"},{"id":95,"name":"Aceitunas","slug":"aceitunas"},{"id":110,"name":"Achuras","slug":"achuras"},{"id":85,"name":"Almacén","slug":"almacen"},{"id":87,"name":"Asado","slug":"asado"},{"id":97,"name":"Bebidas","slug":"bebidas"},{"id":138,"name":"Cabernet","slug":"cabernet"},{"id":134,"name":"Calchaquies","slug":"calchaquies"},{"id":109,"name":"Carnes","slug":"carnes"},{"id":111,"name":"Cerdo","slug":"cerdo"},{"id":126,"name":"Cerveza","slug":"cerveza"},{"id":98,"name":"Con alcohol","slug":"con-alcohol"},{"id":90,"name":"Condimentos","slug":"condimentos"},{"id":93,"name":"Conservas","slug":"conservas"},{"id":112,"name":"Cordero","slug":"cordero"},{"id":92,"name":"Delicatessen","slug":"delicatessen"},{"id":105,"name":"Dips","slug":"dips"},{"id":104,"name":"Dulces","slug":"dulces"},{"id":118,"name":"Embutidos","slug":"embutidos-rebozados"},{"id":113,"name":"Exóticas","slug":"exoticas"},{"id":117,"name":"Fiambreria y Charcuteria de autor","slug":"charcuteria"},{"id":121,"name":"Frutos secos","slug":"frutos-secos"},{"id":116,"name":"Hamburguesas","slug":"hamburguesas"},{"id":86,"name":"Huevos","slug":"huevos"},{"id":135,"name":"Low cost","slug":"low-cost"},{"id":127,"name":"Madurada","slug":"madurada"},{"id":136,"name":"Malbec","slug":"malbec"},{"id":132,"name":"Mendoza","slug":"mendoza"},{"id":103,"name":"Mermeladas","slug":"mermeladas"},{"id":100,"name":"Miel","slug":"miel"},{"id":88,"name":"Panes y Harinas","slug":"panes-y-harinas"},{"id":101,"name":"Pastas y arroces","slug":"pastas-y-arroces"},{"id":131,"name":"Patagonia","slug":"patagonia"},{"id":94,"name":"Pepinos","slug":"pepinos"},{"id":119,"name":"Pescados","slug":"pescados"},{"id":137,"name":"Pinot Noir","slug":"pinot-noir"},{"id":114,"name":"Pollo","slug":"pollo"},{"id":84,"name":"Proveeduría","slug":"proveeduria"},{"id":120,"name":"Quesos","slug":"quesos"},{"id":15,"name":"Rebozados","slug":"uncategorized"},{"id":89,"name":"Sal pimienta y especias","slug":"sal-pimienta-y-especias"},{"id":91,"name":"Salsas","slug":"salsas"},{"id":130,"name":"Selección Malbec","slug":"seleccion-malbec"},{"id":133,"name":"Selección Pinot","slug":"seleccion-pinot"},{"id":129,"name":"Seleccionados","slug":"seleccionados"},{"id":108,"name":"Sin alcohol","slug":"sin-alcohol"},{"id":102,"name":"Snacks","slug":"snacks"},{"id":115,"name":"Vacuna","slug":"vacuna"},{"id":125,"name":"Vinos","slug":"vinos"}]},"timestamp":1768774728877,"sessionId":"debug-session","hypothesisId":"B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:cats","message":"Categorias mapeadas","data":{"requested":["Quesos"],"foundCount":1,"found":[{"id":120,"name":"Quesos"}]},"timestamp":1768774728877,"sessionId":"debug-session","hypothesisId":"B,C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:payload","message":"Payload final","data":{"updatePayload":{"categories":[{"id":120}],"meta_data":[{"key":"_sell_unit_override","value":"kg"}]},"isEmpty":false},"timestamp":1768774728877,"sessionId":"debug-session","hypothesisId":"C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:response","message":"Respuesta Woo","data":{"wooProductId":"3253","responseCategories":[{"id":120,"name":"Quesos"}],"status":"success"},"timestamp":1768774729591,"sessionId":"debug-session","hypothesisId":"D,E"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:entry","message":"Push entrada","data":{"wooProductId":"3251","categories":["Fiambreria y Charcuteria de autor","Quesos"],"sellUnit":"kg"},"timestamp":1768774733211,"sessionId":"debug-session","hypothesisId":"A,B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:url","message":"URL construida","data":{"url":"https://piaf.floda.dev/wp-json/wc/v3/products/3251","base":"https://piaf.floda.dev/wp-json/wc/v3"},"timestamp":1768774733215,"sessionId":"debug-session","hypothesisId":"A"}
|
||||||
|
{"location":"wooSnapshot.js:fetchWooCategoriesByNames","message":"Categorias en Woo","data":{"searchingFor":["Fiambreria y Charcuteria de autor","Quesos"],"normalizedNames":["fiambreria y charcuteria de autor","quesos"],"wooCategoriesCount":50,"wooCategories":[{"id":96,"name":"Aceites acetos y vinagres","slug":"aceites-acetos-y-vinagres"},{"id":99,"name":"Aceitesacetos y vinagres","slug":"aceitesacetos-y-vinagres"},{"id":95,"name":"Aceitunas","slug":"aceitunas"},{"id":110,"name":"Achuras","slug":"achuras"},{"id":85,"name":"Almacén","slug":"almacen"},{"id":87,"name":"Asado","slug":"asado"},{"id":97,"name":"Bebidas","slug":"bebidas"},{"id":138,"name":"Cabernet","slug":"cabernet"},{"id":134,"name":"Calchaquies","slug":"calchaquies"},{"id":109,"name":"Carnes","slug":"carnes"},{"id":111,"name":"Cerdo","slug":"cerdo"},{"id":126,"name":"Cerveza","slug":"cerveza"},{"id":98,"name":"Con alcohol","slug":"con-alcohol"},{"id":90,"name":"Condimentos","slug":"condimentos"},{"id":93,"name":"Conservas","slug":"conservas"},{"id":112,"name":"Cordero","slug":"cordero"},{"id":92,"name":"Delicatessen","slug":"delicatessen"},{"id":105,"name":"Dips","slug":"dips"},{"id":104,"name":"Dulces","slug":"dulces"},{"id":118,"name":"Embutidos","slug":"embutidos-rebozados"},{"id":113,"name":"Exóticas","slug":"exoticas"},{"id":117,"name":"Fiambreria y Charcuteria de autor","slug":"charcuteria"},{"id":121,"name":"Frutos secos","slug":"frutos-secos"},{"id":116,"name":"Hamburguesas","slug":"hamburguesas"},{"id":86,"name":"Huevos","slug":"huevos"},{"id":135,"name":"Low cost","slug":"low-cost"},{"id":127,"name":"Madurada","slug":"madurada"},{"id":136,"name":"Malbec","slug":"malbec"},{"id":132,"name":"Mendoza","slug":"mendoza"},{"id":103,"name":"Mermeladas","slug":"mermeladas"},{"id":100,"name":"Miel","slug":"miel"},{"id":88,"name":"Panes y Harinas","slug":"panes-y-harinas"},{"id":101,"name":"Pastas y arroces","slug":"pastas-y-arroces"},{"id":131,"name":"Patagonia","slug":"patagonia"},{"id":94,"name":"Pepinos","slug":"pepinos"},{"id":119,"name":"Pescados","slug":"pescados"},{"id":137,"name":"Pinot Noir","slug":"pinot-noir"},{"id":114,"name":"Pollo","slug":"pollo"},{"id":84,"name":"Proveeduría","slug":"proveeduria"},{"id":120,"name":"Quesos","slug":"quesos"},{"id":15,"name":"Rebozados","slug":"uncategorized"},{"id":89,"name":"Sal pimienta y especias","slug":"sal-pimienta-y-especias"},{"id":91,"name":"Salsas","slug":"salsas"},{"id":130,"name":"Selección Malbec","slug":"seleccion-malbec"},{"id":133,"name":"Selección Pinot","slug":"seleccion-pinot"},{"id":129,"name":"Seleccionados","slug":"seleccionados"},{"id":108,"name":"Sin alcohol","slug":"sin-alcohol"},{"id":102,"name":"Snacks","slug":"snacks"},{"id":115,"name":"Vacuna","slug":"vacuna"},{"id":125,"name":"Vinos","slug":"vinos"}]},"timestamp":1768774733981,"sessionId":"debug-session","hypothesisId":"B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:cats","message":"Categorias mapeadas","data":{"requested":["Fiambreria y Charcuteria de autor","Quesos"],"foundCount":2,"found":[{"id":117,"name":"Fiambreria y Charcuteria de autor"},{"id":120,"name":"Quesos"}]},"timestamp":1768774733981,"sessionId":"debug-session","hypothesisId":"B,C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:payload","message":"Payload final","data":{"updatePayload":{"categories":[{"id":117},{"id":120}],"meta_data":[{"key":"_sell_unit_override","value":"kg"}]},"isEmpty":false},"timestamp":1768774733981,"sessionId":"debug-session","hypothesisId":"C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:response","message":"Respuesta Woo","data":{"wooProductId":"3251","responseCategories":[{"id":117,"name":"Fiambreria y Charcuteria de autor"},{"id":120,"name":"Quesos"}],"status":"success"},"timestamp":1768774734709,"sessionId":"debug-session","hypothesisId":"D,E"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:entry","message":"Push entrada","data":{"wooProductId":"3251","categories":["Fiambreria y Charcuteria de autor","Quesos"],"sellUnit":"kg"},"timestamp":1768774735556,"sessionId":"debug-session","hypothesisId":"A,B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:url","message":"URL construida","data":{"url":"https://piaf.floda.dev/wp-json/wc/v3/products/3251","base":"https://piaf.floda.dev/wp-json/wc/v3"},"timestamp":1768774735558,"sessionId":"debug-session","hypothesisId":"A"}
|
||||||
|
{"location":"wooSnapshot.js:fetchWooCategoriesByNames","message":"Categorias en Woo","data":{"searchingFor":["Fiambreria y Charcuteria de autor","Quesos"],"normalizedNames":["fiambreria y charcuteria de autor","quesos"],"wooCategoriesCount":50,"wooCategories":[{"id":96,"name":"Aceites acetos y vinagres","slug":"aceites-acetos-y-vinagres"},{"id":99,"name":"Aceitesacetos y vinagres","slug":"aceitesacetos-y-vinagres"},{"id":95,"name":"Aceitunas","slug":"aceitunas"},{"id":110,"name":"Achuras","slug":"achuras"},{"id":85,"name":"Almacén","slug":"almacen"},{"id":87,"name":"Asado","slug":"asado"},{"id":97,"name":"Bebidas","slug":"bebidas"},{"id":138,"name":"Cabernet","slug":"cabernet"},{"id":134,"name":"Calchaquies","slug":"calchaquies"},{"id":109,"name":"Carnes","slug":"carnes"},{"id":111,"name":"Cerdo","slug":"cerdo"},{"id":126,"name":"Cerveza","slug":"cerveza"},{"id":98,"name":"Con alcohol","slug":"con-alcohol"},{"id":90,"name":"Condimentos","slug":"condimentos"},{"id":93,"name":"Conservas","slug":"conservas"},{"id":112,"name":"Cordero","slug":"cordero"},{"id":92,"name":"Delicatessen","slug":"delicatessen"},{"id":105,"name":"Dips","slug":"dips"},{"id":104,"name":"Dulces","slug":"dulces"},{"id":118,"name":"Embutidos","slug":"embutidos-rebozados"},{"id":113,"name":"Exóticas","slug":"exoticas"},{"id":117,"name":"Fiambreria y Charcuteria de autor","slug":"charcuteria"},{"id":121,"name":"Frutos secos","slug":"frutos-secos"},{"id":116,"name":"Hamburguesas","slug":"hamburguesas"},{"id":86,"name":"Huevos","slug":"huevos"},{"id":135,"name":"Low cost","slug":"low-cost"},{"id":127,"name":"Madurada","slug":"madurada"},{"id":136,"name":"Malbec","slug":"malbec"},{"id":132,"name":"Mendoza","slug":"mendoza"},{"id":103,"name":"Mermeladas","slug":"mermeladas"},{"id":100,"name":"Miel","slug":"miel"},{"id":88,"name":"Panes y Harinas","slug":"panes-y-harinas"},{"id":101,"name":"Pastas y arroces","slug":"pastas-y-arroces"},{"id":131,"name":"Patagonia","slug":"patagonia"},{"id":94,"name":"Pepinos","slug":"pepinos"},{"id":119,"name":"Pescados","slug":"pescados"},{"id":137,"name":"Pinot Noir","slug":"pinot-noir"},{"id":114,"name":"Pollo","slug":"pollo"},{"id":84,"name":"Proveeduría","slug":"proveeduria"},{"id":120,"name":"Quesos","slug":"quesos"},{"id":15,"name":"Rebozados","slug":"uncategorized"},{"id":89,"name":"Sal pimienta y especias","slug":"sal-pimienta-y-especias"},{"id":91,"name":"Salsas","slug":"salsas"},{"id":130,"name":"Selección Malbec","slug":"seleccion-malbec"},{"id":133,"name":"Selección Pinot","slug":"seleccion-pinot"},{"id":129,"name":"Seleccionados","slug":"seleccionados"},{"id":108,"name":"Sin alcohol","slug":"sin-alcohol"},{"id":102,"name":"Snacks","slug":"snacks"},{"id":115,"name":"Vacuna","slug":"vacuna"},{"id":125,"name":"Vinos","slug":"vinos"}]},"timestamp":1768774736395,"sessionId":"debug-session","hypothesisId":"B"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:cats","message":"Categorias mapeadas","data":{"requested":["Fiambreria y Charcuteria de autor","Quesos"],"foundCount":2,"found":[{"id":117,"name":"Fiambreria y Charcuteria de autor"},{"id":120,"name":"Quesos"}]},"timestamp":1768774736395,"sessionId":"debug-session","hypothesisId":"B,C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:payload","message":"Payload final","data":{"updatePayload":{"categories":[{"id":117},{"id":120}],"meta_data":[{"key":"_sell_unit_override","value":"kg"}]},"isEmpty":false},"timestamp":1768774736395,"sessionId":"debug-session","hypothesisId":"C"}
|
||||||
|
{"location":"wooSnapshot.js:pushProductToWoo:response","message":"Respuesta Woo","data":{"wooProductId":"3251","responseCategories":[{"id":117,"name":"Fiambreria y Charcuteria de autor"},{"id":120,"name":"Quesos"}],"status":"success"},"timestamp":1768774737141,"sessionId":"debug-session","hypothesisId":"D,E"}
|
||||||
@@ -8,6 +8,8 @@ import "./components/products-crud.js";
|
|||||||
import "./components/aliases-crud.js";
|
import "./components/aliases-crud.js";
|
||||||
import "./components/recommendations-crud.js";
|
import "./components/recommendations-crud.js";
|
||||||
import "./components/quantities-crud.js";
|
import "./components/quantities-crud.js";
|
||||||
|
import "./components/orders-crud.js";
|
||||||
|
import "./components/test-panel.js";
|
||||||
import { connectSSE } from "./lib/sse.js";
|
import { connectSSE } from "./lib/sse.js";
|
||||||
|
|
||||||
connectSSE();
|
connectSSE();
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ class OpsShell extends HTMLElement {
|
|||||||
<button class="nav-btn" data-view="aliases">Equivalencias</button>
|
<button class="nav-btn" data-view="aliases">Equivalencias</button>
|
||||||
<button class="nav-btn" data-view="crosssell">Cross-sell</button>
|
<button class="nav-btn" data-view="crosssell">Cross-sell</button>
|
||||||
<button class="nav-btn" data-view="quantities">Cantidades</button>
|
<button class="nav-btn" data-view="quantities">Cantidades</button>
|
||||||
|
<button class="nav-btn" data-view="orders">Pedidos</button>
|
||||||
|
<button class="nav-btn" data-view="test">Test</button>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<div class="status" id="sseStatus">SSE: connecting…</div>
|
<div class="status" id="sseStatus">SSE: connecting…</div>
|
||||||
@@ -93,6 +95,18 @@ class OpsShell extends HTMLElement {
|
|||||||
<quantities-crud></quantities-crud>
|
<quantities-crud></quantities-crud>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="viewOrders" class="view">
|
||||||
|
<div class="layout-crud">
|
||||||
|
<orders-crud></orders-crud>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="viewTest" class="view">
|
||||||
|
<div class="layout-crud">
|
||||||
|
<test-panel></test-panel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
468
public/components/orders-crud.js
Normal file
468
public/components/orders-crud.js
Normal file
@@ -0,0 +1,468 @@
|
|||||||
|
import { api } from "../lib/api.js";
|
||||||
|
|
||||||
|
function formatDate(dateStr) {
|
||||||
|
if (!dateStr) return "—";
|
||||||
|
const d = new Date(dateStr);
|
||||||
|
return d.toLocaleString("es-AR", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusLabel(status) {
|
||||||
|
const map = {
|
||||||
|
pending: "Pendiente",
|
||||||
|
processing: "Procesando",
|
||||||
|
"on-hold": "En espera",
|
||||||
|
completed: "Completado",
|
||||||
|
cancelled: "Cancelado",
|
||||||
|
refunded: "Reembolsado",
|
||||||
|
failed: "Fallido",
|
||||||
|
};
|
||||||
|
return map[status] || status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusColor(status) {
|
||||||
|
const map = {
|
||||||
|
pending: "#f59e0b",
|
||||||
|
processing: "#3b82f6",
|
||||||
|
"on-hold": "#8b5cf6",
|
||||||
|
completed: "#22c55e",
|
||||||
|
cancelled: "#6b7280",
|
||||||
|
refunded: "#ec4899",
|
||||||
|
failed: "#ef4444",
|
||||||
|
};
|
||||||
|
return map[status] || "#8aa0b5";
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrdersCrud extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.attachShadow({ mode: "open" });
|
||||||
|
this.orders = [];
|
||||||
|
this.selectedOrder = null;
|
||||||
|
this.loading = false;
|
||||||
|
|
||||||
|
this.shadowRoot.innerHTML = `
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
--bg: #0b0f14;
|
||||||
|
--panel: #121823;
|
||||||
|
--muted: #8aa0b5;
|
||||||
|
--text: #e7eef7;
|
||||||
|
--line: #1e2a3a;
|
||||||
|
--blue: #1f6feb;
|
||||||
|
--green: #238636;
|
||||||
|
--red: #da3633;
|
||||||
|
--orange: #f59e0b;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; font-family: system-ui, Segoe UI, Roboto, Arial; }
|
||||||
|
.container {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 400px;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.panel {
|
||||||
|
background: var(--panel);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.panel-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
padding-bottom: 8px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background: var(--blue);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
button:hover { opacity: 0.9; }
|
||||||
|
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
|
button.secondary {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
button.secondary:hover { border-color: var(--blue); color: var(--text); }
|
||||||
|
button.small { padding: 4px 8px; font-size: 11px; }
|
||||||
|
.empty { color: var(--muted); font-size: 12px; text-align: center; padding: 40px; }
|
||||||
|
|
||||||
|
/* Orders table */
|
||||||
|
.orders-table {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px 8px;
|
||||||
|
border-bottom: 2px solid var(--line);
|
||||||
|
color: var(--muted);
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 10px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: var(--panel);
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding: 10px 8px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
tr { cursor: pointer; transition: background 0.15s; }
|
||||||
|
tr:hover { background: rgba(31,111,235,0.1); }
|
||||||
|
tr.selected { background: rgba(31,111,235,0.2); }
|
||||||
|
|
||||||
|
.order-id { font-weight: 700; }
|
||||||
|
.badges { display: flex; gap: 4px; flex-wrap: wrap; }
|
||||||
|
.badge {
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.badge.test { background: var(--orange); color: #000; }
|
||||||
|
.badge.real { background: var(--green); color: #fff; }
|
||||||
|
.badge.whatsapp { background: #25d366; color: #fff; }
|
||||||
|
.badge.web { background: var(--muted); color: #000; }
|
||||||
|
.status-badge {
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.total { font-weight: 600; }
|
||||||
|
.customer-name {
|
||||||
|
max-width: 150px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Detail panel */
|
||||||
|
.detail-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.detail-title {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--blue);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.detail-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 6px 0;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.detail-row:last-child { border-bottom: none; }
|
||||||
|
.detail-label { color: var(--muted); }
|
||||||
|
.detail-value { font-weight: 500; }
|
||||||
|
.items-list {
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.item-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 6px 0;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.item-row:last-child { border-bottom: none; }
|
||||||
|
.item-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.item-qty { color: var(--muted); margin: 0 8px; }
|
||||||
|
.item-total { font-weight: 600; }
|
||||||
|
.detail-empty {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-title">
|
||||||
|
<span>Pedidos de WooCommerce</span>
|
||||||
|
<button id="btnRefresh" class="secondary small">Actualizar</button>
|
||||||
|
</div>
|
||||||
|
<div class="orders-table" id="ordersTable">
|
||||||
|
<div class="empty">Cargando pedidos...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-title">Detalle del Pedido</div>
|
||||||
|
<div id="orderDetail">
|
||||||
|
<div class="detail-empty">Seleccioná un pedido para ver los detalles</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.shadowRoot.getElementById("btnRefresh").onclick = () => this.loadOrders();
|
||||||
|
this.loadOrders();
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadOrders() {
|
||||||
|
const container = this.shadowRoot.getElementById("ordersTable");
|
||||||
|
container.innerHTML = `<div class="empty">Cargando pedidos...</div>`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await api.listRecentOrders({ limit: 50 });
|
||||||
|
this.orders = result.items || [];
|
||||||
|
this.renderTable();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[orders-crud] Error loading orders:", e);
|
||||||
|
container.innerHTML = `<div class="empty">Error cargando pedidos: ${e.message}</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTable() {
|
||||||
|
const container = this.shadowRoot.getElementById("ordersTable");
|
||||||
|
|
||||||
|
if (this.orders.length === 0) {
|
||||||
|
container.innerHTML = `<div class="empty">No hay pedidos</div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Tipo</th>
|
||||||
|
<th>Estado</th>
|
||||||
|
<th>Envío</th>
|
||||||
|
<th>Pago</th>
|
||||||
|
<th>Cliente</th>
|
||||||
|
<th>Total</th>
|
||||||
|
<th>Fecha</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${this.orders.map(order => {
|
||||||
|
const isSelected = this.selectedOrder?.id === order.id;
|
||||||
|
const customerName = [order.billing.first_name, order.billing.last_name].filter(Boolean).join(" ") || order.billing.phone || "—";
|
||||||
|
|
||||||
|
return `
|
||||||
|
<tr class="${isSelected ? "selected" : ""}" data-order-id="${order.id}">
|
||||||
|
<td class="order-id">${order.id}</td>
|
||||||
|
<td>
|
||||||
|
<div class="badges">
|
||||||
|
${order.is_test ? '<span class="badge test">TEST</span>' : '<span class="badge real">REAL</span>'}
|
||||||
|
${order.source === "whatsapp" ? '<span class="badge whatsapp">WA</span>' : '<span class="badge web">WEB</span>'}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td><span class="status-badge" style="background:${statusColor(order.status)}">${statusLabel(order.status)}</span></td>
|
||||||
|
<td><span class="badge" style="background:${order.is_delivery ? '#3b82f6' : '#8b5cf6'};color:#fff">${order.is_delivery ? 'DEL' : 'RET'}</span></td>
|
||||||
|
<td>
|
||||||
|
<div class="badges">
|
||||||
|
<span class="badge" style="background:${order.is_cash ? '#f59e0b' : '#1f6feb'};color:${order.is_cash ? '#000' : '#fff'}">${order.is_cash ? '$' : 'MP'}</span>
|
||||||
|
<span class="badge" style="background:${order.is_paid ? '#22c55e' : '#ef4444'};color:#fff">${order.is_paid ? '✓' : '✗'}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="customer-name" title="${customerName}">${customerName}</td>
|
||||||
|
<td class="total">$${Number(order.total || 0).toLocaleString("es-AR")}</td>
|
||||||
|
<td>${formatDate(order.date_created)}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}).join("")}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.querySelectorAll("tr[data-order-id]").forEach(row => {
|
||||||
|
row.onclick = () => {
|
||||||
|
const orderId = parseInt(row.dataset.orderId);
|
||||||
|
const order = this.orders.find(o => o.id === orderId);
|
||||||
|
if (order) {
|
||||||
|
this.selectOrder(order);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectOrder(order) {
|
||||||
|
this.selectedOrder = order;
|
||||||
|
this.renderTable();
|
||||||
|
this.renderDetail();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDetail() {
|
||||||
|
const container = this.shadowRoot.getElementById("orderDetail");
|
||||||
|
|
||||||
|
if (!this.selectedOrder) {
|
||||||
|
container.innerHTML = `<div class="detail-empty">Seleccioná un pedido para ver los detalles</div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = this.selectedOrder;
|
||||||
|
const customerName = [order.billing.first_name, order.billing.last_name].filter(Boolean).join(" ") || "—";
|
||||||
|
|
||||||
|
// Construir dirección de envío
|
||||||
|
const shippingAddr = [
|
||||||
|
order.shipping?.address_1,
|
||||||
|
order.shipping?.address_2,
|
||||||
|
order.shipping?.city,
|
||||||
|
order.shipping?.state,
|
||||||
|
order.shipping?.postcode
|
||||||
|
].filter(Boolean).join(", ");
|
||||||
|
|
||||||
|
const billingAddr = [
|
||||||
|
order.billing?.address_1,
|
||||||
|
order.billing?.address_2,
|
||||||
|
order.billing?.city,
|
||||||
|
order.billing?.state,
|
||||||
|
order.billing?.postcode
|
||||||
|
].filter(Boolean).join(", ");
|
||||||
|
|
||||||
|
const address = shippingAddr || billingAddr || "—";
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-title">Información General</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Pedido #</span>
|
||||||
|
<span class="detail-value">${order.id}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Estado</span>
|
||||||
|
<span class="status-badge" style="background:${statusColor(order.status)}">${statusLabel(order.status)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Tipo</span>
|
||||||
|
<span class="detail-value">${order.is_test ? "Test" : "Real"} • ${order.source === "whatsapp" ? "WhatsApp" : "Web"}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Fecha</span>
|
||||||
|
<span class="detail-value">${formatDate(order.date_created)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Total</span>
|
||||||
|
<span class="detail-value" style="font-size:16px;color:var(--green)">$${Number(order.total || 0).toLocaleString("es-AR")} ${order.currency || ""}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-title">Envío</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Método</span>
|
||||||
|
<span class="detail-value">
|
||||||
|
<span class="badge ${order.is_delivery ? 'delivery' : 'pickup'}" style="background:${order.is_delivery ? '#3b82f6' : '#8b5cf6'};color:#fff;padding:3px 8px;border-radius:4px;font-size:10px;">
|
||||||
|
${order.is_delivery ? 'DELIVERY' : 'RETIRO'}
|
||||||
|
</span>
|
||||||
|
${order.shipping_method ? `<span style="margin-left:8px;color:var(--muted);font-size:11px;">${order.shipping_method}</span>` : ''}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
${order.is_delivery && address !== "—" ? `
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Dirección</span>
|
||||||
|
<span class="detail-value" style="font-size:11px;">${address}</span>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-title">Pago</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Método</span>
|
||||||
|
<span class="detail-value">
|
||||||
|
<span class="badge" style="background:${order.is_cash ? '#f59e0b' : '#1f6feb'};color:${order.is_cash ? '#000' : '#fff'};padding:3px 8px;border-radius:4px;font-size:10px;">
|
||||||
|
${order.is_cash ? 'EFECTIVO' : 'LINK'}
|
||||||
|
</span>
|
||||||
|
${order.payment_method_title ? `<span style="margin-left:8px;color:var(--muted);font-size:11px;">${order.payment_method_title}</span>` : ''}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Estado</span>
|
||||||
|
<span class="detail-value">
|
||||||
|
<span class="badge" style="background:${order.is_paid ? '#22c55e' : '#ef4444'};color:#fff;padding:3px 8px;border-radius:4px;font-size:10px;">
|
||||||
|
${order.is_paid ? 'PAGADO' : 'PENDIENTE'}
|
||||||
|
</span>
|
||||||
|
${order.date_paid ? `<span style="margin-left:8px;color:var(--muted);font-size:11px;">${formatDate(order.date_paid)}</span>` : ''}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-title">Cliente</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Nombre</span>
|
||||||
|
<span class="detail-value">${customerName}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Teléfono</span>
|
||||||
|
<span class="detail-value">${order.billing.phone || "—"}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Email</span>
|
||||||
|
<span class="detail-value">${order.billing.email || "—"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-title">Productos (${order.line_items.length})</div>
|
||||||
|
<div class="items-list">
|
||||||
|
${order.line_items.length === 0 ? '<div style="color:var(--muted);text-align:center;padding:20px;">Sin productos</div>' :
|
||||||
|
order.line_items.map(item => `
|
||||||
|
<div class="item-row">
|
||||||
|
<span class="item-name" title="${item.name}">${item.name}</span>
|
||||||
|
<span class="item-qty">x${item.quantity}</span>
|
||||||
|
<span class="item-total">$${Number(item.total || 0).toLocaleString("es-AR")}</span>
|
||||||
|
</div>
|
||||||
|
`).join("")
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${order.run_id ? `
|
||||||
|
<div class="detail-section">
|
||||||
|
<div class="detail-title">Metadata</div>
|
||||||
|
<div class="detail-row">
|
||||||
|
<span class="detail-label">Run ID</span>
|
||||||
|
<span class="detail-value" style="font-family:monospace;font-size:10px;">${order.run_id}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : ""}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("orders-crud", OrdersCrud);
|
||||||
533
public/components/test-panel.js
Normal file
533
public/components/test-panel.js
Normal file
@@ -0,0 +1,533 @@
|
|||||||
|
import { api } from "../lib/api.js";
|
||||||
|
|
||||||
|
// Datos aleatorios para generar usuarios de prueba
|
||||||
|
const NOMBRES = ["Juan", "María", "Carlos", "Ana", "Pedro", "Laura", "Diego", "Sofía"];
|
||||||
|
const APELLIDOS = ["García", "Rodríguez", "Martínez", "López", "González", "Fernández", "Pérez"];
|
||||||
|
const CALLES = ["Av. Corrientes", "Av. Santa Fe", "Calle Florida", "Av. Rivadavia", "Av. Cabildo", "Av. Libertador"];
|
||||||
|
|
||||||
|
function randomItem(arr) {
|
||||||
|
return arr[Math.floor(Math.random() * arr.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomInt(min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTestUser() {
|
||||||
|
const randomPhone = `549${randomInt(1000000000, 9999999999)}`;
|
||||||
|
const wa_chat_id = `${randomPhone}@s.whatsapp.net`;
|
||||||
|
const nombre = randomItem(NOMBRES);
|
||||||
|
const apellido = randomItem(APELLIDOS);
|
||||||
|
|
||||||
|
return {
|
||||||
|
wa_chat_id,
|
||||||
|
phone: randomPhone,
|
||||||
|
name: `${nombre} ${apellido}`,
|
||||||
|
address: {
|
||||||
|
first_name: nombre,
|
||||||
|
last_name: apellido,
|
||||||
|
address_1: `${randomItem(CALLES)} ${randomInt(100, 9000)}`,
|
||||||
|
city: "CABA",
|
||||||
|
state: "Buenos Aires",
|
||||||
|
postcode: `${randomInt(1000, 1999)}`,
|
||||||
|
country: "AR",
|
||||||
|
phone: randomPhone,
|
||||||
|
email: `${randomPhone}@no-email.local`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestPanel extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.attachShadow({ mode: "open" });
|
||||||
|
this.products = [];
|
||||||
|
this.selectedProducts = [];
|
||||||
|
this.testUser = null;
|
||||||
|
this.lastOrder = null;
|
||||||
|
this.lastPaymentLink = null;
|
||||||
|
this.loading = false;
|
||||||
|
|
||||||
|
this.shadowRoot.innerHTML = `
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
--bg: #0b0f14;
|
||||||
|
--panel: #121823;
|
||||||
|
--muted: #8aa0b5;
|
||||||
|
--text: #e7eef7;
|
||||||
|
--line: #1e2a3a;
|
||||||
|
--blue: #1f6feb;
|
||||||
|
--green: #238636;
|
||||||
|
--red: #da3633;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; font-family: system-ui, Segoe UI, Roboto, Arial; }
|
||||||
|
.container {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.panel {
|
||||||
|
background: var(--panel);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.panel-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background: var(--blue);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
button:hover { opacity: 0.9; }
|
||||||
|
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
|
button.secondary {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
button.secondary:hover { border-color: var(--blue); color: var(--text); }
|
||||||
|
button.success { background: var(--green); }
|
||||||
|
input, select {
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
color: var(--text);
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
input:focus, select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--blue);
|
||||||
|
}
|
||||||
|
.product-list {
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.product-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 80px 60px 30px;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.product-item:last-child { border-bottom: none; }
|
||||||
|
.product-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.product-qty { text-align: right; }
|
||||||
|
.product-unit { color: var(--muted); }
|
||||||
|
.remove-btn {
|
||||||
|
background: var(--red);
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
.user-info {
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.user-info div { margin-bottom: 4px; }
|
||||||
|
.user-info span { color: var(--muted); }
|
||||||
|
.result {
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.result.success { border-color: var(--green); }
|
||||||
|
.result.error { border-color: var(--red); }
|
||||||
|
.result-label { color: var(--muted); font-size: 10px; text-transform: uppercase; }
|
||||||
|
.result-value { font-weight: 600; margin-top: 4px; }
|
||||||
|
.result-value.big { font-size: 18px; }
|
||||||
|
.result-link {
|
||||||
|
color: var(--blue);
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.row { display: flex; gap: 8px; align-items: center; }
|
||||||
|
.flex-1 { flex: 1; }
|
||||||
|
.loading { opacity: 0.5; pointer-events: none; }
|
||||||
|
.empty { color: var(--muted); font-size: 12px; text-align: center; padding: 20px; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-title">1. Generar Orden de Prueba</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="row">
|
||||||
|
<button id="btnGenerate">Generar Orden Aleatoria</button>
|
||||||
|
<button id="btnClear" class="secondary">Limpiar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Productos seleccionados</div>
|
||||||
|
<div class="product-list" id="productList">
|
||||||
|
<div class="empty">Click "Generar Orden Aleatoria" para comenzar</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Datos del usuario</div>
|
||||||
|
<div class="user-info" id="userInfo">
|
||||||
|
<div class="empty">Se generarán automáticamente</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<button id="btnCreateOrder" class="success" disabled>Crear Orden en WooCommerce</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" id="orderResult" style="display:none;">
|
||||||
|
<div class="result success">
|
||||||
|
<div class="result-label">Orden creada</div>
|
||||||
|
<div class="result-value big" id="orderIdValue">—</div>
|
||||||
|
<div style="margin-top:8px;">
|
||||||
|
<span class="result-label">Total:</span>
|
||||||
|
<span id="orderTotalValue">—</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-title">2. Link de Pago (MercadoPago)</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Monto</div>
|
||||||
|
<div class="row">
|
||||||
|
<input type="number" id="inputAmount" placeholder="Monto en ARS" class="flex-1" />
|
||||||
|
<button id="btnPaymentLink" disabled>Generar Link de Pago</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" id="paymentResult" style="display:none;">
|
||||||
|
<div class="result success">
|
||||||
|
<div class="result-label">Link de pago</div>
|
||||||
|
<a class="result-link" id="paymentLinkValue" href="#" target="_blank">—</a>
|
||||||
|
<div style="margin-top:8px;">
|
||||||
|
<span class="result-label">Preference ID:</span>
|
||||||
|
<span id="preferenceIdValue">—</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-title" style="margin-top:24px;">3. Simular Pago Exitoso</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<p style="font-size:12px;color:var(--muted);margin:0;">
|
||||||
|
Simula el webhook de MercadoPago con status "approved".
|
||||||
|
Esto actualiza la orden en WooCommerce a "processing".
|
||||||
|
</p>
|
||||||
|
<button id="btnSimulateWebhook" disabled>Simular Pago Exitoso</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section" id="webhookResult" style="display:none;">
|
||||||
|
<div class="result success">
|
||||||
|
<div class="result-label">Pago simulado</div>
|
||||||
|
<div class="result-value" id="webhookStatusValue">—</div>
|
||||||
|
<div style="margin-top:8px;">
|
||||||
|
<span class="result-label">Orden status:</span>
|
||||||
|
<span id="webhookOrderStatusValue">—</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.shadowRoot.getElementById("btnGenerate").onclick = () => this.generateRandomOrder();
|
||||||
|
this.shadowRoot.getElementById("btnClear").onclick = () => this.clearAll();
|
||||||
|
this.shadowRoot.getElementById("btnCreateOrder").onclick = () => this.createOrder();
|
||||||
|
this.shadowRoot.getElementById("btnPaymentLink").onclick = () => this.createPaymentLink();
|
||||||
|
this.shadowRoot.getElementById("btnSimulateWebhook").onclick = () => this.simulateWebhook();
|
||||||
|
|
||||||
|
this.loadProducts();
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadProducts() {
|
||||||
|
try {
|
||||||
|
const result = await api.getProductsWithStock();
|
||||||
|
this.products = result.items || [];
|
||||||
|
console.log(`[test-panel] Loaded ${this.products.length} products with stock`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[test-panel] Error loading products:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateRandomOrder() {
|
||||||
|
if (this.products.length === 0) {
|
||||||
|
await this.loadProducts();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.products.length === 0) {
|
||||||
|
alert("No hay productos con stock disponible");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generar usuario aleatorio
|
||||||
|
this.testUser = generateTestUser();
|
||||||
|
|
||||||
|
// Seleccionar 1-3 productos aleatorios
|
||||||
|
const numProducts = randomInt(1, Math.min(3, this.products.length));
|
||||||
|
const shuffled = [...this.products].sort(() => Math.random() - 0.5);
|
||||||
|
this.selectedProducts = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < numProducts; i++) {
|
||||||
|
const product = shuffled[i];
|
||||||
|
const isKg = product.sell_unit === "kg";
|
||||||
|
const quantity = isKg ? randomInt(200, 2000) : randomInt(1, 5);
|
||||||
|
|
||||||
|
this.selectedProducts.push({
|
||||||
|
product_id: product.woo_product_id,
|
||||||
|
name: product.name,
|
||||||
|
quantity,
|
||||||
|
unit: isKg ? "kg" : "unit",
|
||||||
|
price: product.price,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderProductList();
|
||||||
|
this.renderUserInfo();
|
||||||
|
this.updateButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderProductList() {
|
||||||
|
const container = this.shadowRoot.getElementById("productList");
|
||||||
|
|
||||||
|
if (this.selectedProducts.length === 0) {
|
||||||
|
container.innerHTML = `<div class="empty">Click "Generar Orden Aleatoria" para comenzar</div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = this.selectedProducts.map((p, i) => `
|
||||||
|
<div class="product-item">
|
||||||
|
<div class="product-name" title="${p.name}">${p.name}</div>
|
||||||
|
<div class="product-qty">${p.unit === "kg" ? `${p.quantity}g` : `${p.quantity}u`}</div>
|
||||||
|
<div class="product-unit">$${Number(p.price || 0).toFixed(0)}</div>
|
||||||
|
<button class="remove-btn" data-index="${i}">X</button>
|
||||||
|
</div>
|
||||||
|
`).join("");
|
||||||
|
|
||||||
|
container.querySelectorAll(".remove-btn").forEach(btn => {
|
||||||
|
btn.onclick = (e) => {
|
||||||
|
const idx = parseInt(e.target.dataset.index);
|
||||||
|
this.selectedProducts.splice(idx, 1);
|
||||||
|
this.renderProductList();
|
||||||
|
this.updateButtonStates();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderUserInfo() {
|
||||||
|
const container = this.shadowRoot.getElementById("userInfo");
|
||||||
|
|
||||||
|
if (!this.testUser) {
|
||||||
|
container.innerHTML = `<div class="empty">Se generarán automáticamente</div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addr = this.testUser.address;
|
||||||
|
container.innerHTML = `
|
||||||
|
<div><span>Nombre:</span> ${addr.first_name} ${addr.last_name}</div>
|
||||||
|
<div><span>Dirección:</span> ${addr.address_1}</div>
|
||||||
|
<div><span>Ciudad:</span> ${addr.city}, ${addr.state}</div>
|
||||||
|
<div><span>Teléfono:</span> ${addr.phone}</div>
|
||||||
|
<div><span>Email:</span> ${addr.email}</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateButtonStates() {
|
||||||
|
const hasProducts = this.selectedProducts.length > 0;
|
||||||
|
const hasOrder = this.lastOrder?.woo_order_id;
|
||||||
|
const hasPaymentLink = this.lastPaymentLink?.init_point;
|
||||||
|
|
||||||
|
this.shadowRoot.getElementById("btnCreateOrder").disabled = !hasProducts;
|
||||||
|
this.shadowRoot.getElementById("btnPaymentLink").disabled = !hasOrder;
|
||||||
|
this.shadowRoot.getElementById("btnSimulateWebhook").disabled = !hasOrder;
|
||||||
|
|
||||||
|
if (hasOrder) {
|
||||||
|
this.shadowRoot.getElementById("inputAmount").value = this.lastOrder.total || "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOrder() {
|
||||||
|
if (this.loading) return;
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
const btn = this.shadowRoot.getElementById("btnCreateOrder");
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = "Creando...";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const basket = {
|
||||||
|
items: this.selectedProducts.map(p => ({
|
||||||
|
product_id: p.product_id,
|
||||||
|
quantity: p.quantity,
|
||||||
|
unit: p.unit,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await api.createTestOrder({
|
||||||
|
basket,
|
||||||
|
address: this.testUser?.address || null,
|
||||||
|
wa_chat_id: this.testUser?.wa_chat_id || null,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.ok) {
|
||||||
|
this.lastOrder = result;
|
||||||
|
this.shadowRoot.getElementById("orderIdValue").textContent = `#${result.woo_order_id}`;
|
||||||
|
this.shadowRoot.getElementById("orderTotalValue").textContent = `$${Number(result.total || 0).toFixed(2)}`;
|
||||||
|
this.shadowRoot.getElementById("orderResult").style.display = "block";
|
||||||
|
this.shadowRoot.getElementById("inputAmount").value = result.total || "";
|
||||||
|
} else {
|
||||||
|
alert("Error: " + (result.error || "Error desconocido"));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[test-panel] createOrder error:", e);
|
||||||
|
alert("Error creando orden: " + e.message);
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
btn.textContent = "Crear Orden en WooCommerce";
|
||||||
|
this.updateButtonStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPaymentLink() {
|
||||||
|
if (this.loading) return;
|
||||||
|
if (!this.lastOrder?.woo_order_id) {
|
||||||
|
alert("Primero creá una orden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = parseFloat(this.shadowRoot.getElementById("inputAmount").value);
|
||||||
|
if (!amount || amount <= 0) {
|
||||||
|
alert("Ingresá un monto válido");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
const btn = this.shadowRoot.getElementById("btnPaymentLink");
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = "Generando...";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await api.createPaymentLink({
|
||||||
|
woo_order_id: this.lastOrder.woo_order_id,
|
||||||
|
amount,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.ok) {
|
||||||
|
this.lastPaymentLink = result;
|
||||||
|
const linkEl = this.shadowRoot.getElementById("paymentLinkValue");
|
||||||
|
linkEl.href = result.init_point || result.sandbox_init_point || "#";
|
||||||
|
linkEl.textContent = result.init_point || result.sandbox_init_point || "—";
|
||||||
|
this.shadowRoot.getElementById("preferenceIdValue").textContent = result.preference_id || "—";
|
||||||
|
this.shadowRoot.getElementById("paymentResult").style.display = "block";
|
||||||
|
} else {
|
||||||
|
alert("Error: " + (result.error || "Error desconocido"));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[test-panel] createPaymentLink error:", e);
|
||||||
|
alert("Error generando link: " + e.message);
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
btn.textContent = "Generar Link de Pago";
|
||||||
|
this.updateButtonStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async simulateWebhook() {
|
||||||
|
if (this.loading) return;
|
||||||
|
if (!this.lastOrder?.woo_order_id) {
|
||||||
|
alert("Primero creá una orden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
const btn = this.shadowRoot.getElementById("btnSimulateWebhook");
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = "Simulando...";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const amount = parseFloat(this.shadowRoot.getElementById("inputAmount").value) || this.lastOrder.total || 0;
|
||||||
|
|
||||||
|
const result = await api.simulateMpWebhook({
|
||||||
|
woo_order_id: this.lastOrder.woo_order_id,
|
||||||
|
amount,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.ok) {
|
||||||
|
this.shadowRoot.getElementById("webhookStatusValue").textContent = `Payment ${result.payment_id} - ${result.status}`;
|
||||||
|
this.shadowRoot.getElementById("webhookOrderStatusValue").textContent = result.order_status || "processing";
|
||||||
|
this.shadowRoot.getElementById("webhookResult").style.display = "block";
|
||||||
|
} else {
|
||||||
|
alert("Error: " + (result.error || "Error desconocido"));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[test-panel] simulateWebhook error:", e);
|
||||||
|
alert("Error simulando webhook: " + e.message);
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
btn.textContent = "Simular Pago Exitoso";
|
||||||
|
this.updateButtonStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAll() {
|
||||||
|
this.selectedProducts = [];
|
||||||
|
this.testUser = null;
|
||||||
|
this.lastOrder = null;
|
||||||
|
this.lastPaymentLink = null;
|
||||||
|
|
||||||
|
this.renderProductList();
|
||||||
|
this.renderUserInfo();
|
||||||
|
this.updateButtonStates();
|
||||||
|
|
||||||
|
this.shadowRoot.getElementById("orderResult").style.display = "none";
|
||||||
|
this.shadowRoot.getElementById("paymentResult").style.display = "none";
|
||||||
|
this.shadowRoot.getElementById("webhookResult").style.display = "none";
|
||||||
|
this.shadowRoot.getElementById("inputAmount").value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("test-panel", TestPanel);
|
||||||
@@ -184,4 +184,39 @@ export const api = {
|
|||||||
async syncFromWoo() {
|
async syncFromWoo() {
|
||||||
return fetch("/products/sync-from-woo", { method: "POST" }).then(r => r.json());
|
return fetch("/products/sync-from-woo", { method: "POST" }).then(r => r.json());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// --- Testing ---
|
||||||
|
async listRecentOrders({ limit = 20 } = {}) {
|
||||||
|
const u = new URL("/test/orders", location.origin);
|
||||||
|
u.searchParams.set("limit", String(limit));
|
||||||
|
return fetch(u).then(r => r.json());
|
||||||
|
},
|
||||||
|
|
||||||
|
async getProductsWithStock() {
|
||||||
|
return fetch("/test/products-with-stock").then(r => r.json());
|
||||||
|
},
|
||||||
|
|
||||||
|
async createTestOrder({ basket, address, wa_chat_id }) {
|
||||||
|
return fetch("/test/order", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ basket, address, wa_chat_id }),
|
||||||
|
}).then(r => r.json());
|
||||||
|
},
|
||||||
|
|
||||||
|
async createPaymentLink({ woo_order_id, amount }) {
|
||||||
|
return fetch("/test/payment-link", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ woo_order_id, amount }),
|
||||||
|
}).then(r => r.json());
|
||||||
|
},
|
||||||
|
|
||||||
|
async simulateMpWebhook({ woo_order_id, amount }) {
|
||||||
|
return fetch("/test/simulate-webhook", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ woo_order_id, amount }),
|
||||||
|
}).then(r => r.json());
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
92
src/modules/0-ui/controllers/testing.js
Normal file
92
src/modules/0-ui/controllers/testing.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import {
|
||||||
|
handleListRecentOrders,
|
||||||
|
handleGetProductsWithStock,
|
||||||
|
handleCreateTestOrder,
|
||||||
|
handleCreatePaymentLink,
|
||||||
|
handleSimulateMpWebhook,
|
||||||
|
} from "../handlers/testing.js";
|
||||||
|
|
||||||
|
export const makeListRecentOrders = (tenantIdOrFn) => async (req, res) => {
|
||||||
|
try {
|
||||||
|
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||||
|
const limit = parseInt(req.query.limit) || 20;
|
||||||
|
const result = await handleListRecentOrders({ tenantId, limit });
|
||||||
|
res.json(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[testing] listRecentOrders error:", err);
|
||||||
|
res.status(500).json({ ok: false, error: err.message || "internal_error" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const makeGetProductsWithStock = (tenantIdOrFn) => async (req, res) => {
|
||||||
|
try {
|
||||||
|
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||||
|
const result = await handleGetProductsWithStock({ tenantId });
|
||||||
|
res.json(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[testing] getProductsWithStock error:", err);
|
||||||
|
res.status(500).json({ ok: false, error: err.message || "internal_error" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const makeCreateTestOrder = (tenantIdOrFn) => async (req, res) => {
|
||||||
|
try {
|
||||||
|
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||||
|
const { basket, address, wa_chat_id } = req.body || {};
|
||||||
|
|
||||||
|
if (!basket?.items?.length) {
|
||||||
|
return res.status(400).json({ ok: false, error: "basket_required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await handleCreateTestOrder({ tenantId, basket, address, wa_chat_id });
|
||||||
|
res.json(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[testing] createTestOrder error:", err);
|
||||||
|
res.status(500).json({ ok: false, error: err.message || "internal_error" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const makeCreatePaymentLink = (tenantIdOrFn) => async (req, res) => {
|
||||||
|
try {
|
||||||
|
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||||
|
const { woo_order_id, amount } = req.body || {};
|
||||||
|
|
||||||
|
if (!woo_order_id) {
|
||||||
|
return res.status(400).json({ ok: false, error: "woo_order_id_required" });
|
||||||
|
}
|
||||||
|
if (!amount || Number(amount) <= 0) {
|
||||||
|
return res.status(400).json({ ok: false, error: "amount_required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await handleCreatePaymentLink({
|
||||||
|
tenantId,
|
||||||
|
wooOrderId: woo_order_id,
|
||||||
|
amount: Number(amount)
|
||||||
|
});
|
||||||
|
res.json(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[testing] createPaymentLink error:", err);
|
||||||
|
res.status(500).json({ ok: false, error: err.message || "internal_error" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const makeSimulateMpWebhook = (tenantIdOrFn) => async (req, res) => {
|
||||||
|
try {
|
||||||
|
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||||
|
const { woo_order_id, amount } = req.body || {};
|
||||||
|
|
||||||
|
if (!woo_order_id) {
|
||||||
|
return res.status(400).json({ ok: false, error: "woo_order_id_required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await handleSimulateMpWebhook({
|
||||||
|
tenantId,
|
||||||
|
wooOrderId: woo_order_id,
|
||||||
|
amount: Number(amount) || 0
|
||||||
|
});
|
||||||
|
res.json(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[testing] simulateMpWebhook error:", err);
|
||||||
|
res.status(500).json({ ok: false, error: err.message || "internal_error" });
|
||||||
|
}
|
||||||
|
};
|
||||||
131
src/modules/0-ui/handlers/testing.js
Normal file
131
src/modules/0-ui/handlers/testing.js
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import { createOrder, listRecentOrders } from "../../4-woo-orders/wooOrders.js";
|
||||||
|
import { createPreference, reconcilePayment } from "../../6-mercadopago/mercadoPago.js";
|
||||||
|
import { listProducts } from "../db/repo.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lista pedidos recientes de WooCommerce
|
||||||
|
*/
|
||||||
|
export async function handleListRecentOrders({ tenantId, limit = 20 }) {
|
||||||
|
const orders = await listRecentOrders({ tenantId, limit });
|
||||||
|
return { items: orders };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene productos con stock para testing
|
||||||
|
*/
|
||||||
|
export async function handleGetProductsWithStock({ tenantId }) {
|
||||||
|
const allProducts = await listProducts({ tenantId, limit: 500 });
|
||||||
|
const withStock = allProducts.filter(p =>
|
||||||
|
p.stock_status === "instock" &&
|
||||||
|
p.price &&
|
||||||
|
Number(p.price) > 0
|
||||||
|
);
|
||||||
|
return { items: withStock };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea una orden de prueba en WooCommerce
|
||||||
|
*/
|
||||||
|
export async function handleCreateTestOrder({ tenantId, basket, address, wa_chat_id }) {
|
||||||
|
if (!basket?.items?.length) {
|
||||||
|
throw new Error("basket_empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = await createOrder({
|
||||||
|
tenantId,
|
||||||
|
wooCustomerId: null, // Sin customer de Woo para testing
|
||||||
|
basket,
|
||||||
|
address,
|
||||||
|
run_id: `test-${Date.now()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calcular total desde line_items
|
||||||
|
let total = 0;
|
||||||
|
if (order?.raw?.line_items) {
|
||||||
|
for (const item of order.raw.line_items) {
|
||||||
|
total += Number(item.total) || 0;
|
||||||
|
}
|
||||||
|
} else if (order?.raw?.total) {
|
||||||
|
total = Number(order.raw.total) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
woo_order_id: order?.id || null,
|
||||||
|
total,
|
||||||
|
line_items: order?.line_items || [],
|
||||||
|
raw: order?.raw || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea un link de pago de MercadoPago
|
||||||
|
*/
|
||||||
|
export async function handleCreatePaymentLink({ tenantId, wooOrderId, amount }) {
|
||||||
|
if (!wooOrderId) {
|
||||||
|
throw new Error("missing_woo_order_id");
|
||||||
|
}
|
||||||
|
if (!amount || Number(amount) <= 0) {
|
||||||
|
throw new Error("invalid_amount");
|
||||||
|
}
|
||||||
|
|
||||||
|
const pref = await createPreference({
|
||||||
|
tenantId,
|
||||||
|
wooOrderId,
|
||||||
|
amount: Number(amount),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
preference_id: pref?.preference_id || null,
|
||||||
|
init_point: pref?.init_point || null,
|
||||||
|
sandbox_init_point: pref?.sandbox_init_point || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simula un webhook de MercadoPago con pago exitoso
|
||||||
|
* No pasa por el endpoint real (requiere firma HMAC)
|
||||||
|
* Crea un payment mock y llama a reconcilePayment directamente
|
||||||
|
*/
|
||||||
|
export async function handleSimulateMpWebhook({ tenantId, wooOrderId, amount }) {
|
||||||
|
if (!wooOrderId) {
|
||||||
|
throw new Error("missing_woo_order_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crear payment mock con status approved
|
||||||
|
const mockPaymentId = `test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
const mockPayment = {
|
||||||
|
id: mockPaymentId,
|
||||||
|
status: "approved",
|
||||||
|
status_detail: "accredited",
|
||||||
|
external_reference: `${tenantId}|${wooOrderId}`,
|
||||||
|
transaction_amount: Number(amount) || 0,
|
||||||
|
currency_id: "ARS",
|
||||||
|
date_approved: new Date().toISOString(),
|
||||||
|
date_created: new Date().toISOString(),
|
||||||
|
payment_method_id: "test",
|
||||||
|
payment_type_id: "credit_card",
|
||||||
|
payer: {
|
||||||
|
email: "test@test.com",
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
id: `pref-test-${wooOrderId}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reconciliar el pago (actualiza mp_payments y cambia status de orden a processing)
|
||||||
|
const result = await reconcilePayment({
|
||||||
|
tenantId,
|
||||||
|
payment: mockPayment,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
payment_id: mockPaymentId,
|
||||||
|
woo_order_id: result?.woo_order_id || wooOrderId,
|
||||||
|
status: "approved",
|
||||||
|
order_status: "processing",
|
||||||
|
reconciled: result?.payment || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import { makeListAliases, makeCreateAlias, makeUpdateAlias, makeDeleteAlias } fr
|
|||||||
import { makeListRecommendations, makeGetRecommendation, makeCreateRecommendation, makeUpdateRecommendation, makeDeleteRecommendation } from "../../0-ui/controllers/recommendations.js";
|
import { makeListRecommendations, makeGetRecommendation, makeCreateRecommendation, makeUpdateRecommendation, makeDeleteRecommendation } from "../../0-ui/controllers/recommendations.js";
|
||||||
import { makeListProductQtyRules, makeGetProductQtyRules, makeSaveProductQtyRules } from "../../0-ui/controllers/quantities.js";
|
import { makeListProductQtyRules, makeGetProductQtyRules, makeSaveProductQtyRules } from "../../0-ui/controllers/quantities.js";
|
||||||
import { makeDeleteConversation, makeDeleteUser, makeListUsers, makeRetryLast } from "../../0-ui/controllers/admin.js";
|
import { makeDeleteConversation, makeDeleteUser, makeListUsers, makeRetryLast } from "../../0-ui/controllers/admin.js";
|
||||||
|
import { makeListRecentOrders, makeGetProductsWithStock, makeCreateTestOrder, makeCreatePaymentLink, makeSimulateMpWebhook } from "../../0-ui/controllers/testing.js";
|
||||||
|
|
||||||
function nowIso() {
|
function nowIso() {
|
||||||
return new Date().toISOString();
|
return new Date().toISOString();
|
||||||
@@ -82,6 +83,13 @@ export function createSimulatorRouter({ tenantId }) {
|
|||||||
router.get("/runs", makeListRuns(getTenantId));
|
router.get("/runs", makeListRuns(getTenantId));
|
||||||
router.get("/runs/:run_id", makeGetRunById(getTenantId));
|
router.get("/runs/:run_id", makeGetRunById(getTenantId));
|
||||||
|
|
||||||
|
// --- Testing routes ---
|
||||||
|
router.get("/test/orders", makeListRecentOrders(getTenantId));
|
||||||
|
router.get("/test/products-with-stock", makeGetProductsWithStock(getTenantId));
|
||||||
|
router.post("/test/order", makeCreateTestOrder(getTenantId));
|
||||||
|
router.post("/test/payment-link", makeCreatePaymentLink(getTenantId));
|
||||||
|
router.post("/test/simulate-webhook", makeSimulateMpWebhook(getTenantId));
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ async function getWooClient({ tenantId }) {
|
|||||||
return {
|
return {
|
||||||
base,
|
base,
|
||||||
authHeader: { Authorization: `Basic ${auth}` },
|
authHeader: { Authorization: `Basic ${auth}` },
|
||||||
timeout: Math.max(cfg.timeout_ms ?? 20000, 20000),
|
timeout: Math.max(cfg.timeout_ms ?? 60000, 60000),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ async function buildLineItems({ tenantId, basket }) {
|
|||||||
const total = pricePerKg != null ? toMoney(pricePerKg * qty) : null;
|
const total = pricePerKg != null ? toMoney(pricePerKg * qty) : null;
|
||||||
lineItems.push({
|
lineItems.push({
|
||||||
product_id: productId,
|
product_id: productId,
|
||||||
variation_id: it.variation_id ?? null,
|
...(it.variation_id ? { variation_id: it.variation_id } : {}),
|
||||||
quantity: Math.round(qty),
|
quantity: Math.round(qty),
|
||||||
...(total ? { subtotal: total, total } : {}),
|
...(total ? { subtotal: total, total } : {}),
|
||||||
meta_data: [
|
meta_data: [
|
||||||
@@ -150,7 +150,7 @@ async function buildLineItems({ tenantId, basket }) {
|
|||||||
const total = pricePerKg != null ? toMoney(pricePerKg * kilos) : null;
|
const total = pricePerKg != null ? toMoney(pricePerKg * kilos) : null;
|
||||||
lineItems.push({
|
lineItems.push({
|
||||||
product_id: productId,
|
product_id: productId,
|
||||||
variation_id: it.variation_id ?? null,
|
...(it.variation_id ? { variation_id: it.variation_id } : {}),
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
...(total ? { subtotal: total, total } : {}),
|
...(total ? { subtotal: total, total } : {}),
|
||||||
meta_data: [
|
meta_data: [
|
||||||
@@ -224,6 +224,33 @@ export async function updateOrder({ tenantId, wooOrderId, basket, address, run_i
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isRetryableNetworkError(err) {
|
||||||
|
const e0 = err;
|
||||||
|
const e1 = err?.cause;
|
||||||
|
const e2 = err?.cause?.cause;
|
||||||
|
const candidates = [e0, e1, e2].filter(Boolean);
|
||||||
|
const codes = new Set(candidates.map((e) => e.code).filter(Boolean));
|
||||||
|
const names = new Set(candidates.map((e) => e.name).filter(Boolean));
|
||||||
|
const messages = candidates.map((e) => String(e.message || "")).join(" | ").toLowerCase();
|
||||||
|
|
||||||
|
const aborted =
|
||||||
|
names.has("AbortError") ||
|
||||||
|
messages.includes("aborted") ||
|
||||||
|
messages.includes("timeout") ||
|
||||||
|
messages.includes("timed out");
|
||||||
|
|
||||||
|
const retryCodes = new Set(["ECONNRESET", "ETIMEDOUT", "UND_ERR_CONNECT_TIMEOUT", "UND_ERR_SOCKET"]);
|
||||||
|
const byCode = [...codes].some((c) => retryCodes.has(c));
|
||||||
|
|
||||||
|
return aborted || byCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getOrderStatus({ client, wooOrderId }) {
|
||||||
|
const url = `${client.base}/orders/${encodeURIComponent(wooOrderId)}`;
|
||||||
|
const data = await fetchWoo({ url, method: "GET", timeout: client.timeout, headers: client.authHeader });
|
||||||
|
return { id: data?.id, status: data?.status, raw: data };
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateOrderStatus({ tenantId, wooOrderId, status }) {
|
export async function updateOrderStatus({ tenantId, wooOrderId, status }) {
|
||||||
if (!wooOrderId) throw new Error("missing_woo_order_id");
|
if (!wooOrderId) throw new Error("missing_woo_order_id");
|
||||||
const lockKey = `${tenantId}:order:${wooOrderId}:status`;
|
const lockKey = `${tenantId}:order:${wooOrderId}:status`;
|
||||||
@@ -231,8 +258,115 @@ export async function updateOrderStatus({ tenantId, wooOrderId, status }) {
|
|||||||
const client = await getWooClient({ tenantId });
|
const client = await getWooClient({ tenantId });
|
||||||
const payload = { status };
|
const payload = { status };
|
||||||
const url = `${client.base}/orders/${encodeURIComponent(wooOrderId)}`;
|
const url = `${client.base}/orders/${encodeURIComponent(wooOrderId)}`;
|
||||||
const data = await fetchWoo({ url, method: "PUT", body: payload, timeout: client.timeout, headers: client.authHeader });
|
|
||||||
return { id: data?.id || wooOrderId, raw: data };
|
// Timeout corto para el PUT (Woo procesa pero tarda en responder)
|
||||||
|
const putTimeout = 3000;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await fetchWoo({ url, method: "PUT", body: payload, timeout: putTimeout, headers: client.authHeader });
|
||||||
|
return { id: data?.id || wooOrderId, raw: data };
|
||||||
|
} catch (err) {
|
||||||
|
// Si es timeout, verificar si el status cambió con un GET
|
||||||
|
if (isRetryableNetworkError(err)) {
|
||||||
|
if (dbg.wooHttp) console.log("[wooOrders] updateOrderStatus timeout, checking with GET...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const current = await getOrderStatus({ client, wooOrderId });
|
||||||
|
// Si el status ya es el deseado, la operación fue exitosa
|
||||||
|
if (current.status === status) {
|
||||||
|
if (dbg.wooHttp) console.log("[wooOrders] updateOrderStatus confirmed via GET", { wooOrderId, status });
|
||||||
|
return { id: current.id || wooOrderId, raw: current.raw, recovered: true };
|
||||||
|
}
|
||||||
|
} catch (getErr) {
|
||||||
|
// Si falla el GET también, propagar el error original
|
||||||
|
if (dbg.wooHttp) console.log("[wooOrders] updateOrderStatus GET also failed:", getErr.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listRecentOrders({ tenantId, limit = 20 }) {
|
||||||
|
const client = await getWooClient({ tenantId });
|
||||||
|
const url = `${client.base}/orders?per_page=${limit}&orderby=date&order=desc`;
|
||||||
|
const data = await fetchWoo({ url, method: "GET", timeout: client.timeout, headers: client.authHeader });
|
||||||
|
|
||||||
|
if (!Array.isArray(data)) return [];
|
||||||
|
|
||||||
|
// Mapear a formato simplificado
|
||||||
|
return data.map(order => {
|
||||||
|
// Detectar si es orden de test (run_id empieza con "test-")
|
||||||
|
const runIdMeta = order.meta_data?.find(m => m.key === "run_id");
|
||||||
|
const runId = runIdMeta?.value || null;
|
||||||
|
const isTest = runId?.startsWith("test-") || false;
|
||||||
|
const sourceMeta = order.meta_data?.find(m => m.key === "source");
|
||||||
|
const source = sourceMeta?.value || "web";
|
||||||
|
|
||||||
|
// Método de envío (shipping)
|
||||||
|
const shippingLines = order.shipping_lines || [];
|
||||||
|
const shippingMethod = shippingLines[0]?.method_title || shippingLines[0]?.method_id || null;
|
||||||
|
const isDelivery = shippingMethod ?
|
||||||
|
!shippingMethod.toLowerCase().includes("retiro") &&
|
||||||
|
!shippingMethod.toLowerCase().includes("pickup") &&
|
||||||
|
!shippingMethod.toLowerCase().includes("local") : false;
|
||||||
|
|
||||||
|
// Método de pago
|
||||||
|
const paymentMethod = order.payment_method || null;
|
||||||
|
const paymentMethodTitle = order.payment_method_title || null;
|
||||||
|
const isCash = paymentMethod === "cod" ||
|
||||||
|
paymentMethodTitle?.toLowerCase().includes("efectivo") ||
|
||||||
|
paymentMethodTitle?.toLowerCase().includes("cash");
|
||||||
|
|
||||||
|
// Estado de pago (basado en status de la orden)
|
||||||
|
// pending = no pago, processing/completed = pago
|
||||||
|
const isPaid = ["processing", "completed", "on-hold"].includes(order.status);
|
||||||
|
const datePaid = order.date_paid || null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: order.id,
|
||||||
|
status: order.status,
|
||||||
|
total: order.total,
|
||||||
|
currency: order.currency,
|
||||||
|
date_created: order.date_created,
|
||||||
|
date_paid: datePaid,
|
||||||
|
billing: {
|
||||||
|
first_name: order.billing?.first_name || "",
|
||||||
|
last_name: order.billing?.last_name || "",
|
||||||
|
phone: order.billing?.phone || "",
|
||||||
|
email: order.billing?.email || "",
|
||||||
|
address_1: order.billing?.address_1 || "",
|
||||||
|
address_2: order.billing?.address_2 || "",
|
||||||
|
city: order.billing?.city || "",
|
||||||
|
state: order.billing?.state || "",
|
||||||
|
postcode: order.billing?.postcode || "",
|
||||||
|
},
|
||||||
|
shipping: {
|
||||||
|
first_name: order.shipping?.first_name || "",
|
||||||
|
last_name: order.shipping?.last_name || "",
|
||||||
|
address_1: order.shipping?.address_1 || "",
|
||||||
|
address_2: order.shipping?.address_2 || "",
|
||||||
|
city: order.shipping?.city || "",
|
||||||
|
state: order.shipping?.state || "",
|
||||||
|
postcode: order.shipping?.postcode || "",
|
||||||
|
},
|
||||||
|
line_items: (order.line_items || []).map(li => ({
|
||||||
|
id: li.id,
|
||||||
|
name: li.name,
|
||||||
|
quantity: li.quantity,
|
||||||
|
total: li.total,
|
||||||
|
})),
|
||||||
|
source,
|
||||||
|
run_id: runId,
|
||||||
|
is_test: isTest,
|
||||||
|
// Shipping info
|
||||||
|
shipping_method: shippingMethod,
|
||||||
|
is_delivery: isDelivery,
|
||||||
|
// Payment info
|
||||||
|
payment_method: paymentMethod,
|
||||||
|
payment_method_title: paymentMethodTitle,
|
||||||
|
is_cash: isCash,
|
||||||
|
is_paid: isPaid,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -363,6 +363,7 @@ export async function pushProductToWoo({ tenantId, wooProductId, categories, sel
|
|||||||
if (categories && Array.isArray(categories) && categories.length > 0) {
|
if (categories && Array.isArray(categories) && categories.length > 0) {
|
||||||
// Primero obtener las categorías existentes en Woo para mapear nombres a IDs
|
// Primero obtener las categorías existentes en Woo para mapear nombres a IDs
|
||||||
const existingCats = await fetchWooCategoriesByNames({ tenantId, names: categories });
|
const existingCats = await fetchWooCategoriesByNames({ tenantId, names: categories });
|
||||||
|
|
||||||
if (existingCats.length > 0) {
|
if (existingCats.length > 0) {
|
||||||
updatePayload.categories = existingCats.map(c => ({ id: c.id }));
|
updatePayload.categories = existingCats.map(c => ({ id: c.id }));
|
||||||
}
|
}
|
||||||
@@ -424,8 +425,28 @@ async function fetchWooCategoriesByNames({ tenantId, names }) {
|
|||||||
page++;
|
page++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalizar nombres - soportar formato jerárquico "Parent > Child"
|
||||||
|
// Extraer TODAS las partes del path para asignar la jerarquía completa
|
||||||
|
const normalizedNames = [];
|
||||||
|
for (const n of names) {
|
||||||
|
const full = String(n).toLowerCase().trim();
|
||||||
|
// Si tiene formato jerárquico "Parent > Child > Grandchild", extraer cada parte
|
||||||
|
if (full.includes(' > ')) {
|
||||||
|
const parts = full.split(' > ').map(p => p.trim()).filter(Boolean);
|
||||||
|
for (const part of parts) {
|
||||||
|
if (!normalizedNames.includes(part)) {
|
||||||
|
normalizedNames.push(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Categoría simple (sin jerarquía)
|
||||||
|
if (!normalizedNames.includes(full)) {
|
||||||
|
normalizedNames.push(full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Filtrar las que coinciden con los nombres buscados
|
// Filtrar las que coinciden con los nombres buscados
|
||||||
const normalizedNames = names.map(n => String(n).toLowerCase().trim());
|
|
||||||
return allCategories.filter(c =>
|
return allCategories.filter(c =>
|
||||||
normalizedNames.includes(String(c.name).toLowerCase().trim()) ||
|
normalizedNames.includes(String(c.name).toLowerCase().trim()) ||
|
||||||
normalizedNames.includes(String(c.slug).toLowerCase().trim())
|
normalizedNames.includes(String(c.slug).toLowerCase().trim())
|
||||||
|
|||||||
Reference in New Issue
Block a user