diff --git a/ToldoConjunto-debug.apk b/ToldoConjunto-debug.apk index 450e5fa..41875ae 100644 Binary files a/ToldoConjunto-debug.apk and b/ToldoConjunto-debug.apk differ diff --git a/apps/toldo-app/package-lock.json b/apps/toldo-app/package-lock.json index 424475c..df6b100 100644 --- a/apps/toldo-app/package-lock.json +++ b/apps/toldo-app/package-lock.json @@ -10,13 +10,15 @@ "dependencies": { "@capacitor/android": "^6.2.1", "@capacitor/core": "^6.2.1", + "@mdi/font": "^7.4.47", "@toldo/core": "file:../../packages/core", "@toldo/data": "file:../../packages/data", "@toldo/services": "file:../../packages/services", "@toldo/ui": "file:../../packages/ui", "dexie": "^4.0.10", "vue": "^3.5.44", - "vue-router": "file:vue-router-4.4.5.tgz" + "vue-router": "file:vue-router-4.4.5.tgz", + "vuetify": "^4.3.0" }, "devDependencies": { "@capacitor/cli": "^6.2.1", @@ -910,6 +912,11 @@ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==" }, + "node_modules/@mdi/font": { + "version": "7.4.47", + "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.4.47.tgz", + "integrity": "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==" + }, "node_modules/@napi-rs/lzma-linux-x64-gnu": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", @@ -1791,6 +1798,26 @@ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==" }, + "node_modules/@vue/devtools-kit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz", + "integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==", + "optional": true, + "peer": true, + "dependencies": { + "@vue/devtools-shared": "^8.2.1", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz", + "integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==", + "optional": true, + "peer": true + }, "node_modules/@vue/language-core": { "version": "3.3.13", "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.13.tgz", @@ -2055,6 +2082,16 @@ "node": ">=0.6" } }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/bplist-parser": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.2.tgz", @@ -2915,6 +2952,13 @@ "node": ">= 0.4" } }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "optional": true, + "peer": true + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -3971,6 +4015,13 @@ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "optional": true, + "peer": true + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -6042,6 +6093,105 @@ "typescript": ">=5.0.0" } }, + "node_modules/vuetify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-4.3.0.tgz", + "integrity": "sha512-tQpJ/2oKCNeZQb7p3PpwGVca4B9IFUp4LuTpE/bOZ/lIC4U74EJxtriMdB8xj0wtZx40PFGbHPrdfJzZg1FX0A==", + "dependencies": { + "@vuetify/v0": "^1.2.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/johnleider" + }, + "peerDependencies": { + "typescript": ">=4.7", + "vite-plugin-vuetify": ">=2.1.0", + "vue": "^3.5.0 || ^3.6.0-0", + "webpack-plugin-vuetify": ">=3.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vite-plugin-vuetify": { + "optional": true + }, + "webpack-plugin-vuetify": { + "optional": true + } + } + }, + "node_modules/vuetify/node_modules/@vue/devtools-api": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.2.1.tgz", + "integrity": "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==", + "optional": true, + "peer": true, + "dependencies": { + "@vue/devtools-kit": "^8.2.1" + } + }, + "node_modules/vuetify/node_modules/@vuetify/v0": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@vuetify/v0/-/v0-1.3.0.tgz", + "integrity": "sha512-Tvknc64ciwaOdKzifg3maMOiczbEenWxYINoZIotfKQ8VWBs/yeHbvHffW4w1j+xYnNyYi0S9LK5UCzyQrYg9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/johnleider" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/vuetify" + } + ], + "peerDependencies": { + "@adobe/leonardo-contrast-colors": ">=1.0.0", + "@ant-design/colors": ">=7.0.0", + "@flagsmith/flagsmith": ">=11.0.0", + "@floating-ui/dom": ">=1.8.0", + "@js-temporal/polyfill": ">=0.5.0", + "@material/material-color-utilities": ">=0.3.0", + "@vue/devtools-api": ">=7.0.0", + "launchdarkly-js-client-sdk": "^3.0.0", + "posthog-js": "^1.0.0", + "vue": ">=3.5.0 || >=3.6.0-0", + "vue-i18n": ">=10.0.0" + }, + "peerDependenciesMeta": { + "@adobe/leonardo-contrast-colors": { + "optional": true + }, + "@ant-design/colors": { + "optional": true + }, + "@flagsmith/flagsmith": { + "optional": true + }, + "@floating-ui/dom": { + "optional": true + }, + "@js-temporal/polyfill": { + "optional": true + }, + "@material/material-color-utilities": { + "optional": true + }, + "@vue/devtools-api": { + "optional": true + }, + "launchdarkly-js-client-sdk": { + "optional": true + }, + "posthog-js": { + "optional": true + }, + "vue-i18n": { + "optional": true + } + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/apps/toldo-app/package.json b/apps/toldo-app/package.json index a8f5349..9ec83b2 100644 --- a/apps/toldo-app/package.json +++ b/apps/toldo-app/package.json @@ -13,13 +13,15 @@ "dependencies": { "@capacitor/android": "^6.2.1", "@capacitor/core": "^6.2.1", + "@mdi/font": "^7.4.47", "@toldo/core": "file:../../packages/core", "@toldo/data": "file:../../packages/data", "@toldo/services": "file:../../packages/services", "@toldo/ui": "file:../../packages/ui", "dexie": "^4.0.10", "vue": "^3.5.44", - "vue-router": "file:vue-router-4.4.5.tgz" + "vue-router": "file:vue-router-4.4.5.tgz", + "vuetify": "^4.3.0" }, "devDependencies": { "@capacitor/cli": "^6.2.1", diff --git a/apps/toldo-app/src/components/Layout.vue b/apps/toldo-app/src/components/Layout.vue index 3d073ee..7b7e629 100644 --- a/apps/toldo-app/src/components/Layout.vue +++ b/apps/toldo-app/src/components/Layout.vue @@ -1,29 +1,27 @@ + + + ToldoConjunto - CESTA + + - + + + + + + + + + + + + + + + + + + + + diff --git a/apps/toldo-app/src/main.ts b/apps/toldo-app/src/main.ts index 3e79677..404617e 100644 --- a/apps/toldo-app/src/main.ts +++ b/apps/toldo-app/src/main.ts @@ -1,5 +1,6 @@ import { createApp } from 'vue' import App from './App.vue' import router from './router' +import vuetify from './plugins/vuetify' -createApp(App).use(router).mount('#app') +createApp(App).use(router).use(vuetify).mount('#app') diff --git a/apps/toldo-app/src/plugins/vuetify.ts b/apps/toldo-app/src/plugins/vuetify.ts new file mode 100644 index 0000000..e8f6c06 --- /dev/null +++ b/apps/toldo-app/src/plugins/vuetify.ts @@ -0,0 +1,21 @@ +import 'vuetify/styles' +import { createVuetify } from 'vuetify' +import * as components from 'vuetify/components' +import * as directives from 'vuetify/directives' + +export default createVuetify({ + components, + directives, + theme: { + defaultTheme: 'light', + themes: { + light: { + colors: { + primary: '#2d5f3b', + secondary: '#4a7c59', + accent: '#6b8e23', + }, + }, + }, + }, +}) diff --git a/apps/toldo-app/src/views/Home.vue b/apps/toldo-app/src/views/Home.vue index fb1fde7..a270387 100644 --- a/apps/toldo-app/src/views/Home.vue +++ b/apps/toldo-app/src/views/Home.vue @@ -1,7 +1,73 @@ + + diff --git a/apps/toldo-app/src/views/Inventario.vue b/apps/toldo-app/src/views/Inventario.vue index 4d723be..3c617a2 100644 --- a/apps/toldo-app/src/views/Inventario.vue +++ b/apps/toldo-app/src/views/Inventario.vue @@ -1,50 +1,67 @@ @@ -73,14 +90,14 @@ async function cargar() { async function crearProductor() { if (!nuevoProductor.value) return; - await db.productores.add({ id: crypto.randomUUID(), nombre: nuevoProductor.value, activo: true, createdAt: new Date() }); + await db.productores.add({ id: crypto.randomUUID(), nombre: nuevoProductor.value, activo: true, createdAt: new Date() } as any); nuevoProductor.value = ''; await cargar(); } async function crearProducto() { if (!productorSel.value || !nombreProd.value) return; - await db.productos.add({ id: crypto.randomUUID(), productorId: productorSel.value, nombrePresentacion: nombreProd.value, precioUnitario: precioProd.value, activo: true, createdAt: new Date() }); + await db.productos.add({ id: crypto.randomUUID(), productorId: productorSel.value, nombrePresentacion: nombreProd.value, precioUnitario: Number(precioProd.value), activo: true, createdAt: new Date() } as any); nombreProd.value = ''; precioProd.value = 0; await cargar(); @@ -88,14 +105,14 @@ async function crearProducto() { async function ingresoInicial() { if (!prodInv.value || cantInv.value <= 0) return; - await invService.registrarIngresoInicial(prodInv.value, cantInv.value, new Date()); + await invService.registrarIngresoInicial(prodInv.value, Number(cantInv.value), new Date()); cantInv.value = 0; alert('Ingreso inicial registrado'); } async function ingresoSemana() { if (!prodInv2.value || cantInv2.value <= 0 || !recibidoPor.value) return; - await invService.registrarIngresoSemana(prodInv2.value, cantInv2.value, new Date(), recibidoPor.value); + await invService.registrarIngresoSemana(prodInv2.value, Number(cantInv2.value), new Date(), recibidoPor.value); cantInv2.value = 0; recibidoPor.value = ''; alert('Ingreso entre semana registrado'); diff --git a/apps/toldo-app/src/views/Ventas.vue b/apps/toldo-app/src/views/Ventas.vue index 680d9c2..4660174 100644 --- a/apps/toldo-app/src/views/Ventas.vue +++ b/apps/toldo-app/src/views/Ventas.vue @@ -1,38 +1,54 @@ @@ -43,10 +59,8 @@ import { VentasService } from '@toldo/services'; const ventasService = new VentasService(); const productos = ref([]); -const productoSel = ref(''); -const cantidad = ref(1); -const formaPago = ref('EFECTIVO'); const lineas = ref([]); +const formaPago = ref('EFECTIVO'); async function cargar() { productos.value = await db.productos.toArray(); @@ -56,39 +70,49 @@ function getProdNombre(id: string) { return productos.value.find(p => p.id === id)?.nombrePresentacion || id; } -function agregarLinea() { - if (!productoSel.value || cantidad.value <= 0) return; - const prod = productos.value.find(p => p.id === productoSel.value); - if (!prod) return; - lineas.value.push({ - id: crypto.randomUUID(), - ventaId: '', - productoId: prod.id, - cantidad: cantidad.value, - precioUnitario: prod.precioUnitario, - subtotal: prod.precioUnitario * cantidad.value, - formaPago: formaPago.value - }); - productoSel.value = ''; - cantidad.value = 1; +function agregarProducto(prod: any) { + const existente = lineas.value.findIndex(l => l.productoId === prod.id); + if (existente >= 0) { + lineas.value[existente].cantidad++; + lineas.value[existente].subtotal = lineas.value[existente].cantidad * lineas.value[existente].precioUnitario; + } else { + lineas.value.push({ + id: crypto.randomUUID(), + ventaId: '', + productoId: prod.id, + cantidad: 1, + precioUnitario: prod.precioUnitario, + subtotal: prod.precioUnitario, + formaPago: formaPago.value + }); + } +} + +function actualizarLinea(i: number) { + lineas.value[i].subtotal = lineas.value[i].cantidad * lineas.value[i].precioUnitario; +} + +function eliminarLinea(i: number) { + lineas.value.splice(i, 1); } const total = computed(() => lineas.value.reduce((acc, l) => acc + l.subtotal, 0)); async function registrarVenta() { + if (lineas.value.length === 0) return; const ventaId = crypto.randomUUID(); + const lineasVenta = lineas.value.map(l => ({ ...l, ventaId, formaPago: formaPago.value })); const venta = { id: ventaId, fecha: new Date(), - lineas: lineas.value.map(l => ({ ...l, ventaId })), + lineas: lineasVenta, subtotal: total.value, total: total.value, createdAt: new Date() }; - const lineasVenta = lineas.value.map(l => ({ ...l, ventaId })); await ventasService.registrarVenta(venta, lineasVenta); lineas.value = []; - alert('Venta registrada'); + alert(`Venta registrada por $${total.value} - ${formaPago.value}`); } onMounted(cargar); diff --git a/openspec/changes/improve-ui-ux-pos/design.md b/openspec/changes/improve-ui-ux-pos/design.md new file mode 100644 index 0000000..6c95973 --- /dev/null +++ b/openspec/changes/improve-ui-ux-pos/design.md @@ -0,0 +1,20 @@ +# Design: improve-ui-ux-pos + +## UI Library +Use Vuetify 3 with Material Design 3, responsive for tablet. + +## POS Flow +- Product grid by category/producer +- Cart with quantities +- Payment method selection +- Quick sale completion + +## UX Improvements +- Dashboard with day summary +- Guided setup on first run +- Data tables for all entities +- Better forms with validation +- Consistent theming + +## Testing +Playwright tests for web UI covering realistic scenarios diff --git a/openspec/changes/improve-ui-ux-pos/proposal.md b/openspec/changes/improve-ui-ux-pos/proposal.md new file mode 100644 index 0000000..178b96b --- /dev/null +++ b/openspec/changes/improve-ui-ux-pos/proposal.md @@ -0,0 +1,24 @@ +# Proposal: improve-ui-ux-pos + +## Why +Current UI is too simple/minimalist, lacks styling, and the POS flow (create sale) is not intuitive. Users need a realistic POS experience with: +- Proper product catalog by producer +- Easy product selection for sales (like buying 3 lbs coffee, 2 condiments) +- Clear day closure/cash reconciliation flow +- Better UX with a UI component library +- Seed data/onboarding to create producers/products easily + +## What Changes +- Integrate Vuetify 3 for professional UI, themes, components +- Redesign POS/sales screen like real POS systems +- Improve product/producer management with better forms +- Add quick start wizard (setup producers/products) +- Improve cash closure flow with detailed breakdown +- Add realistic test scenarios +- Enhance all views with proper layouts + +## Impact +- apps/toldo-app: UI overhaul with Vuetify +- Better UX for tablet use +- More realistic workflows +- Maintain offline-first, Capacitor compatible diff --git a/openspec/changes/improve-ui-ux-pos/tasks.md b/openspec/changes/improve-ui-ux-pos/tasks.md new file mode 100644 index 0000000..e1dbbb0 --- /dev/null +++ b/openspec/changes/improve-ui-ux-pos/tasks.md @@ -0,0 +1,29 @@ +# Tasks + +## 1. Research & Setup +- [ ] 1.1 Research POS UI patterns +- [ ] 1.2 Install Vuetify 3, mdi icons +- [ ] 1.3 Configure Vuetify theme + +## 2. UI Overhaul +- [ ] 2.1 Redesign Layout with Vuetify (app bar, navigation) +- [ ] 2.2 Redesign Dashboard with day summary +- [ ] 2.3 Redesign Inventory/Producers/Products with data tables +- [ ] 2.4 Redesign POS Sales screen (product grid + cart) +- [ ] 2.5 Redesign Expenses, Adjustments +- [ ] 2.6 Redesign Cash Closure with detailed breakdown +- [ ] 2.7 Redesign Reports +- [ ] 2.8 Redesign Export/Import + +## 3. UX Features +- [ ] 3.1 Add setup wizard for first run +- [ ] 3.2 Add quick product creation +- [ ] 3.3 Improve validation + +## 4. Testing +- [ ] 4.1 Setup Playwright +- [ ] 4.2 Create realistic test scenarios +- [ ] 4.3 Test full day flow + +## 5. Build & Deploy +- [ ] 5.1 Build web and APK