feat: implementar jornadas y validar aliases

This commit is contained in:
mono
2026-10-25 17:20:48 -05:00
parent 89390b5bed
commit 4ffce3e3cd
27 changed files with 537 additions and 29 deletions

View File

@@ -0,0 +1,105 @@
import { db } from '@toldo/data';
import type { JornadaToldo, Venta, VentaLinea, Gasto, CuadreCaja } from '@toldo/core';
import { validarApertura, puedeAbrirJornada } from '@toldo/core';
import { VentasService } from './ventas/VentasService';
import { GastosService } from './gastos/GastosService';
import { CuadreService } from './cuadre/CuadreService';
export class JornadaService {
private ventasService = new VentasService();
private gastosService = new GastosService();
private cuadreService = new CuadreService();
async getJornadaAbierta(): Promise<JornadaToldo | undefined> {
return db.jornadas.where('estado').equals('ABIERTA').first();
}
async abrirJornada(responsables: string[], baseInicial: number): Promise<string> {
const err = validarApertura(responsables, baseInicial);
if (err) throw new Error(err);
const abierta = await this.getJornadaAbierta();
if (!puedeAbrirJornada(abierta ? 1 : 0)) throw new Error('Ya existe una jornada abierta');
const jornada: JornadaToldo = {
id: crypto.randomUUID(),
fecha: new Date(),
estado: 'ABIERTA',
responsables,
baseInicial,
fechaApertura: new Date(),
ventaIds: [],
gastoIds: [],
ajusteIds: [],
createdAt: new Date()
};
return db.jornadas.add(jornada as any);
}
async registrarVentaEnJornada(jornadaId: string, venta: Venta, lineas: VentaLinea[]): Promise<string> {
const jornada: any = await db.jornadas.get(jornadaId);
if (!jornada) throw new Error('Jornada no encontrada');
if (jornada.estado !== 'ABIERTA') throw new Error('No se pueden registrar ventas en jornada cerrada');
const ventaId = await this.ventasService.registrarVenta({ ...venta, id: venta.id }, lineas);
await db.jornadas.put({
...jornada,
ventaIds: [...(jornada.ventaIds || []), venta.id],
updatedAt: new Date()
});
return ventaId;
}
async registrarGastoEnJornada(jornadaId: string, gasto: Gasto): Promise<string> {
const jornada: any = await db.jornadas.get(jornadaId);
if (!jornada) throw new Error('Jornada no encontrada');
if (jornada.estado !== 'ABIERTA') throw new Error('No se pueden registrar gastos en jornada cerrada');
const gastoId = await this.gastosService.registrarGasto(gasto);
await db.jornadas.put({
...jornada,
gastoIds: [...(jornada.gastoIds || []), gasto.id],
updatedAt: new Date()
});
return gastoId;
}
async cerrarJornada(jornadaId: string, cuadreId?: string): Promise<string> {
const jornada: any = await db.jornadas.get(jornadaId);
if (!jornada) throw new Error('Jornada no encontrada');
if (jornada.estado === 'CERRADA') throw new Error('Jornada ya cerrada');
let cid = cuadreId;
if (!cid) {
const cuadreParcial = await this.cuadreService.calcularCuadreDelDia(jornada.fecha, jornada.baseInicial);
const cuadre: CuadreCaja = {
id: crypto.randomUUID(),
fecha: jornada.fecha,
baseInicial: jornada.baseInicial,
totalVentasEfectivo: cuadreParcial.totalVentasEfectivo || 0,
totalVentasQR: cuadreParcial.totalVentasQR || 0,
totalGastosEfectivo: cuadreParcial.totalGastosEfectivo || 0,
totalGastosQR: cuadreParcial.totalGastosQR || 0,
saldoEsperadoEfectivo: cuadreParcial.saldoEsperadoEfectivo || 0,
totalVentas: cuadreParcial.totalVentas || 0,
createdAt: new Date()
} as CuadreCaja;
cid = await this.cuadreService.guardarCuadre(cuadre);
}
await db.jornadas.put({
...jornada,
estado: 'CERRADA',
fechaCierre: new Date(),
cuadreId: cid,
updatedAt: new Date()
});
return cid;
}
async getResumenJornada(jornadaId: string) {
const jornada: any = await db.jornadas.get(jornadaId);
if (!jornada) return null;
const ventas = jornada.ventaIds?.length ? await db.ventas.where('id').anyOf(jornada.ventaIds).toArray() : [];
const gastos = jornada.gastoIds?.length ? await db.gastos.where('id').anyOf(jornada.gastoIds).toArray() : [];
const totalVentas = ventas.reduce((acc: number, v: any) => acc + v.total, 0);
const totalGastos = gastos.reduce((acc: number, g: any) => acc + g.monto, 0);
return { jornada, ventas, gastos, totalVentas, totalGastos };
}
}

View File

@@ -0,0 +1,39 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { JornadaService } from '../JornadaService';
import { db } from '@toldo/data';
describe('Caso de uso jornada', () => {
const service = new JornadaService();
beforeEach(async () => {
await db.jornadas.clear();
await db.ventas.clear();
await db.ventaLineas.clear();
await db.gastos.clear();
await db.cuadres.clear();
await db.productos.clear();
});
it('abrir jornada, registrar venta y gasto, cerrar con cuadre', async () => {
await db.productos.add({ id: 'p1', productorId: 'pr1', nombrePresentacion: 'Café 500g', precioUnitario: 15000, activo: true } as any);
const jid = await service.abrirJornada(['Ana', 'Luis'], 100000);
expect(jid).toBeTruthy();
const ventaId = crypto.randomUUID();
await service.registrarVentaEnJornada(jid, { id: ventaId, fecha: new Date(), subtotal: 30000, total: 30000, createdAt: new Date() } as any, [
{ id: crypto.randomUUID(), ventaId, productoId: 'p1', cantidad: 2, precioUnitario: 15000, subtotal: 30000, formaPago: 'EFECTIVO' } as any
]);
await service.registrarGastoEnJornada(jid, { id: crypto.randomUUID(), fecha: new Date(), concepto: 'Bolsas', monto: 5000, formaPago: 'EFECTIVO', createdAt: new Date() } as any);
const resumen = await service.getResumenJornada(jid);
expect(resumen?.totalVentas).toBe(30000);
expect(resumen?.totalGastos).toBe(5000);
const cuadreId = await service.cerrarJornada(jid);
expect(cuadreId).toBeTruthy();
const abierta = await service.getJornadaAbierta();
expect(abierta).toBeUndefined();
});
});

View File

@@ -5,3 +5,4 @@ export * from './inventario/InventarioService';
export * from './cuadre/CuadreService';
export * from './reportes/ReportesService';
export * from './export-import/ExportImportService';
export * from './JornadaService';