💰 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
| Componente | Tecnología | Versión |
|---|---|---|
| Backend | Django | 5.1 |
| Multi-tenant | django-tenants | 3.6.0 |
| Base de datos | PostgreSQL | 16 |
| Frontend | Vite + Tailwind + Alpine | Latest |
| Testing | pytest + pytest-django | - |
| Calidad | Black, 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
| Campo | Tipo | Descripción |
|---|---|---|
user | ForeignKey | Usuario propietario |
type | Choice | income o expense |
amount | Decimal | Monto positivo |
description | CharField | Descripción del movimiento |
category | ForeignKey | Categoría asignada |
date | DateField | Fecha del movimiento |
Category
| Campo | Tipo | Descripción |
|---|---|---|
name | CharField | Nombre de categoría |
type | Choice | income o expense |
user | ForeignKey | Usuario 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.
| Aspecto | Evaluación |
|---|---|
| Escalabilidad | Alta - cada tenant en schema independiente |
| Seguridad | Fuerte aislamiento de datos |
| Complejidad | Media - requiere marcar modelos tenant=True/False |
| Alternativas | Columna 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
- Fase 1: Modelos base (Tenants, Catálogo, Clientes)
- Fase 2: Implementar tests verdes
- Fase 3: CRUD movimientos y categorías
- Fase 4: Dashboard y reportes básicos