Files
ToldoConjunto/apps/toldo-app/src/__tests__/use-cases.integration.test.ts

123 lines
7.3 KiB
TypeScript

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);
});
});