diff --git a/expenses_manager/expenses/forms.py b/expenses_manager/expenses/forms.py index 83ee34e..e2fed43 100644 --- a/expenses_manager/expenses/forms.py +++ b/expenses_manager/expenses/forms.py @@ -27,6 +27,7 @@ class ExpenseForm(forms.ModelForm): "category", "account", "tags", + "is_transfer", ] widgets = { "date": forms.DateInput(format="%Y-%m-%d", attrs={"type": "date"}), @@ -50,7 +51,7 @@ class AccountForm(forms.ModelForm): class IncomeForm(forms.ModelForm): class Meta: model = Income - fields = ["account", "name", "amount", "date"] + fields = ["account", "name", "amount", "date", "is_transfer"] widgets = {"date": forms.DateInput(format="%Y-%m-%d", attrs={"type": "date"})} def __init__(self, *args, **kwargs): diff --git a/expenses_manager/expenses/migrations/0013_expense_is_transfer_income_is_transfer.py b/expenses_manager/expenses/migrations/0013_expense_is_transfer_income_is_transfer.py new file mode 100644 index 0000000..1f6fd3d --- /dev/null +++ b/expenses_manager/expenses/migrations/0013_expense_is_transfer_income_is_transfer.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.10 on 2026-09-15 10:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('expenses', '0012_alter_account_options_alter_category_options_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='expense', + name='is_transfer', + field=models.BooleanField(default=False, help_text='Márcalo si este movimiento solo mueve dinero entre cuentas propias. No contará como gasto ni como ingreso en los análisis, pero sí afectará al saldo de la cuenta.', verbose_name='Es un traspaso entre cuentas'), + ), + migrations.AddField( + model_name='income', + name='is_transfer', + field=models.BooleanField(default=False, help_text='Márcalo si este movimiento solo mueve dinero entre cuentas propias. No contará como gasto ni como ingreso en los análisis, pero sí afectará al saldo de la cuenta.', verbose_name='Es un traspaso entre cuentas'), + ), + ] diff --git a/expenses_manager/expenses/models.py b/expenses_manager/expenses/models.py index e6dc33b..97e0e19 100644 --- a/expenses_manager/expenses/models.py +++ b/expenses_manager/expenses/models.py @@ -228,6 +228,16 @@ class Expense(models.Model): related_name="expenses", ) + is_transfer = models.BooleanField( + default=False, + verbose_name="Es un traspaso entre cuentas", + help_text=( + "Márcalo si este movimiento solo mueve dinero entre cuentas propias. " + "No contará como gasto ni como ingreso en los análisis, pero sí " + "afectará al saldo de la cuenta." + ), + ) + created_at = models.DateField(auto_now_add=True) class Meta: @@ -246,6 +256,17 @@ class Income(models.Model): name = models.CharField(max_length=150) amount = models.DecimalField(max_digits=12, decimal_places=2) date = models.DateField() + + is_transfer = models.BooleanField( + default=False, + verbose_name="Es un traspaso entre cuentas", + help_text=( + "Márcalo si este movimiento solo mueve dinero entre cuentas propias. " + "No contará como gasto ni como ingreso en los análisis, pero sí " + "afectará al saldo de la cuenta." + ), + ) + created_at = models.DateTimeField(auto_now_add=True) class Meta: @@ -370,6 +391,9 @@ class Goal(models.Model): if self.kind == self.KIND_SAVING: # Provisional: saldo de la cuenta asociada. Cuando exista el # módulo de inversiones, esta rama es lo único que hay que tocar. + # Ojo: aquí los traspasos SÍ cuentan. Mandar dinero a la cuenta de + # ahorro es precisamente cómo se progresa en un objetivo de ahorro, + # así que no filtres is_transfer en esta rama. return self.account.current_balance() if self.account else Decimal("0") if not self.category: @@ -380,9 +404,11 @@ class Goal(models.Model): else: category_ids = [self.category_id] + # Pago y presupuesto miden gasto real, así que los traspasos entre + # cuentas propias no cuentan. La rama de ahorro de arriba es la opuesta. expenses = Expense.objects.filter( owner=self.owner, category_id__in=category_ids - ) + ).exclude(is_transfer=True) start = self._period_start() if start: diff --git a/expenses_manager/expenses/static/expenses/css/base.css b/expenses_manager/expenses/static/expenses/css/base.css index e827586..6593c5a 100644 --- a/expenses_manager/expenses/static/expenses/css/base.css +++ b/expenses_manager/expenses/static/expenses/css/base.css @@ -97,6 +97,31 @@ a.danger:visited, color: var(--color-danger-accent); } +/* Escala tipográfica. Los márgenes van en rem a propósito: los del navegador + van en em y escalan con el tamaño de fuente, dejando huecos irregulares + entre secciones. */ + +h1 { + font-size: 1.625rem; + font-weight: 600; + line-height: 1.2; + margin: 0 0 1rem; +} + +h2 { + font-size: 1.25rem; + font-weight: 600; + line-height: 1.25; + margin: 1.5rem 0 0.75rem; +} + +h3 { + font-size: 1rem; + font-weight: 600; + line-height: 1.3; + margin: 1rem 0 0.5rem; +} + /* ========================= */ /* Topbar / navigation */ /* ========================= */ @@ -273,7 +298,7 @@ a.danger:visited, margin-bottom: 1rem; } -.dashboard-context h2 { +.dashboard-context h1 { margin: 0; } @@ -371,7 +396,8 @@ button[type="submit"]:hover, gap: 0.5rem; } -.filters-main select { +.filters-main select, +.filters-main input[type="date"] { padding: 0.5rem 0.7rem; font-family: inherit; font-size: 0.95rem; @@ -381,11 +407,18 @@ button[type="submit"]:hover, border-radius: 6px; } -.filters-main select:focus { +.filters-main select:focus, +.filters-main input[type="date"]:focus { outline: 2px solid var(--color-focus-ring); outline-offset: 1px; } +.range-notice { + margin-top: 0.5rem; + font-size: 0.9rem; + color: var(--color-text-muted); +} + .filters-advanced { margin-top: 0.5rem; } @@ -707,6 +740,13 @@ tr:hover { color: var(--color-danger-text); } +/* Etiqueta informativa, sin carga de error ni de éxito: marca un movimiento + como lo que es, no como un estado degradado. */ +.badge-neutral { + background-color: var(--color-chip-bg); + color: var(--color-text-muted); +} + .row-inactive { opacity: 0.55; } @@ -898,7 +938,7 @@ tr:hover { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } -.settings-card h3 { +.settings-card h2 { margin: 0 0 0.25rem 0; } diff --git a/expenses_manager/expenses/templates/categories/form.html b/expenses_manager/expenses/templates/categories/form.html index 581fff7..7feeb97 100644 --- a/expenses_manager/expenses/templates/categories/form.html +++ b/expenses_manager/expenses/templates/categories/form.html @@ -5,7 +5,7 @@ {% endblock %} {% block content %} -