import { beforeEach, describe, expect, it } from 'vitest'; import { db } from '@toldo/data'; import { AuditoriaService, CuadreService, EdicionService, ImportInventarioCsvService, JornadaService, ReportesService, } from '@toldo/services'; const fecha = new Date('2026-01-15T10:00:00'); async function prepararBase() { await db.delete(); await db.open(); } async function crearProducto(id = 'producto-1', cantidad = 10) { await db.productores.add({ id: 'productor-1', nombre: 'Productor Uno', activo: true, createdAt: fecha } as any); await db.productos.add({ id, productorId: 'productor-1', nombrePresentacion: 'Cafe 250g', precioUnitario: 10000, activo: true, createdAt: fecha } as any); await db.movimientosInventario.add({ id: `stock-${id}`, fecha, tipo: 'INGRESO_INICIAL', productoId: id, cantidad, createdAt: fecha } as any); } describe('casos de uso operativos offline', () => { beforeEach(prepararBase); it('impide dos jornadas abiertas y no permite operar una jornada cerrada', async () => { const service = new JornadaService(); const jornadaId = await service.abrirJornada(['Ana'], 50000); await expect(service.abrirJornada(['Luis'], 10000)).rejects.toThrow('Ya existe una jornada abierta'); await service.cerrarJornada(jornadaId, 0); await expect(service.registrarGastoEnJornada(jornadaId, { id: 'gasto-cerrado', fecha, concepto: 'Bolsas', monto: 1000, formaPago: 'EFECTIVO', createdAt: fecha } as any)).rejects.toThrow('jornada cerrada'); }); it('cierra caja y jornada juntos con banco QR opcional', async () => { const service = new JornadaService(); const jornadaId = await service.abrirJornada(['Ana'], 50000); const cuadreId = await service.cerrarJornada(jornadaId, 48000, 12000); const cuadre = await db.cuadres.get(cuadreId); const jornada = await db.jornadas.get(jornadaId); expect(cuadre?.saldoRealEfectivo).toBe(48000); expect(cuadre?.totalQRBanco).toBe(12000); expect(jornada?.estado).toBe('CERRADA'); }); it('calcula cuadre mixto de efectivo y QR y registra ambos descuadres', async () => { await crearProducto(); const jornada = new JornadaService(); const jornadaId = await jornada.abrirJornada(['Ana'], 50000); const ventaEfectivo = crypto.randomUUID(); await jornada.registrarVentaEnJornada(jornadaId, { id: ventaEfectivo, fecha, subtotal: 20000, total: 20000, createdAt: fecha } as any, [{ id: crypto.randomUUID(), ventaId: ventaEfectivo, productoId: 'producto-1', cantidad: 2, precioUnitario: 10000, subtotal: 20000, formaPago: 'EFECTIVO' } as any]); const ventaQr = crypto.randomUUID(); await jornada.registrarVentaEnJornada(jornadaId, { id: ventaQr, fecha, subtotal: 10000, total: 10000, createdAt: fecha } as any, [{ id: crypto.randomUUID(), ventaId: ventaQr, productoId: 'producto-1', cantidad: 1, precioUnitario: 10000, subtotal: 10000, formaPago: 'QR' } as any]); await jornada.registrarGastoEnJornada(jornadaId, { id: 'gasto-efectivo', fecha, concepto: 'Bolsas', monto: 5000, formaPago: 'EFECTIVO', createdAt: fecha } as any); await jornada.registrarGastoEnJornada(jornadaId, { id: 'gasto-qr', fecha, concepto: 'Transporte', monto: 2000, formaPago: 'QR', createdAt: fecha } as any); const cuadreService = new CuadreService(); const calculado = await cuadreService.calcularCuadreDelDia(fecha, 50000); expect(calculado.totalVentasEfectivo).toBe(20000); expect(calculado.totalVentasQR).toBe(10000); expect(calculado.saldoEsperadoEfectivo).toBe(65000); const cuadre: any = { ...calculado, id: 'cuadre-1', saldoRealEfectivo: 64000, totalQRBanco: 9000, createdAt: fecha }; await cuadreService.guardarCuadre(cuadre); const guardado = await db.cuadres.get('cuadre-1'); expect(guardado?.descuadreEfectivo).toBe(-1000); expect(guardado?.totalQRRegistrado).toBe(8000); expect(guardado?.descuadreQR).toBe(1000); }); it('rechaza stock insuficiente y revierte venta y movimiento parcial', async () => { await crearProducto('producto-escaso', 1); const jornada = new JornadaService(); const jornadaId = await jornada.abrirJornada(['Ana'], 0); const ventaId = crypto.randomUUID(); await expect(jornada.registrarVentaEnJornada(jornadaId, { id: ventaId, fecha, subtotal: 20000, total: 20000, createdAt: fecha } as any, [{ id: crypto.randomUUID(), ventaId, productoId: 'producto-escaso', cantidad: 2, precioUnitario: 10000, subtotal: 20000, formaPago: 'EFECTIVO' } as any])).rejects.toThrow('Stock insuficiente'); expect(await db.ventas.count()).toBe(0); expect((await db.movimientosInventario.toArray()).filter((movimiento) => movimiento.referenciaId === ventaId)).toHaveLength(0); }); it('importa CSV con normalización, cantidades vacías y errores por fila', async () => { const importer = new ImportInventarioCsvService(); const resultado = await importer.importar('Nombre de producto,Nombre Productor,precio unitario,cantidad\nCafe 250g,Ángel Uno,10000,3\ncafe 250g, angel uno ,10000,\nProducto roto,Otro,precio,2'); expect(await db.productores.count()).toBe(1); expect(await db.productos.count()).toBe(1); expect(await db.movimientosInventario.count()).toBe(1); expect(resultado.movimientos).toBe(1); expect(resultado.errores).toHaveLength(1); }); it('rechaza CSV sin la columna cantidad', async () => { await expect(new ImportInventarioCsvService().importar('Nombre de producto,Nombre Productor,precio unitario\nCafe,Productor,1000')).rejects.toThrow('cantidad'); }); it('audita edición de producto y rechaza edición sin motivo', async () => { await crearProducto(); const editor = new EdicionService(); const original = await db.productos.get('producto-1') as any; await expect(editor.editarProducto({ ...original, precioUnitario: 12000 }, original, { modificadoPor: 'Ana', motivo: '' })).rejects.toThrow('motivo'); await editor.editarProducto({ ...original, precioUnitario: 12000 }, original, { modificadoPor: 'Ana', motivo: 'Precio actualizado' }); const auditoria = await new AuditoriaService().listar('PRODUCTO'); expect(auditoria).toHaveLength(1); expect(JSON.parse(auditoria[0].valoresNuevos).precioUnitario).toBe(12000); }); it('genera reporte por productor y consolidado con ventas y ajustes', async () => { await crearProducto(); const jornada = new JornadaService(); const jornadaId = await jornada.abrirJornada(['Ana'], 0); const ventaId = crypto.randomUUID(); await jornada.registrarVentaEnJornada(jornadaId, { id: ventaId, fecha, subtotal: 10000, total: 10000, createdAt: fecha } as any, [{ id: crypto.randomUUID(), ventaId, productoId: 'producto-1', cantidad: 1, precioUnitario: 10000, subtotal: 10000, formaPago: 'EFECTIVO' } as any]); await db.ajustes.add({ id: 'ajuste-1', fecha, productoId: 'producto-1', cantidadAnterior: 9, cantidadNueva: 8, diferencia: -1, motivo: 'Conteo físico', createdAt: fecha } as any); const reportes = new ReportesService(); const porProductor = await reportes.reportePorProductor(fecha); expect(porProductor[0].totalVentas).toBe(10000); expect(porProductor[0].inventarioInicial).toBe(10); expect(porProductor[0].diferenciaInventario).toBe(-1); const consolidado = await reportes.consolidadoPorRango(new Date('2026-01-15T00:00:00'), new Date('2026-01-15T23:59:59')); expect(consolidado.ventasTotal).toBe(10000); expect(consolidado.porProductor[0].totalVentas).toBe(10000); }); });