feat(pos): add inventory-aware chat context (#31) and VIN decoder (#17)

Chat now fetches tenant inventory summary (brands, counts, low-stock)
and injects it into the AI system prompt so responses prioritize local
stock. VIN decoder uses free NHTSA vPIC API to decode 17-char VINs and
auto-fills the vehicle selector dropdowns when a catalog match is found.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-04 08:14:45 +00:00
parent 5d5a2777eb
commit f9589f4a4e
6 changed files with 406 additions and 5 deletions

View File

@@ -1019,6 +1019,131 @@
});
}
// ─── VIN DECODER ───
var vinInputWrap = document.getElementById('vinInputWrap');
var vinInput = document.getElementById('vinInput');
var vinStatus = document.getElementById('vinStatus');
var vinToggle = document.getElementById('vinToggle');
function toggleVin() {
var isVisible = vinInputWrap.style.display !== 'none';
vinInputWrap.style.display = isVisible ? 'none' : '';
vinToggle.textContent = isVisible ? 'Tienes el VIN?' : 'Ocultar VIN';
if (!isVisible && vinInput) vinInput.focus();
}
function decodeVin() {
var vin = (vinInput.value || '').trim().toUpperCase();
if (vin.length !== 17) {
showVinStatus('El VIN debe tener exactamente 17 caracteres.', true);
return;
}
showVinStatus('Decodificando VIN...', false);
apiFetch(API + '/vin/' + encodeURIComponent(vin)).then(function (data) {
if (!data) {
showVinStatus('Error de conexion al decodificar VIN.', true);
return;
}
if (data.error && !data.make) {
showVinStatus(data.error, true);
return;
}
var parts = [];
if (data.year) parts.push(data.year);
if (data.make) parts.push(data.make);
if (data.model) parts.push(data.model);
if (data.engine) parts.push(data.engine);
var label = parts.join(' ') || 'Vehiculo no reconocido';
// If we got a catalog match, auto-fill the dropdowns
var match = data.catalog_match;
if (match && match.brand_id) {
showVinStatus(label + ' — Encontrado en catalogo, cargando...', false);
_autoFillFromVin(match, data);
} else {
showVinStatus(label + ' — No encontrado en el catalogo TecDoc.', false);
}
});
}
function _autoFillFromVin(match, vinData) {
// Set year dropdown
if (match.year_id) {
vsYear.value = String(match.year_id);
// Trigger brand load
apiFetch(API + '/brands?year_id=' + match.year_id).then(function (brandData) {
var brands = brandData && (brandData.data || brandData);
if (!brands) return;
vsBrand.innerHTML = '<option value="">Marca...</option>';
brands.forEach(function (b) {
vsBrand.innerHTML += '<option value="' + b.id_brand + '">' + esc(b.name_brand) + '</option>';
});
vsBrand.disabled = false;
vsClear.style.display = '';
if (match.brand_id) {
vsBrand.value = String(match.brand_id);
// Load models
apiFetch(API + '/models?brand_id=' + match.brand_id + '&year_id=' + match.year_id).then(function (modelData) {
var models = modelData && (modelData.data || modelData);
if (!models) return;
vsModel.innerHTML = '<option value="">Modelo...</option>';
models.forEach(function (m) {
vsModel.innerHTML += '<option value="' + m.id_model + '">' + esc(m.display_name || m.name_model) + '</option>';
});
vsModel.disabled = false;
if (match.model_id) {
vsModel.value = String(match.model_id);
// Load engines
apiFetch(API + '/engines?model_id=' + match.model_id + '&year_id=' + match.year_id).then(function (engData) {
var engines = engData && (engData.data || engData);
if (!engines) return;
vsEngine.innerHTML = '<option value="">Motor...</option>';
engines.forEach(function (e) {
var elabel = e.name_engine + (e.trim_level ? ' (' + e.trim_level + ')' : '');
vsEngine.innerHTML += '<option value="' + e.id_mye + '">' + esc(elabel) + '</option>';
});
vsEngine.disabled = false;
// Auto-select engine if only one or if match specifies it
if (match.id_mye) {
vsEngine.value = String(match.id_mye);
vsEngineChanged();
showVinStatus('Vehiculo cargado desde VIN.', false);
} else if (engines.length === 1) {
vsEngine.value = engines[0].id_mye;
vsEngineChanged();
showVinStatus('Vehiculo cargado desde VIN.', false);
} else {
showVinStatus('Selecciona el motor para continuar.', false);
}
});
}
});
}
});
}
}
function showVinStatus(msg, isError) {
vinStatus.style.display = msg ? '' : 'none';
vinStatus.textContent = msg;
vinStatus.style.color = isError ? 'var(--color-error)' : 'var(--color-text-muted)';
}
// Allow Enter key in VIN input to trigger decode
if (vinInput) {
vinInput.addEventListener('keydown', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
decodeVin();
}
});
}
window.CatalogApp = {
toggleCart: toggleCart,
goToCheckout: goToCheckout,
@@ -1033,6 +1158,8 @@
vsEngineChanged: vsEngineChanged,
vsClear: vsClearAll,
startBarcodeScan: startBarcodeScan,
toggleVin: toggleVin,
decodeVin: decodeVin,
};
// ─── INIT ───