diff --git a/dashboard/admin.min.js b/dashboard/admin.min.js
new file mode 100644
index 0000000..508f99b
--- /dev/null
+++ b/dashboard/admin.min.js
@@ -0,0 +1 @@
+let currentPage={parts:1,aftermarket:1,crossref:1,fitment:1},categoriesCache=[],groupsCache=[],partsCache=[],manufacturersCache=[],brandsCache=[],pendingImportData=null,searchTimeout=null;const csvFormats={parts:{columns:["oem_part_number","name","name_es","group_id","description","description_es","weight_kg","material"],required:["oem_part_number","name","group_id"],example:"oem_part_number,name,name_es,group_id,description,description_es,weight_kg,material\n04465-33450,Brake Pad Set Front,Pastillas de Freno Delanteras,5,Front disc brake pads,Pastillas de freno de disco delanteras,1.2,Ceramic"},aftermarket:{columns:["oem_part_id","manufacturer_id","part_number","name","name_es","quality_tier","price_usd","warranty_months"],required:["oem_part_id","manufacturer_id","part_number"],example:"oem_part_id,manufacturer_id,part_number,name,name_es,quality_tier,price_usd,warranty_months\n1,3,BP-1234,Brake Pad Set,Pastillas de Freno,premium,45.99,24"},manufacturers:{columns:["name","type","quality_tier","country","website"],required:["name","type"],example:"name,type,quality_tier,country,website\nBrembo,aftermarket,premium,Italy,https://www.brembo.com"},categories:{columns:["name","name_es","slug","icon_name","display_order"],required:["name"],example:"name,name_es,slug,icon_name,display_order\nBrake System,Sistema de Frenos,brakes,brake,1"},groups:{columns:["category_id","name","name_es","display_order"],required:["category_id","name"],example:"category_id,name,name_es,display_order\n1,Brake Pads,Pastillas de Freno,1"},crossref:{columns:["part_id","cross_reference_number","reference_type","source","notes"],required:["part_id","cross_reference_number","reference_type"],example:"part_id,cross_reference_number,reference_type,source,notes\n1,D1210,interchange,Manufacturer,Compatible replacement"},fitment:{columns:["model_year_engine_id","part_id","quantity_required","position","fitment_notes"],required:["model_year_engine_id","part_id"],example:"model_year_engine_id,part_id,quantity_required,position,fitment_notes\n1,1,2,front,Fits all trims"}};function initSidebar(){const e=document.querySelectorAll(".sidebar-item");e.forEach((t=>{t.addEventListener("click",(()=>{showSection(t.dataset.section),e.forEach((e=>e.classList.remove("active"))),t.classList.add("active")}))}))}function showSection(e){document.querySelectorAll(".admin-section").forEach((e=>e.classList.remove("active")));const t=document.getElementById(`section-${e}`);if(t)switch(t.classList.add("active"),e){case"dashboard":loadDashboard();break;case"categories":loadCategories();break;case"groups":loadGroups();break;case"parts":loadParts();break;case"manufacturers":loadManufacturers();break;case"aftermarket":loadAftermarket();break;case"crossref":loadCrossRefs();break;case"fitment":loadFitment();break;case"diagrams":break;case"users":loadUsers()}document.querySelectorAll(".sidebar-item").forEach((t=>{t.classList.toggle("active",t.dataset.section===e)}))}function openModal(e){document.getElementById(e).classList.add("active")}function closeModal(e){document.getElementById(e).classList.remove("active")}function showAlert(e,t="success"){const a=document.getElementById("alertContainer"),n=document.createElement("div");n.className=`alert alert-${t}`,n.innerHTML=`${"success"===t?"✓":"✕"} ${e}`,a.appendChild(n),setTimeout((()=>n.remove()),5e3)}function debounceSearch(e){searchTimeout&&clearTimeout(searchTimeout),searchTimeout=setTimeout(e,300)}async function loadDashboard(){try{const e=await fetch("/api/admin/stats").then((e=>e.json()));document.getElementById("statCategories").textContent=e.categories||0,document.getElementById("statGroups").textContent=e.groups||0,document.getElementById("statParts").textContent=e.parts||0,document.getElementById("statAftermarket").textContent=e.aftermarket||0,document.getElementById("statManufacturers").textContent=e.manufacturers||0,document.getElementById("statFitment").textContent=e.fitment||0}catch(e){console.error("Error loading dashboard:",e)}}async function loadCategories(){try{const e=await fetch("/api/categories"),t=await e.json();categoriesCache=flattenCategories(t);const a=document.getElementById("categoriesTable");if(0===categoriesCache.length)return void(a.innerHTML='
No hay categorías
');a.innerHTML=categoriesCache.map((e=>`\n
\n
${e.id}
\n
${e.name}
\n
${e.name_es||"-"}
\n
${e.slug||"-"}
\n
${e.icon_name||"-"}
\n
${e.display_order||0}
\n
\n \n \n
\n
\n `)).join(""),updateCategorySelects()}catch(e){console.error("Error loading categories:",e),showAlert("Error al cargar categorías","error")}}function flattenCategories(e,t=0){let a=[];return e.forEach((e=>{a.push({...e,level:t}),e.children&&e.children.length>0&&(a=a.concat(flattenCategories(e.children,t+1)))})),a}function updateCategorySelects(){["groupCategoryFilter","groupCategory"].forEach((e=>{const t=document.getElementById(e);if(!t)return;const a=t.value,n=e.includes("Filter")?'':'';t.innerHTML=n+categoriesCache.map((e=>``)).join(""),t.value=a}))}function openCategoryModal(e=null){if(document.getElementById("categoryId").value="",document.getElementById("categoryName").value="",document.getElementById("categoryNameEs").value="",document.getElementById("categorySlug").value="",document.getElementById("categoryIcon").value="",document.getElementById("categoryOrder").value="0",document.getElementById("categoryModalTitle").textContent="Nueva Categoría",e){const t=categoriesCache.find((t=>t.id===e));t&&(document.getElementById("categoryId").value=t.id,document.getElementById("categoryName").value=t.name,document.getElementById("categoryNameEs").value=t.name_es||"",document.getElementById("categorySlug").value=t.slug||"",document.getElementById("categoryIcon").value=t.icon_name||"",document.getElementById("categoryOrder").value=t.display_order||0,document.getElementById("categoryModalTitle").textContent="Editar Categoría")}openModal("categoryModal")}function editCategory(e){openCategoryModal(e)}async function saveCategory(){const e=document.getElementById("categoryId").value,t={name:document.getElementById("categoryName").value,name_es:document.getElementById("categoryNameEs").value||null,slug:document.getElementById("categorySlug").value||null,icon_name:document.getElementById("categoryIcon").value||null,display_order:parseInt(document.getElementById("categoryOrder").value)||0};try{const a=e?`/api/admin/categories/${e}`:"/api/admin/categories",n=e?"PUT":"POST",r=await fetch(a,{method:n,headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok){const e=await r.json();throw new Error(e.error||"Error al guardar")}closeModal("categoryModal"),showAlert(e?"Categoría actualizada":"Categoría creada"),loadCategories()}catch(e){showAlert(e.message,"error")}}async function deleteCategory(e){if(confirm("¿Estás seguro de eliminar esta categoría?"))try{if(!(await fetch(`/api/admin/categories/${e}`,{method:"DELETE"})).ok)throw new Error("Error al eliminar");showAlert("Categoría eliminada"),loadCategories()}catch(e){showAlert(e.message,"error")}}async function loadGroups(){try{const e=document.getElementById("groupCategoryFilter").value;let t="/api/admin/groups";e&&(t+=`?category_id=${e}`);const a=await fetch(t),n=await a.json();groupsCache=n;const r=document.getElementById("groupsTable");if(0===n.length)return void(r.innerHTML='
No hay grupos
');r.innerHTML=n.map((e=>`\n
\n
${e.id}
\n
${e.name}
\n
${e.name_es||"-"}
\n
${e.category_name||"-"}
\n
${e.display_order||0}
\n
\n \n \n
\n
\n `)).join(""),updateGroupSelects()}catch(e){console.error("Error loading groups:",e),showAlert("Error al cargar grupos","error")}}function updateGroupSelects(){["partGroupFilter","partGroup"].forEach((e=>{const t=document.getElementById(e);if(!t)return;const a=t.value,n=e.includes("Filter")?'':'';t.innerHTML=n+groupsCache.map((e=>``)).join(""),t.value=a}))}function openGroupModal(e=null){document.getElementById("groupId").value="",document.getElementById("groupName").value="",document.getElementById("groupNameEs").value="",document.getElementById("groupOrder").value="0",document.getElementById("groupModalTitle").textContent="Nuevo Grupo";if(document.getElementById("groupCategory").innerHTML=''+categoriesCache.map((e=>``)).join(""),e){const t=groupsCache.find((t=>t.id===e));t&&(document.getElementById("groupId").value=t.id,document.getElementById("groupName").value=t.name,document.getElementById("groupNameEs").value=t.name_es||"",document.getElementById("groupCategory").value=t.category_id,document.getElementById("groupOrder").value=t.display_order||0,document.getElementById("groupModalTitle").textContent="Editar Grupo")}openModal("groupModal")}function editGroup(e){openGroupModal(e)}async function saveGroup(){const e=document.getElementById("groupId").value,t={category_id:parseInt(document.getElementById("groupCategory").value),name:document.getElementById("groupName").value,name_es:document.getElementById("groupNameEs").value||null,display_order:parseInt(document.getElementById("groupOrder").value)||0};try{const a=e?`/api/admin/groups/${e}`:"/api/admin/groups",n=e?"PUT":"POST",r=await fetch(a,{method:n,headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok){const e=await r.json();throw new Error(e.error||"Error al guardar")}closeModal("groupModal"),showAlert(e?"Grupo actualizado":"Grupo creado"),loadGroups()}catch(e){showAlert(e.message,"error")}}async function deleteGroup(e){if(confirm("¿Estás seguro de eliminar este grupo?"))try{if(!(await fetch(`/api/admin/groups/${e}`,{method:"DELETE"})).ok)throw new Error("Error al eliminar");showAlert("Grupo eliminado"),loadGroups()}catch(e){showAlert(e.message,"error")}}async function loadParts(){try{const e=document.getElementById("partSearch").value,t=document.getElementById("partGroupFilter").value;let a=`/api/parts?page=${currentPage.parts}&per_page=20`;e&&(a+=`&search=${encodeURIComponent(e)}`),t&&(a+=`&group_id=${t}`);const n=await fetch(a),r=await n.json();partsCache=r.data||[];const o=document.getElementById("partsTable");if(0===partsCache.length)return o.innerHTML='
\n `)).join(""),renderPagination("partsPagination",r.pagination,"parts",loadParts),0===groupsCache.length&&await loadGroups()}catch(e){console.error("Error loading parts:",e),showAlert("Error al cargar partes","error")}}function openPartModal(e=null){document.getElementById("partId").value="",document.getElementById("partOemNumber").value="",document.getElementById("partName").value="",document.getElementById("partNameEs").value="",document.getElementById("partDescription").value="",document.getElementById("partDescriptionEs").value="",document.getElementById("partWeight").value="",document.getElementById("partMaterial").value="",document.getElementById("partModalTitle").textContent="Nueva Parte OEM",resetPartImagePreview();document.getElementById("partGroup").innerHTML=''+groupsCache.map((e=>``)).join(""),e&&fetchPartDetail(e),openModal("partModal")}async function fetchPartDetail(e){try{const t=await fetch(`/api/parts/${e}`),a=await t.json();document.getElementById("partId").value=a.id,document.getElementById("partOemNumber").value=a.oem_part_number,document.getElementById("partName").value=a.name,document.getElementById("partNameEs").value=a.name_es||"",document.getElementById("partGroup").value=a.group_id,document.getElementById("partDescription").value=a.description||"",document.getElementById("partDescriptionEs").value=a.description_es||"",document.getElementById("partModalTitle").textContent="Editar Parte OEM";const n=document.getElementById("partImagePreview");a.image_url?(n.innerHTML=``,document.getElementById("partImageUrl").value=a.image_url):resetPartImagePreview()}catch(e){console.error("Error fetching part:",e)}}function editPart(e){openPartModal(e)}async function savePart(){const e=document.getElementById("partId").value;let t=document.getElementById("partImageUrl").value||null;const a=document.getElementById("partImagePreview").querySelector("img");a&&a.src.startsWith("data:")&&(t=await uploadPartImage(a.src));const n={oem_part_number:document.getElementById("partOemNumber").value,name:document.getElementById("partName").value,name_es:document.getElementById("partNameEs").value||null,group_id:parseInt(document.getElementById("partGroup").value),description:document.getElementById("partDescription").value||null,description_es:document.getElementById("partDescriptionEs").value||null,weight_kg:parseFloat(document.getElementById("partWeight").value)||null,material:document.getElementById("partMaterial").value||null,image_url:t};try{const t=e?`/api/admin/parts/${e}`:"/api/admin/parts",a=e?"PUT":"POST",r=await fetch(t,{method:a,headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!r.ok){const e=await r.json();throw new Error(e.error||"Error al guardar")}closeModal("partModal"),showAlert(e?"Parte actualizada":"Parte creada"),loadParts()}catch(e){showAlert(e.message,"error")}}function previewPartImage(e){const t=document.getElementById("partImagePreview");if(e.files&&e.files[0]){const a=new FileReader;a.onload=function(e){t.innerHTML=``},a.readAsDataURL(e.files[0])}}async function uploadPartImage(e){try{const t=await fetch("/api/admin/upload-image",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({image:e})});if(!t.ok)throw new Error("Error uploading image");return(await t.json()).url}catch(e){return console.error("Image upload failed:",e),null}}function resetPartImagePreview(){document.getElementById("partImagePreview").innerHTML='📷 Sin imagen',document.getElementById("partImageUrl").value="",document.getElementById("partImageFile").value=""}async function deletePart(e){if(confirm("¿Estás seguro de eliminar esta parte?"))try{if(!(await fetch(`/api/admin/parts/${e}`,{method:"DELETE"})).ok)throw new Error("Error al eliminar");showAlert("Parte eliminada"),loadParts()}catch(e){showAlert(e.message,"error")}}async function loadManufacturers(){try{const e=await fetch("/api/manufacturers");manufacturersCache=await e.json();const t=document.getElementById("manufacturersTable");if(0===manufacturersCache.length)return void(t.innerHTML='
No hay fabricantes
');t.innerHTML=manufacturersCache.map((e=>`\n
\n
${e.id}
\n
${e.name}
\n
${e.type||"-"}
\n
${e.quality_tier||"-"}
\n
${e.country||"-"}
\n
\n \n \n
\n
\n `)).join(""),updateManufacturerSelects()}catch(e){console.error("Error loading manufacturers:",e),showAlert("Error al cargar fabricantes","error")}}function updateManufacturerSelects(){const e=document.getElementById("aftermarketManufacturerFilter");if(e){const t=e.value;e.innerHTML=''+manufacturersCache.map((e=>``)).join(""),e.value=t}const t=document.getElementById("aftermarketManufacturer");t&&(t.innerHTML=''+manufacturersCache.map((e=>``)).join(""))}function openManufacturerModal(e=null){if(document.getElementById("manufacturerId").value="",document.getElementById("manufacturerName").value="",document.getElementById("manufacturerType").value="aftermarket",document.getElementById("manufacturerQuality").value="standard",document.getElementById("manufacturerCountry").value="",document.getElementById("manufacturerWebsite").value="",document.getElementById("manufacturerModalTitle").textContent="Nuevo Fabricante",e){const t=manufacturersCache.find((t=>t.id===e));t&&(document.getElementById("manufacturerId").value=t.id,document.getElementById("manufacturerName").value=t.name,document.getElementById("manufacturerType").value=t.type||"aftermarket",document.getElementById("manufacturerQuality").value=t.quality_tier||"standard",document.getElementById("manufacturerCountry").value=t.country||"",document.getElementById("manufacturerWebsite").value=t.website||"",document.getElementById("manufacturerModalTitle").textContent="Editar Fabricante")}openModal("manufacturerModal")}function editManufacturer(e){openManufacturerModal(e)}async function saveManufacturer(){const e=document.getElementById("manufacturerId").value,t={name:document.getElementById("manufacturerName").value,type:document.getElementById("manufacturerType").value,quality_tier:document.getElementById("manufacturerQuality").value,country:document.getElementById("manufacturerCountry").value||null,website:document.getElementById("manufacturerWebsite").value||null};try{const a=e?`/api/admin/manufacturers/${e}`:"/api/admin/manufacturers",n=e?"PUT":"POST",r=await fetch(a,{method:n,headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok){const e=await r.json();throw new Error(e.error||"Error al guardar")}closeModal("manufacturerModal"),showAlert(e?"Fabricante actualizado":"Fabricante creado"),loadManufacturers()}catch(e){showAlert(e.message,"error")}}async function deleteManufacturer(e){if(confirm("¿Estás seguro de eliminar este fabricante?"))try{if(!(await fetch(`/api/admin/manufacturers/${e}`,{method:"DELETE"})).ok)throw new Error("Error al eliminar");showAlert("Fabricante eliminado"),loadManufacturers()}catch(e){showAlert(e.message,"error")}}async function loadAftermarket(){try{const e=document.getElementById("aftermarketSearch").value,t=document.getElementById("aftermarketManufacturerFilter").value;let a=`/api/aftermarket?page=${currentPage.aftermarket}&per_page=20`;e&&(a+=`&search=${encodeURIComponent(e)}`),t&&(a+=`&manufacturer_id=${t}`);const n=await fetch(a),r=await n.json(),o=document.getElementById("aftermarketTable");if(!r.data||0===r.data.length)return o.innerHTML='
\n `)).join(""),renderPagination("aftermarketPagination",r.pagination,"aftermarket",loadAftermarket)}catch(e){console.error("Error loading aftermarket:",e),showAlert("Error al cargar partes aftermarket","error")}}async function openAftermarketModal(e=null){document.getElementById("aftermarketId").value="",document.getElementById("aftermarketPartNumber").value="",document.getElementById("aftermarketName").value="",document.getElementById("aftermarketNameEs").value="",document.getElementById("aftermarketQuality").value="standard",document.getElementById("aftermarketPrice").value="",document.getElementById("aftermarketWarranty").value="",document.getElementById("aftermarketModalTitle").textContent="Nueva Parte Aftermarket",await loadPartsForSelect("aftermarketOemPart"),updateManufacturerSelects(),openModal("aftermarketModal")}async function editAftermarket(e){await openAftermarketModal(e);try{const t=await fetch("/api/aftermarket?search=&per_page=200"),a=await t.json(),n=(a.data||a).find((t=>t.id===e));n&&(document.getElementById("aftermarketId").value=n.id,document.getElementById("aftermarketPartNumber").value=n.part_number||"",document.getElementById("aftermarketName").value=n.name||"",document.getElementById("aftermarketNameEs").value=n.name_es||"",document.getElementById("aftermarketQuality").value=n.quality_tier||"standard",document.getElementById("aftermarketPrice").value=n.price_usd||"",document.getElementById("aftermarketWarranty").value=n.warranty_months||"",n.oem_part_id&&(document.getElementById("aftermarketOemPart").value=n.oem_part_id),n.manufacturer_id&&(document.getElementById("aftermarketManufacturer").value=n.manufacturer_id),document.getElementById("aftermarketModalTitle").textContent="Editar Parte Aftermarket")}catch(e){console.error("Error loading aftermarket for edit:",e)}}async function saveAftermarket(){const e=document.getElementById("aftermarketId").value,t={oem_part_id:parseInt(document.getElementById("aftermarketOemPart").value),manufacturer_id:parseInt(document.getElementById("aftermarketManufacturer").value),part_number:document.getElementById("aftermarketPartNumber").value,name:document.getElementById("aftermarketName").value||null,name_es:document.getElementById("aftermarketNameEs").value||null,quality_tier:document.getElementById("aftermarketQuality").value,price_usd:parseFloat(document.getElementById("aftermarketPrice").value)||null,warranty_months:parseInt(document.getElementById("aftermarketWarranty").value)||null};try{const a=e?`/api/admin/aftermarket/${e}`:"/api/admin/aftermarket",n=e?"PUT":"POST",r=await fetch(a,{method:n,headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok){const e=await r.json();throw new Error(e.error||"Error al guardar")}closeModal("aftermarketModal"),showAlert(e?"Parte actualizada":"Parte creada"),loadAftermarket()}catch(e){showAlert(e.message,"error")}}async function deleteAftermarket(e){if(confirm("¿Estás seguro de eliminar esta parte aftermarket?"))try{if(!(await fetch(`/api/admin/aftermarket/${e}`,{method:"DELETE"})).ok)throw new Error("Error al eliminar");showAlert("Parte eliminada"),loadAftermarket()}catch(e){showAlert(e.message,"error")}}async function loadCrossRefs(){try{const e=await fetch(`/api/admin/crossref?page=${currentPage.crossref}&per_page=20`),t=await e.json(),a=document.getElementById("crossrefTable");if(!t.data||0===t.data.length)return void(a.innerHTML='
No hay cross-references
');a.innerHTML=t.data.map((e=>`\n
\n
${e.id}
\n
${e.oem_part_number} - ${e.part_name}
\n
${e.cross_reference_number}
\n
${e.reference_type}
\n
${e.source||"-"}
\n
\n \n \n
\n
\n `)).join(""),renderPagination("crossrefPagination",t.pagination,"crossref",loadCrossRefs)}catch(e){console.error("Error loading cross-refs:",e)}}async function openCrossRefModal(e=null){document.getElementById("crossrefId").value="",document.getElementById("crossrefNumber").value="",document.getElementById("crossrefType").value="interchange",document.getElementById("crossrefSource").value="",document.getElementById("crossrefNotes").value="",document.getElementById("crossrefModalTitle").textContent="Nueva Cross-Reference",await loadPartsForSelect("crossrefPart"),openModal("crossrefModal")}function editCrossRef(e){openCrossRefModal(e)}async function saveCrossRef(){const e=document.getElementById("crossrefId").value,t={part_id:parseInt(document.getElementById("crossrefPart").value),cross_reference_number:document.getElementById("crossrefNumber").value,reference_type:document.getElementById("crossrefType").value,source:document.getElementById("crossrefSource").value||null,notes:document.getElementById("crossrefNotes").value||null};try{const a=e?`/api/admin/crossref/${e}`:"/api/admin/crossref",n=e?"PUT":"POST",r=await fetch(a,{method:n,headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok){const e=await r.json();throw new Error(e.error||"Error al guardar")}closeModal("crossrefModal"),showAlert(e?"Referencia actualizada":"Referencia creada"),loadCrossRefs()}catch(e){showAlert(e.message,"error")}}async function deleteCrossRef(e){if(confirm("¿Estás seguro de eliminar esta referencia?"))try{if(!(await fetch(`/api/admin/crossref/${e}`,{method:"DELETE"})).ok)throw new Error("Error al eliminar");showAlert("Referencia eliminada"),loadCrossRefs()}catch(e){showAlert(e.message,"error")}}async function loadFitment(){try{const e=document.getElementById("fitmentBrandFilter").value,t=document.getElementById("fitmentModelFilter").value;let a=`/api/admin/fitment?page=${currentPage.fitment}&per_page=20`;e&&(a+=`&brand=${encodeURIComponent(e)}`),t&&(a+=`&model=${encodeURIComponent(t)}`);const n=await fetch(a),r=await n.json(),o=document.getElementById("fitmentTable");if(!r.data||0===r.data.length)return o.innerHTML='
\n `)).join(""),renderPagination("fitmentPagination",r.pagination,"fitment",loadFitment)}catch(e){console.error("Error loading fitment:",e)}}async function loadFitmentModels(){const e=document.getElementById("fitmentBrandFilter").value,t=document.getElementById("fitmentModelFilter");if(!e)return t.innerHTML='',void loadFitment();try{const a=await fetch(`/api/models?brand=${encodeURIComponent(e)}`),n=await a.json();t.innerHTML=''+n.map((e=>``)).join(""),loadFitment()}catch(e){console.error("Error loading models:",e)}}async function openFitmentModal(){document.getElementById("fitmentId").value="",document.getElementById("fitmentQuantity").value="1",document.getElementById("fitmentPosition").value="",document.getElementById("fitmentNotes").value="",document.getElementById("fitmentModalTitle").textContent="Nuevo Fitment",await loadVehiclesForSelect("fitmentVehicle"),await loadPartsForSelect("fitmentPart"),openModal("fitmentModal")}async function saveFitment(){const e=document.getElementById("fitmentId").value,t={model_year_engine_id:parseInt(document.getElementById("fitmentVehicle").value),part_id:parseInt(document.getElementById("fitmentPart").value),quantity_required:parseInt(document.getElementById("fitmentQuantity").value)||1,position:document.getElementById("fitmentPosition").value||null,fitment_notes:document.getElementById("fitmentNotes").value||null};try{const a=e?`/api/admin/fitment/${e}`:"/api/admin/fitment",n=e?"PUT":"POST",r=await fetch(a,{method:n,headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok){const e=await r.json();throw new Error(e.error||"Error al guardar")}closeModal("fitmentModal"),showAlert(e?"Fitment actualizado":"Fitment creado"),loadFitment()}catch(e){showAlert(e.message,"error")}}async function deleteFitment(e){if(confirm("¿Estás seguro de eliminar este fitment?"))try{if(!(await fetch(`/api/admin/fitment/${e}`,{method:"DELETE"})).ok)throw new Error("Error al eliminar");showAlert("Fitment eliminado"),loadFitment()}catch(e){showAlert(e.message,"error")}}async function loadPartsForSelect(e){const t=document.getElementById(e);if(t)try{const e=await fetch("/api/parts?per_page=100"),a=await e.json();t.innerHTML=''+(a.data||[]).map((e=>``)).join("")}catch(e){console.error("Error loading parts for select:",e)}}async function loadVehiclesForSelect(e){const t=document.getElementById(e);if(t)try{const e=await fetch("/api/model-year-engine?per_page=100"),a=await e.json(),n=a.data||a;t.innerHTML=''+n.map((e=>``)).join("")}catch(e){console.error("Error loading vehicles for select:",e)}}async function loadBrands(){try{const e=await fetch("/api/brands");brandsCache=await e.json();const t=document.getElementById("fitmentBrandFilter");t&&(t.innerHTML=''+brandsCache.map((e=>``)).join(""))}catch(e){console.error("Error loading brands:",e)}}function renderPagination(e,t,a,n){const r=document.getElementById(e);if(!r||!t)return void(r.innerHTML="");const{page:o,total_pages:i}=t;if(i<=1)return void(r.innerHTML="");let d="";d+=``;const l=Math.max(1,o-2),c=Math.min(i,o+2);for(let e=l;e<=c;e++)d+=``;d+=``,r.innerHTML=d}function goToPage(e,t,a){currentPage[e]=t,window[a]()}function initDropZone(){const e=document.getElementById("dropZone"),t=document.getElementById("csvFile");e.addEventListener("click",(()=>t.click())),e.addEventListener("dragover",(t=>{t.preventDefault(),e.classList.add("dragover")})),e.addEventListener("dragleave",(()=>{e.classList.remove("dragover")})),e.addEventListener("drop",(t=>{t.preventDefault(),e.classList.remove("dragover");const a=t.dataTransfer.files[0];a&&a.name.endsWith(".csv")?handleCsvFile(a):showAlert("Por favor selecciona un archivo CSV","error")})),t.addEventListener("change",(e=>{const t=e.target.files[0];t&&handleCsvFile(t)}))}function initImportTypeChange(){document.getElementById("importType").addEventListener("change",updateCsvFormatHelp),updateCsvFormatHelp()}function updateCsvFormatHelp(){const e=document.getElementById("importType").value,t=csvFormats[e],a=document.getElementById("csvFormatHelp");t&&(a.innerHTML=`\n
Columnas requeridas: ${t.required.join(", ")}
\n
Todas las columnas: ${t.columns.join(", ")}
\n
Ejemplo:
\n
${t.example}
\n `)}function handleCsvFile(e){const t=new FileReader;t.onload=e=>{const t=parseCSV(e.target.result);t.length<2?showAlert("El archivo CSV debe tener al menos un encabezado y una fila de datos","error"):(pendingImportData=t,showImportPreview(t))},t.readAsText(e)}function parseCSV(e){const t=e.split(/\r?\n/).filter((e=>e.trim())),a=[];for(const e of t){const t=[];let n="",r=!1;for(let a=0;a"+t.map((e=>`
'}}function toggleBulkPart(e,t){const a=e.querySelector('input[type="checkbox"]');a.disabled||(a.checked=!a.checked,e.classList.toggle("selected",a.checked),updateBulkSelectedCount())}function updateBulkSelectedCount(){const e=document.querySelectorAll('#bulkPartsContainer input[type="checkbox"]:checked:not(:disabled)');document.getElementById("bulkSelectedCount").textContent=`${e.length} partes seleccionadas`}async function saveBulkFitments(){if(!bulkSelectedMYEId)return void showAlert("Selecciona un vehículo primero","error");const e=document.querySelectorAll("#bulkPartsContainer .bulk-part-item"),t=[];if(e.forEach((e=>{const a=e.querySelector('input[type="checkbox"]');if(a.checked&&!a.disabled){const n=parseInt(a.dataset.partId),r=parseInt(e.querySelector(".bulk-part-qty").value)||1;t.push({model_year_engine_id:bulkSelectedMYEId,part_id:n,quantity_required:r})}})),0!==t.length)try{const e=await fetch("/api/admin/import/fitment",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({records:t})}),a=await e.json();if(!e.ok)throw new Error(a.error||"Error al guardar");showAlert(`${a.imported} fitments creados exitosamente`),await loadBulkParts(),loadFitment(),loadDashboard()}catch(e){showAlert(e.message,"error")}else showAlert("Selecciona al menos una parte","error")}const originalShowSection=showSection;showSection=function(e){originalShowSection(e),"fitment"===e&&initBulkEditor()};let currentEditorDiagramId=null,currentEditorHotspots=[],partSearchTimeout=null;async function searchDiagramsAdmin(){const e=document.getElementById("diagramSearchInput").value.trim(),t=document.getElementById("diagramSearchResults");if(e){t.innerHTML='
"})),r.innerHTML=l,r.querySelectorAll(".category-header").forEach((function(e){e.addEventListener("click",(function(){var t=e.getAttribute("data-cat"),a=r.querySelector('[data-cat-body="'+t+'"]');e.classList.toggle("collapsed"),a.classList.toggle("collapsed")}))})),r.querySelectorAll(".btn-add-part").forEach((function(t){t.addEventListener("click",(function(){var n,r,i;n=parseInt(t.getAttribute("data-group-id")),r=document.querySelector('[data-group-parts="'+n+'"]'),(i=document.createElement("div")).className="part-row",i.innerHTML='',r.appendChild(i),i.querySelector(".pr-oem").focus(),i.querySelector(".pr-oem").addEventListener("blur",(function(){var e=this.value.trim();e&&c("/api/captura/parts/check-oem?oem="+encodeURIComponent(e)).then((function(e){e.exists&&(i.querySelector(".pr-name").value=e.part.name_part||"",i.querySelector(".pr-name").style.borderColor="var(--success)",i.dataset.existingPartId=e.part.id_part)}))})),i.querySelector(".pr-save").addEventListener("click",(function(){!function(t,n){var r=t.querySelector(".pr-oem").value.trim(),i=t.querySelector(".pr-name").value.trim(),d=parseInt(t.querySelector(".pr-qty").value)||1;if(!r)return o("Ingresa el numero OEM","error"),void t.querySelector(".pr-oem").focus();var l=t.querySelector(".pr-save");l.disabled=!0,l.textContent="...";var s=t.dataset.existingPartId;function u(s){c("/api/admin/fitment",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model_year_engine_id:e,part_id:s,quantity_required:d})}).then((function(e){var l={id_vehicle_part:e.id,part_id:s,oem_part_number:r,name_part:i,quantity_required:d,group_id:n};a.push(l),t.outerHTML=v(l),f(),o("Parte guardada: "+r),document.querySelectorAll(".part-row.saved .pr-delete").forEach((function(e){e.onclick=function(){var t=e.closest(".part-row"),n=t.getAttribute("data-fitment-id");n?c("/api/admin/fitment/"+n,{method:"DELETE"}).then((function(){a=a.filter((function(e){return e.id_vehicle_part!==parseInt(n)})),t.remove(),f(),o("Parte eliminada")})).catch((function(e){o(e.message,"error")})):t.remove()}}))})).catch((function(e){o(e.message,"error"),l.disabled=!1,l.textContent="✓"}))}s?u(parseInt(s)):c("/api/admin/parts",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oem_part_number:r,name:i||r,group_id:n})}).then((function(e){u(e.id)})).catch((function(e){o(e.message,"error"),l.disabled=!1,l.textContent="✓"}))}(i,n)})),i.querySelector(".pr-delete").addEventListener("click",(function(){i.remove()}))}))}))}(n.groups,a),f()}))}(r)}))})),u("oem-vehicle-pagination",n.pagination,(function(e){i=e,s()}))}))}function u(e,t,a){var n=document.getElementById(e);!t||t.total_pages<=1?n.innerHTML="":(n.innerHTML="Pag '+t.page+" de "+t.total_pages+" ("+t.total+" total)',n.querySelectorAll("button").forEach((function(e){e.addEventListener("click",(function(){a(parseInt(e.getAttribute("data-p")))}))})))}function p(){document.getElementById("oem-vehicle-select").style.display="block",document.getElementById("oem-part-entry").style.display="none",e=null,s()}function m(){0!==a.length?c("/api/captura/vehicles/"+e+"/status",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({status:"completed"})}).then((function(){o("Vehiculo completado"),p()})):o("Registra al menos una parte antes de marcar como terminado","error")}function v(e){return''}function f(){var e=a.length,t=Math.min(100,Math.round(e/63*100));document.getElementById("oem-progress-fill").style.width=t+"%",document.getElementById("oem-progress-text").textContent=e+" partes registradas",document.querySelectorAll(".category-header h3").forEach((function(e){var t=e.closest(".category-section").querySelectorAll(".part-row.saved"),a=e.textContent.replace(/\s*\(\d+\)$/,"");e.textContent=a+" ("+t.length+")"}))}document.getElementById("oem-model-filter").addEventListener("input",(function(){clearTimeout(l),l=setTimeout((function(){i=1,s()}),400)}));function g(e){e=e||1;var t=document.getElementById("aftermarket-search").value,a=document.getElementById("aftermarket-list");a.innerHTML='
'})).join(""),t.forEach((function(e){y(e.id_part)})),a.querySelectorAll(".af-save-btn").forEach((function(e){e.addEventListener("click",(function(){!function(e){var t=e.getAttribute("data-part-id"),a=e.querySelector(".af-manufacturer").value,n=e.querySelector(".af-partnum").value.trim(),r=e.querySelector(".af-name").value.trim(),i=e.querySelector(".af-quality").value,d=e.querySelector(".af-price").value,l=e.querySelector(".af-warranty").value;if(!n)return void o("Ingresa el numero de parte aftermarket","error");c("/api/admin/aftermarket",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({oem_part_id:parseInt(t),manufacturer_id:parseInt(a),part_number:n,name:r,quality_tier:i,price_usd:d?parseFloat(d):null,warranty_months:l?parseInt(l):null})}).then((function(){o("Intercambio guardado: "+n),e.querySelector(".af-partnum").value="",e.querySelector(".af-name").value="",e.querySelector(".af-price").value="",e.querySelector(".af-warranty").value="",y(parseInt(t))})).catch((function(e){o(e.message,"error")}))}(e.closest(".part-detail-card"))}))})),u("aftermarket-pagination",e.pagination,(function(e){g(e)}))}))}function y(e){c("/api/captura/parts/"+e+"/aftermarket").then((function(t){var a=document.querySelector('[data-af-list="'+e+'"]');if(0!==t.length){var n='
\n ';try{const t=await fetch(`/api/vehicles/${e}/categories`);if(!t.ok)throw new Error("Error al cargar categorías");this.allCategories=await t.json(),this.displayCategories()}catch(e){console.error("Error:",e),a.innerHTML=`\n
\n ';try{let t;t=this.selectedVehicleId?`/api/vehicles/${this.selectedVehicleId}/groups?category_id=${e}`:`/api/categories/${e}/groups`;const a=await fetch(t);if(!a.ok)throw new Error("Error al cargar grupos");const s=await a.json();let n=[];if(this.selectedVehicleId&&(10===e||11===e))try{const t=await fetch(`/api/vehicles/${this.selectedVehicleId}/diagrams/by-category?category_id=${e}`);if(t.ok){const e=await t.json();for(const t of e)n.push(...t.diagrams)}}catch(e){console.error("Error loading diagrams for strip:",e)}this.displayGroups(s,e,n)}catch(e){console.error("Error:",e),a.innerHTML=`\n
\n ';this.openModalWithFocus("partDetailModal");try{const[a,s,n]=await Promise.all([fetch(`/api/parts/${e}`),fetch(`/api/parts/${e}/alternatives`),fetch(`/api/parts/${e}/cross-references`)]);if(!a.ok)throw new Error("Error al cargar detalle de la parte");const i=await a.json(),r=s.ok?await s.json():[],o=n.ok?await n.json():[];t.innerHTML=`\n
\n \n Vehiculo no encontrado en la base de datos\n
\n \n
\n
\n `,a.innerHTML=`\n
\n
\n ${t}\n VIN Valido\n
\n\n
${r} ${n} ${i} ${l}
\n\n
\n
\n
\n Marca\n ${n}\n
\n
\n Modelo\n ${i}\n
\n
\n Año\n ${r}\n
\n
\n Motor\n ${o}\n
\n
\n
\n
\n Combustible\n ${c}\n
\n
\n Transmision\n ${m}\n
\n
\n Traccion\n ${d}\n
\n
\n Pais\n ${h}\n
\n
\n
\n\n ${v}\n
\n `}async viewVinParts(e,t){const a=document.getElementById("vinDecoderModal");a&&a.classList.remove("active");const s=document.getElementById("searchResultsContent");document.getElementById("searchResultsModalLabel").innerHTML=` Partes para VIN: ${e.substring(0,8)}...`,s.innerHTML='\n
\n \n
Cargando partes...
\n
\n ';this.openModalWithFocus("searchResultsModal");try{const a=await fetch(`/api/vin/${encodeURIComponent(e)}/parts`);if(!a.ok)throw new Error("Error al cargar partes");const s=await a.json(),n=s.parts||s;this.displayVinParts(n,e,t)}catch(e){console.error("Error:",e),s.innerHTML=t?`\n
\n No se encontraron partes especificas para este VIN.\n
\n `),s.innerHTML=i}searchManuallyFromVin(e,t,a){const s=bootstrap.Modal.getInstance(document.getElementById("vinDecoderModal"));s&&s.hide(),setTimeout((async()=>{try{const t=await fetch("/api/brands");if(t.ok){const a=(await t.json()).find((t=>t.toLowerCase()===e.toLowerCase()||t.toLowerCase().includes(e.toLowerCase())||e.toLowerCase().includes(t.toLowerCase())));if(a)return void this.goToModels(a)}alert(`La marca "${e}" no se encontro en la base de datos. Mostrando todas las marcas disponibles.`),this.goToBrands()}catch(e){console.error("Error:",e),this.goToBrands()}}),300)}}let dashboard;document.addEventListener("DOMContentLoaded",(()=>{dashboard=new VehicleDashboard}));
\ No newline at end of file
diff --git a/dashboard/enhanced-search.min.js b/dashboard/enhanced-search.min.js
new file mode 100644
index 0000000..258eb5c
--- /dev/null
+++ b/dashboard/enhanced-search.min.js
@@ -0,0 +1 @@
+const enhancedSearch={config:{minChars:2,debounceMs:300,maxResults:8,maxRecent:5,storageKey:"nexus_recent_searches"},state:{query:"",results:{parts:[],vehicles:[]},highlightedIndex:-1,isOpen:!1,isLoading:!1,filtersVisible:!1,debounceTimer:null},elements:{},init(){this.cacheElements(),this.loadCategories(),this.renderRecentSearches(),this.setupClickOutside()},cacheElements(){this.elements={input:document.getElementById("searchInput"),dropdown:document.getElementById("searchDropdown"),loading:document.getElementById("searchLoading"),filters:document.getElementById("searchFilters"),recent:document.getElementById("searchRecent"),recentItems:document.getElementById("searchRecentItems"),resultsContainer:document.getElementById("searchResultsContainer"),partsResults:document.getElementById("partsResults"),partsResultsList:document.getElementById("partsResultsList"),vehiclesResults:document.getElementById("vehiclesResults"),vehiclesResultsList:document.getElementById("vehiclesResultsList"),noResults:document.getElementById("searchNoResults"),footer:document.getElementById("searchFooter"),categoryFilter:document.getElementById("searchCategoryFilter"),typeFilter:document.getElementById("searchTypeFilter")}},async loadCategories(){try{const e=await fetch("/api/categories"),t=await e.json();this.elements.categoryFilter&&(this.elements.categoryFilter.innerHTML=''+this.flattenCategories(t).map((e=>``)).join(""))}catch(e){console.error("Error loading categories:",e)}},flattenCategories(e,t=[]){return e.forEach((e=>{t.push(e),e.children&&e.children.length&&this.flattenCategories(e.children,t)})),t},onInput(e){this.state.query=e.trim(),this.state.debounceTimer&&clearTimeout(this.state.debounceTimer),this.state.query.length{this.performSearch()}),this.config.debounceMs)},onKeydown(e){const t=this.getAllResultItems();switch(e.key){case"ArrowDown":e.preventDefault(),this.highlightNext(t);break;case"ArrowUp":e.preventDefault(),this.highlightPrevious(t);break;case"Enter":e.preventDefault(),this.state.highlightedIndex>=0&&t[this.state.highlightedIndex]?t[this.state.highlightedIndex].click():this.state.query.length>=this.config.minChars&&this.viewAllResults();break;case"Escape":this.close(),this.elements.input.blur();break;case"Tab":if(this.state.isOpen&&this.state.highlightedIndex>=0&&t[this.state.highlightedIndex]){e.preventDefault();const s=t[this.state.highlightedIndex].dataset.autocomplete;s&&this.elements.input&&(this.elements.input.value=s,this.state.query=s,this.performSearch())}}},onFocus(){this.state.query.length>=this.config.minChars?this.open():this.showRecent()},getAllResultItems(){return Array.from(this.elements.dropdown.querySelectorAll(".search-result-item"))},highlightNext(e){0!==e.length&&(this.state.highlightedIndex++,this.state.highlightedIndex>=e.length&&(this.state.highlightedIndex=0),this.updateHighlight(e))},highlightPrevious(e){0!==e.length&&(this.state.highlightedIndex--,this.state.highlightedIndex<0&&(this.state.highlightedIndex=e.length-1),this.updateHighlight(e))},updateHighlight(e){e.forEach(((e,t)=>{e.classList.toggle("highlighted",t===this.state.highlightedIndex),t===this.state.highlightedIndex&&e.scrollIntoView({block:"nearest"})}))},async performSearch(){const e=this.state.query,t=this.elements.categoryFilter?.value,s=this.elements.typeFilter?.value||"all";this.showLoading(!0),this.open();try{let i=`/api/search?q=${encodeURIComponent(e)}&limit=${this.config.maxResults}`;t&&(i+=`&category_id=${t}`),"all"!==s&&(i+=`&type=${s}`);const a=await fetch(i),n=await a.json();this.state.results={parts:n.parts||[],vehicles:n.vehicles||[],vehicleParts:n.vehicle_parts||[],matchedVehicle:n.matched_vehicle||null},this.renderResults()}catch(e){console.error("Search error:",e),this.showNoResults()}finally{this.showLoading(!1)}},applyFilters(){this.state.query.length>=this.config.minChars&&this.performSearch()},renderResults(){const{parts:e,vehicles:t,vehicleParts:s,matchedVehicle:i}=this.state.results,a=s&&s.length>0,n=e.length>0||t.length>0||a;if(this.elements.recent&&(this.elements.recent.style.display="none"),a&&i&&this.elements.partsResults&&this.elements.partsResultsList){this.elements.partsResults.style.display="block";const e=`\n
"})).join(""),s.querySelectorAll(".customer-dropdown-item").forEach((function(e){e.addEventListener("click",(function(){d(parseInt(e.getAttribute("data-id")))}))}))),s.style.display="block"}))}),300)})),o.addEventListener("blur",(function(){setTimeout((function(){s.style.display="none"}),200)})),document.getElementById("btn-change-customer").addEventListener("click",(function(){e=null,document.getElementById("customer-info").style.display="none",document.getElementById("customer-select").style.display="block",o.value="",o.focus(),_()})),document.getElementById("btn-new-customer").addEventListener("click",(function(){document.getElementById("modal-new-customer").style.display="flex",document.getElementById("nc-name").focus()})),document.getElementById("nc-cancel").addEventListener("click",(function(){document.getElementById("modal-new-customer").style.display="none"})),document.getElementById("nc-save").addEventListener("click",(function(){var e=document.getElementById("nc-name").value.trim();e?a("/api/pos/customers",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,rfc:document.getElementById("nc-rfc").value.trim()||null,business_name:document.getElementById("nc-business").value.trim()||null,phone:document.getElementById("nc-phone").value.trim()||null,email:document.getElementById("nc-email").value.trim()||null,address:document.getElementById("nc-address").value.trim()||null,credit_limit:parseFloat(document.getElementById("nc-credit").value)||0,payment_terms:parseInt(document.getElementById("nc-terms").value)||30})}).then((function(t){n("Cliente creado: "+e),document.getElementById("modal-new-customer").style.display="none",d(t.id),["nc-name","nc-rfc","nc-business","nc-phone","nc-email","nc-address"].forEach((function(e){document.getElementById(e).value=""})),document.getElementById("nc-credit").value="0",document.getElementById("nc-terms").value="30"})).catch((function(e){n(e.message,"error")})):n("Ingresa el nombre del cliente","error")}));var u=null,l=document.getElementById("part-search"),m=document.getElementById("part-results"),p=[],y=-1;function f(){m.querySelectorAll(".part-result-item").forEach((function(e,t){t===y?(e.classList.add("part-result-active"),e.scrollIntoView({block:"nearest"})):e.classList.remove("part-result-active")}))}function v(e){var n;e>=0&&e
"})).join(""),p.querySelectorAll(".nav-card").forEach((function(e){e.addEventListener("click",(function(){j.nxPartType={slug:this.dataset.slug,name:this.dataset.name},Ce(1)}))}))}else pe("Sin tipos","No hay tipos de parte en este subgrupo.")}))}()}))}))):pe("Sin subgrupos","No hay subgrupos con partes disponibles.")}))}))}))):pe("Sin supplies","No hay grupos de Shop Supplies disponibles.")}))}if(!(!j.engine||!j.engine.id_mye))return j.level="categories",void Te();try{De()}catch(e){}j.level="brands",j.brand=j.model=j.year=j.engine=null,he()}}else alert("Catálogo OEM próximamente. Por favor usa el modo Local o Shop Supplies.")}},Re(),se(),function(){var a="nexus:years-all",t=sessionStorage.getItem(a);if(t){var n=JSON.parse(t),o=n.data||n||[];if(!o.length){o=[];for(var i=2026;i>=1990;i--)o.push({id_year:i,year_car:i})}F.innerHTML=''+o.map((function(e){return'"})).join("")}else de(e+"/years-all").then((function(e){if(e){var t=e.data||e;if(!t||!t.length){t=[];for(var n=2026;n>=1990;n--)t.push({id_year:n,year_car:n})}sessionStorage.setItem(a,JSON.stringify(e)),F.innerHTML=''+t.map((function(e){return'"})).join("")}})).catch((function(){for(var e=[],a=2026;a>=1990;a--)e.push(a);F.innerHTML=''+e.map((function(e){return'"})).join("")}))}(),he()}else window.location.href="/pos/login";function se(){document.querySelectorAll("#modeToggle button").forEach((function(e){e.getAttribute("data-mode")===q?e.classList.add("is-active"):e.classList.remove("is-active")}))}function le(){if(!R){var e=JSON.parse(JSON.stringify(j));e.page=O,history.pushState(e,"","/pos/catalog")}}function de(e){V&&(V.abort(),V=null);var a={headers:t};return(0===e.indexOf("/pos/api/")&&-1!==e.indexOf("mode=")||-1!==e.indexOf("/years")||-1!==e.indexOf("/brands")||-1!==e.indexOf("/models")||-1!==e.indexOf("/engines")||-1!==e.indexOf("/categories")||-1!==e.indexOf("/groups")||-1!==e.indexOf("/part-types")||-1!==e.indexOf("/parts")||-1!==e.indexOf("/search"))&&(V=new AbortController,a.signal=V.signal),fetch(e,a).then((function(e){return 401===e.status?(localStorage.removeItem("pos_token"),window.location.href="/pos/login",null):e.json()})).catch((function(e){return"AbortError"===e.name||console.error("API error:",e),null}))}function ce(){l.classList.add("is-visible"),p.innerHTML="",m.style.display="none",m.innerHTML="",d.classList.remove("is-visible"),v.innerHTML="";var e=document.getElementById("diagLink");e&&"categories"!==j.level&&(e.style.display="none")}function ue(){l.classList.remove("is-visible")}function pe(e,a){c.textContent=e,u.textContent=a||"",d.classList.add("is-visible"),p.innerHTML="",m.style.display="none"}function me(e){return(parseFloat(e)||0).toFixed(2)}function ve(e){if(!e)return"";var a=document.createElement("div");return a.textContent=e,a.innerHTML}function ge(){var e=[];e.push({label:"Catalogo",action:"loadBrands"}),j.brand&&e.push({label:j.brand.name,action:"loadModels"}),j.model&&e.push({label:j.model.name,action:"loadYears"}),j.year&&e.push({label:String(j.year.year),action:"loadEngines"}),j.engine&&e.push({label:j.engine.name,action:"loadCategories"}),j.nxGroup?e.push({label:j.nxGroup.name,action:"loadNxSubgroups"}):j.category&&e.push({label:j.category.name,action:"loadGroups"}),j.nxSubgroup?e.push({label:j.nxSubgroup.name,action:"loadNxPartTypes"}):j.group&&e.push({label:j.group.name,action:"loadPartTypes"}),j.nxPartType?e.push({label:j.nxPartType.name,action:null}):j.partType&&e.push({label:j.partType.name,action:null});for(var a="",t=0;t0&&(a+='/'),t'+ve(e[t].label)+"":a+=''+ve(e[t].label)+"";n.innerHTML=a,n.querySelectorAll("[data-bc-action]").forEach((function(e){e.addEventListener("click",(function(){var e=this.dataset.bcAction;"loadBrands"===e?(ye(),he()):"loadModels"===e?(fe("models"),be()):"loadYears"===e?(fe("years"),xe()):"loadEngines"===e?(fe("engines"),Ee()):"loadCategories"===e?(fe("categories"),Te()):"loadGroups"===e?(fe("groups"),ke()):"loadPartTypes"===e?(fe("part_types"),Ie()):"loadNxSubgroups"===e?(fe("groups"),Se()):"loadNxPartTypes"===e&&(fe("part_types"),Le())}))}))}function ye(){j.level="brands",le(),j.brand=j.model=j.year=j.engine=j.category=j.group=j.partType=null,j.nxGroup=j.nxSubgroup=j.nxPartType=null}function fe(e){var a=["brands","models","years","engines","categories","groups","part_types","parts"].indexOf(e);if(a<=0)ye();else{j.level=e;for(var t=[null,["model"],["year"],["engine"],["category","nxGroup"],["group","nxSubgroup"],["partType","nxPartType"],null],n=a;n=0?"":"none"}))}}function he(){j.level="brands",le(),ge(),r.textContent="Selecciona una marca",_e(!0),ce();var a="nexus:brands:"+q,t=sessionStorage.getItem(a);if(t)return ue(),void function(e){if(!e||!e.data||!e.data.length)return e?void pe("Sin marcas","El catalogo no tiene marcas con partes disponibles."):void ze();p.className="nav-grid",p.innerHTML=e.data.map((function(e){return'
'+ve(e.name_brand)+"
"})).join(""),p.querySelectorAll(".nav-card").forEach((function(e){e.addEventListener("click",(function(){j.brand={id:parseInt(this.dataset.brandId),name:this.dataset.name},be()}))}))}(JSON.parse(t));de(e+"/brands?mode="+q).then((function(e){if(ue(),e&&e.data&&sessionStorage.setItem(a,JSON.stringify(e)),!e||!e.data||!e.data.length)return e?void pe("Sin marcas","El catalogo no tiene marcas con partes disponibles."):void ze();p.className="nav-grid",p.innerHTML=e.data.map((function(e){return'
'+ve(e.name_brand)+"
"})).join(""),p.querySelectorAll(".nav-card").forEach((function(e){e.addEventListener("click",(function(){j.brand={id:parseInt(this.dataset.brandId),name:this.dataset.name},be()}))}))}))}function be(){j.level="models",le(),ge(),r.textContent="Modelos de "+j.brand.name,_e(!0),ce(),de(e+"/models?brand_id="+j.brand.id).then((function(e){ue(),e&&e.data&&e.data.length?(p.className="nav-grid",p.innerHTML=e.data.map((function(e){return'
'+ve(e.display_name||e.name_model)+"
"})).join(""),p.querySelectorAll(".nav-card").forEach((function(e){e.addEventListener("click",(function(){j.model={id:parseInt(this.dataset.modelId),name:this.dataset.name},xe()}))}))):pe("Sin modelos","No hay modelos con partes para "+j.brand.name)}))}function xe(){j.level="years",le(),ge(),r.textContent=j.brand.name+" "+j.model.name+" — Anios",_e(!1),ce(),de(e+"/years?model_id="+j.model.id).then((function(e){ue(),e&&e.data&&e.data.length?(p.className="nav-grid nav-grid--years",p.innerHTML=e.data.map((function(e){return'
'+e.year_car+"
"})).join(""),p.querySelectorAll(".nav-card").forEach((function(e){e.addEventListener("click",(function(){j.year={id:parseInt(this.dataset.yearId),year:parseInt(this.dataset.year)},Ee()}))}))):pe("Sin anios","No hay anios con partes para este modelo.")}))}function Ee(){j.level="engines",le(),ge(),r.textContent=j.brand.name+" "+j.model.name+" "+j.year.year+" — Motor",_e(!1),ce(),de(e+"/engines?model_id="+j.model.id+"&year_id="+j.year.id).then((function(e){if(ue(),e&&e.data&&e.data.length){if(1===e.data.length){var a=e.data[0];return j.engine={id_mye:a.id_mye,name:a.name_engine+(a.trim_level?" "+a.trim_level:"")},void Te()}p.className="nav-grid",p.innerHTML=e.data.map((function(e){var a=e.name_engine+(e.trim_level?" — "+e.trim_level:"");return'
'+ve(e.name_engine)+"
"+(e.trim_level?'
'+ve(e.trim_level)+"
":"")+"
"})).join(""),p.querySelectorAll(".nav-card").forEach((function(e){e.addEventListener("click",(function(){j.engine={id_mye:parseInt(this.dataset.myeId),name:this.dataset.name},Te()}))}))}else pe("Sin motores","No hay configuraciones de motor para esta combinacion.")}))}function ke(){j.level="groups",le(),ge(),r.textContent=j.category.name,_e(!0),ce(),de(e+"/groups?mye_id="+j.engine.id_mye+"&category_id="+j.category.id).then((function(e){ue(),e&&e.data&&e.data.length?(p.className="nav-grid",p.innerHTML=e.data.map((function(e){return'
'+ve(e.name)+'
'+e.part_count+" partes
"})).join(""),p.querySelectorAll(".nav-card").forEach((function(e){e.addEventListener("click",(function(){j.group={id:parseInt(this.dataset.groupId),name:this.dataset.name},j.partType=null,Ie()}))}))):pe("Sin subcategorias","No hay subcategorias para "+j.category.name)}))}function Ie(){j.level="part_types",j.partType=null,le(),ge(),r.textContent=j.group.name,_e(!0),ce(),de(e+"/part-types?mye_id="+j.engine.id_mye+"&group_id="+j.group.id).then((function(e){if(ue(),e&&e.data&&e.data.length){if(1===e.data.length){var a=e.data[0];return j.partType={slug:a.slug,name:a.name},void Me(1)}p.className="nav-grid",p.innerHTML=e.data.map((function(e){var a=e.sample_image?'':"";return'
"})).join(""),p.querySelectorAll(".nav-card").forEach((function(e){e.addEventListener("click",(function(){j.nxGroup={slug:this.dataset.slug,name:this.dataset.name},j.nxSubgroup=null,j.nxPartType=null,Se()}))}))):pe("Sin categorias Local","Ninguna parte de este vehiculo mapea al catalogo Local.")}))):(j.level="categories",le(),ge(),r.textContent="Categorias de partes",_e(!0),ce(),de(e+"/categories?mye_id="+j.engine.id_mye).then((function(e){ue(),e&&e.data&&e.data.length?(p.className="nav-grid",p.innerHTML=e.data.map((function(e){return'
'+ve(e.name)+'
'+e.part_count+" partes
"})).join(""),p.querySelectorAll(".nav-card").forEach((function(e){e.addEventListener("click",(function(){j.category={id:parseInt(this.dataset.catId),name:this.dataset.name},ke()}))}))):pe("Sin categorias","No hay partes catalogadas para este vehiculo.")})))}function Se(){j.level="groups",le(),ge(),r.textContent=j.nxGroup.name,_e(!0),ce(),de(e+"/groups?mode=local&mye_id="+j.engine.id_mye+"&category_slug="+encodeURIComponent(j.nxGroup.slug)).then((function(e){ue(),e&&e.data&&e.data.length?(p.className="nav-grid",p.innerHTML=e.data.map((function(e){return'
'+ve(e.name)+'
'+e.part_count+" partes
"})).join(""),p.querySelectorAll(".nav-card").forEach((function(e){e.addEventListener("click",(function(){j.nxSubgroup={slug:this.dataset.slug,name:this.dataset.name},j.nxPartType=null,Le()}))}))):pe("Sin subcategorias","No hay subcategorias en "+j.nxGroup.name)}))}function Le(){j.level="part_types",j.nxPartType=null,le(),ge(),r.textContent=j.nxSubgroup.name,_e(!0),ce(),de(e+"/part-types?mode=local&mye_id="+j.engine.id_mye+"&group_slug="+encodeURIComponent(j.nxGroup.slug)+"&subgroup_slug="+encodeURIComponent(j.nxSubgroup.slug)).then((function(e){if(ue(),e&&e.data&&e.data.length){if(1===e.data.length){var a=e.data[0];return j.nxPartType={slug:a.slug,name:a.name},void Me(1)}p.className="nav-grid",p.innerHTML=e.data.map((function(e){var a=e.sample_image?'':"";return'
"})).join(""),p.querySelectorAll(".nav-card").forEach((function(e){e.addEventListener("click",(function(){j.nxPartType={slug:this.dataset.slug,name:this.dataset.name},Me(1)}))}))}else pe("Sin tipos de parte","No hay tipos de parte en "+j.nxSubgroup.name)}))}function Ce(a){j.level="parts",le(),O=a||1,ge(),r.textContent=j.nxPartType.name,_e(!1),ce(),p.innerHTML="",de(e+"/shop-supplies/parts?group_slug="+encodeURIComponent(j.nxGroup.slug)+"&subgroup_slug="+encodeURIComponent(j.nxSubgroup.slug)+"&part_type_slug="+encodeURIComponent(j.nxPartType.slug)+"&page="+O+"&per_page=30").then((function(e){ue(),e&&e.data&&e.data.length?(m.style.display="",m.innerHTML=e.data.map((function(e){var a;a=e.in_stock_network||e.bodega_count>0?''+e.bodega_count+" bodega"+(e.bodega_count>1?"s":"")+"":'Sin stock';var t=e.image_url?'':'',n=1===e.priority_tier?" part-card--tier1":2===e.priority_tier?" part-card--tier2":"",o="";if(e.manufacturer){var i=1===e.priority_tier?'★':"";o='
"})).join(""),m.querySelectorAll(".part-card").forEach((function(e){e.addEventListener("click",(function(){var e=this.dataset.partId;"string"==typeof e&&0===e.indexOf("inv:")||we(parseInt(e))}))})),e.pagination&&Be(e.pagination)}else pe("Sin partes","No hay partes en este grupo.")}))}function Be(e){if(!e||e.total_pages<=1)v.innerHTML="";else{var a="";e.page<=1?a+='':a+='',function(e,a){if(a<=7){for(var t=[],n=1;n<=a;n++)t.push(n);return t}var o=[1];e>3&&o.push("...");for(var i=Math.max(2,e-1);i<=Math.min(a-1,e+1);i++)o.push(i);e...':t===e.page?a+='":a+='"})),e.page>=e.total_pages?a+='':a+='',v.innerHTML=a,v.querySelectorAll("[data-page]").forEach((function(e){e.addEventListener("click",(function(){g.scrollTo({top:0,behavior:"smooth"}),Me(parseInt(this.dataset.page))}))}))}}function we(a){_.innerHTML='
'})).join(""),M.textContent="$"+me(a),B.textContent="$"+me(t),w.textContent="$"+me(a+t)}function $e(){var e=T.classList.toggle("open");S.classList.toggle("open",e)}function Ve(){$.length&&(localStorage.setItem("pos_cart",JSON.stringify($)),window.location.href="/pos/sale")}function ze(){document.getElementById("offlineBanner").style.display="",document.getElementById("offlineBannerText").innerHTML="Modo offline — Mostrando solo tu inventario local.",r.textContent="Inventario local",_e(!1),pe("Sin conexion al catalogo","Verifica tu conexion. El catalogo TecDoc requiere acceso al servidor central.")}function Je(){var e=W.value;if(e){var a=F.options[F.selectedIndex].text,t=Y.options[Y.selectedIndex].text,n=Q.options[Q.selectedIndex].text,o=W.options[W.selectedIndex].text;j.brand={id:parseInt(Y.value),name:t},j.model={id:parseInt(Q.value),name:n},j.year={id:parseInt(F.value),year:a},j.engine={id_mye:parseInt(e),name:o},j.level="categories",le(),Te(),setTimeout((function(){var e=document.getElementById("pageBody");e&&e.scrollIntoView({behavior:"smooth",block:"start"})}),300)}}function De(){F.value="",Y.innerHTML='',Q.innerHTML='',W.innerHTML='',Y.disabled=!0,Q.disabled=!0,W.disabled=!0,X.style.display="none",j.level="brands",j.brand=null,j.model=null,j.year=null,j.engine=null,j.category=null,j.group=null,j.partType=null,j.nxGroup=null,j.nxSubgroup=null,j.nxPartType=null,O=1,le(),he()}function Ze(e,a){ae.style.display=e?"":"none",ae.textContent=e,ae.style.color=a?"var(--color-error)":"var(--color-text-muted)"}function Fe(){var a=(ee.value||"").trim().toUpperCase();!a||a.length<5?Ze("Ingresa una placa valida (Ej: ABC-1234).",!0):(Ze("Buscando placa...",!1),de(e+"/plate/"+encodeURIComponent(a)).then((function(e){if(e)if(e.error)Ze(e.error,!0);else{if(!e.found)return ae.style.display="",ae.innerHTML='Placa no registrada. Registrar vehiculo',void(ae.style.color="var(--color-warning, #e6a700)");var a=[];e.year&&a.push(e.year),e.make&&a.push(e.make),e.model&&a.push(e.model);var t=a.join(" ")||"Vehiculo encontrado",n=e.catalog_match;n&&n.brand_id?(Ze(t+" — Cargando catalogo...",!1),Qe(n,e)):Ze(t+" — No encontrado en el catalogo TecDoc.",!1)}else Ze("Error de conexion al buscar placa.",!0)})))}function Ye(){var a=(oe.value||"").trim().toUpperCase();17===a.length?(We("Decodificando VIN...",!1),de(e+"/vin/"+encodeURIComponent(a)).then((function(e){if(e)if(!e.error||e.make){var a=[];e.year&&a.push(e.year),e.make&&a.push(e.make),e.model&&a.push(e.model),e.engine&&a.push(e.engine);var t=a.join(" ")||"Vehiculo no reconocido",n=e.catalog_match;n&&n.brand_id?(We(t+" — Encontrado en catalogo, cargando...",!1),Qe(n,e)):We(t+" — No encontrado en el catalogo TecDoc.",!1)}else We(e.error,!0);else We("Error de conexion al decodificar VIN.",!0)}))):We("El VIN debe tener exactamente 17 caracteres.",!0)}function Qe(a,t){a.year_id&&(F.value=String(a.year_id),de(e+"/brands?year_id="+a.year_id).then((function(t){var n=t&&(t.data||t);n&&(Y.innerHTML=''+n.map((function(e){return'"})).join(""),Y.disabled=!1,X.style.display="",a.brand_id&&(Y.value=String(a.brand_id),de(e+"/models?brand_id="+a.brand_id+"&year_id="+a.year_id).then((function(t){var n=t&&(t.data||t);n&&(Q.innerHTML=''+n.map((function(e){return'"})).join(""),Q.disabled=!1,a.model_id&&(Q.value=String(a.model_id),de(e+"/engines?model_id="+a.model_id+"&year_id="+a.year_id).then((function(e){var t=e&&(e.data||e);t&&(W.innerHTML=''+t.map((function(e){var a=e.name_engine+(e.trim_level?" ("+e.trim_level+")":"");return'"})).join(""),W.disabled=!1,a.id_mye?(W.value=String(a.id_mye),Je(),We("Vehiculo cargado desde VIN.",!1)):1===t.length?(W.value=t[0].id_mye,Je(),We("Vehiculo cargado desde VIN.",!1)):We("Selecciona el motor para continuar.",!1))}))))}))))})))}function We(e,a){ie.style.display=e?"":"none",ie.textContent=e,ie.style.color=a?"var(--color-error)":"var(--color-text-muted)"}}();
\ No newline at end of file
diff --git a/pos/static/js/chat.min.js b/pos/static/js/chat.min.js
new file mode 100644
index 0000000..04f8242
--- /dev/null
+++ b/pos/static/js/chat.min.js
@@ -0,0 +1 @@
+!function(){"use strict";let e=!1,t=!1,n=!1,a=null;const s=[],i="webkitSpeechRecognition"in window||"SpeechRecognition"in window;function c(){const e=document.createElement("button");e.className="chat-fab",e.id="chatFab",e.title="Asistente IA",e.innerHTML="💬",e.setAttribute("aria-label","Abrir asistente IA");const t=document.createElement("div");t.className="chat-panel",t.id="chatPanel",t.innerHTML=`\n
\n
Asistente IA — Buscar partes
\n \n
\n
\n
Hola, soy el asistente de Nexus. Dime que refaccion necesitas y te ayudo a encontrarla.
\n
\n \n
\n
\n
\n \n \n \n ${i?'':""}\n \n
\n `,document.body.appendChild(e),document.body.appendChild(t),e.addEventListener("click",l),document.getElementById("chatClose").addEventListener("click",l),document.getElementById("chatSend").addEventListener("click",u),document.getElementById("chatInput").addEventListener("keydown",(function(e){"Enter"!==e.key||e.shiftKey||(e.preventDefault(),u())})),document.getElementById("chatCam").addEventListener("click",(function(){document.getElementById("chatImageInput").click()})),document.getElementById("chatImageInput").addEventListener("change",r),i&&document.getElementById("chatMic").addEventListener("click",o),document.getElementById("chatInput").addEventListener("input",(function(){this.style.height="auto",this.style.height=Math.min(this.scrollHeight,80)+"px"}))}function o(){n?function(){a&&(a.abort(),a=null);n=!1;const e=document.getElementById("chatMic");e&&e.classList.remove("listening")}():function(){const e=window.SpeechRecognition||window.webkitSpeechRecognition;if(!e)return;a=new e,a.lang="es-MX",a.continuous=!1,a.interimResults=!0;const t=document.getElementById("chatInput"),s=document.getElementById("chatMic"),i=t.placeholder;a.onstart=function(){n=!0,s.classList.add("listening"),t.placeholder="Escuchando...",t.value=""},a.onresult=function(e){let n="",a="";for(let t=e.resultIndex;t5242880)return void m("La imagen es muy grande (max 5MB).","ai");const a=new FileReader;a.onload=function(e){const n=document.getElementById("chatMessages"),a=document.getElementById("chatTyping"),i=document.createElement("div");i.className="chat-msg user chat-msg-image",i.innerHTML='Identificar esta parte',n.insertBefore(i,a),y();var c=e.target.result,o="Identifica esta parte automotriz y sugiere terminos de busqueda.";s.push({role:"user",content:o}),s.length>20&&s.splice(0,2),t=!0,document.getElementById("chatSend").disabled=!0,g(!0);var r=d();fetch("/pos/api/chat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+r},body:JSON.stringify({message:o,image:c,history:s.slice(-10)})}).then((function(e){return e.json()})).then((function(e){const t=e.response||"No pude identificar la parte. Intenta describirla con texto.";m(t,"ai"),s.push({role:"assistant",content:t}),e.vehicle&&e.vehicle.brand_id&&p(e.vehicle),e.search_results&&e.search_results.length>0&&h(e.search_results)})).catch((function(e){m("Error al procesar imagen: "+e.message,"ai")})).finally((function(){t=!1,document.getElementById("chatSend").disabled=!1,g(!1)}))},a.readAsDataURL(n)}function l(){e=!e;const t=document.getElementById("chatPanel"),n=document.getElementById("chatFab");e?(t.classList.add("open"),n.style.display="none",document.getElementById("chatInput").focus()):(t.classList.remove("open"),n.style.display="flex")}function d(){return window.__pos&&window.__pos.token?window.__pos.token:localStorage.getItem("pos_token")||""}async function u(){if(t)return;const e=document.getElementById("chatInput"),n=e.value.trim();if(n){e.value="",e.style.height="auto",m(n,"user"),s.push({role:"user",content:n}),s.length>20&&s.splice(0,2),t=!0,document.getElementById("chatSend").disabled=!0,g(!0);try{const e=d(),t=await fetch("/pos/api/chat",{method:"POST",headers:{"Content-Type":"application/json",Authorization:"Bearer "+e},body:JSON.stringify({message:n,history:s.slice(-10)})}),a=await t.json();if(!t.ok)return void m("Error: "+(a.error||t.statusText),"ai");const i=a.response||"Sin respuesta.";m(i,"ai"),s.push({role:"assistant",content:i}),a.vehicle&&a.vehicle.brand_id&&p(a.vehicle),a.search_results&&a.search_results.length>0&&h(a.search_results)}catch(e){m("Error de conexion: "+e.message,"ai")}finally{t=!1,document.getElementById("chatSend").disabled=!1,g(!1)}}}function m(e,t){const n=document.getElementById("chatMessages"),a=document.getElementById("chatTyping"),s=document.createElement("div");s.className="chat-msg "+t,s.textContent=e,n.insertBefore(s,a),y()}function p(e){const t=document.getElementById("chatMessages"),n=document.getElementById("chatTyping"),a=document.createElement("div");a.className="chat-vehicle-banner";let s=""+f(e.brand||"")+" "+f(e.model||"")+"";e.year&&(s+=" "+e.year),e.mye_options&&e.mye_options.length>0&&(s+=" Motorizaciones encontradas:",e.mye_options.forEach((function(e){s+=" • "+f(e.engine),e.trim&&(s+=" ("+f(e.trim)+")")}))),a.innerHTML=s,t.insertBefore(a,n),y()}function h(e){const t=document.getElementById("chatMessages"),n=document.getElementById("chatTyping"),a=document.createElement("div");a.className="chat-parts",e.slice(0,8).forEach((function(e){const t=document.createElement("div");t.className="chat-part-card";const n="local"===e.source,s=e.local_stock||0,i=s>0?"in-stock":"",c=s>0?s+" en stock":"Sin stock local",o=e.name_es||e.name_part||"",r=e.oem_part_number||e.part_number||"",d=e.brand||"",u=e.price_1?"$"+parseFloat(e.price_1).toFixed(2):"",m=n?'MI INVENTARIO':'CATÁLOGO';t.innerHTML='
'+f(r)+m+(u?" — "+u:"")+'
'+f(o)+(d?' ('+f(d)+")":"")+'
'+f(c)+"
",t.addEventListener("click",(function(){e.id_part&&"function"==typeof window.openPartDetail&&(window.openPartDetail(e.id_part),l())})),a.appendChild(t)})),t.insertBefore(a,n),y()}function g(e){const t=document.getElementById("chatTyping");t&&t.classList.toggle("visible",e),e&&y()}function y(){const e=document.getElementById("chatMessages");e&&(e.scrollTop=e.scrollHeight)}function f(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",c):c()}();
\ No newline at end of file
diff --git a/pos/static/js/config.min.js b/pos/static/js/config.min.js
new file mode 100644
index 0000000..514813b
--- /dev/null
+++ b/pos/static/js/config.min.js
@@ -0,0 +1 @@
+const Config=(()=>{const e="/pos/api/config";let t=[];function a(){return localStorage.getItem("pos_token")||""}function n(){return!!a()||(window.location.href="/pos/login",!1)}function r(){return{Authorization:`Bearer ${a()}`,"Content-Type":"application/json"}}function o(e,t){var a=document.createElement("div");a.className="cfg-toast cfg-toast--"+(t||"ok"),a.textContent=e,document.body.appendChild(a),setTimeout((function(){a.remove()}),3e3)}function d(e){var t=document.getElementById(e);t&&(t.style.display="flex")}function c(e){var t=document.getElementById(e);t&&(t.style.display="none")}function i(e){document.documentElement.setAttribute("data-theme",e);try{localStorage.setItem("nexus-theme",e)}catch(e){}document.querySelectorAll(".theme-btn").forEach((function(t){t.classList.toggle("is-active",t.dataset.themeTarget===e)})),document.querySelectorAll(".theme-option").forEach((function(e){e.classList.remove("is-selected")}));var t="industrial"===e?0:1,a=document.querySelectorAll(".theme-option");a[t]&&a[t].classList.add("is-selected")}function l(e){i(e)}function s(){var e=new Date,t=String(e.getHours()).padStart(2,"0"),a=String(e.getMinutes()).padStart(2,"0"),n=document.getElementById("live-clock");n&&(n.textContent=t+":"+a)}window.setTheme=i,window.selectThemeOption=l;var u={owner:"Dueno",admin:"Admin",cashier:"Cajero",warehouse:"Almacenista",accountant:"Contador"},m={owner:"badge--owner",admin:"badge--blue",cashier:"badge--green",warehouse:"badge--yellow",accountant:"badge--purple"};function v(e){if(!e)return"";var t=document.createElement("div");return t.textContent=e,t.innerHTML}async function y(){var a=document.getElementById("branches-grid");if(!a)return[];try{var n=await fetch(e+"/branches",{headers:r()});if(!n.ok)throw new Error("Failed");var o=await n.json();t=o.data||[]}catch(e){return console.error("Config.loadBranches:",e),t=[],a.innerHTML='
');const o=n.data.slice(0,20),r=await Promise.all(o.map((t=>d(`/pos/api/sales/${t.id}`)))),i={};for(const t of r)if(t&&t.items)for(const e of t.items){const t=e.part_number||e.name;i[t]||(i[t]={name:e.name,part_number:e.part_number||"",qty:0,revenue:0}),i[t].qty+=e.quantity||0,i[t].revenue+=e.subtotal||0}const l=Object.values(i).sort(((t,e)=>e.revenue-t.revenue)).slice(0,5);if(0===l.length)return void(s.innerHTML='
');f.innerHTML=e}();var r}())}))},selectHotspot:C,viewPart:function(e){window.open("/pos/catalog?part="+e,"_blank")},searchPart:function(e){window.open("/pos/catalog?search="+encodeURIComponent(e),"_blank")},addToCart:function(e){alert("Funcion disponible desde el catalogo. Busca la parte para agregarla al carrito.")}},"loading"===document.readyState?document.addEventListener("DOMContentLoaded",B):B()}else window.location.href="/pos/login";function B(){!function(){try{var e=JSON.parse(localStorage.getItem("pos_user")||"{}");if(E&&(E.textContent=e.name||"--"),L&&(L.textContent=e.role||"--"),b){var t=e.name||"--";b.textContent=t.split(" ").map((function(e){return e[0]||""})).join("").substring(0,2).toUpperCase()}}catch(e){}}(),k(e+"/",(function(e,t){if(!e&&t&&t.data){n.diagrams=t.data;var a={};t.data.forEach((function(e){var t=e.category_name||"Other";a[t]||(a[t]={name:t,name_es:e.category_name_es||t,diagrams:[]}),a[t].diagrams.push(e)})),n.categories=Object.values(a),w()}else h.style.display="flex"})),function(){c&&c.addEventListener("click",I);l&&l.addEventListener("click",(function(){D(1.25)}));d&&d.addEventListener("click",(function(){D(.8)}));u&&u.addEventListener("click",M);v&&v.addEventListener("click",A);_&&_.addEventListener("input",w);r&&(r.addEventListener("wheel",(function(e){e.preventDefault(),D(e.deltaY<0?1.1:.9)}),{passive:!1}),r.addEventListener("mousedown",H),r.addEventListener("mousemove",q),r.addEventListener("mouseup",O),r.addEventListener("mouseleave",O),r.addEventListener("touchstart",(function(e){1===e.touches.length&&H({clientX:e.touches[0].clientX,clientY:e.touches[0].clientY,preventDefault:function(){}})})),r.addEventListener("touchmove",(function(e){1===e.touches.length&&(e.preventDefault(),q({clientX:e.touches[0].clientX,clientY:e.touches[0].clientY}))}),{passive:!1}),r.addEventListener("touchend",O));document.addEventListener("keydown",(function(e){"viewer"===n.view&&("Escape"===e.key&&(n.selectedHotspot?A():I()),"+"!==e.key&&"="!==e.key||D(1.15),"-"===e.key&&D(.87),"0"===e.key&&M())}))}()}function k(e,t){y.style.display="flex",h.style.display="none",fetch(e,{headers:a}).then((function(e){return e.json()})).then((function(e){y.style.display="none",t(null,e)})).catch((function(e){y.style.display="none",t(e)}))}function w(){n.view="list",o.style.display="none",i.style.display="block";var e=(_&&_.value||"").toLowerCase(),t="";n.categories.forEach((function(a){var n=a.diagrams.filter((function(t){return!e||(-1!==(t.name_es||t.name||"").toLowerCase().indexOf(e)||-1!==(t.category_name_es||"").toLowerCase().indexOf(e)||-1!==(t.group_name_es||"").toLowerCase().indexOf(e))}));0!==n.length&&(t+='
",g.innerHTML=r}(t)}}function P(e){var t=n.currentDiagram;if(!t)return{name:"Parte "+e,desc:""};var a=(t.name||"").toLowerCase();return-1!==a.indexOf("brak")||-1!==a.indexOf("freno")?{1:{name:"Disco de freno",desc:"Disco ventilado de freno delantero. Se recomienda cambiar en pares."},2:{name:"Caliper de freno",desc:"Caliper con pistones, incluye purga. Verificar compatibilidad con tipo de pastilla."},3:{name:"Pastillas de freno",desc:"Juego de pastillas con indicador de desgaste. Material ceramico o semi-metalico."},4:{name:"Manguera de freno",desc:"Manguera flexible de alta presion. Revisar por grietas cada 40,000 km."},5:{name:"Cilindro maestro",desc:"Cilindro maestro con deposito de liquido de frenos. Incluye empaques."}}[e]||{name:"Parte "+e,desc:""}:-1!==a.indexOf("susp")?{1:{name:"Amortiguador",desc:"Amortiguador delantero de gas. Se recomienda cambiar en pares."},2:{name:"Resorte helicoidal",desc:"Resorte de suspension delantera. Verificar altura libre."},3:{name:"Brazo de control",desc:"Brazo inferior de control con bujes. Incluye herraje de montaje."},4:{name:"Rotula",desc:"Rotula inferior de suspension. Incluye guardapolvo y seguros."},5:{name:"Barra de acoplamiento",desc:"Barra de acoplamiento de direccion con terminales. Requiere alineacion."}}[e]||{name:"Parte "+e,desc:""}:(-1!==a.indexOf("engine")||-1!==a.indexOf("motor"))&&{1:{name:"Filtro de aire",desc:"Filtro de aire del motor. Cambiar cada 15,000-20,000 km."},2:{name:"Bujias",desc:"Juego de bujias. Verificar tipo (platino, iridio) segun especificacion del motor."},3:{name:"Banda serpentina",desc:"Banda de accesorios. Revisar tension y desgaste. Incluye alternador, A/C y direccion."},4:{name:"Junta de culata",desc:"Junta de cabeza de cilindros. Material MLS multicapa. Requiere torque especifico."},5:{name:"Filtro de aceite",desc:"Filtro de aceite del motor. Cambiar en cada servicio de aceite."}}[e]||{name:"Parte "+e,desc:""}}function A(){p.classList.remove("is-open"),n.selectedHotspot=null;var e=s.querySelector("svg");e&&e.querySelectorAll("[data-hotspot]").forEach((function(e){e.style.fill="transparent",e.style.stroke="none"})),f&&f.querySelectorAll(".part-item").forEach((function(e){e.classList.remove("is-active")}))}function D(e){n.scale=Math.max(.3,Math.min(5,n.scale*e)),S()}function M(){n.scale=1,n.panX=0,n.panY=0,S()}function S(){s&&(s.style.transform="translate("+n.panX+"px, "+n.panY+"px) scale("+n.scale+")")}function H(e){e.button&&0!==e.button||(n.isPanning=!0,n.lastPointer={x:e.clientX,y:e.clientY})}function q(e){if(n.isPanning&&n.lastPointer){var t=e.clientX-n.lastPointer.x,a=e.clientY-n.lastPointer.y;n.panX+=t,n.panY+=a,n.lastPointer={x:e.clientX,y:e.clientY},S()}}function O(){n.isPanning=!1,n.lastPointer=null}function Y(e){if(!e)return"";var t=document.createElement("div");return t.appendChild(document.createTextNode(e)),t.innerHTML}}();
\ No newline at end of file
diff --git a/pos/static/js/fleet.min.js b/pos/static/js/fleet.min.js
new file mode 100644
index 0000000..7975c8a
--- /dev/null
+++ b/pos/static/js/fleet.min.js
@@ -0,0 +1 @@
+var Fleet=function(){"use strict";var e="/pos/api/fleet",t=localStorage.getItem("pos_token"),n=[],a=1,o=null;function l(){return{"Content-Type":"application/json",Authorization:"Bearer "+t}}function c(e){return null==e?"0":parseFloat(e).toLocaleString("es-MX")}function i(e){return null==e?"$0.00":"$"+parseFloat(e).toLocaleString("es-MX",{minimumFractionDigits:2,maximumFractionDigits:2})}function d(e){return e?new Date(e).toLocaleDateString("es-MX",{day:"2-digit",month:"short",year:"numeric"}):"—"}function r(e){if(!e)return"";var t=document.createElement("div");return t.textContent=e,t.innerHTML}function u(){fetch(e+"/stats",{headers:l()}).then((function(e){return e.json()})).then((function(e){document.getElementById("statTotal").textContent=c(e.total_vehicles),document.getElementById("statOverdue").textContent=c(e.overdue_count),document.getElementById("statUpcoming").textContent=c(e.upcoming_this_month),document.getElementById("statCost").textContent=i(e.total_cost_month)})).catch((function(){}))}function s(){var t=(document.getElementById("searchInput")||{}).value||"",o=e+"/vehicles?page="+a+"&per_page=50";t&&(o+="&q="+encodeURIComponent(t)),fetch(o,{headers:l()}).then((function(e){return e.json()})).then((function(e){var t;(function(e){var t=document.getElementById("vehicleGrid");if(!e.length)return void(t.innerHTML='
'}))}function y(){document.getElementById("scheduleModal").classList.remove("is-open")}function f(){var e=document.getElementById("logModal");document.getElementById("logScheduleSelect").innerHTML='',document.getElementById("logType").value="",document.getElementById("logMileage").value="",document.getElementById("logCost").value="",document.getElementById("logParts").value="",document.getElementById("logNotes").value="",e.classList.add("is-open")}function _(){document.getElementById("logModal").classList.remove("is-open")}return document.addEventListener("DOMContentLoaded",(function(){document.querySelectorAll(".tab-btn").forEach((function(e){e.addEventListener("click",(function(){var e=this.getAttribute("data-tab");document.querySelectorAll(".tab-btn").forEach((function(e){e.classList.remove("is-active")})),document.querySelectorAll(".tab-panel").forEach((function(e){e.classList.remove("is-active")})),this.classList.add("is-active"),document.getElementById("tab-"+e).classList.add("is-active"),"maintenance"===e&&h(),"history"===e&&g(),"alerts"===e&&p()}))}));var e=document.getElementById("searchInput");e&&e.addEventListener("input",(function(){clearTimeout(o),o=setTimeout((function(){a=1,s()}),300)})),document.getElementById("btnNewVehicle").addEventListener("click",(function(){m()})),u(),s()})),{openVehicleModal:m,closeVehicleModal:v,saveVehicle:function(){var t=document.getElementById("vehEditId").value,n={plate:document.getElementById("vehPlate").value.trim(),vin:document.getElementById("vehVin").value.trim(),make:document.getElementById("vehMake").value.trim(),model:document.getElementById("vehModel").value.trim(),year:parseInt(document.getElementById("vehYear").value)||null,current_mileage:parseInt(document.getElementById("vehMileage").value)||0,fuel_type:document.getElementById("vehFuel").value,color:document.getElementById("vehColor").value.trim(),owner_name:document.getElementById("vehOwner").value.trim(),notes:document.getElementById("vehNotes").value.trim()};if(n.plate||n.vin){var a=e+"/vehicles",o="POST";t&&(a+="/"+t,o="PUT"),fetch(a,{method:o,headers:l(),body:JSON.stringify(n)}).then((function(e){return e.json()})).then((function(e){e.error?alert(e.error):(v(),s(),u())})).catch((function(e){alert("Error: "+e.message)}))}else alert("Se requiere placa o VIN")},viewVehicle:function(t){fetch(e+"/vehicles/"+t,{headers:l()}).then((function(e){return e.json()})).then((function(e){m(e)}))},goPage:function(e){a=e,s()},openScheduleModal:function(e){var t=document.getElementById("scheduleModal");document.getElementById("schedType").value="",document.getElementById("schedIntervalKm").value="",document.getElementById("schedIntervalMonths").value="",document.getElementById("schedNextDate").value="",document.getElementById("schedNextKm").value="",document.getElementById("schedNotes").value="",e&&(document.getElementById("schedVehicleSelect").value=e),t.classList.add("is-open")},closeScheduleModal:y,saveSchedule:function(){var t=document.getElementById("schedVehicleSelect").value;if(t){var n=document.getElementById("schedType").value.trim();if(n){var a={maintenance_type:n,interval_km:parseInt(document.getElementById("schedIntervalKm").value)||null,interval_months:parseInt(document.getElementById("schedIntervalMonths").value)||null,next_due_at:document.getElementById("schedNextDate").value||null,next_due_km:parseInt(document.getElementById("schedNextKm").value)||null,notes:document.getElementById("schedNotes").value.trim()};fetch(e+"/vehicles/"+t+"/schedules",{method:"POST",headers:l(),body:JSON.stringify(a)}).then((function(e){return e.json()})).then((function(e){e.error?alert(e.error):(y(),h(),u())})).catch((function(e){alert("Error: "+e.message)}))}else alert("Ingrese el tipo de mantenimiento")}else alert("Seleccione un vehiculo")},openLogModal:f,openLogModalFor:function(t,a,o){f(),document.getElementById("logVehicleSelect").value=t,o&&(document.getElementById("logType").value=o),fetch(e+"/vehicles/"+t+"/schedules",{headers:l()}).then((function(e){return e.json()})).then((function(e){var t='';(e.data||[]).forEach((function(e){var n=e.id==a?" selected":"";t+='"})),document.getElementById("logScheduleSelect").innerHTML=t}));var c=n.find((function(e){return e.id==t}));c&&(document.getElementById("logMileage").value=c.current_mileage||"")},closeLogModal:_,saveLog:function(){var t=document.getElementById("logVehicleSelect").value;if(t){var n=document.getElementById("logType").value.trim();if(n){var a={schedule_id:parseInt(document.getElementById("logScheduleSelect").value)||null,maintenance_type:n,mileage_at:parseInt(document.getElementById("logMileage").value)||null,cost:parseFloat(document.getElementById("logCost").value)||0,parts_used:document.getElementById("logParts").value.trim(),notes:document.getElementById("logNotes").value.trim()};fetch(e+"/vehicles/"+t+"/log",{method:"POST",headers:l(),body:JSON.stringify(a)}).then((function(e){return e.json()})).then((function(e){if(e.error)alert(e.error);else{_(),u();var t=document.querySelector(".tab-btn.is-active");if(t){var n=t.getAttribute("data-tab");"maintenance"===n&&h(),"history"===n&&g(),"alerts"===n&&p(),"vehicles"===n&&s()}}})).catch((function(e){alert("Error: "+e.message)}))}else alert("Ingrese el tipo de mantenimiento")}else alert("Seleccione un vehiculo")}}}();
\ No newline at end of file
diff --git a/pos/static/js/i18n.min.js b/pos/static/js/i18n.min.js
new file mode 100644
index 0000000..783a6a1
--- /dev/null
+++ b/pos/static/js/i18n.min.js
@@ -0,0 +1 @@
+var I18N={es:{dashboard:"Dashboard",pos:"Punto de Venta",catalog:"Catalogo",inventory:"Inventario",diagrams:"Diagramas",customers:"Clientes",invoicing:"Facturacion",accounting:"Contabilidad",reports:"Reportes",fleet:"Flotillas",whatsapp:"WhatsApp",config:"Configuracion",nav_main:"Principal",nav_management:"Gestion",nav_system:"Sistema",search:"Buscar",save:"Guardar",cancel:"Cancelar",delete:"Eliminar",edit:"Editar",new:"Nuevo",close:"Cerrar",confirm:"Confirmar",back:"Regresar",next:"Siguiente",print:"Imprimir",export:"Exportar",import:"Importar",refresh:"Actualizar",loading:"Cargando...",no_results:"Sin resultados",error:"Error",success:"Exito",warning:"Advertencia",total:"Total",subtotal:"Subtotal",tax:"IVA",price:"Precio",unit_price:"Precio Unitario",cost:"Costo",discount:"Descuento",margin:"Margen",profit:"Utilidad",balance:"Saldo",amount:"Monto",quantity:"Cantidad",stock:"Existencias",min_stock:"Minimo",max_stock:"Maximo",sku:"SKU",barcode:"Codigo de Barras",brand:"Marca",category:"Categoria",description:"Descripcion",location:"Ubicacion",name:"Nombre",date:"Fecha",status:"Estado",actions:"Acciones",id:"ID",type:"Tipo",notes:"Notas",charge:"Cobrar",quote:"Cotizacion",layaway:"Apartado",credit:"Credito",cash:"Efectivo",transfer:"Transferencia",card:"Tarjeta",mixed:"Mixto",change:"Cambio",customer:"Cliente",general_public:"Publico General",sale:"Venta",sales:"Ventas",ticket:"Ticket",receipt:"Recibo",payment:"Pago",payment_method:"Metodo de Pago",add_to_cart:"Agregar",clear_cart:"Limpiar Carrito",hold_sale:"Pausar Venta",recall_sale:"Retomar Venta",cancel_sale:"Cancelar Venta",confirm_payment:"Confirmar Pago",cash_received:"Efectivo Recibido",amount_due:"Total a Pagar",remaining:"Faltante",phone:"Telefono",email:"Correo",address:"Direccion",rfc:"RFC",credit_limit:"Limite de Credito",credit_balance:"Saldo de Credito",price_tier:"Nivel de Precio",invoice:"Factura",cfdi:"CFDI",stamp:"Timbrar",cancel_invoice:"Cancelar Factura",appearance:"Apariencia",business_data:"Datos de la Empresa",employees:"Empleados",printers:"Impresoras",branches:"Sucursales",fiscal_params:"Parametros Fiscales",system_prefs:"Preferencias del Sistema",currency_config:"Moneda",language:"Idioma",theme:"Tema",dark_theme:"Tema oscuro",light_theme:"Tema claro",logout:"Cerrar sesion",currency:"Moneda",exchange_rate:"Tipo de Cambio",default_currency:"Moneda Predeterminada",mxn:"Peso Mexicano",usd:"Dolar Estadounidense",role_owner:"Dueno",role_admin:"Administrador",role_cashier:"Cajero",role_warehouse:"Almacen",role_accountant:"Contador",daily_sales:"Ventas del Dia",weekly_sales:"Ventas de la Semana",monthly_sales:"Ventas del Mes",top_products:"Productos Mas Vendidos",low_stock:"Bajo Stock",vehicle:"Vehiculo",plate:"Placa",vin:"VIN",mileage:"Kilometraje",yes:"Si",no:"No",all:"Todos",active:"Activo",inactive:"Inactivo",pending:"Pendiente",completed:"Completado",cancelled:"Cancelado"},en:{dashboard:"Dashboard",pos:"Point of Sale",catalog:"Catalog",inventory:"Inventory",diagrams:"Diagrams",customers:"Customers",invoicing:"Invoicing",accounting:"Accounting",reports:"Reports",fleet:"Fleet",whatsapp:"WhatsApp",config:"Settings",nav_main:"Main",nav_management:"Management",nav_system:"System",search:"Search",save:"Save",cancel:"Cancel",delete:"Delete",edit:"Edit",new:"New",close:"Close",confirm:"Confirm",back:"Back",next:"Next",print:"Print",export:"Export",import:"Import",refresh:"Refresh",loading:"Loading...",no_results:"No results",error:"Error",success:"Success",warning:"Warning",total:"Total",subtotal:"Subtotal",tax:"Tax",price:"Price",unit_price:"Unit Price",cost:"Cost",discount:"Discount",margin:"Margin",profit:"Profit",balance:"Balance",amount:"Amount",quantity:"Quantity",stock:"Stock",min_stock:"Minimum",max_stock:"Maximum",sku:"SKU",barcode:"Barcode",brand:"Brand",category:"Category",description:"Description",location:"Location",name:"Name",date:"Date",status:"Status",actions:"Actions",id:"ID",type:"Type",notes:"Notes",charge:"Charge",quote:"Quote",layaway:"Layaway",credit:"Credit",cash:"Cash",transfer:"Transfer",card:"Card",mixed:"Mixed",change:"Change",customer:"Customer",general_public:"Walk-in Customer",sale:"Sale",sales:"Sales",ticket:"Ticket",receipt:"Receipt",payment:"Payment",payment_method:"Payment Method",add_to_cart:"Add",clear_cart:"Clear Cart",hold_sale:"Hold Sale",recall_sale:"Recall Sale",cancel_sale:"Cancel Sale",confirm_payment:"Confirm Payment",cash_received:"Cash Received",amount_due:"Amount Due",remaining:"Remaining",phone:"Phone",email:"Email",address:"Address",rfc:"Tax ID (RFC)",credit_limit:"Credit Limit",credit_balance:"Credit Balance",price_tier:"Price Tier",invoice:"Invoice",cfdi:"CFDI",stamp:"Stamp",cancel_invoice:"Cancel Invoice",appearance:"Appearance",business_data:"Business Info",employees:"Employees",printers:"Printers",branches:"Branches",fiscal_params:"Tax Settings",system_prefs:"System Preferences",currency_config:"Currency",language:"Language",theme:"Theme",dark_theme:"Dark theme",light_theme:"Light theme",logout:"Log out",currency:"Currency",exchange_rate:"Exchange Rate",default_currency:"Default Currency",mxn:"Mexican Peso",usd:"US Dollar",role_owner:"Owner",role_admin:"Administrator",role_cashier:"Cashier",role_warehouse:"Warehouse",role_accountant:"Accountant",daily_sales:"Daily Sales",weekly_sales:"Weekly Sales",monthly_sales:"Monthly Sales",top_products:"Top Products",low_stock:"Low Stock",vehicle:"Vehicle",plate:"Plate",vin:"VIN",mileage:"Mileage",yes:"Yes",no:"No",all:"All",active:"Active",inactive:"Inactive",pending:"Pending",completed:"Completed",cancelled:"Cancelled"}},currentLang=localStorage.getItem("pos_lang")||"es";window.t=function(e){return I18N[currentLang]&&I18N[currentLang][e]||I18N.es&&I18N.es[e]||e},window.setLang=function(e){currentLang=e,localStorage.setItem("pos_lang",e),location.reload()},window.getLang=function(){return currentLang};
\ No newline at end of file
diff --git a/pos/static/js/inventory.js b/pos/static/js/inventory.js
index a269aef..594bb27 100644
--- a/pos/static/js/inventory.js
+++ b/pos/static/js/inventory.js
@@ -549,7 +549,7 @@
// Product image section
html += '
';
if (data.image_url) {
- html += '';
+ html += '';
html += '
';
html += '';
html += '';
diff --git a/pos/static/js/inventory.min.js b/pos/static/js/inventory.min.js
new file mode 100644
index 0000000..d5d87ea
--- /dev/null
+++ b/pos/static/js/inventory.min.js
@@ -0,0 +1 @@
+!function(){"use strict";var t="/pos/api/inventory",e=localStorage.getItem("pos_token");if(e){var o={Authorization:"Bearer "+e,"Content-Type":"application/json"},n=1,a="",r=null,i=window.switchTab;window.switchTab=function(t){"function"==typeof i&&i(t),"alertas"===t&&v(),"stock"===t&&m(n)};var s,d=document.getElementById("productSearch");d&&d.addEventListener("input",(function(){clearTimeout(s),s=setTimeout((function(){m(1,d.value.trim())}),350)})),window._loadItems=function(t){m(t)},window.loadItems=function(t,e){m(t,e)},window.viewHistory=function(e){c(t+"/items/"+e+"/history").then((function(t){if(t){var e=t.data||[],o="";e.length?(o='
',document.getElementById("historyContent").innerHTML=o,document.getElementById("historyModal").classList.add("is-open")}}))},window.viewProductDetail=g,window.uploadItemImage=function(o){var n=document.createElement("input");n.type="file",n.accept="image/jpeg,image/png,image/webp",n.onchange=function(){if(n.files&&n.files[0]){var a=n.files[0];if(a.size>5242880)alert("Imagen demasiado grande (max 5 MB)");else{var r=new FormData;r.append("file",a);var i=document.getElementById("imgUploadStatus");i&&(i.textContent="Subiendo..."),fetch(t+"/items/"+o+"/image",{method:"POST",headers:{Authorization:"Bearer "+e},body:r}).then((function(t){return t.json()})).then((function(t){t.image_url?g(o):i&&(i.textContent=t.error||"Error")})).catch((function(){i&&(i.textContent="Error de red")}))}}},n.click()},window.deleteItemImage=function(o){confirm("Eliminar imagen de este producto?")&&fetch(t+"/items/"+o+"/image",{method:"DELETE",headers:{Authorization:"Bearer "+e}}).then((function(t){return t.json()})).then((function(t){t.message?g(o):alert(t.error||"Error")})).catch((function(){alert("Error de red")}))},window.closeHistoryModal=function(){document.getElementById("historyModal").classList.remove("is-open")},window.showCreateModal=function(){document.getElementById("createModal").classList.add("is-open");var e=document.getElementById("newPartNumber");e&&!e._classifyBound&&(e._classifyBound=!0,e.addEventListener("blur",(function(){var e=this.value.trim();if(!(e.length<3)){var o,n,a=document.getElementById("newName");if(!a||!a.value.trim())o=e,(n=document.getElementById("createResult")).innerHTML='Consultando IA...',c(t+"/classify/"+encodeURIComponent(o)).then((function(t){if(t){t.name&&(document.getElementById("newName").value=t.name),t.brand&&(document.getElementById("newBrand").value=t.brand);var e=[];t.name&&e.push(t.name),t.brand&&e.push(t.brand),t.vehicle&&e.push(t.vehicle),t.category&&e.push(t.category),e.length>0?n.innerHTML='Sugerido por IA: '+u(e.join(" | "))+"":n.innerHTML='IA no pudo identificar este numero de parte'}})).catch((function(){n.innerHTML=""}))}})))},window.closeCreateModal=function(){document.getElementById("createModal").classList.remove("is-open"),document.getElementById("createResult").innerHTML=""},window.createItem=function(){var e={part_number:document.getElementById("newPartNumber").value.trim(),name:document.getElementById("newName").value.trim(),brand:document.getElementById("newBrand").value.trim(),barcode:document.getElementById("newBarcode").value.trim()||void 0,cost:parseFloat(document.getElementById("newCost").value)||0,price_1:parseFloat(document.getElementById("newPrice1").value)||0,price_2:parseFloat(document.getElementById("newPrice2").value)||0,price_3:parseFloat(document.getElementById("newPrice3").value)||0,min_stock:parseInt(document.getElementById("newMinStock").value)||0,initial_stock:parseInt(document.getElementById("newInitialStock").value)||0,location:document.getElementById("newLocation").value.trim()};e.part_number&&e.name?c(t+"/items",{method:"POST",body:JSON.stringify(e)}).then((function(t){t&&t.id?(document.getElementById("createResult").innerHTML='Creado ID '+t.id+" | Barcode: "+t.barcode+"",m(n)):document.getElementById("createResult").innerHTML=''+(t?t.error||"Error":"Error de red")+""})):document.getElementById("createResult").innerHTML='Numero de parte y nombre son obligatorios'},window.showPurchaseModal=function(){document.getElementById("purchaseModal").classList.add("is-open")},window.closePurchaseModal=function(){document.getElementById("purchaseModal").classList.remove("is-open"),document.getElementById("purchaseResult").innerHTML=""},window.recordPurchase=function(){var e={inventory_id:parseInt(document.getElementById("purchaseItemId").value),quantity:parseInt(document.getElementById("purchaseQty").value),unit_cost:parseFloat(document.getElementById("purchaseCost").value),supplier_invoice:document.getElementById("purchaseInvoice").value.trim(),notes:document.getElementById("purchaseNotes").value.trim()};e.inventory_id&&e.quantity&&e.unit_cost?c(t+"/purchase",{method:"POST",body:JSON.stringify(e)}).then((function(t){document.getElementById("purchaseResult").innerHTML=t&&t.operation_id?'Compra registrada (op #'+t.operation_id+")":''+(t?t.error||"Error":"Error de red")+""})):document.getElementById("purchaseResult").innerHTML='Complete todos los campos obligatorios'},window.showAdjustmentModal=function(){document.getElementById("adjustmentModal").classList.add("is-open")},window.closeAdjustmentModal=function(){document.getElementById("adjustmentModal").classList.remove("is-open"),document.getElementById("adjustResult").innerHTML=""},window.recordAdjustment=function(){var e={inventory_id:parseInt(document.getElementById("adjustItemId").value),quantity:parseInt(document.getElementById("adjustQty").value),reason:document.getElementById("adjustReason").value.trim()};e.inventory_id&&void 0!==e.quantity&&e.reason?c(t+"/adjustment",{method:"POST",body:JSON.stringify(e)}).then((function(t){document.getElementById("adjustResult").innerHTML=t&&t.operation_id?'Ajuste registrado (op #'+t.operation_id+")":''+(t?t.error||"Error":"Error de red")+""})):document.getElementById("adjustResult").innerHTML='Complete todos los campos (razon obligatoria)'},window.showTransferModal=function(){document.getElementById("transferModal").classList.add("is-open")},window.closeTransferModal=function(){document.getElementById("transferModal").classList.remove("is-open"),document.getElementById("transferResult").innerHTML=""},window.recordTransfer=function(){var e={inventory_id:parseInt(document.getElementById("transferItemId").value),from_branch_id:parseInt(document.getElementById("transferFrom").value),to_branch_id:parseInt(document.getElementById("transferTo").value),quantity:parseInt(document.getElementById("transferQty").value),notes:document.getElementById("transferNotes").value.trim()};e.inventory_id&&e.from_branch_id&&e.to_branch_id&&e.quantity?c(t+"/transfer",{method:"POST",body:JSON.stringify(e)}).then((function(t){document.getElementById("transferResult").innerHTML=t&&t.out_operation_id?'Transferencia registrada':''+(t?t.error||"Error":"Error de red")+""})):document.getElementById("transferResult").innerHTML='Complete todos los campos'},window.showCountModal=function(){document.getElementById("countModal").classList.add("is-open"),document.querySelectorAll("#countLines .count-row").length||p()},window.closeCountModal=function(){document.getElementById("countModal").classList.remove("is-open")},window.addCountLine=p,window.startPhysicalCount=function(){var e=document.querySelectorAll("#countLines .count-row"),o=[];e.forEach((function(t){var e=parseInt(t.querySelector(".count-inv-id").value),n=parseInt(t.querySelector(".count-qty").value);e&&!isNaN(n)&&o.push({inventory_id:e,counted_quantity:n})})),o.length?c(t+"/physical-count/start",{method:"POST",body:JSON.stringify({items:o})}).then((function(t){if(t&&t.count_id){r=t.count_id;var e='
",document.getElementById("countResults").innerHTML=e}else document.getElementById("countResults").innerHTML=''+(t?t.error||"Error":"Error de red")+""})):alert("Agregue al menos una linea")},window.approvePhysicalCount=function(){r?c(t+"/physical-count/approve",{method:"POST",body:JSON.stringify({count_id:r})}).then((function(t){t&&"approved"===t.status?(document.getElementById("countResults").innerHTML=''+u(t.message)+"",r=null):document.getElementById("countResults").innerHTML+=' '+(t?t.error||"Error":"Error de red")+""})):alert("No hay borrador activo")},window.cancelDraft=function(){r=null,document.getElementById("countResults").innerHTML='Borrador cancelado'},window.loadAlerts=v,window.printBarcode=function(t,e,o){var n=window.open("","_blank","width=400,height=250");n.document.write("Etiqueta"),n.document.write("
`}}function d(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}let p=null;function u(t){p=t;const e=document.getElementById("modalCancelOverlay");e&&(e.style.display="flex")}async function m(){if(!p)return;const t=document.getElementById("modalCancelOverlay");if(!t)return;const e=t.querySelector('input[name="motivo-sat"]:checked');if(!e)return void alert("Selecciona un motivo de cancelacion.");const n=e.value,o=t.querySelector('input[type="text"]'),c=o?o.value.trim():"";if("01"!==n||c){if(confirm("Confirmar cancelacion ante el SAT?"))try{const e={motive:n};c&&(e.replacement_uuid=c),await a(`/cancel/${p}`,{method:"POST",body:JSON.stringify(e)}),t.style.display="none",p=null,r(),alert("CFDI cancelado exitosamente.")}catch(t){alert("Error: "+t.message)}}else alert("UUID sustituto requerido para motivo 01.")}function v(){const t=document.getElementById("newInvoiceModalOverlay");if(!t)return;const e=t.querySelector("#invoiceSaleId");e&&(e.value=""),document.getElementById("invoiceResult").innerHTML="",t.style.display="flex"}function y(){const t=document.getElementById("newInvoiceModalOverlay");t&&(t.style.display="none")}async function g(){const t=parseInt(document.getElementById("invoiceSaleId").value),e=document.getElementById("invoiceResult");if(t)try{const n=await a("/invoice",{method:"POST",body:JSON.stringify({sale_id:t})});e.innerHTML='Factura generada: '+(n.provisional_folio||"CFDI-"+(n.id||""))+"",r(),setTimeout((()=>y()),1500)}catch(t){e.innerHTML='Error: '+t.message+""}else e.innerHTML='Ingrese un ID de venta valido.'}function f(){alert("Nota de credito: proximamente")}return document.addEventListener("DOMContentLoaded",(function(){(e()||(window.location.href="/pos/login",0))&&(!function(){const t=document.getElementById("live-clock");if(!t)return;const e=()=>{const e=new Date;t.textContent=e.toLocaleTimeString("es-MX",{hour:"2-digit",minute:"2-digit",second:"2-digit"})};e(),setInterval(e,1e3)}(),function(){const t=document.getElementById("modalDetalleOverlay");if(!t)return;const e=t.querySelector('button[onclick*="modalDetalleOverlay"]');e&&(e.onclick=()=>{t.style.display="none"})}(),function(){const t=document.getElementById("modalCancelOverlay");if(!t)return;const e=t.querySelectorAll("div:last-child button");e.length>=2&&(e[e.length-1].onclick=()=>m(),e[e.length-2].onclick=()=>{t.style.display="none",p=null})}(),r())})),window.switchTab=o,window.showNewInvoiceModal=v,window.closeNewInvoiceModal=y,window.submitNewInvoice=g,window.notaCreditoPlaceholder=f,{switchTab:o,loadFacturas:r,loadNotas:s,loadComplementos:l,loadCancelaciones:i,showDetail:async function(t){const e=document.getElementById("modalDetalleOverlay");if(e)try{const o=await a(`/queue/${t}`),c=e.querySelector(".modal-card");if(!c)return;const r=c.querySelector("div > div:first-child > div:first-child"),s=c.querySelector("div > div:first-child > div:nth-child(2)");r&&(r.textContent="Detalle de Factura"),s&&(s.textContent=`${o.provisional_folio||"CFDI-"+o.id} — ${"stamped"===o.status?"Timbrada":"cancelled"===o.status?"Cancelada":"pending"===o.status?"Pendiente":o.status||""}`);const l=c.querySelector("div:nth-child(2)");l&&(l.innerHTML=`\n
\n
\n
Emisor
\n
${o.emisor_name||"Nexus Autoparts SA de CV"}
\n
${o.emisor_rfc||""}
\n
\n
\n
Receptor
\n
${o.customer_name||"-"}
\n
${o.rfc||""}
\n
\n
\n
UUID
\n
${o.uuid_fiscal||"Sin timbrar"}
\n
\n
\n
Total
\n
$${n(o.total)}
\n
\n
\n ${o.error_message?`
Error: ${d(o.error_message)}
`:""}\n ${o.xml_signed||o.xml_unsigned?`\n
Vista previa XML
\n
${d(o.xml_signed||o.xml_unsigned)}
\n `:""}`);const i=c.querySelector("div:last-child button:last-child");i&&"stamped"===o.status?(i.style.display="",i.onclick=()=>{e.style.display="none",u(t)}):i&&(i.style.display="none"),e.dataset.cfdiId=t,e.dataset.saleId=o.sale_id||"",e.style.display="flex"}catch(t){alert("Error al cargar detalle: "+t.message)}},showCancelModal:u,confirmCancel:m,processQueue:async function(){if(confirm("Procesar todos los CFDIs pendientes?"))try{const t=await a("/queue/process",{method:"POST"});alert(`Procesados: ${t.processed}, Timbrados: ${t.stamped}, Fallidos: ${t.failed}`),r()}catch(t){alert("Error: "+t.message)}},showNewInvoiceModal:v,closeNewInvoiceModal:y,submitNewInvoice:g,notaCreditoPlaceholder:f}})();
\ No newline at end of file
diff --git a/pos/static/js/kiosk.min.js b/pos/static/js/kiosk.min.js
new file mode 100644
index 0000000..d0d02af
--- /dev/null
+++ b/pos/static/js/kiosk.min.js
@@ -0,0 +1 @@
+!function(){"use strict";var e="pos_kiosk_mode";function n(){return window.matchMedia("(display-mode: standalone)").matches||!0===window.navigator.standalone}function t(){return void 0!==window.Capacitor&&window.Capacitor.isNativePlatform&&window.Capacitor.isNativePlatform()}function o(){var o=localStorage.getItem(e);return"true"===o||"false"!==o&&(n()||t())}var a=!1;function i(){if(!a){var e=document.documentElement,n=e.requestFullscreen||e.webkitRequestFullscreen||e.mozRequestFullScreen||e.msRequestFullscreen;n&&(n.call(e).catch((function(){})),a=!0)}}var c=null;async function r(){if("wakeLock"in navigator)try{(c=await navigator.wakeLock.request("screen")).addEventListener("release",(function(){c=null}))}catch(e){}}function u(){window.addEventListener("beforeunload",(function(e){o()&&(e.preventDefault(),e.returnValue="")})),document.addEventListener("contextmenu",(function(e){o()&&e.preventDefault()}));var e=["click","touchstart","keydown"];function n(){o()&&(i(),r()),e.forEach((function(e){document.removeEventListener(e,n)}))}e.forEach((function(e){document.addEventListener(e,n,{once:!1})})),function(){var e=localStorage.getItem("pos_token");if(e&&-1!==window.location.pathname.indexOf("/pos/login"))try{var n=e.split(".");if(3!==n.length)return;var t=JSON.parse(atob(n[1].replace(/-/g,"+").replace(/_/g,"/"))).exp;t&&1e3*t>Date.now()&&(window.location.href="/pos/")}catch(e){}}()}document.addEventListener("visibilitychange",(function(){"visible"===document.visibilityState&&o()&&!c&&r()})),window.NexusKiosk={isEnabled:o,isPWA:n,isCapacitor:t,enable:function(){localStorage.setItem(e,"true"),i(),r()},disable:function(){localStorage.setItem(e,"false"),c&&(c.release(),c=null),document.fullscreenElement&&document.exitFullscreen().catch((function(){}))},toggle:function(){return o()?window.NexusKiosk.disable():window.NexusKiosk.enable(),o()}},o()&&u(),window.addEventListener("storage",(function(n){n.key===e&&"true"===n.newValue&&u()}))}();
\ No newline at end of file
diff --git a/pos/static/js/login.min.js b/pos/static/js/login.min.js
new file mode 100644
index 0000000..2d990fd
--- /dev/null
+++ b/pos/static/js/login.min.js
@@ -0,0 +1 @@
+!function(){"use strict";var t="",e=document.querySelectorAll("#pinDots .pin-dot"),n=document.getElementById("loginError"),o=new URLSearchParams(window.location.search).get("tenant")||localStorage.getItem("pos_tenant_id"),a=localStorage.getItem("pos_device_id");function i(){e.forEach((function(e,n){e.classList.toggle("filled",n=4||(t+=e,i(),n.textContent="",4===t.length&&submitPin())},window.clearPin=function(){t="",i(),n.textContent=""},window.submitPin=function(){4===t.length&&(n.textContent="",fetch("/pos/api/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tenant_id:parseInt(o),pin:t,device_id:a})}).then((function(t){return t.json().then((function(e){return{ok:t.ok,data:e}}))})).then((function(t){if(!t.ok)return n.textContent=t.data.error||"Error de autenticacion",void clearPin();localStorage.setItem("pos_token",t.data.token),localStorage.setItem("pos_employee",JSON.stringify(t.data.employee)),localStorage.setItem("pos_tenant_id",o),window.location.href="/pos/catalog"})).catch((function(){n.textContent="Error de conexion",clearPin()})))},document.addEventListener("keydown",(function(t){t.key>="0"&&t.key<="9"?addDigit(t.key):"Backspace"===t.key?clearPin():"Enter"===t.key&&submitPin()}));var r=localStorage.getItem("pos_token");r&&o&&(!function(t){try{var e=t.split(".");if(3!==e.length)return!1;var n=e[1].replace(/-/g,"+").replace(/_/g,"/"),o=JSON.parse(atob(n));return!!o.exp&&1e3*o.exp>Date.now()+3e4}catch(t){return!1}}(r)?(localStorage.removeItem("pos_token"),localStorage.removeItem("pos_employee")):window.location.href="/pos/catalog")}();
\ No newline at end of file
diff --git a/pos/static/js/native-bridge.min.js b/pos/static/js/native-bridge.min.js
new file mode 100644
index 0000000..24f5a76
--- /dev/null
+++ b/pos/static/js/native-bridge.min.js
@@ -0,0 +1 @@
+!function(){"use strict";window.NexusNative={isNative:"undefined"!=typeof Capacitor,_scanStream:null,_scanVideo:null,async scanBarcode(){if(this.isNative)try{const{Camera:e}=await import("@capacitor/camera");return await e.getPhoto({quality:90,resultType:"base64"})}catch(e){return null}return"BarcodeDetector"in window?new Promise((async e=>{try{const t=await navigator.mediaDevices.getUserMedia({video:{facingMode:"environment",width:{ideal:1280},height:{ideal:720}}});this._scanStream=t;const a=document.createElement("div");a.id="barcode-scan-overlay",a.style.cssText="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:99999;display:flex;flex-direction:column;align-items:center;justify-content:center;";const i=document.createElement("video");i.autoplay=!0,i.playsInline=!0,i.style.cssText="width:90%;max-width:500px;border-radius:12px;border:3px solid #F5A623;",i.srcObject=t,this._scanVideo=i;const o=document.createElement("p");o.textContent="Apunta al codigo de barras...",o.style.cssText="color:#fff;font-size:16px;margin-top:16px;font-family:sans-serif;";const s=document.createElement("button");s.textContent="Cancelar",s.style.cssText="margin-top:16px;padding:10px 24px;background:#F5A623;color:#000;border:none;border-radius:6px;font-size:15px;cursor:pointer;font-weight:bold;",s.onclick=()=>{this.stopScan(),a.remove(),e(null)},a.appendChild(i),a.appendChild(o),a.appendChild(s),document.body.appendChild(a);const n=new BarcodeDetector({formats:["ean_13","ean_8","code_128","code_39","qr_code","upc_a","upc_e"]}),r=async()=>{if(this._scanStream){try{const t=await n.detect(i);if(t.length>0){const i=t[0].rawValue;return o.textContent="Codigo detectado: "+i,o.style.color="#4CAF50",void setTimeout((()=>{this.stopScan(),a.remove(),e(i)}),400)}}catch(e){}requestAnimationFrame(r)}};i.onloadedmetadata=()=>{i.play(),requestAnimationFrame(r)}}catch(t){console.error("Camera access error:",t),alert("No se pudo acceder a la camara: "+t.message),e(null)}})):(alert("Tu navegador no soporta escaneo de codigos de barras. Usa Chrome 83+ o un dispositivo movil."),null)},stopScan(){this._scanStream&&(this._scanStream.getTracks().forEach((e=>e.stop())),this._scanStream=null),this._scanVideo=null;var e=document.getElementById("barcode-scan-overlay");e&&e.remove()},async registerPush(){if(!this.isNative)return null;try{const{PushNotifications:e}=await import("@capacitor/push-notifications");"granted"===(await e.requestPermissions()).receive&&await e.register(),e.addListener("registration",(e=>{console.log("Push token:",e.value)})),e.addListener("pushNotificationReceived",(e=>{console.log("Push received:",e)}))}catch(e){console.log("Push not available:",e)}},async vibrate(){if(this.isNative)try{const{Haptics:e,ImpactStyle:t}=await import("@capacitor/haptics");await e.impact({style:t.Light})}catch(e){}},async setupStatusBar(){if(this.isNative)try{const{StatusBar:e,Style:t}=await import("@capacitor/status-bar");await e.setStyle({style:t.Dark}),await e.setBackgroundColor({color:"#0d0d0d"})}catch(e){}}},window.NexusNative.isNative&&(window.NexusNative.setupStatusBar(),window.NexusNative.registerPush())}();
\ No newline at end of file
diff --git a/pos/static/js/offline-banner.min.js b/pos/static/js/offline-banner.min.js
new file mode 100644
index 0000000..dc2d7fc
--- /dev/null
+++ b/pos/static/js/offline-banner.min.js
@@ -0,0 +1 @@
+!function(){"use strict";var n=document.getElementById("offlineBanner"),e=document.getElementById("offlineBannerText");if(n&&e){var t=null;navigator.onLine||o(),window.addEventListener("offline",o),window.addEventListener("online",(function(){clearTimeout(t),n.className="banner banner--success",n.style.display="flex",n.style.animation="slideDown 0.35s ease-out forwards",e.innerHTML="Conexion restaurada — Sincronizando datos...",t=setTimeout((function(){n.style.animation="slideUp 0.3s ease-in forwards",n.addEventListener("animationend",(function e(){n.style.display="none",n.removeEventListener("animationend",e)}),{once:!0})}),3e3)}))}function o(){clearTimeout(t),n.className="banner banner--error",n.style.display="flex",n.style.animation="slideDown 0.35s ease-out forwards",e.innerHTML="Conexion perdida — Intentando reconectar..."}}();
\ No newline at end of file
diff --git a/pos/static/js/onboarding.min.js b/pos/static/js/onboarding.min.js
new file mode 100644
index 0000000..ba81a36
--- /dev/null
+++ b/pos/static/js/onboarding.min.js
@@ -0,0 +1 @@
+!function(){"use strict";if("true"!==localStorage.getItem("pos_onboarding_done")){var e,a,n=0,o={branchRenamed:!1,productCreated:!1,employeeCreated:!1},t=[function(){var e="";try{e=localStorage.getItem("pos_business_name")||""}catch(e){}var a=e||"tu negocio";return i("div",{className:"onb-step-enter"},[i("div",{className:"onb-icon"},["🚀"]),i("h2",{className:"onb-title"},"Bienvenido a Nexus POS"),i("p",{className:"onb-desc"},"Vamos a configurar "+a+" en unos minutos. Este asistente te guiara por los pasos esenciales para empezar a vender.")])},function(){var e=i("div",{className:"onb-step-enter"},[i("div",{className:"onb-icon"},["🏪"]),i("h2",{className:"onb-title"},"Tu Primera Sucursal"),i("p",{className:"onb-desc"},'Ya creamos la sucursal "Principal" para ti. Puedes renombrarla si quieres.')]),a=i("input",{className:"onb-input",type:"text",placeholder:"Principal",value:"Principal",id:"onb-branch-name"}),n=i("div",{className:"onb-error",id:"onb-branch-msg"});return e.appendChild(i("div",{className:"onb-form"},[i("div",{className:"onb-field"},[i("label",{className:"onb-label"},"Nombre de sucursal"),a,n])])),e},function(){var e=i("div",{className:"onb-step-enter"},[i("div",{className:"onb-icon"},["📦"]),i("h2",{className:"onb-title"},"Agrega Tu Primer Producto"),i("p",{className:"onb-desc"},"Registra una pieza para probar el sistema. Despues podras agregar mas desde Inventario.")]);if(o.productCreated)return e.appendChild(i("div",{className:"onb-success"},["✅ ","Producto creado exitosamente."])),e;var a=i("div",{className:"onb-error",id:"onb-product-msg"});return e.appendChild(i("div",{className:"onb-form"},[i("div",{className:"onb-field"},[i("label",{className:"onb-label"},"Numero de parte"),i("input",{className:"onb-input",type:"text",id:"onb-pn",placeholder:"Ej: FIL-ACE-001"})]),i("div",{className:"onb-field"},[i("label",{className:"onb-label"},"Nombre"),i("input",{className:"onb-input",type:"text",id:"onb-pname",placeholder:"Ej: Filtro de aceite"})]),i("div",{className:"onb-field"},[i("label",{className:"onb-label"},"Precio de venta ($)"),i("input",{className:"onb-input",type:"number",id:"onb-pprice",placeholder:"0.00",min:"0",step:"0.01"})]),i("div",{className:"onb-field"},[i("label",{className:"onb-label"},"Stock inicial"),i("input",{className:"onb-input",type:"number",id:"onb-pstock",placeholder:"0",min:"0",step:"1"})]),a])),e},function(){var e=i("div",{className:"onb-step-enter"},[i("div",{className:"onb-icon"},["👤"]),i("h2",{className:"onb-title"},"Crea Tu Primer Empleado"),i("p",{className:"onb-desc"},"Agrega un usuario para el punto de venta. El PIN se usara para iniciar turno.")]);if(o.employeeCreated)return e.appendChild(i("div",{className:"onb-success"},["✅ ","Empleado creado exitosamente."])),e;var a=i("div",{className:"onb-error",id:"onb-emp-msg"});return e.appendChild(i("div",{className:"onb-form"},[i("div",{className:"onb-field"},[i("label",{className:"onb-label"},"Nombre"),i("input",{className:"onb-input",type:"text",id:"onb-ename",placeholder:"Ej: Juan Perez"})]),i("div",{className:"onb-field"},[i("label",{className:"onb-label"},"PIN (4 digitos)"),i("input",{className:"onb-input",type:"password",id:"onb-epin",placeholder:"****",maxlength:"4"})]),i("div",{className:"onb-field"},[i("label",{className:"onb-label"},"Rol"),i("select",{className:"onb-select",id:"onb-erole"},[i("option",{value:"cashier"},"Cajero"),i("option",{value:"warehouse"},"Almacenista"),i("option",{value:"admin"},"Administrador"),i("option",{value:"accountant"},"Contador")])]),a])),e},function(){return i("div",{className:"onb-step-enter"},[i("div",{className:"onb-icon"},["✅"]),i("h2",{className:"onb-title"},"Listo! Tu Sistema Esta Configurado"),i("p",{className:"onb-desc"},"Ya puedes empezar a usar Nexus POS. Aqui tienes accesos rapidos:"),i("div",{className:"onb-links"},[i("a",{className:"onb-link-card",href:"/pos/catalog"},[i("span",{className:"onb-link-icon"},"📖"),"Catalogo"]),i("a",{className:"onb-link-card",href:"/pos/"},[i("span",{className:"onb-link-icon"},"💻"),"Punto de Venta"]),i("a",{className:"onb-link-card",href:"/pos/inventory"},[i("span",{className:"onb-link-icon"},"📦"),"Inventario"])])])}],s=[l,async function(){var e=document.getElementById("onb-branch-name");e&&e.value.trim()&&(o.branchRenamed=!0),l()},async function(){if(o.productCreated)l();else{var e=(document.getElementById("onb-pn")||{}).value||"",a=(document.getElementById("onb-pname")||{}).value||"",n=parseFloat((document.getElementById("onb-pprice")||{}).value)||0,t=parseInt((document.getElementById("onb-pstock")||{}).value)||0,s=document.getElementById("onb-product-msg");if(e.trim()&&a.trim())try{s&&(s.textContent=""),await r("/pos/api/inventory/items",{method:"POST",body:JSON.stringify({part_number:e.trim(),name:a.trim(),price_1:n,initial_stock:t})}),o.productCreated=!0,m(),setTimeout(l,800)}catch(e){s&&(s.textContent=e.message||"Error al crear producto.")}else s&&(s.textContent="Numero de parte y nombre son obligatorios.")}},async function(){if(o.employeeCreated)l();else{var e=(document.getElementById("onb-ename")||{}).value||"",a=(document.getElementById("onb-epin")||{}).value||"",n=(document.getElementById("onb-erole")||{}).value||"cashier",t=document.getElementById("onb-emp-msg");if(e.trim())if(!a||a.length<4)t&&(t.textContent="El PIN debe tener al menos 4 digitos.");else try{t&&(t.textContent=""),await r("/pos/api/config/employees",{method:"POST",body:JSON.stringify({name:e.trim(),pin:a,role:n})}),o.employeeCreated=!0,m(),setTimeout(l,800)}catch(e){t&&(t.textContent=e.message||"Error al crear empleado.")}else t&&(t.textContent="El nombre es obligatorio.")}},function(){c()}];"loading"===document.readyState?document.addEventListener("DOMContentLoaded",p):p()}function i(e,a,n){var o=document.createElement(e);return a&&Object.keys(a).forEach((function(e){"className"===e?o.className=a[e]:0===e.indexOf("on")?o.addEventListener(e.slice(2).toLowerCase(),a[e]):o.setAttribute(e,a[e])})),n&&(Array.isArray(n)||(n=[n]),n.forEach((function(e){"string"==typeof e?o.appendChild(document.createTextNode(e)):e&&o.appendChild(e)}))),o}async function r(e,a){var n=function(){try{return localStorage.getItem("pos_token")||""}catch(e){return""}}(),o={"Content-Type":"application/json"};n&&(o.Authorization="Bearer "+n);var t=await fetch(e,Object.assign({headers:o},a||{})),s=await t.json();if(!t.ok)throw new Error(s.error||"Error "+t.status);return s}function l(){n<4&&(n++,m())}function d(){l()}function c(){localStorage.setItem("pos_onboarding_done","true"),e&&e.parentNode&&(e.style.opacity="0",e.style.transition="opacity var(--duration-normal) var(--ease-in)",setTimeout((function(){e.parentNode.removeChild(e)}),250))}function m(){a.innerHTML="",a.appendChild(t[n]()),function(){var a=e.querySelector(".onboarding-footer");if(!a)return;a.innerHTML="";var t=i("div",{className:"onb-actions"});0===n?t.appendChild(i("span")):n<4?t.appendChild(i("button",{className:"onb-btn onb-btn--ghost",onClick:d},"Saltar")):t.appendChild(i("span"));var r=["Empezar","Siguiente","Guardar Producto","Guardar Empleado","Ir al Sistema"],l=i("button",{className:"onb-btn onb-btn--primary",onClick:s[n]},r[n]);2===n&&o.productCreated&&(l.textContent="Siguiente");3===n&&o.employeeCreated&&(l.textContent="Siguiente");t.appendChild(l),a.appendChild(t);for(var c=i("div",{className:"onb-progress"}),m=0;m<5;m++){var p="onb-dot";m===n?p+=" is-active":m✕',r.appendChild(a),n.forEach((function(t){var o=document.createElement("div"),n=document.createElement("label");n.style.cssText="display:block;font-size:12px;color:var(--color-text-muted);margin-bottom:4px;text-transform:uppercase;letter-spacing:0.05em;",n.textContent=t.label,o.appendChild(n);var a=document.createElement("select");a.style.cssText="width:100%;padding:8px 10px;background:var(--glass-bg,#222);border:1px solid var(--glass-border,#444);border-radius:6px;color:var(--color-text-primary,#fff);font-size:13px;",a.dataset.filterColumn=t.column;var i=document.createElement("option");i.value="",i.textContent=t.allLabel||"Todos",a.appendChild(i),(t.values||[]).forEach((function(t){if(t){var e=document.createElement("option");e.value=t,e.textContent=t,a.appendChild(e)}})),a.addEventListener("change",(function(){e(r)})),o.appendChild(a),r.appendChild(o)}));var i=document.createElement("button");i.style.cssText="padding:8px;background:transparent;border:1px dashed var(--glass-border,#444);border-radius:6px;color:var(--color-text-muted);cursor:pointer;font-size:12px;",i.textContent="Limpiar filtros",i.addEventListener("click",(function(){r.querySelectorAll("select").forEach((function(t){t.value=""})),e(r)})),r.appendChild(i);var l=o.parentElement;l&&(l.style.position="relative"),(l||document.body).appendChild(r),t=r,setTimeout((function(){document.addEventListener("click",(function t(e){r.contains(e.target)||e.target===o||(closeFilterPanel(),document.removeEventListener("click",t))}))}),100)},window.closeFilterPanel=function(){t&&(t.remove(),t=null)},window.getUniqueColumnValues=function(t,e,o){o=o||30;var n={};return t?(t.querySelectorAll("tbody tr").forEach((function(t){var o=t.querySelectorAll("td");if(o[e]){var r=o[e].textContent.trim();r&&"-"!==r&&""!==r&&(n[r]=(n[r]||0)+1)}})),Object.keys(n).sort((function(t,e){return n[e]-n[t]})).slice(0,o)):[]};var o=null;function n(t,e){fetch("/pos/api/quotations/"+t+"/print",{method:"POST",headers:{Authorization:"Bearer "+e,"Content-Type":"application/json"},body:JSON.stringify({printer_type:"browser"})}).then((function(t){return t.json()})).then((function(o){var n="Cotización #"+o.id+"";n+="",n+="
COTIZACIÓN
",n+='
COT-'+o.id+"
",n+="
Fecha: "+(o.created_at||"").substring(0,10)+"
",o.customer_name&&(n+="
Cliente: "+o.customer_name+"
"),o.wa_phone&&(n+="
WhatsApp: "+o.wa_phone+"
"),n+="
",(o.items||[]).forEach((function(t){n+="
"+t.quantity+"x "+t.name+'
$'+t.subtotal.toFixed(2)+"
",t.part_number&&(n+='
#'+t.part_number+"
")})),n+="
",n+='
Subtotal: $'+o.subtotal.toFixed(2)+"
",n+='
IVA: $'+o.tax_total.toFixed(2)+"
",n+='
TOTAL: $'+o.total.toFixed(2)+"
",n+='
Esta cotización no es comprobante fiscal Precios sujetos a disponibilidad
',n+="";var a=window.open("","_blank","width=400,height=600");a.document.write(n),a.document.close(),setTimeout((function(){a.print()}),500),r(t,e)})).catch((function(t){console.error("[auto-print] Browser print failed:",t)}))}function r(t,e){fetch("/pos/api/quotations/"+t+"/mark-printed",{method:"POST",headers:{Authorization:"Bearer "+e}}).catch((function(){}))}(window.startAutoPrint=function(){if(!o){0;var t=localStorage.getItem("pos_token");t&&(o=setInterval((function(){fetch("/pos/api/quotations/print-queue",{headers:{Authorization:"Bearer "+t}}).then((function(t){return t.json()})).then((function(e){e.data&&e.data.length&&e.data.forEach((function(e){console.log("[auto-print] Cotización #"+e.id+" confirmada por WhatsApp — imprimiendo..."),showToast("🖨️ Imprimiendo cotización #"+e.id+" (WhatsApp)","ok"),function(t,e){"undefined"!=typeof NexusPrinter&&NexusPrinter.isConnected&&NexusPrinter.isConnected()?fetch("/pos/api/quotations/"+t+"/print",{method:"POST",headers:{Authorization:"Bearer "+e,"Content-Type":"application/json"},body:JSON.stringify({printer_type:"escpos_raw",width:80})}).then((function(t){return t.arrayBuffer()})).then((function(o){NexusPrinter.sendRaw(new Uint8Array(o)),r(t,e)})).catch((function(o){console.error("[auto-print] Thermal print failed:",o),n(t,e)})):n(t,e)}(e.id,t)}))})).catch((function(){}))}),15e3),console.log("[auto-print] Enabled — polling every 15s"))}},window.stopAutoPrint=function(){o&&(clearInterval(o),o=null)},-1!==window.location.pathname.indexOf("/pos/sale")||-1!==window.location.pathname.indexOf("/pos/quotation")||-1!==window.location.pathname.indexOf("/pos/dashboard"))&&(localStorage.getItem("pos_token")&&setTimeout((function(){startAutoPrint()}),3e3));if(!document.getElementById("pos-utils-styles")){var a=document.createElement("style");a.id="pos-utils-styles",a.textContent="@keyframes slideInRight{from{transform:translateX(100%);opacity:0}to{transform:translateX(0);opacity:1}}.filter-panel select:focus{outline:none;border-color:var(--color-primary,#F5A623);box-shadow:0 0 0 2px var(--glow-color-soft,rgba(245,166,35,0.15));}",document.head.appendChild(a)}}();
\ No newline at end of file
diff --git a/pos/static/js/pos.min.js b/pos/static/js/pos.min.js
new file mode 100644
index 0000000..9fa3744
--- /dev/null
+++ b/pos/static/js/pos.min.js
@@ -0,0 +1 @@
+const POS=(()=>{let e=localStorage.getItem("pos_token")||"",t=[],n=-1,o=null,a=null,i="efectivo",r=!1,c=100,s=null,l=null,d=null,m=null;const u=localStorage.getItem("pos_currency")||"MXN",p={MXN:"$",USD:"US$"},y="USD"===u?"en-US":"es-MX",v=e=>(p[u]||"$")+parseFloat(e||0).toLocaleString(y,{minimumFractionDigits:2,maximumFractionDigits:2});async function g(t,n={}){n.headers={"Content-Type":"application/json",Authorization:"Bearer "+e};const o=await fetch(t,n),a=await o.json();if(!o.ok)throw new Error(a.error||`HTTP ${o.status}`);return a}function f(e){const t=document.getElementById("toastContainer");if(!t)return;const n=document.createElement("div");n.className="toast",n.textContent=e,t.appendChild(n),setTimeout((()=>n.remove()),2100)}function h(e){const n=t.find((t=>t.inventory_id===e.inventory_id));if(n)return n.quantity+=e.quantity||1,void E();t.push({inventory_id:e.inventory_id||e.id,part_number:e.part_number||"",name:e.name||"",quantity:e.quantity||1,unit_price:parseFloat(e.unit_price||e.price_1||0),unit_cost:parseFloat(e.cost||0),discount_pct:parseFloat(e.discount_pct||0),tax_rate:parseFloat(e.tax_rate||.16),stock:e.stock||0}),E(),f(`${e.name||"Articulo"} agregado`)}function _(e){t.splice(e,1),n>=t.length&&(n=t.length-1),E()}function E(){const e=document.getElementById("cartBody"),o=document.getElementById("cartTable"),a=document.getElementById("cartEmpty");if(0===t.length)return o.style.display="none",a.style.display="flex",void b();o.style.display="",a.style.display="none";let i="";t.forEach(((e,t)=>{const o=e.unit_price*e.quantity,a=o-o*e.discount_pct/100,c=r?`
",v.innerHTML=I.length?H:r("Sin datos de vendedores");var k={};_.forEach((function(t){var a=t.payment_method||"Otro";k[a]=(k[a]||0)+t.total}));var B=Object.entries(k).sort((function(t,a){return a[1]-t[1]})),A=(B.length>0&&B[0][1],{cash:"Efectivo",card:"Tarjeta",transfer:"Transferencia",credit:"Credito",mixed:"Mixto"}),D=["","pay-method__bar--b","pay-method__bar--c","pay-method__bar--d"],P='
",p.innerHTML=V}catch(t){i.innerHTML=o("Error cargando ventas: "+t.message),v.innerHTML="",m.innerHTML="",p.innerHTML=""}}async function v(){d.inventario=!0;var t=document.getElementById("inventario-kpis"),n=document.getElementById("inventario-valorizacion"),l=document.getElementById("inventario-abc"),s=document.getElementById("inventario-low-stock"),i=document.getElementById("inventario-no-movement");t.innerHTML='
Cargando...
',n.innerHTML='
Cargando...
',l.innerHTML='
Cargando...
',s.innerHTML='
Cargando...
',i.innerHTML='
Cargando...
';try{var[h,v,m,p]=await Promise.all([c("/pos/api/inventory/reports/valuation"),c("/pos/api/inventory/reports/abc"),c("/pos/api/inventory/reports/low-stock"),c("/pos/api/inventory/reports/no-movement")]);t.innerHTML=g("Valor Total Inventario","$"+a(h.grand_total),e(h.item_count)+" SKUs activos")+g("Clasificacion A",e(v.summary.A)+" SKUs","80% del volumen de ventas")+g("Stock Bajo",e(m.count)+" productos","debajo del minimo")+g("Sin Movimiento",e(p.count)+" productos",">"+p.days_threshold+" dias");var u=(h.data||[]).slice(0,20),y='
Inventario ValorizadoTop 20 de '+e(h.item_count)+"
",i.innerHTML=M.length?x:r("No hay productos sin movimiento")}catch(a){t.innerHTML=o("Error cargando inventario: "+a.message),n.innerHTML="",l.innerHTML="",s.innerHTML="",i.innerHTML=""}}async function m(){d.clientes=!0;var t=document.getElementById("clientes-kpis"),n=document.getElementById("clientes-aging");t.innerHTML='
",n.innerHTML=s.length?h:r("No hay saldos pendientes de credito")}catch(a){t.innerHTML=o("Error cargando datos de clientes: "+a.message),n.innerHTML=""}}async function p(){d.financieros=!0;var t=document.getElementById("fin-month"),e=document.getElementById("fin-year"),l=parseInt(e.value),s=parseInt(t.value),i=document.getElementById("financieros-kpis"),h=document.getElementById("financieros-income"),v=document.getElementById("financieros-balance"),m=document.getElementById("financieros-trial"),p=document.getElementById("financieros-cortes");i.innerHTML='