La tabla de gastos mostraba "No hay gastos / Anade el primero" tanto cuando el
usuario no tiene ningun gasto como cuando sus filtros no devuelven nada. En el
segundo caso el mensaje miente y ademas empuja a crear un gasto que no hace
falta.
Ahora, con cualquier filtro puesto (ano, mes, categoria, etiquetas, cuenta o
rango), el mensaje dice que no hay gastos que coincidan y ofrece quitarlos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El listado de ingresos era el unico que seguia volcando la tabla entera sin
filtros, sin totales y sin paginacion: con unos cuantos anos de datos la
pagina se vuelve inmanejable y no hay forma de responder "cuanto ingrese en
marzo".
Se reutiliza lo que ya existe en el listado de gastos en vez de inventar nada:
los mismos filtros de ano, mes y cuenta, el mismo rango de fechas libre con la
misma precedencia y el mismo aviso, la misma paginacion de diez por pagina y
el mismo query_params para que los enlaces de pagina no pierdan los filtros.
Los ingresos marcados como traspaso se siguen listando, porque son movimientos
reales de la cuenta y hay que poder verlos y corregirlos, pero el total los
excluye: de ahi la etiqueta "Total (sin traspasos)".
El estado vacio distingue entre no tener ingresos y que los filtros no
devuelvan nada, para no invitar a crear un ingreso cuando el problema es el
filtro. El colspan de esa fila es 5, que son las columnas de esta tabla.
select_related("account") mantiene el numero de consultas plano: sin el, cada
fila pedia su cuenta por separado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cubren las reglas que no se ven leyendo la vista: que los extremos son
inclusivos, que cada fecha puede ir sola, que un rango invertido o una fecha
ilegible se ignoran en vez de vaciar el listado, y que el rango tiene
precedencia sobre los selectores de ano y mes.
En el dashboard se comprueba ademas que la comparativa queda desactivada y
avisada cuando hay rango, que el eje del grafico cambia de dia a mes al pasar
el umbral, y que los huecos del eje salen a cero en lugar de desaparecer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un rango que solo funciona en el listado obliga a cambiar de pantalla para
responder "cuanto gaste en ese viaje". El dashboard acepta ahora los mismos
date_from y date_to, con la misma precedencia sobre ano y mes.
Dos decisiones que no son obvias:
La comparativa se desactiva cuando hay rango. Un rango arbitrario no tiene un
"periodo anterior" evidente, y elegir uno por el usuario daria un porcentaje
que parece significativo sin serlo. Se distingue entre pedirla y poder
aplicarla (compare_requested / compare_enabled) para poder avisar de que se
ha ignorado en vez de desmarcar la casilla en silencio.
El eje del grafico lo decide la longitud del rango, no el mes: por debajo de
62 dias se agrupa por dia y por encima por mes, que es donde un grafico
diario deja de leerse. El eje se rellena con ceros en los huecos, igual que
ya hacia el resto, para que un periodo sin gastos no colapse la escala. La
agrupacion diaria usa el propio campo date y no TruncDate, porque date ya es
un DateField y TruncDate le aplicaria una conversion de zona horaria que en
SQLite revienta si no esta instalada la base de datos de husos.
La tarjeta de proyeccion a fin de mes se oculta con rango activo: proyectar a
fin de mes desde un periodo arbitrario no significa nada.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Los selectores de ano y mes solo saben expresar periodos naturales completos.
"Del 15 de marzo al 3 de abril" o "desde que empece el trabajo nuevo" no se
pueden pedir, y son justo las preguntas que uno le hace a un gestor de gastos.
Se anaden dos parametros date_from y date_to, opcionales por separado: "desde
el 1 de marzo" sin fecha final es un filtro perfectamente valido. Si el rango
viene invertido se ignoran las dos fechas, porque devolver una lista vacia sin
explicacion es peor que descartar un filtro mal puesto.
Cuando hay rango, manda el rango y los selectores de ano y mes no se aplican:
combinar ambos criterios da intersecciones que el usuario no ha pedido y no
puede ver. La plantilla lo avisa con un mensaje y un enlace para quitarlo, en
vez de dejar los selectores mostrando un periodo que no es el que se esta
viendo.
Los campos de fecha comparten estilo con el resto de filtros, incluido el
anillo de foco, para que no desentonen con los selectores de al lado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La plantilla cerraba un div que nunca se habia abierto, asi que el
</div> del .table-wrap acababa cerrando el contenedor de la pagina y todo lo
que viniera despues quedaba fuera de su sitio. Ahora las aperturas y los
cierres cuadran.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El campo is_transfer tiene dos comportamientos opuestos y uno de ellos falla
en silencio: si el filtro se cuela en un calculo de saldo, los numeros siguen
apareciendo, solo que mal. Los tests se organizan en dos bloques que cubren
justo esa frontera.
- Analisis: el traspaso no cuenta en los KPIs de home ni del dashboard, ni en
el total del listado, ni en la comparativa de periodos, ni en el progreso de
los objetivos de pago y presupuesto.
- Saldo: el traspaso SI cuenta en current_balance, monthly_balance,
balance_until, monthly_net y en el objetivo de ahorro.
Tambien se cubre que el listado de gastos siga mostrando el traspaso aunque su
total lo descuente, que es la parte que un filtro puesto de mas romperia.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sin esto el campo is_transfer no es alcanzable: no aparece en los formularios
de gasto ni de ingreso, asi que nadie puede marcar un movimiento como
traspaso.
Ademas, un movimiento que no suma al total tiene que verse distinto o el
usuario no entendera por que sus numeros no cuadran con la tabla. Se anade un
distintivo "Traspaso" en el listado de gastos, en los ultimos movimientos de
home y en los gastos recientes del dashboard, que son los tres sitios donde
se listan movimientos sin agregarlos.
El distintivo usa una variante nueva, .badge-neutral, en vez de reutilizar
.badge-inactive: un traspaso es informacion, no un estado degradado, y
pintarlo con el color de error o de cuenta inactiva daria a entender que algo
va mal. Las variables que usa (--color-chip-bg y --color-text-muted) ya
existen en los dos temas.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un traspaso mueve dinero entre cuentas propias: suma al saldo de cada cuenta
pero no es gasto ni ingreso real. Las agregaciones de analisis (KPIs, graficos
de home y dashboard, comparativa de periodos, totales del listado y objetivos
de pago y presupuesto) pasan a excluirlo.
Hay dos comportamientos opuestos sobre el mismo campo y conviene no
confundirlos:
- Analisis: excluye los traspasos. De ahi el helper _analysis_expenses, que
documenta en su docstring donde NO debe usarse.
- Saldo: los incluye. _account_balances, current_balance, monthly_balance,
balance_until y monthly_net quedan intactos a proposito; filtrarlos ahi
descuadraria todos los saldos de la aplicacion de forma silenciosa, que es
mucho peor y menos evidente que inflar un KPI.
Por el mismo motivo la rama saving de Goal.progress tampoco filtra: mandar
dinero a la cuenta de ahorro es justo como se progresa en ese objetivo.
Los ultimos movimientos de home dejan de colgar de la consulta de KPIs y usan
la suya propia: son un listado, no un analisis, y un traspaso tiene que poder
verse ahi. Lo mismo en el listado de gastos, donde la tabla los sigue
mostrando y solo los totales los descuentan.
Los managers por defecto de Expense e Income no se tocan.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un traspaso entre cuentas propias se registra hoy como dos movimientos: un
gasto en la cuenta de origen y un ingreso en la de destino. Los saldos
cuadran, pero el gasto infla los analisis (KPIs, graficos, objetivos) con
dinero que nunca ha salido del patrimonio del usuario.
Marcar el movimiento hace falta en los dos modelos, no en uno solo. Una
"categoria de traspaso" cubriria unicamente la mitad de la operacion, porque
Income no tiene categoria; de ahi el booleano en ambos.
El campo solo se anade aqui: ninguna consulta lo usa todavia, asi que este
commit no cambia ningun numero de la aplicacion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
base.css nunca ha definido tamanos de encabezado, asi que los ponia el
navegador: 32px el h1, 24px el h2 y 18,7px el h3. En una vista densa de
datos como el dashboard eso compite con las propias cifras, y ademas dejaba
los tamanos a merced de que un encabezado cambiara de nivel.
Fija 1.625rem / 1.25rem / 1rem con font-weight 600. Los margenes van en rem
a proposito: los del navegador van en em, escalan con el tamano de fuente y
dejan huecos distintos entre secciones que deberian separarse igual.
Va en la zona de estilos base, antes de los componentes, para que las
reglas que ya ajustan encabezados concretos (.dashboard-context h1,
.settings-card h2, .home-section > h2, .info-panel-content h2) sigan
ganando por orden.
Afecta a toda la aplicacion. Lo que mas se nota es el titulo de pagina:
pierde 21px de aire por arriba, porque el margen superior del h1 pasa a 0 y
el padding de .content impedia que colapsara. Entre secciones de la home no
cambia nada, ahi manda el margin-bottom de 2.5rem de .home-section. Ningun
media query toca tamanos de fuente, asi que en movil la escala es la misma,
y el bloque no declara colores, asi que el tema oscuro no se entera.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cuatro paginas tenian su titulo en <h2> sin ningun <h1> encima: el
formulario de categoria, ajustes, el login y la ayuda de contrasena. Y el
listado de categorias si tenia <h1>, pero colgaba de el dos <h3>, saltandose
el nivel intermedio.
Sube los titulos de pagina a <h1> y los subtitulos al nivel que les toca:
las dos secciones del listado de categorias a <h2> y las tres tarjetas de
ajustes a <h2>. El selector .settings-card h3 pasa a h2 para seguirlas y
mantener su margin: 0 0 0.25rem, que es lo que evita que el titulo abra
hueco dentro de la tarjeta.
En login y ayuda de contrasena el <h1> ademas importa para el layout:
.auth-container tiene padding: 2rem, y el padding impide que colapse el
margen superior del primer hijo. Con la escala que viene despues, un <h2>
ahi sumaria su margen a ese padding y dejaria el titulo a 56px del borde;
el <h1> no lleva margen superior.
Verificado renderizando las 36 vistas: todas con un solo h1 y sin saltos
de nivel.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El dashboard empezaba en <h2> y bajaba hasta <h4>, sin ningun <h1>. La
pagina no tenia titulo de documento y las secciones colgaban de un nivel
que no existia, asi que un lector de pantalla no podia recorrerla por
encabezados.
Sube cada nivel uno: el nombre de la cuenta (o "Todas las cuentas") pasa a
<h1>, las secciones a <h2> y el nombre de cada cuenta dentro de su tarjeta
de grafica a <h3>. El selector .dashboard-context h2 pasa a h1 para seguir
al elemento.
Solo cambian los niveles. Como base.css no definia tamanos de encabezado,
el aspecto lo daban los valores por defecto del navegador y esto si mueve
los tamanos; el commit de la escala tipografica los fija.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El bloque <script> del formulario de periodo definia clearPeriodPreset y
tres consts, y no lo llamaba nadie: no hay ningun onchange ni listener que
lo use. Debajo habia ademas un segundo <script> entero comentado, de una
version anterior de los presets. Los ids hiddenPeriod, yearSelect y
monthSelect siguen en el formulario, que los usa para las etiquetas.
La seccion "Comparativa" del final mostraba la misma diferencia y el mismo
porcentaje que "Resumen comparativo" ya ensena arriba, y ambas salen bajo
el mismo {% if compare_enabled %}. Es un subconjunto estricto: la de arriba
anade los totales de los dos periodos. La de abajo ademas imprimia
kpi_difference_abs con un "+" condicional, asi que una bajada se veia como
"12,00 € (-13,3%)", sin signo en la cifra y con el porcentaje en negativo.
Se queda la de arriba, que imprime "-12,00 €".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La vista pasaba de 226 lineas y mezclaba tres cosas distintas: traducir los
parametros de la URL a un periodo, calcular los KPIs y montar las series de
las graficas por cuenta. Saca las dos que son autonomas a _resolve_period y
_build_account_charts y deja dashboard en 170.
De paso quita el N+1: la vista llamaba a current_balance() una vez por
cuenta para el KPI y otra vez por cuenta dentro del bucle de graficas, y
cada llamada son dos queries. Ahora los saldos salen de _account_balances,
que los calcula con dos agregados, y se reparten por un dict indexado por
id. Medido con CaptureQueriesContext: con 1/3/6 cuentas se pasa de 25/53/95
queries a 21/37/61.
accounts deja de ser un QuerySet y pasa a lista, porque ahora se recorre
mas de una vez, y la cuenta seleccionada se busca en memoria en vez de con
otra query.
Lo que sigue costando una query por cuenta es monthly_balance(), que no se
toca aqui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La vista dashboard concentra los presets de periodo, el filtro por cuenta,
la comparativa y las series de las graficas, y solo tenia cubierto el
filtro por ano. Antes de tocarla conviene fijar por escrito lo que hace
hoy, para que el refactor que viene no pueda cambiarlo sin que salte algo.
Cubre los tres presets (this_month, last_month, this_year), el filtro por
cuenta con su KPI de saldo, los valores por defecto de la comparativa
cuando no esta activada, la diferencia y la tendencia cuando si lo esta,
el chart_type segun se mire un mes o el ano entero, y el recorte a diez
categorias de la grafica de distribucion.
El test de la grafica de categorias comprueba tambien que by_category
sigue trayendo las doce: el recorte es solo de la grafica, no del listado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La vista calculaba chart_type ("day" o "month") pero nunca lo metia en el
contexto, asi que la plantilla lo leia siempre vacio y el
{% if chart_type == 'day' %} del titulo de la grafica no se cumplia nunca.
El encabezado decia "Por Meses" tambien al mirar un mes suelto, cuando la
serie que se dibuja debajo es por dias.
Solo anade la clave que faltaba; el calculo ya estaba bien.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>