- The inline anti-flash script now applies the correct theme before any CSS renders
- Without the hardcoded attribute, the browser never paints with the wrong theme
- Remove setTimeout re-application of theme in app-init.js that caused flash
- Fix quotations.html: add missing i18n.js and correct script load order
- Fix whatsapp.html: add missing app-init.js before sidebar.js
- Ensure i18n.js always loads before sidebar.js for proper translation
- Add skip-validation checkbox for accounts where ML validation fails due to config
- Detect 'User has not mode' errors and show detailed actionable help box
- Include direct links to ML seller config and support
- build_item_payload now sends only mode in shipping payload by default
- Let ML determine free_shipping/local_pick_up based on account config
- Better error message for mandatory free shipping scenario
- Add get_shipping_preferences to meli_service.py
- Add check_meli_shipping_config to validate ME2 adoption before publishing
- Include local_pick_up and free_shipping in item payload
- Translate ME2/mode errors to actionable Spanish messages
- Check shipping config in both validate_items and publish_items
- Batch scroll renders with requestAnimationFrame to avoid multiple DOM updates per frame
- Add will-change, contain and content-visibility CSS for smoother compositing
- Add cache-bust to virtual-scroll.js
- Add MercadoLibre OAuth, listings, orders, webhooks and category search
- New marketplace_external_bp.py, meli_service.py, marketplace_external_service.py
- New marketplace_external.html/js with ML management UI
- Inventory: bulk publish to ML with category autocomplete, listing type and shipping selectors
- Inventory: new .btn--meli styles, select/label CSS fixes
- WhatsApp bridge: rate limiting, 440/515/408 error handling, stale watchdog
- DB migration v3.4_meli_integration.sql for marketplace_listings, orders, sync_queue
- Add Celery tasks for ML sync and webhook processing
- Sidebar: MercadoLibre navigation link
Cards/Grid:
- Add min-width:0 to .device-grid to prevent grid overflow
- Add max-width:100%, overflow:hidden, word-break:break-word to .device-card
- Add min-width:0 and overflow-wrap to .device-card__body
- Bump config.css cache-bust to v=2
Onboarding:
- Add GET/POST /pos/api/config/onboarding-status endpoints in config_bp.py
- onboarding.js now checks server first before showing wizard
- On finish, POSTs completion to server (tenant_config table)
- Falls back to localStorage for fast path and offline resilience
- Bump onboarding.js cache-bust to v=2 in catalog.html
- Add min-width:0 and overflow-wrap:break-word to .device-card
- Add min-width:0 and overflow-wrap:break-word to .device-card__body
- Prevents grid items from expanding beyond their cell when content is wide
- Bump config.css cache-bust to v=2
The sidebar injected by sidebar.js is position:fixed with width:260px,
but most pages lacked margin-left on their main content, causing text
to be hidden behind the sidebar.
Affected pages:
- accounting, config, inventory, invoicing (.main)
- catalog, customers, diagrams, reports (.main-content)
Already fixed: dashboard, quotations
Not affected: fleet, whatsapp (use pos-main-offset), pos (no sidebar)
- Add 'Corte Z' button in secondary actions panel
- Add modal showing register summary before closing:
- opening amount, total sales, cash sales, change given
- cash movements in/out, cancellations, expected cash
- payment method breakdown and movement detail list
- loadCutX() fetches current register summary (read-only)
- confirmCutZ() calls POST /pos/api/register/cut-z with counted amount
- Auto-fills closing amount with expected cash
- Shows toast with difference after closing
- Resets register state to 'Sin caja abierta' after close
- Bump pos.css and pos.js cache-bust to v=3
- Add 'Open Register' modal with register number and opening amount inputs
- loadRegister now shows clickable warning when no register is open
- checkout() opens register modal instead of plain alert when no register
- Add openRegister() API call to POST /pos/api/register/open
- Expose showOpenRegisterModal, closeOpenRegisterModal, openRegister globally
- Add cache-bust query params to pos.css and pos.js
- Add GET /operations endpoint with filtering by type, pagination, date range
- Join with inventory, employees, branches for rich display
- Add tbody IDs and footer/pagination IDs to operation tables in HTML
- Add loadOperations() JS function with renderOperationRow() per type
- Integrate loadOperations into switchTab for auto-load on tab change
- Update recordPurchase/Adjustment/Transfer to refresh respective lists
- Expose loadOperations globally for HTML inline script access
- Add ID column to stock table header and renderInventoryRow
- Add 'Entrada' button on each stock row to open purchase modal pre-filled
- Show inventory ID in product detail popup
- recordPurchase: close modal, clear form, reload stats and stock list on success
- Fix colspan 11 -> 12 for empty state rows
- Expose loadInventoryStats globally so inventory.js can call it after CRUD
- Fix token key: use pos_token (not access_token) to match auth scheme
- After successful POST /items: close modal, clear form inputs, reload stats
- Bump inventory.js cache-bust query param v3 -> v4
- Add POS_INTERNAL_URL config for cross-VM API calls
- create_demo now calls POS /internal/whatsapp-bridge after tenant creation
- delete_tenant now destroys bridge container before dropping DB
- Graceful fallback if bridge provisioning fails
- Add Dockerfile.whatsapp-bridge with Baileys + env var support
- Modify whatsapp-bridge-server.js to accept PORT, TENANT_ID, WEBHOOK_BASE
- Add internal_bp.py with endpoints to provision/destroy bridges via Docker
- Register internal_bp in app.py
- Each tenant gets isolated container, port, and volume
- Refactor whatsapp_service.py to accept bridge_url parameter
- whatsapp_bp.py: remove hardcoded tenant_id=11, use g.tenant_id
- whatsapp_bp.py: webhook now accepts ?tenant_id param with fallback
- config_bp.py: add GET/PUT /config/whatsapp endpoints
- Each tenant can now have its own Baileys bridge URL and settings
- Complete Flask-based control panel for multi-tenant POS instances
- Dashboard with global stats, system health, and recent demos
- Demo provisioning in 1 click with auto-expiration tracking
- Tenant management: activate/deactivate, reset data, delete
- Health monitoring: PostgreSQL, Redis, disk, memory, systemd services
- Migration orchestration UI for running schema updates across all tenants
- JWT authentication with manager_users table
- Dark theme SPA frontend with real-time search and actions
- systemd service file included
Brand catalog now follows the same navigation as the regular catalog:
1. Brands -> 2. Models -> 3. Years -> 4. Engines -> 5. Categories -> 6. Parts
Backend:
- Add /mye-parts endpoint for MYE-specific parts with category filter
- Uses existing /models, /years, /engines, /categories endpoints
Frontend:
- Complete rewrite of brand-catalog.js with breadcrumb navigation
- State machine: brands -> models -> years -> engines -> categories -> parts
- Search and pagination preserved at parts level
- Breadcrumb allows jumping back to any previous step
Add updatefound listener in catalog.html that reloads the page
automatically when a new service worker is activated. This ensures
users get the latest HTML and JS without manual hard refresh.
- Bump cache to nexus-pos-v6 to force invalidation
- HTML pages now use network-first instead of cache-first
This ensures users always get the latest HTML with correct
JS/CSS references (?v=3) instead of stale cached HTML
- Remove HTML pages from APP_SHELL precache (only static assets)
- Keep cache-first for JS/CSS/images
- Add #brandCatalogSearch container in HTML for search inputs
- Move brand search input out of renderBrandList so it persists while typing
- Move parts search input out of renderPartsList so it persists while typing
- Reset now clears search container
- Bump JS cache bust to v=3
Update service worker cache name to nexus-pos-v5 to force cache
invalidation. Add brand-catalog.js to APP_SHELL precache list.
This should resolve stale cached JS causing parse errors.
/vehicle-brands now uses get_brands_for_mode('oem') to return the same
36 North American brands (Mexico/USA/Canada) as the regular catalog flow,
instead of all 619 brands in the database.
brand-catalog.js was missing Authorization header on fetch calls,
causing 401 Unauthorized errors. Now reads pos_token from localStorage
and includes Bearer token in every request. Also handles 401 responses
by redirecting to /pos/login. Bump JS cache bust to v=2.
Backend:
- Add 'search' param to /brand-parts endpoint (filters oem_part_number and name via ILIKE)
- Keep count query accurate with search filter
Frontend (brand-catalog.js):
- Brand search input: filters 619 brands locally while typing
- Parts pagination: Previous/Next buttons with page counter (50 per page)
- Parts search within category: search input + Enter key triggers backend search
- Visual polish: stock badges, empty-state messages, responsive layout
- Loading states and breadcrumbs improved
Remove broken location blocks for static files that had no proxy_pass,
which caused all .js/.css files to return 404 or HTML.
Add explicit /pos/static/ location with proxy_pass + cache headers.
Update nexus-pos.conf in repo to match live config.
- 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
- Added save_qwen_fitment() in inventory_vehicle_compat.py to centralize
inserting QWEN results into inventory_vehicle_compat
- Simplified inventory_bp.py create_item() and auto_match_item_vehicles()
to use the centralized function, removing duplicated INSERT logic
inventory_vehicle_compat.get_compatibility was trying to JOIN tenant
inventory_vehicle_compat with master tables (model_year_engine, brands,
models, years, engines) on a single tenant connection. Those tables only
exist in the master DB, causing the query to fail silently.
Fix: split into two queries:
1. Fetch MYE IDs from tenant's inventory_vehicle_compat
2. Resolve vehicle details from master DB via ANY(%s)
3. Merge results
Also fixes the argument mismatch: inventory_bp passed (tenant, master,
item_id) but the function only accepted 2 args.
Replaced simple exact-match with 8-layer fallback strategy:
1. Exact normalized part number (parts.oem_part_number)
2. Exact normalized aftermarket part number
3. Exact normalized cross-reference number
4. Partial ILIKE match on OEM numbers
5. Partial ILIKE match on aftermarket numbers
6. Partial ILIKE match on cross-reference numbers
7. Separator-stripped fallback (KYB-343412 → KYB343412)
8. Name-based search on parts.name_part / parts.name_es
and aftermarket_parts.name_aftermarket_parts when no part_number hit
Brand-aware filtering: when brand hint is provided and not 'GENERAL',
only returns MYEs for vehicles of that brand.
Limits: max 20 part IDs per layer, max 200 MYEs total.
Test: BPR5ES + TOYOTA → matched True, 2 parts, 200 MYEs inserted.
- tenant_db.py: add rollback() before returning conn to pool to prevent
'idle in transaction (aborted)' state that exhausts the pool
- tenant_db.py: increase pool maxconn from 10 to 20 for better concurrency
- inventory_vehicle_compat.py: fix column name cross_ref_number ->
cross_reference_number to match actual schema