feat: mejorar ux y validaciones del toldo

This commit is contained in:
mono
2026-10-25 18:09:01 -05:00
parent 4ffce3e3cd
commit 8e286d6fec
24 changed files with 387 additions and 121 deletions

View File

@@ -7,7 +7,7 @@ describe('ExportImportService', () => {
it('exporta con versionado', () => {
const data = {
productores: [], productos: [], movimientosInventario: [],
ventas: [], ventaLineas: [], gastos: [], ajustes: [], cuadres: []
ventas: [], ventaLineas: [], gastos: [], ajustes: [], cuadres: [], jornadas: []
};
const exp = service.exportAll(data);
expect(exp.schemaVersion).toBe(CURRENT_SCHEMA_VERSION);
@@ -25,14 +25,19 @@ describe('ExportImportService', () => {
schemaVersion: 999,
appVersion: '0.0.1',
exportedAt: new Date().toISOString(),
data: { productores: [], productos: [], movimientosInventario: [], ventas: [], ventaLineas: [], gastos: [], ajustes: [], cuadres: [] }
data: { productores: [], productos: [], movimientosInventario: [], ventas: [], ventaLineas: [], gastos: [], ajustes: [], cuadres: [], jornadas: [] }
};
expect(() => service.import(bad)).toThrow('Versión de esquema incompatible');
});
it('importa datos válidos v1', () => {
const exp = service.exportAll({ productores: [], productos: [], movimientosInventario: [], ventas: [], ventaLineas: [], gastos: [], ajustes: [], cuadres: [] });
const exp = service.exportAll({ productores: [], productos: [], movimientosInventario: [], ventas: [], ventaLineas: [], gastos: [], ajustes: [], cuadres: [], jornadas: [] });
const imp = service.import(exp);
expect(imp.schemaVersion).toBe(CURRENT_SCHEMA_VERSION);
});
it('migra exportaciones v1 sin jornadas', () => {
const legacy = { schemaVersion: 1, appVersion: '0.0.1', exportedAt: new Date().toISOString(), data: { productores: [], productos: [], movimientosInventario: [], ventas: [], ventaLineas: [], gastos: [], ajustes: [], cuadres: [] } };
expect(service.import(legacy).data.jornadas).toEqual([]);
});
});

View File

@@ -51,6 +51,10 @@ export class CuadreService {
if (cuadre.saldoRealEfectivo !== undefined && cuadre.saldoEsperadoEfectivo !== undefined) {
cuadre.descuadreEfectivo = calcularDescuadreEfectivo(cuadre.saldoEsperadoEfectivo, cuadre.saldoRealEfectivo);
}
if (cuadre.totalQRBanco !== undefined) {
cuadre.totalQRRegistrado = cuadre.totalVentasQR - cuadre.totalGastosQR;
cuadre.descuadreQR = cuadre.totalQRBanco - cuadre.totalQRRegistrado;
}
return db.cuadres.add(cuadre);
}
}

View File

@@ -1,4 +1,4 @@
import type { Productor, Producto, MovimientoInventario, Venta, VentaLinea, Gasto, Ajuste, CuadreCaja } from '@toldo/core';
import type { Productor, Producto, MovimientoInventario, Venta, VentaLinea, Gasto, Ajuste, CuadreCaja, JornadaToldo } from '@toldo/core';
export interface ExportData {
schemaVersion: number;
@@ -13,6 +13,7 @@ export interface ExportData {
gastos: Gasto[];
ajustes: Ajuste[];
cuadres: CuadreCaja[];
jornadas?: JornadaToldo[];
};
}
@@ -51,6 +52,6 @@ export class ExportImportService {
throw new Error('Versión de esquema incompatible. Actualice la aplicación.');
}
// Simple pass-through for v1; migrations would go here
return data as ExportData;
return { ...(data as ExportData), data: { ...(data as ExportData).data, jornadas: (data as ExportData).data.jornadas ?? [] } };
}
}

View File

@@ -17,10 +17,14 @@ export class ReportesService {
for (const productor of productores) {
const prodsProductor = productos.filter(p => p.productorId === productor.id);
let totalVentas = 0;
let inventarioInicial = 0;
let diferenciaInventario = 0;
const salidas: any[] = [];
for (const prod of prodsProductor) {
const lineasProd = lineas.filter(l => l.productoId === prod.id && ventas.some(v => v.id === l.ventaId));
const movimientos = await db.movimientosInventario.where('productoId').equals(prod.id).toArray();
inventarioInicial += movimientos.filter(m => m.tipo === 'INGRESO_INICIAL').reduce((sum, m) => sum + m.cantidad, 0);
for (const lp of lineasProd) {
totalVentas += lp.subtotal;
salidas.push({ producto: prod.nombrePresentacion, cantidad: lp.cantidad, valor: lp.subtotal });
@@ -29,6 +33,8 @@ export class ReportesService {
reporte.push({
productor: productor.nombre,
inventarioInicial,
diferenciaInventario: (ajustes.filter(a => prodsProductor.some(p => p.id === a.productoId)) as any[]).reduce((sum, a) => sum + a.diferencia, 0),
ajustes: ajustes.filter(a => prodsProductor.some(p => p.id === a.productoId)),
salidas,
totalVentas
@@ -51,6 +57,15 @@ export class ReportesService {
const ventas = await db.ventas.where('fecha').between(fechaInicio, fechaFin).toArray();
const gastos = await db.gastos.where('fecha').between(fechaInicio, fechaFin).toArray();
const ajustes = await db.ajustes.where('fecha').between(fechaInicio, fechaFin).toArray();
return { ventas: ventas.length, gastos: gastos.length, ajustes: ajustes.length };
const lineas = await db.ventaLineas.toArray();
const ventasTotal = ventas.reduce((total, venta) => total + venta.total, 0);
const gastosTotal = gastos.reduce((total, gasto) => total + gasto.monto, 0);
const productores = await db.productores.toArray();
const productos = await db.productos.toArray();
const porProductor = productores.map((productor) => {
const ids = productos.filter((p) => p.productorId === productor.id).map((p) => p.id);
return { productor: productor.nombre, totalVentas: lineas.filter((l) => ids.includes(l.productoId) && ventas.some((v) => v.id === l.ventaId)).reduce((total, l) => total + l.subtotal, 0) };
});
return { ventas: ventas.length, gastos: gastos.length, ajustes: ajustes.length, ventasTotal, gastosTotal, porProductor, fechaInicio, fechaFin };
}
}

View File

@@ -3,8 +3,16 @@ import type { Venta, VentaLinea } from '@toldo/core';
export class VentasService {
async registrarVenta(venta: Venta, lineas: VentaLinea[]) {
if (!lineas.length || lineas.some((linea) => linea.cantidad <= 0)) {
throw new Error('La venta debe tener cantidades mayores que cero');
}
// Validar stock mínimo conceptual - se puede expandir
return db.transaction('rw', db.ventas, db.ventaLineas, async () => {
for (const linea of lineas) {
const movimientos = await db.movimientosInventario.where('productoId').equals(linea.productoId).toArray();
const stock = movimientos.reduce((total, movimiento) => total + movimiento.cantidad, 0);
if (stock < linea.cantidad) throw new Error(`Stock insuficiente para ${linea.productoId}. Disponible: ${stock}`);
}
await db.ventas.add(venta);
await db.ventaLineas.bulkAdd(lineas);
// Registrar movimientos de venta