feat: estructura base OpenSpec + Skills + arquitectura limpia Capacitor+Dextie
This commit is contained in:
21
packages/core/package.json
Normal file
21
packages/core/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@toldo/core",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "~6.0.3",
|
||||
"vitest": "^2.1.8"
|
||||
}
|
||||
}
|
||||
25
packages/core/src/__tests__/cuadreRules.test.ts
Normal file
25
packages/core/src/__tests__/cuadreRules.test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { calcularSaldoEsperadoEfectivo, calcularDescuadreEfectivo, calcularTotalVentas, validateAjusteMotivo } from '../rules/cuadreRules';
|
||||
|
||||
describe('cuadreRules', () => {
|
||||
it('calcula saldo esperado en efectivo', () => {
|
||||
expect(calcularSaldoEsperadoEfectivo(10000, 5000, 2000)).toBe(13000);
|
||||
});
|
||||
|
||||
it('calcula descuadre en efectivo', () => {
|
||||
expect(calcularDescuadreEfectivo(13000, 12500)).toBe(-500);
|
||||
});
|
||||
|
||||
it('calcula total de ventas', () => {
|
||||
expect(calcularTotalVentas(5000, 3000)).toBe(8000);
|
||||
});
|
||||
|
||||
it('lanza error si ajuste sin motivo', () => {
|
||||
expect(() => validateAjusteMotivo('')).toThrow('El ajuste debe tener un motivo obligatorio');
|
||||
expect(() => validateAjusteMotivo(' ')).toThrow('El ajuste debe tener un motivo obligatorio');
|
||||
});
|
||||
|
||||
it('acepta ajuste con motivo', () => {
|
||||
expect(() => validateAjusteMotivo('Corrección de inventario')).not.toThrow();
|
||||
});
|
||||
});
|
||||
11
packages/core/src/entities/Ajuste.ts
Normal file
11
packages/core/src/entities/Ajuste.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface Ajuste {
|
||||
id: string;
|
||||
fecha: Date;
|
||||
productoId: string;
|
||||
cantidadAnterior: number;
|
||||
cantidadNueva: number;
|
||||
diferencia: number;
|
||||
motivo: string;
|
||||
modificadoPor?: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
18
packages/core/src/entities/CuadreCaja.ts
Normal file
18
packages/core/src/entities/CuadreCaja.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export interface CuadreCaja {
|
||||
id: string;
|
||||
fecha: Date;
|
||||
baseInicial: number;
|
||||
totalVentasEfectivo: number;
|
||||
totalVentasQR: number;
|
||||
totalGastosEfectivo: number;
|
||||
totalGastosQR: number;
|
||||
saldoEsperadoEfectivo: number;
|
||||
saldoRealEfectivo?: number;
|
||||
descuadreEfectivo?: number;
|
||||
totalQRRegistrado?: number;
|
||||
totalQRBanco?: number;
|
||||
descuadreQR?: number;
|
||||
totalVentas: number;
|
||||
observaciones?: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
8
packages/core/src/entities/Gasto.ts
Normal file
8
packages/core/src/entities/Gasto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface Gasto {
|
||||
id: string;
|
||||
fecha: Date;
|
||||
concepto: string;
|
||||
monto: number;
|
||||
formaPago?: 'EFECTIVO' | 'QR';
|
||||
createdAt: Date;
|
||||
}
|
||||
13
packages/core/src/entities/MovimientoInventario.ts
Normal file
13
packages/core/src/entities/MovimientoInventario.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export type TipoMovimiento = 'INGRESO_INICIAL' | 'INGRESO_SEMANA' | 'AJUSTE' | 'VENTA';
|
||||
|
||||
export interface MovimientoInventario {
|
||||
id: string;
|
||||
fecha: Date;
|
||||
tipo: TipoMovimiento;
|
||||
productoId: string;
|
||||
cantidad: number;
|
||||
motivo?: string;
|
||||
recibidoPor?: string;
|
||||
referenciaId?: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
8
packages/core/src/entities/Producto.ts
Normal file
8
packages/core/src/entities/Producto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface Producto {
|
||||
id: string;
|
||||
productorId: string;
|
||||
nombrePresentacion: string;
|
||||
precioUnitario: number;
|
||||
activo: boolean;
|
||||
createdAt: Date;
|
||||
}
|
||||
6
packages/core/src/entities/Productor.ts
Normal file
6
packages/core/src/entities/Productor.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface Productor {
|
||||
id: string;
|
||||
nombre: string;
|
||||
activo: boolean;
|
||||
createdAt: Date;
|
||||
}
|
||||
20
packages/core/src/entities/Venta.ts
Normal file
20
packages/core/src/entities/Venta.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export type FormaPago = 'EFECTIVO' | 'QR';
|
||||
|
||||
export interface VentaLinea {
|
||||
id: string;
|
||||
ventaId: string;
|
||||
productoId: string;
|
||||
cantidad: number;
|
||||
precioUnitario: number;
|
||||
subtotal: number;
|
||||
formaPago: FormaPago;
|
||||
}
|
||||
|
||||
export interface Venta {
|
||||
id: string;
|
||||
fecha: Date;
|
||||
lineas: VentaLinea[];
|
||||
subtotal: number;
|
||||
total: number;
|
||||
createdAt: Date;
|
||||
}
|
||||
7
packages/core/src/entities/index.ts
Normal file
7
packages/core/src/entities/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from './Productor';
|
||||
export * from './Producto';
|
||||
export * from './MovimientoInventario';
|
||||
export * from './Venta';
|
||||
export * from './Gasto';
|
||||
export * from './Ajuste';
|
||||
export * from './CuadreCaja';
|
||||
2
packages/core/src/index.ts
Normal file
2
packages/core/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './entities';
|
||||
export * from './rules/cuadreRules';
|
||||
25
packages/core/src/rules/cuadreRules.ts
Normal file
25
packages/core/src/rules/cuadreRules.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export function validateAjusteMotivo(motivo: string): void {
|
||||
if (!motivo || motivo.trim().length === 0) {
|
||||
throw new Error('El ajuste debe tener un motivo obligatorio');
|
||||
}
|
||||
}
|
||||
|
||||
export function calcularDescuadreEfectivo(saldoEsperado: number, saldoReal: number): number {
|
||||
return saldoReal - saldoEsperado;
|
||||
}
|
||||
|
||||
export function calcularSaldoEsperadoEfectivo(
|
||||
baseInicial: number,
|
||||
totalVentasEfectivo: number,
|
||||
totalGastosEfectivo: number
|
||||
): number {
|
||||
return baseInicial + totalVentasEfectivo - totalGastosEfectivo;
|
||||
}
|
||||
|
||||
export function calcularTotalVentas(totalVentasEfectivo: number, totalVentasQR: number): number {
|
||||
return totalVentasEfectivo + totalVentasQR;
|
||||
}
|
||||
|
||||
export function calcularDiferenciaInventario(cantidadEsperada: number, cantidadReal: number): number {
|
||||
return cantidadReal - cantidadEsperada;
|
||||
}
|
||||
14
packages/core/tsconfig.json
Normal file
14
packages/core/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
23
packages/data/package.json
Normal file
23
packages/data/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@toldo/data",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@toldo/core": "workspace:*",
|
||||
"dexie": "^4.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "~6.0.3"
|
||||
}
|
||||
}
|
||||
29
packages/data/src/db/schema.ts
Normal file
29
packages/data/src/db/schema.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import Dexie, { Table } from 'dexie';
|
||||
import type { Productor, Producto, MovimientoInventario, Venta, VentaLinea, Gasto, Ajuste, CuadreCaja } from '@toldo/core';
|
||||
|
||||
export class ToldoDB extends Dexie {
|
||||
productores!: Table<Productor, string>;
|
||||
productos!: Table<Producto, string>;
|
||||
movimientosInventario!: Table<MovimientoInventario, string>;
|
||||
ventas!: Table<Venta, string>;
|
||||
ventaLineas!: Table<VentaLinea, string>;
|
||||
gastos!: Table<Gasto, string>;
|
||||
ajustes!: Table<Ajuste, string>;
|
||||
cuadres!: Table<CuadreCaja, string>;
|
||||
|
||||
constructor() {
|
||||
super('ToldoConjuntoDB');
|
||||
this.version(1).stores({
|
||||
productores: 'id, nombre, activo',
|
||||
productos: 'id, productorId, nombrePresentacion, [productorId+nombrePresentacion]',
|
||||
movimientosInventario: 'id, fecha, tipo, productoId',
|
||||
ventas: 'id, fecha',
|
||||
ventaLineas: 'id, ventaId, productoId, formaPago',
|
||||
gastos: 'id, fecha',
|
||||
ajustes: 'id, fecha, productoId',
|
||||
cuadres: 'id, fecha'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const db = new ToldoDB();
|
||||
1
packages/data/src/index.ts
Normal file
1
packages/data/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './db/schema';
|
||||
17
packages/data/tsconfig.json
Normal file
17
packages/data/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@toldo/core": ["../core/src"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
25
packages/services/package.json
Normal file
25
packages/services/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@toldo/services",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@toldo/core": "workspace:*",
|
||||
"@toldo/data": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "~6.0.3",
|
||||
"vitest": "^2.1.8"
|
||||
}
|
||||
}
|
||||
38
packages/services/src/__tests__/ExportImportService.test.ts
Normal file
38
packages/services/src/__tests__/ExportImportService.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { ExportImportService, CURRENT_SCHEMA_VERSION } from '../export-import/ExportImportService';
|
||||
|
||||
describe('ExportImportService', () => {
|
||||
const service = new ExportImportService();
|
||||
|
||||
it('exporta con versionado', () => {
|
||||
const data = {
|
||||
productores: [], productos: [], movimientosInventario: [],
|
||||
ventas: [], ventaLineas: [], gastos: [], ajustes: [], cuadres: []
|
||||
};
|
||||
const exp = service.exportAll(data);
|
||||
expect(exp.schemaVersion).toBe(CURRENT_SCHEMA_VERSION);
|
||||
expect(exp.appVersion).toBe('0.0.1');
|
||||
expect(exp.exportedAt).toBeTruthy();
|
||||
expect(exp.data).toEqual(data);
|
||||
});
|
||||
|
||||
it('valida y rechaza formato inválido', () => {
|
||||
expect(() => service.import({})).toThrow('Formato de archivo inválido');
|
||||
});
|
||||
|
||||
it('rechaza esquema incompatible más nuevo', () => {
|
||||
const bad = {
|
||||
schemaVersion: 999,
|
||||
appVersion: '0.0.1',
|
||||
exportedAt: new Date().toISOString(),
|
||||
data: { productores: [], productos: [], movimientosInventario: [], ventas: [], ventaLineas: [], gastos: [], ajustes: [], cuadres: [] }
|
||||
};
|
||||
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 imp = service.import(exp);
|
||||
expect(imp.schemaVersion).toBe(CURRENT_SCHEMA_VERSION);
|
||||
});
|
||||
});
|
||||
56
packages/services/src/export-import/ExportImportService.ts
Normal file
56
packages/services/src/export-import/ExportImportService.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { Productor, Producto, MovimientoInventario, Venta, VentaLinea, Gasto, Ajuste, CuadreCaja } from '@toldo/core';
|
||||
|
||||
export interface ExportData {
|
||||
schemaVersion: number;
|
||||
appVersion: string;
|
||||
exportedAt: string;
|
||||
data: {
|
||||
productores: Productor[];
|
||||
productos: Producto[];
|
||||
movimientosInventario: MovimientoInventario[];
|
||||
ventas: Venta[];
|
||||
ventaLineas: VentaLinea[];
|
||||
gastos: Gasto[];
|
||||
ajustes: Ajuste[];
|
||||
cuadres: CuadreCaja[];
|
||||
};
|
||||
}
|
||||
|
||||
export const CURRENT_SCHEMA_VERSION = 1;
|
||||
export const APP_VERSION = '0.0.1';
|
||||
|
||||
export class ExportImportService {
|
||||
exportAll(data: ExportData['data']): ExportData {
|
||||
return {
|
||||
schemaVersion: CURRENT_SCHEMA_VERSION,
|
||||
appVersion: APP_VERSION,
|
||||
exportedAt: new Date().toISOString(),
|
||||
data
|
||||
};
|
||||
}
|
||||
|
||||
validate(data: unknown): data is ExportData {
|
||||
if (!data || typeof data !== 'object') {
|
||||
throw new Error('Formato de archivo inválido');
|
||||
}
|
||||
const d = data as any;
|
||||
if (typeof d.schemaVersion !== 'number') throw new Error('Formato de archivo inválido');
|
||||
if (typeof d.appVersion !== 'string') throw new Error('Formato de archivo inválido');
|
||||
if (!d.exportedAt) throw new Error('Formato de archivo inválido');
|
||||
if (!d.data || typeof d.data !== 'object') throw new Error('Formato de archivo inválido');
|
||||
const required = ['productores', 'productos', 'movimientosInventario', 'ventas', 'ventaLineas', 'gastos', 'ajustes', 'cuadres'];
|
||||
for (const key of required) {
|
||||
if (!Array.isArray(d.data[key])) throw new Error('Formato de archivo inválido');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
import(data: unknown): ExportData {
|
||||
this.validate(data);
|
||||
if ((data as ExportData).schemaVersion > CURRENT_SCHEMA_VERSION) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
1
packages/services/src/index.ts
Normal file
1
packages/services/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './export-import/ExportImportService';
|
||||
18
packages/services/tsconfig.json
Normal file
18
packages/services/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@toldo/core": ["../core/src"],
|
||||
"@toldo/data": ["../data/src"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
23
packages/ui/package.json
Normal file
23
packages/ui/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@toldo/ui",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@toldo/core": "workspace:*",
|
||||
"@toldo/services": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "~6.0.3"
|
||||
}
|
||||
}
|
||||
8
packages/ui/src/components/Button.ts
Normal file
8
packages/ui/src/components/Button.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface ButtonProps {
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function createButton(props: ButtonProps): string {
|
||||
return `<button ${props.disabled ? 'disabled' : ''}>${props.label}</button>`;
|
||||
}
|
||||
1
packages/ui/src/index.ts
Normal file
1
packages/ui/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './components/Button';
|
||||
18
packages/ui/tsconfig.json
Normal file
18
packages/ui/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@toldo/core": ["../core/src"],
|
||||
"@toldo/services": ["../services/src"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user