fix: asegurar runtime android y diagnostico pos
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-app-bar color="primary" elevation="2">
|
||||
<v-app-bar-nav-icon data-testid="navigation-toggle" class="d-md-none" aria-label="Abrir navegación" @click="drawer = !drawer" />
|
||||
<v-btn icon="mdi-menu" data-testid="navigation-toggle" aria-label="Abrir navegación" @click="drawer = !drawer" />
|
||||
<v-app-bar-title>ToldoConjunto - CESTA</v-app-bar-title>
|
||||
<v-chip v-if="jornada" color="success" variant="tonal" class="mr-3" prepend-icon="mdi-circle">Jornada activa</v-chip>
|
||||
</v-app-bar>
|
||||
|
||||
@@ -3,10 +3,16 @@ import '@mdi/font/css/materialdesignicons.css'
|
||||
import { createVuetify } from 'vuetify'
|
||||
import * as components from 'vuetify/components'
|
||||
import * as directives from 'vuetify/directives'
|
||||
import { aliases, mdi } from 'vuetify/iconsets/mdi'
|
||||
|
||||
export default createVuetify({
|
||||
components,
|
||||
directives,
|
||||
icons: {
|
||||
defaultSet: 'mdi',
|
||||
aliases,
|
||||
sets: { mdi },
|
||||
},
|
||||
theme: {
|
||||
defaultTheme: 'light',
|
||||
themes: {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<v-card-text>
|
||||
<div class="text-subtitle-1 font-weight-bold">{{ prod.nombrePresentacion }}</div>
|
||||
<div class="text-h6 text-primary">${{ prod.precioUnitario.toLocaleString() }}</div>
|
||||
<div class="text-caption" :class="prod.stock > 0 ? 'text-medium-emphasis' : 'text-error'">Disponible: {{ prod.stock }}</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
@@ -32,7 +33,7 @@
|
||||
<v-list-item-subtitle>
|
||||
<v-row align="center" class="mt-1">
|
||||
<v-col cols="4">
|
||||
<div class="d-flex align-center justify-center ga-1"><v-btn icon="mdi-minus" size="small" variant="tonal" aria-label="Disminuir cantidad" @click="cambiarCantidad(i, -1)" /><span class="quantity-value">{{ l.cantidad }}</span><v-btn icon="mdi-plus" size="small" variant="tonal" aria-label="Aumentar cantidad" @click="cambiarCantidad(i, 1)" /></div>
|
||||
<div class="d-flex align-center justify-center ga-1"><v-btn icon="mdi-minus" size="small" variant="tonal" aria-label="Disminuir cantidad" :disabled="l.cantidad <= 1" @click="cambiarCantidad(i, -1)" /><span class="quantity-value">{{ l.cantidad }}</span><v-btn icon="mdi-plus" size="small" variant="tonal" aria-label="Aumentar cantidad" :disabled="l.cantidad >= getStock(l.productoId)" @click="cambiarCantidad(i, 1)" /></div>
|
||||
</v-col>
|
||||
<v-col cols="4">${{ l.precioUnitario }}</v-col>
|
||||
<v-col cols="4">${{ l.subtotal }}</v-col>
|
||||
@@ -66,6 +67,7 @@ import { VentasService, JornadaService, EdicionService } from '@toldo/services';
|
||||
const ventasService = new VentasService();
|
||||
const jornadaService = new JornadaService();
|
||||
const productos = ref<any[]>([]);
|
||||
const stockByProduct = ref<Record<string, number>>({});
|
||||
const lineas = ref<any[]>([]);
|
||||
const formaPago = ref('EFECTIVO');
|
||||
const jornadaAbierta = ref<any>(null);
|
||||
@@ -88,7 +90,9 @@ const edicionService = new EdicionService();
|
||||
|
||||
async function cargar() {
|
||||
const productores = await db.productores.toArray();
|
||||
productos.value = (await db.productos.toArray()).map((producto) => ({ ...producto, productorNombre: productores.find((productor) => productor.id === producto.productorId)?.nombre ?? '' }));
|
||||
const movimientos = await db.movimientosInventario.toArray();
|
||||
stockByProduct.value = movimientos.reduce<Record<string, number>>((stock, movimiento) => { stock[movimiento.productoId] = (stock[movimiento.productoId] ?? 0) + movimiento.cantidad; return stock; }, {});
|
||||
productos.value = (await db.productos.toArray()).map((producto) => ({ ...producto, stock: stockByProduct.value[producto.id] ?? 0, productorNombre: productores.find((productor) => productor.id === producto.productorId)?.nombre ?? '' }));
|
||||
jornadaAbierta.value = await jornadaService.getJornadaAbierta();
|
||||
const ventasDelDia = await ventasService.getVentasDelDia(new Date());
|
||||
ventas.value = await Promise.all(ventasDelDia.map(async (venta) => ({ ...venta, lineas: await ventasService.getLineasPorVenta(venta.id) })));
|
||||
@@ -97,10 +101,13 @@ async function cargar() {
|
||||
function getProdNombre(id: string) {
|
||||
return productos.value.find(p => p.id === id)?.nombrePresentacion || id;
|
||||
}
|
||||
function getStock(id: string) { return stockByProduct.value[id] ?? 0; }
|
||||
|
||||
function agregarProducto(prod: any) {
|
||||
if (getStock(prod.id) <= 0) { error.value = 'Este producto no tiene inventario disponible. Registra primero el inventario inicial.'; return; }
|
||||
const existente = lineas.value.findIndex(l => l.productoId === prod.id);
|
||||
if (existente >= 0) {
|
||||
if (lineas.value[existente].cantidad >= getStock(prod.id)) { error.value = `Stock insuficiente. Disponible: ${getStock(prod.id)}.`; return; }
|
||||
lineas.value[existente].cantidad++;
|
||||
lineas.value[existente].subtotal = lineas.value[existente].cantidad * lineas.value[existente].precioUnitario;
|
||||
} else {
|
||||
@@ -162,7 +169,7 @@ async function registrarVenta() {
|
||||
lineas.value = [];
|
||||
exito.value = `Venta registrada por $${total.value.toLocaleString()} en ${formaPago.value}.`;
|
||||
} catch (e: any) {
|
||||
error.value = e.message;
|
||||
error.value = e.message.includes('objectStore') ? 'No se pudo abrir el almacenamiento local. Cierra y vuelve a abrir la aplicación para actualizar la base de datos.' : e.message;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { expect, test } from '@playwright/test';
|
||||
|
||||
test('muestra el panel y permite abrir navegación móvil', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.locator('.mdi-menu')).toBeVisible();
|
||||
await expect(page.getByText('Panel de control')).toBeVisible();
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.getByTestId('navigation-toggle').click();
|
||||
@@ -26,6 +27,7 @@ test('permite volver a inicio desde Jornada e Inventario', async ({ page }) => {
|
||||
if ((await page.viewportSize())!.width < 960) await page.getByTestId('navigation-toggle').click();
|
||||
await page.getByRole('link', { name: 'Inventario', exact: true }).click();
|
||||
await expect(page.getByText('Inventario y Productos')).toBeVisible();
|
||||
await expect(page.locator('.mdi-home-outline')).toBeVisible();
|
||||
await page.getByTestId('back-button').click();
|
||||
await expect(page.getByText('Panel de control')).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-10-25
|
||||
22
openspec/changes/fix-android-webview-runtime/design.md
Normal file
22
openspec/changes/fix-android-webview-runtime/design.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Design
|
||||
|
||||
## Context
|
||||
|
||||
La app usa nombres de iconos MDI, pero el set no estaba declarado explícitamente. El botón de navegación dependía de `d-md-none`, lo que puede ocultarlo en WebViews con un ancho reportado distinto al esperado.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:** garantizar iconos MDI empaquetados, menú visible y diagnóstico claro de stock.
|
||||
|
||||
**Non-Goals:** cambiar el application id, la base local o el flujo de firma Android.
|
||||
|
||||
## Decisions
|
||||
|
||||
- Declarar `aliases`, `mdi` y `defaultSet` en `createVuetify` además de importar el CSS de MDI.
|
||||
- Usar un botón de menú explícito siempre visible; el drawer seguirá siendo permanente en escritorio y temporal en móvil.
|
||||
- Cargar stock desde movimientos y bloquear el agregado de productos sin existencias con explicación.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- El botón de menú ocupa espacio adicional en escritorio, pero evita una dependencia frágil del breakpoint en WebView.
|
||||
- El stock se calcula localmente; esto es consistente con el modo offline.
|
||||
27
openspec/changes/fix-android-webview-runtime/proposal.md
Normal file
27
openspec/changes/fix-android-webview-runtime/proposal.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Proposal
|
||||
|
||||
## Why
|
||||
|
||||
La APK mostraba controles sin iconos y ocultaba el menú aunque los assets MDI estuvieran presentes, dificultando la operación en Android. Además, el POS no distinguía visualmente la falta de inventario de un error de persistencia.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Configurar explícitamente el set de iconos MDI de Vuetify y cargar su fuente.
|
||||
- Mostrar siempre un botón de menú accesible en el encabezado.
|
||||
- Mostrar stock disponible en cada producto y mensajes accionables al cobrar.
|
||||
- Mantener pruebas web de navegación y persistencia de ventas.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `android-webview-runtime`: controles e iconos operables en APK/WebView.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `toldo-poc`: experiencia del POS y diagnóstico de inventario.
|
||||
|
||||
## Impact
|
||||
|
||||
- `apps/toldo-app/src/plugins/vuetify.ts`, Layout y POS.
|
||||
- Build Capacitor/Android y pruebas de regresión.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Spec Delta
|
||||
|
||||
## Purpose
|
||||
|
||||
Garantiza que la versión Android muestre los controles de navegación y operación con iconos visibles y que los errores de venta puedan resolverse desde la interfaz.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Iconos y menú operables
|
||||
The system SHALL render MDI icons for navigation, quantity controls, deletion, and breadcrumbs in the Android WebView.
|
||||
|
||||
#### Scenario: Menú APK
|
||||
- **WHEN** the app opens on Android
|
||||
- **THEN** a visible menu button opens the navigation drawer
|
||||
- **AND** breadcrumb and action icons are visible
|
||||
|
||||
### Requirement: Diagnóstico de stock
|
||||
The system SHALL show available stock and explain when a product cannot be added or sold because inventory is insufficient.
|
||||
|
||||
#### Scenario: Producto sin inventario
|
||||
- **WHEN** user selects a product with zero stock
|
||||
- **THEN** the product is not added to the cart
|
||||
- **AND** the UI explains that initial inventory must be registered
|
||||
12
openspec/changes/fix-android-webview-runtime/tasks.md
Normal file
12
openspec/changes/fix-android-webview-runtime/tasks.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Tasks
|
||||
|
||||
## 1. Runtime Android
|
||||
|
||||
- [x] 1.1 Declarar y empaquetar MDI en Vuetify; verificar que el build contiene las fuentes
|
||||
- [x] 1.2 Hacer visible el menú y mantener breadcrumbs/iconos operables; verificar con Playwright
|
||||
- [x] 1.3 Mostrar stock y errores accionables en POS; verificar tests de venta y stock
|
||||
|
||||
## 2. Entrega
|
||||
|
||||
- [x] 2.1 Ejecutar tests unitarios, `vue-tsc` y Playwright
|
||||
- [x] 2.2 Generar APK final y comprobar assets/metadatos Android
|
||||
Reference in New Issue
Block a user