feat(whatsapp): QWEN primary AI backend, Hermes fallback, conversation history, vehicle persistence, demo prompts

- Add QWEN (qwen3.6) as primary AI backend with short system prompt
- Hermes remains as fallback with 45s timeout
- Increase QWEN timeout to 35s, max_tokens to 4000
- Add conversation history loading from whatsapp_messages (last 4 msgs)
- Persist detected vehicle in whatsapp_sessions table
- Add 'limpiar chat' / 'nuevo chat' / 'reset' commands to clear history
- Fix CSS conflict: rename whatsapp chat-panel classes to wa-chat-panel
- Fix JS ID conflicts with chat.js widget (waChatPanel, waChatMessages, etc.)
- Improve no-stock response: conversational with alternatives
- Split search_query by | for multi-part lookups
- Add DEMO_PROMPTS.md and DEMO_PROMPTS_V2.md
This commit is contained in:
2026-05-06 20:27:14 +00:00
parent 371d72887e
commit ff45905b49
33 changed files with 3040 additions and 445 deletions

View File

@@ -1,21 +1,88 @@
const { test, expect } = require('@playwright/test');
test.describe('Nexus POS — Inventory', () => {
test('inventory page loads with table or grid', async ({ page }) => {
await page.goto('/pos/inventory');
await expect(page.locator('#inventoryTable, .data-table, #partsGrid, .grid, table')).toBeVisible({ timeout: 10000 });
const content = await page.locator('body').textContent();
expect(content).toMatch(/inventario|stock|producto|parte/i);
const FAKE_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjk5OTk5OTk5OTksIm5hbWUiOiJUZXN0IFVzZXIifQ.signature';
async function setupAuth(page) {
await page.goto('/pos/login');
await page.evaluate((token) => {
localStorage.setItem('pos_token', token);
localStorage.setItem('pos_tenant_id', '11');
}, FAKE_TOKEN);
}
async function mockInventoryAPIs(page) {
await page.route(/\/pos\/api\/inventory\/items\?page=.*&per_page=.*/, async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
data: [
{
id: 1,
barcode: '123456789',
part_number: 'TEST-001',
name: 'Producto de prueba',
brand: 'TestBrand',
stock: 10,
cost: 50.0,
price_1: 100.0,
price_2: 90.0,
price_3: 80.0,
location: 'A-1',
},
],
pagination: { page: 1, total_pages: 1, total: 1 },
}),
});
});
test('product detail modal or panel opens', async ({ page }) => {
await page.route(/\/pos\/api\/inventory\/items\/\d+/, async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
id: 1,
barcode: '123456789',
part_number: 'TEST-001',
name: 'Producto de prueba',
brand: 'TestBrand',
location: 'A-1',
stock: 10,
cost: 50.0,
price_1: 100.0,
price_2: 90.0,
price_3: 80.0,
history: [],
}),
});
});
}
test.describe('Nexus POS — Inventory', () => {
test('inventory page loads with table', async ({ page }) => {
await setupAuth(page);
await mockInventoryAPIs(page);
await page.goto('/pos/inventory');
// Try clicking first row or card
const firstRow = page.locator('.data-table tbody tr, .grid .card, .inventory-row').first();
await firstRow.waitFor({ state: 'visible', timeout: 10000 }).catch(() => {});
if (await firstRow.isVisible().catch(() => false)) {
await firstRow.click();
await expect(page.locator('.modal, .detail-panel, #detailPanel, [role="dialog"]')).toBeVisible({ timeout: 5000 });
}
await expect(page).toHaveTitle(/Inventario/i);
await expect(page.locator('#stockTable')).toBeVisible({ timeout: 5000 });
// Wait for virtual-scroll rows to render
await page.waitForSelector('#productTableBody tr', { timeout: 5000 });
const rows = page.locator('#productTableBody tr');
await expect(rows.first()).toBeVisible();
});
test('product detail modal opens', async ({ page }) => {
await setupAuth(page);
await mockInventoryAPIs(page);
await page.goto('/pos/inventory');
await page.waitForSelector('#productTableBody tr', { timeout: 5000 });
const firstRow = page.locator('#productTableBody tr').first();
await firstRow.click();
// The detail view opens inside #historyModal
await expect(page.locator('#historyModal')).toHaveClass(/is-open/, { timeout: 5000 });
await expect(page.locator('#historyContent')).toContainText('Producto de prueba');
});
});