Files
ToldoConjunto/openspec/changes/add-toldo-poc-capacitor/design.md

8.1 KiB

Design: add-toldo-poc-capacitor

Architecture Overview

This project follows a clean, layered architecture to ensure testability, maintainability, and ease of evolution.

┌─────────────────────────────────────────────────────────────┐
│                        apps/toldo-app                        │
│                    (Vite + Capacitor UI)                     │
└─────────────────────────┬───────────────────────────────────┘
                          │ depends on
┌─────────────────────────▼───────────────────────────────────┐
│                      packages/ui                             │
│              (Reusable, small UI components)                 │
└─────────────────────────┬───────────────────────────────────┘
                          │ depends on
┌─────────────────────────▼───────────────────────────────────┐
│                    packages/services                         │
│              (Application use cases/orchestration)           │
└─────────────┬───────────────────────────┬───────────────────┘
              │ depends on               │ depends on
┌─────────────▼───────────┐   ┌───────────▼───────────────────┐
│     packages/data       │   │       packages/core           │
│  (Dexie, repositories)  │   │  (Domain, entities, rules)    │
└─────────────────────────┘   └───────────────────────────────┘

Dependency Rules (Critical)

  • core/: NO dependencies on data/, services/, ui/, apps/, @capacitor/*, or any framework. Pure TypeScript only.
  • data/: Depends only on core/. Implements repository interfaces defined in core/ or data/contracts.
  • services/: Depends on core/ and data/. Contains application logic/use cases. No direct UI dependencies.
  • ui/: Depends on core/ and services/ via interfaces/contracts. No direct Dexie or Capacitor imports.
  • apps/toldo-app/: Assembles everything, configures DI/composition root, handles Capacitor plugins (mockable), UI composition only here.

SOLID Principles

  • Single Responsibility (SRP): Each class/function/module has one reason to change. Components are small and focused.
  • Open/Closed (OCP): Open for extension, closed for modification. Use interfaces and composition.
  • Liskov Substitution (LSP): Subtypes must be substitutable for base types.
  • Interface Segregation (ISP): Many specific interfaces over one general interface. Repository interfaces are granular.
  • Dependency Inversion (DIP): Depend on abstractions, not concretions. core defines interfaces, data implements them.

Domain Model

Core Entities

// Producer
interface Productor {
  id: string;
  nombre: string;
  activo: boolean;
  createdAt: Date;
}

// Product - different per producer even if same name
interface Producto {
  id: string;
  productorId: string;
  nombrePresentacion: string; // e.g., "Café molido 250gr Santa Ana"
  precioUnitario: number;
  activo: boolean;
  createdAt: Date;
}

// Initial inventory / movements
interface MovimientoInventario {
  id: string;
  fecha: Date;
  tipo: 'INGRESO_INICIAL' | 'INGRESO_SEMANA' | 'AJUSTE' | 'VENTA';
  productoId: string;
  cantidad: number;
  motivo?: string; // required for AJUSTE
  recibidoPor?: string;
  referenciaId?: string; // links to venta/ajuste
  createdAt: Date;
}

// Sale
interface Venta {
  id: string;
  fecha: Date;
  lineas: VentaLinea[];
  subtotal: number;
  total: number;
  createdAt: Date;
}

interface VentaLinea {
  id: string;
  ventaId: string;
  productoId: string;
  cantidad: number;
  precioUnitario: number;
  subtotal: number;
  formaPago: 'EFECTIVO' | 'QR';
}

// Expense
interface Gasto {
  id: string;
  fecha: Date;
  concepto: string;
  monto: number;
  formaPago?: 'EFECTIVO' | 'QR';
  createdAt: Date;
}

// Adjustment with reason
interface Ajuste {
  id: string;
  fecha: Date;
  productoId: string;
  cantidadAnterior: number;
  cantidadNueva: number;
  diferencia: number;
  motivo: string; // REQUIRED
  modificadoPor?: string;
  createdAt: Date;
}

// Cash reconciliation
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;
}

Critical Rules

  1. Products unique by producer: producto is uniquely identified by (productorId, nombrePresentacion). Same name from different producers = different products.
  2. Adjustments require reason: All adjustments must have a non-empty motivo. Adjustments are tracked separately and marked as changed in reports.
  3. Inventory reconciliation: Initial inventory vs real differences must be recorded. End-of-day inventory should match initial inventory minus sold products.
  4. Cash vs QR separation: Track cash and QR separately in sales, expenses, and reconciliation. Compare QR registered vs bank.
  5. Traceability: All modifications (adjustments to inventory/sales/cash discrepancies/QR discrepancies) must be logged with reason.
  6. Movements: Track inventory movements both on market day and during the week (week restocks).

Data Layer (Dexie)

Schema Versioning

  • Database schema versioned with Dexie migrations
  • Export/import JSON has schemaVersion, appVersion, exportedAt
  • Import validates versions and migrates or rejects with clear message

Tables

db.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'
});

Repository Pattern

All data access goes through repository interfaces in core/contracts or data/contracts. Implementations in packages/data using Dexie.

Testing Strategy

Layer Tool Approach
Core (domain/rules) Vitest TDD mandatory - write tests first, pure unit tests, no mocks needed
Data (repos) Vitest + fake-indexeddb Integration tests with in-memory IndexedDB
Services (use cases) Vitest Unit tests with mocked repositories
UI components Vitest + @testing-library/* Test behavior, not implementation; user interactions
Capacitor plugins vi.mock Always mock @capacitor/* in unit tests

Build & Distribution

  • Dev: vite dev server
  • Web build: vite build outputs to dist/, servable by static HTTP server
  • Capacitor sync: npx cap sync android copies web assets and updates native project
  • APK: Build via Android Studio or ./gradlew assembleDebug in android/ folder
  • Offline: All assets bundled in APK, IndexedDB persists data, no network required

Quality Gates

  • TypeScript strict mode enabled
  • All core logic tested (TDD)
  • Components small and focused (< ~150 lines when possible)
  • Linting passes
  • Type checking passes
  • Tests pass
  • Build succeeds
  • SOLID principles followed (validated by architecture skill)