feat(voice): implementa voz y TTS en chats POS y dashboard

- Agrega TTS (speechSynthesis) a chat.js del POS para leer respuestas IA
- Copia lógica de voz completa (STT + TTS) a dashboard/chat-public.js
- Extiende estilos TTS en chat.css y chat-public.css
- Agrega chat widget a 13 templates POS que no lo tenían
- Corrige duplicado de chat.css en diagrams.html
- Minifica assets actualizados
- 73/73 tests pasan
This commit is contained in:
2026-04-28 00:53:57 +00:00
parent 1f909f4c42
commit afb3b2405c
20 changed files with 443 additions and 10 deletions

View File

@@ -1,5 +1,5 @@
/* ========================================================================== /* ==========================================================================
NEXUS — Public Catalog Chat Widget NEXUS — Public Catalog Chat Widget (Voice + TTS enabled)
Reuses design tokens from tokens.css Reuses design tokens from tokens.css
========================================================================== */ ========================================================================== */
@@ -228,6 +228,99 @@
.chat-send-btn:hover { background: var(--color-primary-hover, #e5952f); } .chat-send-btn:hover { background: var(--color-primary-hover, #e5952f); }
.chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; } .chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* ─── Header Actions (TTS toggle + close) ─── */
.chat-header-actions {
display: flex;
align-items: center;
gap: 8px;
}
.chat-tts-toggle {
background: none;
border: none;
color: #fff;
font-size: 1rem;
cursor: pointer;
padding: 4px;
line-height: 1;
opacity: 0.9;
transition: opacity 0.15s ease;
}
.chat-tts-toggle:hover { opacity: 1; }
.chat-tts-toggle.off { opacity: 0.4; }
/* ─── Mic Button (Voice Input) ─── */
.chat-mic-btn {
width: 38px;
height: 38px;
border-radius: 8px;
border: 1px solid var(--color-border, #333);
background: var(--color-bg-base, #111);
color: var(--color-text-secondary, #aaa);
font-size: 1.1rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.chat-mic-btn:hover {
border-color: var(--color-accent, #F5A623);
color: var(--color-accent, #F5A623);
}
.chat-mic-btn.listening {
background: #f85149;
border-color: #f85149;
color: #fff;
animation: micPulse 1.4s infinite;
}
@keyframes micPulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(248, 81, 73, 0.4); }
50% { box-shadow: 0 0 0 10px rgba(248, 81, 73, 0); }
}
/* ─── TTS Button ─── */
.chat-tts-btn {
background: none;
border: none;
color: #8b949e;
font-size: 0.85rem;
cursor: pointer;
padding: 2px 4px;
margin-left: 6px;
border-radius: 4px;
transition: color 0.2s, background 0.2s;
}
.chat-tts-btn:hover { color: #58a6ff; background: rgba(88,166,255,0.1); }
.chat-tts-btn.tts-active { color: #58a6ff; }
/* ─── Voice Toast ─── */
.chat-voice-toast {
position: fixed;
bottom: 160px;
left: 50%;
transform: translateX(-50%) translateY(10px);
background: rgba(0, 0, 0, 0.8);
color: #fff;
padding: 8px 18px;
border-radius: 8px;
font-size: 0.85rem;
z-index: 9999;
opacity: 0;
transition: opacity 0.3s, transform 0.3s;
pointer-events: none;
}
.chat-voice-toast.visible {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
@media (max-width: 480px) { @media (max-width: 480px) {
.chat-panel { .chat-panel {
width: calc(100vw - 16px); width: calc(100vw - 16px);

View File

@@ -1,15 +1,20 @@
// /home/Autopartes/dashboard/chat-public.js // /home/Autopartes/dashboard/chat-public.js
// Public catalog chatbot — no auth required, calls /api/chat // Public catalog chatbot — voice + TTS enabled
(function () { (function () {
'use strict'; 'use strict';
var isOpen = false; var isOpen = false;
var isSending = false; var isSending = false;
var isListening = false;
var recognition = null;
var history = []; var history = [];
var ttsEnabled = true;
var ttsUtterance = null;
var hasSpeechAPI = ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window);
var hasTTS = ('speechSynthesis' in window);
function init() { function init() {
// FAB button
var fab = document.createElement('button'); var fab = document.createElement('button');
fab.className = 'chat-fab'; fab.className = 'chat-fab';
fab.id = 'chatFab'; fab.id = 'chatFab';
@@ -17,21 +22,24 @@
fab.innerHTML = '💬'; fab.innerHTML = '💬';
fab.setAttribute('aria-label', 'Abrir asistente IA'); fab.setAttribute('aria-label', 'Abrir asistente IA');
// Chat panel
var panel = document.createElement('div'); var panel = document.createElement('div');
panel.className = 'chat-panel'; panel.className = 'chat-panel';
panel.id = 'chatPanel'; panel.id = 'chatPanel';
panel.innerHTML = panel.innerHTML =
'<div class="chat-header">' + '<div class="chat-header">' +
'<h3>Asistente — Buscar partes</h3>' + '<h3>Asistente — Buscar partes</h3>' +
'<div class="chat-header-actions">' +
(hasTTS ? '<button class="chat-tts-toggle" id="chatTtsToggle" aria-label="Activar lectura de respuestas" title="Activar lectura de respuestas">&#128266;</button>' : '') +
'<button class="chat-header-close" id="chatClose" aria-label="Cerrar">&times;</button>' + '<button class="chat-header-close" id="chatClose" aria-label="Cerrar">&times;</button>' +
'</div>' + '</div>' +
'</div>' +
'<div class="chat-messages" id="chatMessages">' + '<div class="chat-messages" id="chatMessages">' +
'<div class="chat-msg ai">Hola, soy el asistente de Nexus Autoparts. Dime que refaccion buscas y te ayudo a encontrarla en el catalogo.</div>' + '<div class="chat-msg ai">Hola, soy el asistente de Nexus Autoparts. Dime que refaccion buscas y te ayudo a encontrarla en el catalogo.</div>' +
'<div class="chat-typing" id="chatTyping"><span></span><span></span><span></span></div>' + '<div class="chat-typing" id="chatTyping"><span></span><span></span><span></span></div>' +
'</div>' + '</div>' +
'<div class="chat-input-area">' + '<div class="chat-input-area">' +
'<textarea class="chat-input" id="chatInput" placeholder="Ej: Balatas para Tsuru 2015..." rows="1"></textarea>' + '<textarea class="chat-input" id="chatInput" placeholder="Ej: Balatas para Tsuru 2015..." rows="1"></textarea>' +
(hasSpeechAPI ? '<button class="chat-mic-btn" id="chatMic" aria-label="Entrada por voz" title="Entrada por voz">&#127908;</button>' : '') +
'<button class="chat-send-btn" id="chatSend" aria-label="Enviar">&#9654;</button>' + '<button class="chat-send-btn" id="chatSend" aria-label="Enviar">&#9654;</button>' +
'</div>'; '</div>';
@@ -52,8 +60,139 @@
this.style.height = 'auto'; this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 80) + 'px'; this.style.height = Math.min(this.scrollHeight, 80) + 'px';
}); });
if (hasSpeechAPI) {
document.getElementById('chatMic').addEventListener('click', toggleVoice);
} }
if (hasTTS) {
document.getElementById('chatTtsToggle').addEventListener('click', toggleTTS);
}
// Stop TTS when closing chat
document.getElementById('chatClose').addEventListener('click', function () {
if (hasTTS) stopSpeaking();
});
}
// ─── TTS ───
function toggleTTS() {
ttsEnabled = !ttsEnabled;
var btn = document.getElementById('chatTtsToggle');
if (btn) {
btn.classList.toggle('off', !ttsEnabled);
btn.setAttribute('title', ttsEnabled ? 'Desactivar lectura de respuestas' : 'Activar lectura de respuestas');
}
if (!ttsEnabled) stopSpeaking();
}
function speak(text) {
if (!hasTTS || !ttsEnabled || !text) return;
stopSpeaking();
ttsUtterance = new SpeechSynthesisUtterance(text);
ttsUtterance.lang = 'es-MX';
ttsUtterance.rate = 1.1;
ttsUtterance.pitch = 1;
window.speechSynthesis.speak(ttsUtterance);
}
function stopSpeaking() {
if (hasTTS && window.speechSynthesis.speaking) {
window.speechSynthesis.cancel();
}
ttsUtterance = null;
}
// ─── Voice Input ───
function toggleVoice() {
if (isListening) { stopVoice(); return; }
startVoice();
}
function startVoice() {
var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) return;
recognition = new SpeechRecognition();
recognition.lang = 'es-MX';
recognition.continuous = false;
recognition.interimResults = true;
var input = document.getElementById('chatInput');
var micBtn = document.getElementById('chatMic');
var savedPlaceholder = input.placeholder;
recognition.onstart = function () {
isListening = true;
micBtn.classList.add('listening');
input.placeholder = 'Escuchando...';
input.value = '';
stopSpeaking();
};
recognition.onresult = function (e) {
var interim = '';
var finalTranscript = '';
for (var i = e.resultIndex; i < e.results.length; i++) {
if (e.results[i].isFinal) {
finalTranscript += e.results[i][0].transcript;
} else {
interim += e.results[i][0].transcript;
}
}
if (finalTranscript) {
input.value = finalTranscript;
} else {
input.value = interim;
}
};
recognition.onend = function () {
isListening = false;
micBtn.classList.remove('listening');
input.placeholder = savedPlaceholder;
recognition = null;
if (input.value.trim()) {
sendMessage();
}
};
recognition.onerror = function (e) {
isListening = false;
micBtn.classList.remove('listening');
input.placeholder = savedPlaceholder;
recognition = null;
if (e.error === 'no-speech' || e.error === 'audio-capture' || e.error === 'not-allowed') {
showVoiceToast('No se detecto voz');
}
};
recognition.start();
}
function stopVoice() {
if (recognition) {
recognition.abort();
recognition = null;
}
isListening = false;
var micBtn = document.getElementById('chatMic');
if (micBtn) micBtn.classList.remove('listening');
}
function showVoiceToast(msg) {
var toast = document.createElement('div');
toast.className = 'chat-voice-toast';
toast.textContent = msg;
document.body.appendChild(toast);
setTimeout(function () { toast.classList.add('visible'); }, 10);
setTimeout(function () {
toast.classList.remove('visible');
setTimeout(function () { toast.remove(); }, 300);
}, 2000);
}
// ─── Chat UI ───
function toggleChat() { function toggleChat() {
isOpen = !isOpen; isOpen = !isOpen;
var panel = document.getElementById('chatPanel'); var panel = document.getElementById('chatPanel');
@@ -104,6 +243,8 @@
addBubble(aiMsg, 'ai'); addBubble(aiMsg, 'ai');
history.push({ role: 'assistant', content: aiMsg }); history.push({ role: 'assistant', content: aiMsg });
if (ttsEnabled) speak(aiMsg);
if (data.search_results && data.search_results.length > 0) { if (data.search_results && data.search_results.length > 0) {
addPartResults(data.search_results); addPartResults(data.search_results);
} }
@@ -149,7 +290,6 @@
card.style.cursor = 'pointer'; card.style.cursor = 'pointer';
card.addEventListener('click', function () { card.addEventListener('click', function () {
// Search in catalog
var searchInput = document.getElementById('searchInput'); var searchInput = document.getElementById('searchInput');
if (searchInput && partNum) { if (searchInput && partNum) {
searchInput.value = partNum; searchInput.value = partNum;

View File

@@ -1,5 +1,5 @@
/* ========================================================================== /* ==========================================================================
NEXUS — Public Catalog Chat Widget NEXUS — Public Catalog Chat Widget (Voice + TTS enabled)
Reuses design tokens from tokens.css Reuses design tokens from tokens.css
========================================================================== */ ========================================================================== */
@@ -228,6 +228,99 @@
.chat-send-btn:hover { background: var(--color-primary-hover, #e5952f); } .chat-send-btn:hover { background: var(--color-primary-hover, #e5952f); }
.chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; } .chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* ─── Header Actions (TTS toggle + close) ─── */
.chat-header-actions {
display: flex;
align-items: center;
gap: 8px;
}
.chat-tts-toggle {
background: none;
border: none;
color: #fff;
font-size: 1rem;
cursor: pointer;
padding: 4px;
line-height: 1;
opacity: 0.9;
transition: opacity 0.15s ease;
}
.chat-tts-toggle:hover { opacity: 1; }
.chat-tts-toggle.off { opacity: 0.4; }
/* ─── Mic Button (Voice Input) ─── */
.chat-mic-btn {
width: 38px;
height: 38px;
border-radius: 8px;
border: 1px solid var(--color-border, #333);
background: var(--color-bg-base, #111);
color: var(--color-text-secondary, #aaa);
font-size: 1.1rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.chat-mic-btn:hover {
border-color: var(--color-accent, #F5A623);
color: var(--color-accent, #F5A623);
}
.chat-mic-btn.listening {
background: #f85149;
border-color: #f85149;
color: #fff;
animation: micPulse 1.4s infinite;
}
@keyframes micPulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(248, 81, 73, 0.4); }
50% { box-shadow: 0 0 0 10px rgba(248, 81, 73, 0); }
}
/* ─── TTS Button ─── */
.chat-tts-btn {
background: none;
border: none;
color: #8b949e;
font-size: 0.85rem;
cursor: pointer;
padding: 2px 4px;
margin-left: 6px;
border-radius: 4px;
transition: color 0.2s, background 0.2s;
}
.chat-tts-btn:hover { color: #58a6ff; background: rgba(88,166,255,0.1); }
.chat-tts-btn.tts-active { color: #58a6ff; }
/* ─── Voice Toast ─── */
.chat-voice-toast {
position: fixed;
bottom: 160px;
left: 50%;
transform: translateX(-50%) translateY(10px);
background: rgba(0, 0, 0, 0.8);
color: #fff;
padding: 8px 18px;
border-radius: 8px;
font-size: 0.85rem;
z-index: 9999;
opacity: 0;
transition: opacity 0.3s, transform 0.3s;
pointer-events: none;
}
.chat-voice-toast.visible {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
@media (max-width: 480px) { @media (max-width: 480px) {
.chat-panel { .chat-panel {
width: calc(100vw - 16px); width: calc(100vw - 16px);

File diff suppressed because one or more lines are too long

View File

@@ -104,6 +104,27 @@
.chat-header-close:hover { opacity: 1; } .chat-header-close:hover { opacity: 1; }
.chat-header-actions {
display: flex;
align-items: center;
gap: var(--space-2);
}
.chat-tts-toggle {
background: none;
border: none;
color: #fff;
font-size: 1rem;
cursor: pointer;
padding: var(--space-1);
line-height: 1;
opacity: 0.9;
transition: opacity var(--duration-fast) var(--ease-in-out);
}
.chat-tts-toggle:hover { opacity: 1; }
.chat-tts-toggle.off { opacity: 0.35; }
/* ─── Messages Area ─── */ /* ─── Messages Area ─── */
.chat-messages { .chat-messages {

View File

@@ -104,6 +104,27 @@
.chat-header-close:hover { opacity: 1; } .chat-header-close:hover { opacity: 1; }
.chat-header-actions {
display: flex;
align-items: center;
gap: var(--space-2);
}
.chat-tts-toggle {
background: none;
border: none;
color: #fff;
font-size: 1rem;
cursor: pointer;
padding: var(--space-1);
line-height: 1;
opacity: 0.9;
transition: opacity var(--duration-fast) var(--ease-in-out);
}
.chat-tts-toggle:hover { opacity: 1; }
.chat-tts-toggle.off { opacity: 0.35; }
/* ─── Messages Area ─── */ /* ─── Messages Area ─── */
.chat-messages { .chat-messages {

View File

@@ -9,8 +9,11 @@
let isSending = false; let isSending = false;
let isListening = false; let isListening = false;
let recognition = null; let recognition = null;
let ttsEnabled = true;
let ttsUtterance = null;
const history = []; // conversation history for AI context const history = []; // conversation history for AI context
const hasSpeechAPI = ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window); const hasSpeechAPI = ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window);
const hasTTS = ('speechSynthesis' in window);
// ─── Build DOM ─── // ─── Build DOM ───
function init() { function init() {
@@ -71,6 +74,16 @@
document.getElementById('chatMic').addEventListener('click', toggleVoice); document.getElementById('chatMic').addEventListener('click', toggleVoice);
} }
// TTS toggle
if (hasTTS) {
document.getElementById('chatTtsToggle').addEventListener('click', toggleTTS);
}
// Stop TTS when closing
document.getElementById('chatClose').addEventListener('click', function () {
if (hasTTS) stopSpeaking();
});
// Auto-resize textarea // Auto-resize textarea
document.getElementById('chatInput').addEventListener('input', function () { document.getElementById('chatInput').addEventListener('input', function () {
this.style.height = 'auto'; this.style.height = 'auto';
@@ -170,6 +183,34 @@
}, 2000); }, 2000);
} }
// ─── TTS (Text-to-Speech) ───
function toggleTTS() {
ttsEnabled = !ttsEnabled;
const btn = document.getElementById('chatTtsToggle');
if (btn) {
btn.classList.toggle('off', !ttsEnabled);
btn.setAttribute('title', ttsEnabled ? 'Desactivar lectura de respuestas' : 'Activar lectura de respuestas');
}
if (!ttsEnabled) stopSpeaking();
}
function speak(text) {
if (!hasTTS || !ttsEnabled || !text) return;
stopSpeaking();
ttsUtterance = new SpeechSynthesisUtterance(text);
ttsUtterance.lang = 'es-MX';
ttsUtterance.rate = 1.1;
ttsUtterance.pitch = 1;
window.speechSynthesis.speak(ttsUtterance);
}
function stopSpeaking() {
if (hasTTS && window.speechSynthesis.speaking) {
window.speechSynthesis.cancel();
}
ttsUtterance = null;
}
// ─── Image Upload (Part identification placeholder) ─── // ─── Image Upload (Part identification placeholder) ───
function handleImageUpload(e) { function handleImageUpload(e) {
const file = e.target.files && e.target.files[0]; const file = e.target.files && e.target.files[0];
@@ -314,6 +355,7 @@
const aiMsg = data.response || 'Sin respuesta.'; const aiMsg = data.response || 'Sin respuesta.';
addBubble(aiMsg, 'ai'); addBubble(aiMsg, 'ai');
history.push({ role: 'assistant', content: aiMsg }); history.push({ role: 'assistant', content: aiMsg });
speak(aiMsg);
// Vehicle info // Vehicle info
if (data.vehicle && data.vehicle.brand_id) { if (data.vehicle && data.vehicle.brand_id) {

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Contabilidad — Nexus Autoparts POS</title> <title>Contabilidad — Nexus Autoparts POS</title>
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="stylesheet" href="/pos/static/css/tokens.css" /> <link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="stylesheet" href="/pos/static/css/common.css" /> <link rel="stylesheet" href="/pos/static/css/common.css" />
<link rel="stylesheet" href="/pos/static/css/sidebar.css" /> <link rel="stylesheet" href="/pos/static/css/sidebar.css" />
@@ -495,5 +496,6 @@
<script src="/pos/static/js/sync-engine.js" defer></script> <script src="/pos/static/js/sync-engine.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script> <script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
<script src="/pos/static/js/chat.js" defer></script>
</body> </body>
</html> </html>

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Configuración — Nexus Autoparts POS</title> <title>Configuración — Nexus Autoparts POS</title>
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="stylesheet" href="/pos/static/css/tokens.css" /> <link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="stylesheet" href="/pos/static/css/common.css" /> <link rel="stylesheet" href="/pos/static/css/common.css" />
<link rel="stylesheet" href="/pos/static/css/sidebar.css" /> <link rel="stylesheet" href="/pos/static/css/sidebar.css" />
@@ -688,5 +689,6 @@
<script src="/pos/static/js/sync-engine.js" defer></script> <script src="/pos/static/js/sync-engine.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script> <script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
<script src="/pos/static/js/chat.js" defer></script>
</body> </body>
</html> </html>

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nexus Autoparts — Clientes</title> <title>Nexus Autoparts — Clientes</title>
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="stylesheet" href="/pos/static/css/tokens.css" /> <link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="stylesheet" href="/pos/static/css/common.css" /> <link rel="stylesheet" href="/pos/static/css/common.css" />
<link rel="stylesheet" href="/pos/static/css/sidebar.css" /> <link rel="stylesheet" href="/pos/static/css/sidebar.css" />
@@ -623,5 +624,6 @@
<script src="/pos/static/js/offline-banner.js" defer></script> <script src="/pos/static/js/offline-banner.js" defer></script>
<script src="/pos/static/js/sync-engine.js" defer></script> <script src="/pos/static/js/sync-engine.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script> <script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
<script src="/pos/static/js/chat.js" defer></script>
</body> </body>
</html> </html>

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nexus Autoparts — Dashboard</title> <title>Nexus Autoparts — Dashboard</title>
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="stylesheet" href="/pos/static/css/tokens.css" /> <link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="stylesheet" href="/pos/static/css/common.css" /> <link rel="stylesheet" href="/pos/static/css/common.css" />
<link rel="stylesheet" href="/pos/static/css/sidebar.css" /> <link rel="stylesheet" href="/pos/static/css/sidebar.css" />
@@ -460,5 +461,6 @@
<script src="/pos/static/js/sync-engine.js" defer></script> <script src="/pos/static/js/sync-engine.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script> <script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
<script src="/pos/static/js/chat.js" defer></script>
</body> </body>
</html> </html>

View File

@@ -5,11 +5,11 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Diagramas — Nexus Autoparts POS</title> <title>Diagramas — Nexus Autoparts POS</title>
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="stylesheet" href="/pos/static/css/tokens.css" /> <link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="stylesheet" href="/pos/static/css/common.css" /> <link rel="stylesheet" href="/pos/static/css/common.css" />
<link rel="stylesheet" href="/pos/static/css/sidebar.css" /> <link rel="stylesheet" href="/pos/static/css/sidebar.css" />
<link rel="stylesheet" href="/pos/static/css/pos-glass.css" /> <link rel="stylesheet" href="/pos/static/css/pos-glass.css" />
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="stylesheet" href="/pos/static/css/onboarding.css" /> <link rel="stylesheet" href="/pos/static/css/onboarding.css" />
<link rel="manifest" href="/pos/static/pwa/manifest.json" /> <link rel="manifest" href="/pos/static/pwa/manifest.json" />
<meta name="theme-color" content="#F5A623" /> <meta name="theme-color" content="#F5A623" />
@@ -154,5 +154,6 @@
<script src="/pos/static/js/sidebar.js" defer></script> <script src="/pos/static/js/sidebar.js" defer></script>
<script src="/pos/static/js/diagrams.js" defer></script> <script src="/pos/static/js/diagrams.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script> <script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
<script src="/pos/static/js/chat.js" defer></script>
</body> </body>
</html> </html>

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Flotillas — Nexus Autoparts POS</title> <title>Flotillas — Nexus Autoparts POS</title>
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="stylesheet" href="/pos/static/css/tokens.css" /> <link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="stylesheet" href="/pos/static/css/common.css" /> <link rel="stylesheet" href="/pos/static/css/common.css" />
<link rel="stylesheet" href="/pos/static/css/sidebar.css" /> <link rel="stylesheet" href="/pos/static/css/sidebar.css" />
@@ -308,5 +309,6 @@
<script src="/pos/static/js/offline-banner.js" defer></script> <script src="/pos/static/js/offline-banner.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script> <script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
<script src="/pos/static/js/chat.js" defer></script>
</body> </body>
</html> </html>

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Inventario — Nexus Autoparts POS</title> <title>Inventario — Nexus Autoparts POS</title>
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="stylesheet" href="/pos/static/css/tokens.css" /> <link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="stylesheet" href="/pos/static/css/common.css" /> <link rel="stylesheet" href="/pos/static/css/common.css" />
<link rel="stylesheet" href="/pos/static/css/sidebar.css" /> <link rel="stylesheet" href="/pos/static/css/sidebar.css" />
@@ -818,5 +819,6 @@
<script src="/pos/static/js/offline-banner.js" defer></script> <script src="/pos/static/js/offline-banner.js" defer></script>
<script src="/pos/static/js/sync-engine.js" defer></script> <script src="/pos/static/js/sync-engine.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script> <script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
<script src="/pos/static/js/chat.js" defer></script>
</body> </body>
</html> </html>

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Facturación CFDI — Nexus Autoparts POS</title> <title>Facturación CFDI — Nexus Autoparts POS</title>
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="stylesheet" href="/pos/static/css/tokens.css" /> <link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="stylesheet" href="/pos/static/css/common.css" /> <link rel="stylesheet" href="/pos/static/css/common.css" />
<link rel="stylesheet" href="/pos/static/css/sidebar.css" /> <link rel="stylesheet" href="/pos/static/css/sidebar.css" />
@@ -1057,5 +1058,6 @@
<script src="/pos/static/js/sync-engine.js" defer></script> <script src="/pos/static/js/sync-engine.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script> <script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
<script src="/pos/static/js/chat.js" defer></script>
</body> </body>
</html> </html>

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Marketplace B2B — Nexus Autoparts POS</title> <title>Marketplace B2B — Nexus Autoparts POS</title>
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="stylesheet" href="/pos/static/css/tokens.css" /> <link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="stylesheet" href="/pos/static/css/common.css" /> <link rel="stylesheet" href="/pos/static/css/common.css" />
<link rel="stylesheet" href="/pos/static/css/sidebar.css" /> <link rel="stylesheet" href="/pos/static/css/sidebar.css" />
@@ -512,5 +513,6 @@
} }
})(); })();
</script> </script>
<script src="/pos/static/js/chat.js" defer></script>
</body> </body>
</html> </html>

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Cotizaciones — Nexus Autoparts POS</title> <title>Cotizaciones — Nexus Autoparts POS</title>
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="stylesheet" href="/pos/static/css/tokens.css" /> <link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="stylesheet" href="/pos/static/css/common.css" /> <link rel="stylesheet" href="/pos/static/css/common.css" />
<link rel="stylesheet" href="/pos/static/css/sidebar.css" /> <link rel="stylesheet" href="/pos/static/css/sidebar.css" />
@@ -147,5 +148,6 @@
loadQuotes(); loadQuotes();
})(); })();
</script> </script>
<script src="/pos/static/js/chat.js" defer></script>
</body> </body>
</html> </html>

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Reportes — Nexus Autoparts POS</title> <title>Reportes — Nexus Autoparts POS</title>
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="stylesheet" href="/pos/static/css/tokens.css" /> <link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="stylesheet" href="/pos/static/css/common.css" /> <link rel="stylesheet" href="/pos/static/css/common.css" />
<link rel="stylesheet" href="/pos/static/css/sidebar.css" /> <link rel="stylesheet" href="/pos/static/css/sidebar.css" />
@@ -323,5 +324,6 @@
<script src="/pos/static/js/sync-engine.js" defer></script> <script src="/pos/static/js/sync-engine.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script> <script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
<script src="/pos/static/js/chat.js" defer></script>
</body> </body>
</html> </html>

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WhatsApp — Nexus Autoparts POS</title> <title>WhatsApp — Nexus Autoparts POS</title>
<link rel="stylesheet" href="/pos/static/css/chat.css" />
<link rel="stylesheet" href="/pos/static/css/tokens.css" /> <link rel="stylesheet" href="/pos/static/css/tokens.css" />
<link rel="stylesheet" href="/pos/static/css/common.css" /> <link rel="stylesheet" href="/pos/static/css/common.css" />
<link rel="stylesheet" href="/pos/static/css/sidebar.css" /> <link rel="stylesheet" href="/pos/static/css/sidebar.css" />
@@ -130,5 +131,6 @@ function posLogout(){localStorage.removeItem('pos_token');window.location.href='
<script src="/pos/static/js/pos-utils.js" defer></script> <script src="/pos/static/js/pos-utils.js" defer></script>
<script src="/pos/static/js/sidebar.js" defer></script> <script src="/pos/static/js/sidebar.js" defer></script>
<script src="/pos/static/js/chat.js" defer></script>
</body> </body>
</html> </html>