feat: UI completa funcional para todos los módulos

This commit is contained in:
mono
2026-10-24 22:57:04 -05:00
parent c81ef268ff
commit 27b084dd52
21 changed files with 461 additions and 121 deletions

View File

@@ -0,0 +1 @@
:root{--text:#6b6375;--text-h:#08060d;--bg:#fff;--border:#e5e4e7;--code-bg:#f4f3ec;--accent:#aa3bff;--accent-bg:#aa3bff1a;--accent-border:#aa3bff80;--social-bg:#f4f3ec80;--shadow:#0000001a 0 10px 15px -3px, #0000000d 0 4px 6px -2px;--sans:system-ui, "Segoe UI", Roboto, sans-serif;--heading:system-ui, "Segoe UI", Roboto, sans-serif;--mono:ui-monospace, Consolas, monospace;font:18px/145% var(--sans);letter-spacing:.18px;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;color:var(--text);background:var(--bg);font-synthesis:none;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}@media (width<=1024px){:root{font-size:16px}}@media (prefers-color-scheme:dark){:root{--text:#9ca3af;--text-h:#f3f4f6;--bg:#16171d;--border:#2e303a;--code-bg:#1f2028;--accent:#c084fc;--accent-bg:#c084fc26;--accent-border:#c084fc80;--social-bg:#2f303a80;--shadow:#0006 0 10px 15px -3px, #00000040 0 4px 6px -2px}#social .button-icon{filter:invert()brightness(2)}}body{margin:0}h1,h2{font-family:var(--heading);color:var(--text-h);font-weight:500}h1{letter-spacing:-1.68px;margin:32px 0;font-size:56px}@media (width<=1024px){h1{margin:20px 0;font-size:36px}}h2{letter-spacing:-.24px;margin:0 0 8px;font-size:24px;line-height:118%}@media (width<=1024px){h2{font-size:20px}}p{margin:0}code,.counter{font-family:var(--mono);color:var(--text-h);border-radius:4px;display:inline-flex}code{background:var(--code-bg);padding:4px 8px;font-size:15px;line-height:135%}.counter{color:var(--accent);background:var(--accent-bg);border:2px solid #0000;border-radius:5px;margin-bottom:24px;padding:5px 10px;font-size:16px;transition:border-color .3s}.counter:hover{border-color:var(--accent-border)}.counter:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.hero{position:relative}.hero .base,.hero .framework,.hero .vite{margin:0 auto;inset-inline:0}.hero .base{z-index:0;width:170px;position:relative}.hero .framework,.hero .vite{position:absolute}.hero .framework{z-index:1;height:28px;top:34px;transform:perspective(2000px)rotate(300deg)rotateX(44deg)rotateY(39deg)scale(1.4)}.hero .vite{z-index:0;width:auto;height:26px;top:107px;transform:perspective(2000px)rotate(300deg)rotateX(40deg)rotateY(39deg)scale(.8)}#app{text-align:center;border-inline:1px solid var(--border);box-sizing:border-box;flex-direction:column;width:1126px;max-width:100%;min-height:100svh;margin:0 auto;display:flex}#center{flex-direction:column;flex-grow:1;place-content:center;place-items:center;gap:25px;display:flex}@media (width<=1024px){#center{gap:18px;padding:32px 20px 24px}}#next-steps{border-top:1px solid var(--border);text-align:left;display:flex}#next-steps>div{flex:1 1 0;padding:32px}@media (width<=1024px){#next-steps>div{padding:24px 20px}}#next-steps .icon{width:22px;height:22px;margin-bottom:16px}@media (width<=1024px){#next-steps{text-align:center;flex-direction:column}}#docs{border-right:1px solid var(--border)}@media (width<=1024px){#docs{border-right:none;border-bottom:1px solid var(--border)}}#next-steps ul{gap:8px;margin:32px 0 0;padding:0;list-style:none;display:flex}#next-steps ul .logo{height:18px}#next-steps ul a{color:var(--text-h);background:var(--social-bg);border-radius:6px;align-items:center;gap:8px;padding:6px 12px;font-size:16px;text-decoration:none;transition:box-shadow .3s;display:flex}#next-steps ul a:hover{box-shadow:var(--shadow)}#next-steps ul a .button-icon{width:18px;height:18px}@media (width<=1024px){#next-steps ul{flex-wrap:wrap;justify-content:center;margin-top:20px}#next-steps ul li{flex:calc(50% - 8px)}#next-steps ul a{box-sizing:border-box;justify-content:center;width:100%}}#spacer{border-top:1px solid var(--border);height:88px}@media (width<=1024px){#spacer{height:48px}}.ticks{width:100%;position:relative}.ticks:before,.ticks:after{content:"";border:5px solid #0000;position:absolute;top:-4.5px}.ticks:before{border-left-color:var(--border);left:0}.ticks:after{border-right-color:var(--border);right:0}.layout[data-v-c31748c9]{font-family:system-ui,sans-serif}header[data-v-c31748c9]{color:#fff;background:#2d5f3b;padding:1rem}nav[data-v-c31748c9]{flex-wrap:wrap;gap:1rem;margin-top:.5rem;display:flex}nav a[data-v-c31748c9]{color:#fff;text-decoration:none}nav a.router-link-active[data-v-c31748c9]{text-decoration:underline}main[data-v-c31748c9]{padding:1rem}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

Before

Width:  |  Height:  |  Size: 496 B

View File

@@ -1,95 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
import heroImg from '../assets/hero.png'
import viteLogo from '../assets/vite.svg'
import vueLogo from '../assets/vue.svg'
const count = ref(0)
</script>
<template>
<section id="center">
<div class="hero">
<img :src="heroImg" class="base" width="170" height="179" alt="" />
<img :src="vueLogo" class="framework" alt="Vue logo" />
<img :src="viteLogo" class="vite" alt="Vite logo" />
</div>
<div>
<h1>Get started</h1>
<p>Edit <code>src/App.vue</code> and save to test <code>HMR</code></p>
</div>
<button type="button" class="counter" @click="count++">
Count is {{ count }}
</button>
</section>
<div class="ticks"></div>
<section id="next-steps">
<div id="docs">
<svg class="icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#documentation-icon"></use>
</svg>
<h2>Documentation</h2>
<p>Your questions, answered</p>
<ul>
<li>
<a href="https://vite.dev/" target="_blank">
<img class="logo" :src="viteLogo" alt="" />
Explore Vite
</a>
</li>
<li>
<a href="https://vuejs.org/" target="_blank">
<img class="button-icon" :src="vueLogo" alt="" />
Learn more
</a>
</li>
</ul>
</div>
<div id="social">
<svg class="icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#social-icon"></use>
</svg>
<h2>Connect with us</h2>
<p>Join the Vite community</p>
<ul>
<li>
<a href="https://github.com/vitejs/vite" target="_blank">
<svg class="button-icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#github-icon"></use>
</svg>
GitHub
</a>
</li>
<li>
<a href="https://chat.vite.dev/" target="_blank">
<svg class="button-icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#discord-icon"></use>
</svg>
Discord
</a>
</li>
<li>
<a href="https://x.com/vite_js" target="_blank">
<svg class="button-icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#x-icon"></use>
</svg>
X.com
</a>
</li>
<li>
<a href="https://bsky.app/profile/vite.dev" target="_blank">
<svg class="button-icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#bluesky-icon"></use>
</svg>
Bluesky
</a>
</li>
</ul>
</div>
</section>
<div class="ticks"></div>
<section id="spacer"></section>
</template>

View File

@@ -1,5 +1,4 @@
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'

View File

@@ -1,6 +1,68 @@
<template>
<div>
<h2>Ajustes</h2>
<p>Módulo en desarrollo</p>
<p><strong>Requiere motivo obligatorio</strong></p>
<section>
<select v-model="productoSel">
<option value="">Seleccionar producto</option>
<option v-for="prod in productos" :key="prod.id" :value="prod.id">{{ prod.nombrePresentacion }}</option>
</select>
<input v-model.number="cantidadNueva" type="number" placeholder="Cantidad nueva" />
<input v-model="motivo" placeholder="Motivo del ajuste (OBLIGATORIO)" />
<button @click="ajustar">Realizar Ajuste</button>
</section>
<section>
<h3>Ajustes del día</h3>
<ul>
<li v-for="a in ajustes" :key="a.id">{{ a.productoId }}: {{ a.cantidadAnterior }} → {{ a.cantidadNueva }} | Motivo: {{ a.motivo }}</li>
</ul>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { db } from '@toldo/data';
import { AjustesService, InventarioService } from '@toldo/services';
import { validateAjusteMotivo } from '@toldo/core';
const ajustesService = new AjustesService();
const invService = new InventarioService();
const productos = ref<any[]>([]);
const productoSel = ref('');
const cantidadNueva = ref(0);
const motivo = ref('');
const ajustes = ref<any[]>([]);
async function cargar() {
productos.value = await db.productos.toArray();
ajustes.value = await ajustesService.getAjustesDelDia(new Date());
}
async function ajustar() {
try {
validateAjusteMotivo(motivo.value);
if (!productoSel.value) return;
const saldoActual = await invService.calcularSaldoProducto(productoSel.value);
const diferencia = cantidadNueva.value - saldoActual;
await ajustesService.crearAjuste({
id: crypto.randomUUID(),
fecha: new Date(),
productoId: productoSel.value,
cantidadAnterior: saldoActual,
cantidadNueva: cantidadNueva.value,
diferencia,
motivo: motivo.value,
createdAt: new Date()
});
motivo.value = '';
cantidadNueva.value = 0;
await cargar();
alert('Ajuste registrado con motivo');
} catch (e: any) {
alert(e.message);
}
}
onMounted(cargar);
</script>

View File

@@ -1,6 +1,46 @@
<template>
<div>
<h2>Cuadre</h2>
<p>Módulo en desarrollo</p>
<h2>Cuadre de Caja</h2>
<section>
<label>Base Inicial: </label>
<input v-model.number="baseInicial" type="number" />
<button @click="calcular">Calcular Cuadre</button>
</section>
<section v-if="cuadre">
<h3>Resultado</h3>
<p>Ventas Efectivo: ${{ cuadre.totalVentasEfectivo }}</p>
<p>Ventas QR: ${{ cuadre.totalVentasQR }}</p>
<p>Gastos Efectivo: ${{ cuadre.totalGastosEfectivo }}</p>
<p>Gastos QR: ${{ cuadre.totalGastosQR }}</p>
<p><strong>Saldo Esperado Efectivo: ${{ cuadre.saldoEsperadoEfectivo }}</strong></p>
<p>Total Ventas: ${{ cuadre.totalVentas }}</p>
<label>Saldo Real en Efectivo: </label>
<input v-model.number="saldoReal" type="number" />
<button @click="guardar">Guardar Cuadre</button>
<p v-if="descuadre !== null">Descuadre: ${{ descuadre }}</p>
</section>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { CuadreService } from '@toldo/services';
const cuadreService = new CuadreService();
const baseInicial = ref(0);
const cuadre = ref<any>(null);
const saldoReal = ref(0);
const descuadre = ref<number | null>(null);
async function calcular() {
cuadre.value = await cuadreService.calcularCuadreDelDia(new Date(), baseInicial.value);
}
async function guardar() {
if (!cuadre.value) return;
cuadre.value.saldoRealEfectivo = saldoReal.value;
await cuadreService.guardarCuadre({ ...cuadre.value, id: crypto.randomUUID(), createdAt: new Date() });
descuadre.value = saldoReal.value - cuadre.value.saldoEsperadoEfectivo;
alert('Cuadre guardado');
}
</script>

View File

@@ -1,6 +1,70 @@
<template>
<div>
<h2>Exportar</h2>
<p>Módulo en desarrollo</p>
<h2>Exportar / Importar</h2>
<section>
<h3>Exportar Datos</h3>
<button @click="exportar">Exportar JSON</button>
</section>
<section>
<h3>Importar Datos</h3>
<input type="file" accept=".json" @change="onFileChange" />
<button @click="importar" :disabled="!archivo">Importar JSON</button>
</section>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { db } from '@toldo/data';
import { ExportImportService } from '@toldo/services';
const exportService = new ExportImportService();
const archivo = ref<File | null>(null);
function onFileChange(e: any) {
archivo.value = e.target.files[0];
}
async function exportar() {
const data = {
productores: await db.productores.toArray(),
productos: await db.productos.toArray(),
movimientosInventario: await db.movimientosInventario.toArray(),
ventas: await db.ventas.toArray(),
ventaLineas: await db.ventaLineas.toArray(),
gastos: await db.gastos.toArray(),
ajustes: await db.ajustes.toArray(),
cuadres: await db.cuadres.toArray(),
};
const exp = exportService.exportAll(data);
const blob = new Blob([JSON.stringify(exp, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `toldo-export-${new Date().toISOString().split('T')[0]}.json`;
a.click();
URL.revokeObjectURL(url);
}
async function importar() {
if (!archivo.value) return;
const text = await archivo.value.text();
try {
const json = JSON.parse(text);
const imp = exportService.import(json);
if (imp.data.productores.length) await db.productores.bulkPut(imp.data.productores);
if (imp.data.productos.length) await db.productos.bulkPut(imp.data.productos);
if (imp.data.movimientosInventario.length) await db.movimientosInventario.bulkPut(imp.data.movimientosInventario);
if (imp.data.ventas.length) await db.ventas.bulkPut(imp.data.ventas);
if (imp.data.ventaLineas.length) await db.ventaLineas.bulkPut(imp.data.ventaLineas);
if (imp.data.gastos.length) await db.gastos.bulkPut(imp.data.gastos);
if (imp.data.ajustes.length) await db.ajustes.bulkPut(imp.data.ajustes);
if (imp.data.cuadres.length) await db.cuadres.bulkPut(imp.data.cuadres);
alert('Datos importados correctamente');
} catch (e: any) {
alert(e.message);
}
}
</script>

View File

@@ -1,6 +1,53 @@
<template>
<div>
<h2>Gastos</h2>
<p>Módulo en desarrollo</p>
<section>
<input v-model="concepto" placeholder="Concepto (cinta, lapicero, bolsas...)" />
<input v-model.number="monto" type="number" placeholder="Monto" />
<select v-model="formaPago">
<option value="EFECTIVO">Efectivo</option>
<option value="QR">QR</option>
</select>
<button @click="registrar">Registrar Gasto</button>
</section>
<section>
<h3>Gastos del día</h3>
<ul>
<li v-for="g in gastos" :key="g.id">{{ g.concepto }} - ${{ g.monto }} ({{ g.formaPago }})</li>
</ul>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { GastosService } from '@toldo/services';
const gastosService = new GastosService();
const concepto = ref('');
const monto = ref(0);
const formaPago = ref<'EFECTIVO' | 'QR'>('EFECTIVO');
const gastos = ref<any[]>([]);
async function cargar() {
gastos.value = await gastosService.getGastosDelDia(new Date());
}
async function registrar() {
if (!concepto.value || monto.value <= 0) return;
await gastosService.registrarGasto({
id: crypto.randomUUID(),
fecha: new Date(),
concepto: concepto.value,
monto: monto.value,
formaPago: formaPago.value,
createdAt: new Date()
} as any);
concepto.value = '';
monto.value = 0;
await cargar();
alert('Gasto registrado');
}
onMounted(cargar);
</script>

View File

@@ -1,6 +1,105 @@
<template>
<div>
<h2>Inventario</h2>
<p>Módulo en desarrollo</p>
<section>
<h3>Productores</h3>
<input v-model="nuevoProductor" placeholder="Nombre del productor" />
<button @click="crearProductor">Agregar Productor</button>
<ul>
<li v-for="p in productores" :key="p.id">{{ p.nombre }}</li>
</ul>
</section>
<section>
<h3>Productos (por productor)</h3>
<select v-model="productorSel">
<option value="">Seleccionar productor</option>
<option v-for="p in productores" :key="p.id" :value="p.id">{{ p.nombre }}</option>
</select>
<input v-model="nombreProd" placeholder="Ej: Café molido 250gr" />
<input v-model.number="precioProd" type="number" placeholder="Precio unitario" />
<button @click="crearProducto">Agregar Producto</button>
<ul>
<li v-for="prod in productos" :key="prod.id">{{ prod.nombrePresentacion }} - ${{ prod.precioUnitario }}</li>
</ul>
</section>
<section>
<h3>Ingreso Inicial</h3>
<select v-model="prodInv">
<option value="">Seleccionar producto</option>
<option v-for="prod in productos" :key="prod.id" :value="prod.id">{{ prod.nombrePresentacion }}</option>
</select>
<input v-model.number="cantInv" type="number" placeholder="Cantidad" />
<button @click="ingresoInicial">Registrar Ingreso Inicial</button>
</section>
<section>
<h3>Ingreso entre semana</h3>
<select v-model="prodInv2">
<option value="">Seleccionar producto</option>
<option v-for="prod in productos" :key="prod.id" :value="prod.id">{{ prod.nombrePresentacion }}</option>
</select>
<input v-model.number="cantInv2" type="number" placeholder="Cantidad" />
<input v-model="recibidoPor" placeholder="Recibido por" />
<button @click="ingresoSemana">Registrar Ingreso</button>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { db } from '@toldo/data';
import { InventarioService } from '@toldo/services';
const invService = new InventarioService();
const productores = ref<any[]>([]);
const productos = ref<any[]>([]);
const nuevoProductor = ref('');
const productorSel = ref('');
const nombreProd = ref('');
const precioProd = ref(0);
const prodInv = ref('');
const cantInv = ref(0);
const prodInv2 = ref('');
const cantInv2 = ref(0);
const recibidoPor = ref('');
async function cargar() {
productores.value = await db.productores.toArray();
productos.value = await db.productos.toArray();
}
async function crearProductor() {
if (!nuevoProductor.value) return;
await db.productores.add({ id: crypto.randomUUID(), nombre: nuevoProductor.value, activo: true, createdAt: new Date() });
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() });
nombreProd.value = '';
precioProd.value = 0;
await cargar();
}
async function ingresoInicial() {
if (!prodInv.value || cantInv.value <= 0) return;
await invService.registrarIngresoInicial(prodInv.value, 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);
cantInv2.value = 0;
recibidoPor.value = '';
alert('Ingreso entre semana registrado');
}
onMounted(cargar);
</script>

View File

@@ -1,6 +1,38 @@
<template>
<div>
<h2>Reportes</h2>
<p>Módulo en desarrollo</p>
<button @click="generarPorProductor">Reporte por Productor</button>
<button @click="generarCuadre">Reporte de Cuadre</button>
<section v-if="reporteProd.length">
<h3>Por Productor</h3>
<div v-for="r in reporteProd" :key="r.productor">
<h4>{{ r.productor }}</h4>
<p>Ventas: ${{ r.totalVentas }}</p>
<p>Ajustes: {{ r.ajustes.length }}</p>
</div>
</section>
<section v-if="reporteCua.length">
<h3>Cuadre</h3>
<div v-for="c in reporteCua" :key="c.id">
<p>Base: ${{ c.baseInicial }} | Ventas: ${{ c.totalVentas }} | Esperado Efectivo: ${{ c.saldoEsperadoEfectivo }} | Descuadre: ${{ c.descuadreEfectivo }}</p>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ReportesService } from '@toldo/services';
const reportesService = new ReportesService();
const reporteProd = ref<any[]>([]);
const reporteCua = ref<any[]>([]);
async function generarPorProductor() {
reporteProd.value = await reportesService.reportePorProductor(new Date());
}
async function generarCuadre() {
reporteCua.value = await reportesService.reporteCuadre(new Date());
}
</script>

View File

@@ -1,6 +1,95 @@
<template>
<div>
<h2>Ventas</h2>
<p>Módulo en desarrollo</p>
<section>
<h3>Nueva Venta</h3>
<select v-model="productoSel">
<option value="">Seleccionar producto</option>
<option v-for="prod in productos" :key="prod.id" :value="prod.id">{{ prod.nombrePresentacion }} - ${{ prod.precioUnitario }}</option>
</select>
<input v-model.number="cantidad" type="number" placeholder="Cantidad" />
<select v-model="formaPago">
<option value="EFECTIVO">Efectivo</option>
<option value="QR">QR</option>
</select>
<button @click="agregarLinea">Agregar Línea</button>
</section>
<section>
<h3>Carrito</h3>
<table v-if="lineas.length">
<thead><tr><th>Producto</th><th>Cant</th><th>Precio</th><th>Subtotal</th><th>Forma</th></tr></thead>
<tbody>
<tr v-for="(l, i) in lineas" :key="i">
<td>{{ getProdNombre(l.productoId) }}</td>
<td>{{ l.cantidad }}</td>
<td>${{ l.precioUnitario }}</td>
<td>${{ l.subtotal }}</td>
<td>{{ l.formaPago }}</td>
</tr>
</tbody>
</table>
<p v-if="lineas.length"><strong>Total: ${{ total }}</strong></p>
<button v-if="lineas.length" @click="registrarVenta">Registrar Venta</button>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { db } from '@toldo/data';
import { VentasService } from '@toldo/services';
const ventasService = new VentasService();
const productos = ref<any[]>([]);
const productoSel = ref('');
const cantidad = ref(1);
const formaPago = ref('EFECTIVO');
const lineas = ref<any[]>([]);
async function cargar() {
productos.value = await db.productos.toArray();
}
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;
}
const total = computed(() => lineas.value.reduce((acc, l) => acc + l.subtotal, 0));
async function registrarVenta() {
const ventaId = crypto.randomUUID();
const venta = {
id: ventaId,
fecha: new Date(),
lineas: lineas.value.map(l => ({ ...l, ventaId })),
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');
}
onMounted(cargar);
</script>

View File

@@ -2,14 +2,18 @@
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"types": ["vite/client"],
"allowArbitraryExtensions": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"paths": {
"@toldo/core": ["../../packages/core/src"],
"@toldo/data": ["../../packages/data/src"],
"@toldo/services": ["../../packages/services/src"],
"@toldo/ui": ["../../packages/ui/src"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}