CLAUDE.md documenta la estructura de directorios (que tiene tres niveles llamados expenses_manager y despista), los comandos habituales, las variables de entorno y las decisiones de auth, para no tener que deducirlo de settings.py cada vez. ANALISIS_code.md es un informe de solo lectura sobre UX, deuda tecnica y mantenibilidad, ordenado por impacto. Sirve de lista de trabajo pendiente; varios de sus puntos ya estan resueltos en los commits anteriores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8.5 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Repository layout
This git repo has a nested directory structure: the Django project root is expenses_manager/ (contains manage.py), and inside that lives the expenses_manager/ settings package plus the expenses app. So paths look like:
expenses_manager/ <- repo root
expenses_manager/ <- Django project root (manage.py here)
expenses_manager/ <- settings package (settings.py, urls.py, wsgi/asgi)
expenses/ <- the single Django app with all models/views/forms
pytest.ini
requirements.txt
Jenkinsfile
All commands below assume you're in expenses_manager/expenses_manager/ (the directory with manage.py).
Commands
# Install deps (a conda env or any virtualenv works)
pip install -r requirements.txt
# Run the dev server
python manage.py runserver
# Run all tests (pytest, not manage.py test)
pytest
# Run a single test file / test
pytest expenses/tests/test_dashboard.py
pytest expenses/tests/test_goals.py::test_budget_is_exceeded
# Run with coverage (what CI/Jenkins runs)
pytest --cov
# Migrations
python manage.py makemigrations
python manage.py migrate
# Seed demo data (guarded: only runs with DEBUG=True)
python manage.py seed_demo
Tests use pytest-django; pytest.ini sets DJANGO_SETTINGS_MODULE=expenses_manager.settings. There is a shared expenses/tests/conftest.py with fixtures (user, auth_client, account, category). Tests needing DB access must be marked @pytest.mark.django_db (or set pytestmark = pytest.mark.django_db at module level).
Configuration & environment
Settings are driven entirely by environment variables read from a .env file (via python-dotenv) next to manage.py. A single settings.py serves both local dev and production; behaviour switches on DEBUG:
SECRET_KEY— required whenDEBUG=False(the app raisesImproperlyConfiguredand refuses to start if missing). WithDEBUG=Trueit falls back to an insecure dev key.DEBUG— defaults toFalse. WhenTrue, SQLite is used by default and the production hardening block (secure cookies, SSL redirect, proxy header) is skipped.- Database is selected by
DB_ENGINE:postgresqluses Postgres withDB_NAME/DB_USER/DB_PASSWORD/DB_HOST/DB_PORT; any other value (or unset) falls back to SQLite. There is noDATABASE_URL— the DB config uses these separate variables. ALLOWED_HOSTSandCSRF_TRUSTED_ORIGINSare comma-separated env vars.
See .env.example for the full list. CI (Jenkins) sets DEBUG=True and a throwaway SECRET_KEY in its environment block so tests run against SQLite without the hardening block.
Architecture
This is a single-app Django project (expenses) — there is no REST API or frontend build step; views render server-side Django templates directly. All views live in one expenses/views.py, forms in one expenses/forms.py, models in one expenses/models.py.
Ownership model: every domain model (Category, Account, Tag, Expense, Income, Goal) has an owner FK to settings.AUTH_USER_MODEL. Every view filters querysets by owner=request.user — there is no shared/global data between users. When adding a new view or form, follow this same pattern (filter querysets by owner, scope get_object_or_404 by owner too) to avoid leaking data across accounts.
Core models (expenses/models.py):
Account— holdsinitial_balanceand computes running balances.current_balance()sums all incomes/expenses against the initial balance.monthly_balance(year)builds a running-balance series per month for charts, and patches the current month's entry with the livecurrent_balance()so today's number is always exact.balance_until(date)andmonthly_net(year)support other reporting views (both use aggregated queries, not month-by-month loops). Accounts are soft-deleted (active=False), never physically removed, to preserve historical balances; inactive accounts are filtered out of form querysets but shown dimmed in the account list.Expense— belongs to anAccountand aCategory(bothPROTECTon delete, so you can't delete a Category/Account that has expenses), optionally tagged withTag(M2M).Income— same shape as Expense but simpler (no category/tags).FuelEntry— aOneToOneFieldextension ofExpensefor tracking car fuel fill-ups (odometer, liters);fuel_create/fuel_edit/fuel_deleteviews create/update/delete the pairedExpense+FuelEntrytogether (delete cascades via the OneToOne), auto-assigning a "Gasolina" category scoped to the owner viaget_or_create.FuelEntryFormis aModelFormofExpenseplus theFuelEntry-specific fields.Goal— a target with akindfield (payment,budget, orsaving):payment(debt/repayment) andbudgetaggregate expenses in aCategory(optionally including subcategories viainclude_subcategories+Category.descendant_ids()).budgetresets perperiod(month/year): only the current period counts, andis_exceeded()flags going over.savingmeasures progress via the associatedAccount's balance (provisional — this is the branch to change when an investments module is added).progressis acached_property(accessed asgoal.progress, no parentheses), memoized per instance becausepercentage()/bar_width()/progress_state()/is_exceeded()all derive from it.progress()is scoped by_period_start(), not aggregated across all time.
Category— supports self-referentialparentfor subcategories;descendant_ids()returns a category plus all its descendants. Forms must scope theparentqueryset byownerand, when editing, exclude the category itself and its descendants to prevent cycles (there was a past bug where parent categories leaked across users, and another whereclean_parentsilently dropped the value — see git history). Full CRUD exists (category_edit/category_delete); deletion catchesProtectedErrorwhen the category has expenses.
Views/forms convention: forms that need to scope choice fields by user (category, account, tags, parent) accept a user kwarg in __init__ and filter querysets there, rather than doing it in the view. Views pass user=request.user when instantiating these forms both on GET and POST. Delete views require POST and render a confirmation template on GET (pattern established across tag_delete, goal_delete, etc.).
Dashboard view (expenses/views.py:dashboard) is the most complex view: it supports period presets (this_month/last_month/this_year), per-account filtering, and an optional prior-period comparison (compare=1) that diffs current vs. previous period totals overall and by category. It also builds per-account annual balance chart data via Account.monthly_balance(). Note: this view is large (~200+ lines) and is a known refactor target.
Templates: organized by feature under expenses/templates/ — expenses/ (expense/account/income/tag views + dashboard/home), plus separate categories/, fuel/, goals/, settings/, registration/ subfolders. Template naming is inconsistent by folder: files under expenses/templates/expenses/ use a resource prefix (account_list.html, tag_confirm_delete.html), while categories/, goals/, fuel/ use unprefixed names (list.html, form.html, confirm_delete.html) — check the target folder before naming a new template. UI strings are in Spanish; keep that consistent when touching UI code.
Auth: uses Django's built-in django.contrib.auth views/urls (login/logout/password change) mounted at /accounts/. The app's own financial-account CRUD lives under /finance-accounts/ (renamed from /accounts/ to avoid colliding with the auth namespace). There is no password-reset-by-email flow by design: resets are done by the admin via manage.py changepassword, and a static registration/password_help.html page (route password-help/) tells users to contact the admin. LOGIN_URL/LOGIN_REDIRECT_URL/LOGOUT_REDIRECT_URL are set in settings. All app views are decorated with @login_required.
Workflow
- Work happens on the
devbranch;mainonly receives tested merges. Jenkins runs the test suite onmain. - Migrations are generated in development and committed; never run
makemigrationsin production (it would create migrations not in git). Onlymigrateruns on the NAS.