Saltar al contenido principal

💰 Money Tracker

Sistema de gestión financiera personal y POS multi-tenant con arquitectura de esquemas PostgreSQL.

📋 Contexto​

Se requiere un sistema que permita a múltiples usuarios/comercios gestionar sus movimientos financieros (ingresos/gastos) con aislamiento completo de datos y soporte para múltiples divisas.

🎯 Objetivo​

Construir un POS multi-tenant donde cada usuario pueda registrar movimientos, categorizarlos y calcular balances en tiempo real.

📦 Alcance​

Incluye:

  • Gestión de movimientos (ingresos/gastos)
  • Categorización personalizada por usuario
  • Cálculo de balance, totales por tipo y rangos de fecha
  • Arquitectura multi-tenant con django-tenants
  • Pipeline frontend Vite + Tailwind + Alpine.js

Excluye:

  • Facturación electrónica
  • Integraciones bancarias
  • Reportes avanzados (v2)

🏛️ Arquitectura​

Stack Tecnológico​

ComponenteTecnologíaVersión
BackendDjango5.1
Multi-tenantdjango-tenants3.6.0
Base de datosPostgreSQL16
FrontendVite + Tailwind + AlpineLatest
Testingpytest + pytest-django-
CalidadBlack, isort, flake8, mypy-

Estructura del Proyecto​

pos_multitenant/
├── docker/
│ ├── docker-compose.yml
│ └── env.db
├── src/
│ ├── pos_multitenant/
│ │ ├── settings/
│ │ │ ├── base.py
│ │ │ ├── dev.py
│ │ │ └── test.py
│ │ └── wsgi.py
│ └── apps/
│ └── movements/
├── frontend/
│ ├── vite.config.js
│ ├── tailwind.config.js
│ └── src/
├── docs/adr/
└── tests/

🗄️ Modelos​

Movement​

CampoTipoDescripción
userForeignKeyUsuario propietario
typeChoiceincome o expense
amountDecimalMonto positivo
descriptionCharFieldDescripción del movimiento
categoryForeignKeyCategoría asignada
dateDateFieldFecha del movimiento

Category​

CampoTipoDescripción
nameCharFieldNombre de categoría
typeChoiceincome o expense
userForeignKeyUsuario propietario

Managers Personalizados​

# Métodos disponibles
Movement.objects.calculate_balance(user, start_date, end_date)
Movement.objects.calculate_income_total(user, start_date, end_date)
Movement.objects.calculate_expense_total(user, start_date, end_date)
Movement.objects.in_date_range(start, end)

⚙️ Setup Inicial​

1. Docker Compose (PostgreSQL)​

services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: pos_multitenant_dev
POSTGRES_USER: pos_user
POSTGRES_PASSWORD: pos_password
ports:
- "5433:5432"

2. Variables de Entorno (.env)​

DATABASE_URL=postgresql://pos_user:pos_password@localhost:5433/pos_multitenant_dev
DJANGO_SECRET_KEY=your-secret-key
DJANGO_DEBUG=True

3. Instalación​

# Crear entorno
pipenv --python 3.12
pipenv install django==5.1 psycopg[binary] django-tenants==3.6.0
pipenv install --dev pytest pytest-django black isort flake8 mypy django-stubs

# Base de datos
docker compose up -d
pipenv run python src/manage.py migrate

# Frontend
cd frontend
npm install
npm run dev

🧪 Testing (TDD)​

Primer Test (Rojo)​

# tests/test_tenant_setup.py
def test_tenant_model_is_configured():
assert hasattr(settings, "TENANT_MODEL")
assert settings.TENANT_MODEL is not None

Tests de Movimientos​

class BalanceCalculationTest(TestCase):
def test_calculate_total_balance(self):
# Income: 2500, Expenses: 1000
balance = Movement.objects.calculate_balance(self.user)
self.assertEqual(balance, Decimal('1500.00'))

def test_balance_by_date_range(self):
balance = Movement.objects.calculate_balance(
self.user,
start_date=yesterday,
end_date=today
)
self.assertEqual(balance, Decimal('400.00'))

🔧 Tooling de Calidad​

Pre-commit (.pre-commit-config.yaml)​

repos:
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/pycqa/flake8
rev: 7.1.0
hooks:
- id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.1
hooks:
- id: mypy

MyPy (mypy.ini)​

[mypy]
python_version = 3.12
plugins = mypy_django_plugin.main
ignore_missing_imports = True
strict_optional = True

[mypy.plugins.django-stubs]
django_settings_module = pos_multitenant.settings.dev

🌐 Frontend (Vite + Tailwind)​

Configuración Tailwind​

// tailwind.config.js
export default {
content: ["../src/**/*.html", "../src/**/*.py", "./src/**/*.{js,ts}"],
theme: {
extend: {},
},
plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography")],
};

Integración Alpine.js​

// src/main.js
import Alpine from "alpinejs";
import "../styles/tailwind.css";

window.Alpine = Alpine;
Alpine.start();

✅ ADR-0001: Decisión Multi-Tenant​

Decisión: Usar django-tenants con PostgreSQL schemas.

AspectoEvaluación
EscalabilidadAlta - cada tenant en schema independiente
SeguridadFuerte aislamiento de datos
ComplejidadMedia - requiere marcar modelos tenant=True/False
AlternativasColumna tenant_id (descartado por riesgo de fuga)

📋 Checklist Fase 0​

  • Estructura proyecto con Pipenv
  • PostgreSQL vía Docker
  • Settings modulares (base/dev/test)
  • Tooling: Black, isort, flake8, mypy
  • Pre-commit configurado
  • Pipeline Vite + Tailwind listo
  • Primer test TDD en rojo

🚀 Siguientes Pasos​

  1. Fase 1: Modelos base (Tenants, Catálogo, Clientes)
  2. Fase 2: Implementar tests verdes
  3. Fase 3: CRUD movimientos y categorías
  4. Fase 4: Dashboard y reportes básicos

📚 Referencias​