Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-01
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
## Context

See proposal.md for motivation. The current app already distinguishes `MonthlyExpense.source` between `template` and `manual`, and `generateMonthlyExpenses()` is idempotent by template-period id. The gap is UI state: the top-level initialization affordance should be hidden in the empty month state and only shown once the period already has expenses. The new requirement also needs a transient toast/alert when the user clicks "Iniciar mes" without templates.

Concrete files likely to change:
- `src/views/DashboardView.vue`
- `src/views/PendingView.vue`
- `src/stores/expenseStore.ts`
- `src/services/expenseService.ts`

## Goals / Non-Goals

**Goals:**
- Derive the top-level month-initialization CTA from whether the period has any expenses.
- Hide the top-level CTA on the empty dashboard / empty Gastos state.
- Surface a click-triggered no-templates toast/alert on dashboard and Gastos.
- Preserve the current Firestore model and idempotent template generation flow.

**Non-Goals:**
- No new collections, fields, or backend endpoints.
- No changes to template generation payload shape.
- No changes to monthly expense creation rules for manual expenses.
- No redesign of the dashboard beyond the visibility and placement of the initialization action.

## Decisions

1. **Use existing expense data as the source of truth for initialization state**
- Decision: compute `hasExpensesInPeriod` from the loaded monthly expenses and use that to control the top-level CTA visibility.
- Rationale: the new behavior is based on the presence of any expense, not on whether the month has been materialized from templates.
- Alternatives considered: keeping the CTA visible until a template-sourced expense exists. Rejected because it conflicts with the requested empty-state behavior.

2. **Expose the action in more than one visible surface**
- Decision: show the top-level CTA only when there is at least one expense, and add a small template-empty notice in Dashboard and Gastos.
- Rationale: this matches the requested empty-state simplification while still keeping the generation path discoverable.
- Alternatives considered: keeping a permanent notice visible in the header or empty state. Rejected because the request prefers feedback only on click.

3. **Keep generation idempotent and unchanged**
- Decision: reuse `generateMonthlyExpenses(periodKey)` as-is for the actual materialization logic.
- Rationale: the backend already handles repeated initialization safely through deterministic IDs.
- Alternatives considered: a new service method for "re-initialize". Rejected because it would be redundant and increase maintenance cost.

## Risks / Trade-offs

- [Risk] Users may still see an initialization CTA even when it will create zero new expenses because the template set is empty. → Mitigation: preserve the existing empty-template feedback from the current generation flow.
- [Risk] The UI may briefly reflect stale initialization state before expenses finish loading. → Mitigation: derive the state only after the month expenses request resolves and keep a loading skeleton/disabled action while pending.
- [Risk] The click-triggered no-templates toast could be missed by the user if it is too subtle. → Mitigation: use the app's standard toast severity and copy that explains the next step clearly.

## Migration Plan

- No data migration is needed.
- Ship the UI condition change first, then verify that manual expense creation no longer removes the initialization action.
- Rollback is straightforward: revert the visibility condition and any derived state helper.

## Open Questions

None.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Why

Hoy el estado del mes se infiere demasiado pronto: al crear un gasto manual desde Gastos, el dashboard deja de mostrar la acción de iniciar el mes aunque todavía no existan instancias generadas desde plantillas. Eso bloquea un flujo válido y deja al usuario sin una forma obvia de materializar sus gastos recurrentes del período.

## What Changes

- Ocultar la acción superior de inicializar el mes cuando el período no tenga gastos todavía.
- Mostrar la acción superior de inicializar el mes cuando ya exista al menos un gasto del período, incluso si es adicional/manual.
- Mostrar un toast o alert solo al intentar iniciar el mes cuando no existan plantillas creadas.
- Conservar la separación entre ExpenseTemplate y MonthlyExpense: las plantillas siguen siendo la fuente para generar gastos mensuales idempotentes.

## Non-goals

- No cambiar el modelo de datos Firestore ni las colecciones existentes.
- No alterar el payload de ExpenseTemplate ni de MonthlyExpense.
- No impedir la creación de gastos manuales mientras el mes esté sin inicializar.
- No introducir nuevas dependencias ni una nueva fuente de verdad para el período.

## Capabilities

### Modified Capabilities
- `dashboard-experience`: ajustar el estado y la visibilidad de la acción de “Iniciar mes” para que dependa de la existencia de al menos un gasto del período y no aparezca en el estado vacío.
- `monthly-expenses`: mantener una acción de inicialización accesible en la vista de Gastos cuando ya exista al menos un gasto del período, y mostrar una notificación solo cuando el usuario intenta iniciar el mes sin plantillas creadas.

## Impact

Touchpoints de UI en Dashboard y Gastos/Pendientes, más la lógica derivada que distingue gastos manuales de instancias de plantilla. No se esperan cambios en rutas, colecciones ni en la forma en que se guardan ExpenseTemplate o MonthlyExpense.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## ADDED Requirements

### Requirement: Month initialization CTA is hidden on an empty dashboard
The dashboard SHALL show the top-level "Iniciar mes" action only when the current period already has at least one MonthlyExpense. When the current period has no expenses, the dashboard SHALL show only the empty-state card for that month and SHALL NOT show the top-level initialization action.

#### Scenario: Empty month hides the top CTA
- **GIVEN** the current period has no MonthlyExpense documents
- **WHEN** the user opens the dashboard
- **THEN** the dashboard shows the empty-state card for the month
- **AND** the top-level "Iniciar mes" action is not shown

#### Scenario: Any expense makes the top CTA visible
- **GIVEN** the current period has at least one MonthlyExpense document
- **WHEN** the user opens the dashboard
- **THEN** the dashboard shows the top-level "Iniciar mes" action

### Requirement: Dashboard warns only when starting without templates
The dashboard SHALL notify the user only when they attempt to start the month and no ExpenseTemplate documents exist. The dashboard SHALL NOT show a permanent no-templates notice in the header or empty state.

#### Scenario: Clicking Iniciar mes without templates shows a warning
- **GIVEN** the user has no ExpenseTemplate documents
- **WHEN** they click "Iniciar mes" from the dashboard
- **THEN** the dashboard shows a warning that there are no templates created
- **AND** the dashboard does not start the month
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## ADDED Requirements

### Requirement: Gastos keeps month initialization accessible once the period has expenses
The Gastos view SHALL keep the initialization action available after the first load when the current period already has at least one MonthlyExpense document. When the current period has no expenses, the view SHALL not show that top-level initialization action.

#### Scenario: Empty month hides the action
- **GIVEN** the current period has no MonthlyExpense documents
- **WHEN** the user opens Gastos
- **THEN** the top-level "Iniciar mes" action is not shown

#### Scenario: Any expense makes the action visible
- **GIVEN** the current period has at least one MonthlyExpense document
- **WHEN** the user opens Gastos
- **THEN** the top-level "Iniciar mes" action is visible

### Requirement: Gastos warns only when starting without templates
The Gastos view SHALL notify the user only when they attempt to start the month and no ExpenseTemplate documents exist. The view SHALL NOT show a permanent no-templates notice in the header or the empty state.

#### Scenario: Clicking Iniciar mes without templates shows a warning
- **GIVEN** the user has no ExpenseTemplate documents
- **WHEN** they click "Iniciar mes" from Gastos
- **THEN** the view shows a warning that there are no templates created
- **AND** the month is not started
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## 1. State derivation

- [x] 1.1 Add a derived check for whether the current period has any expenses, and verify the empty month hides the top initialization action.
- [x] 1.2 Add a loaded-state check for template lists, and verify click-time template warnings only fire after templates have loaded.

## 2. Dashboard entry point

- [x] 2.1 [ui] Update DashboardView so the top "Iniciar mes" affordance is hidden on an empty month, and verify the empty dashboard shows the card action instead.
- [x] 2.2 [ui] Show a toast when Dashboard "Iniciar mes" is clicked without templates, and verify no persistent warning is rendered in the header or card.

## 3. Gastos entry point

- [x] 3.1 [ui] Update PendingView so the top initialization action is hidden on an empty month, and verify it appears after any expense exists.
- [x] 3.2 [ui] Show a toast when Gastos "Iniciar mes" is clicked without templates, and verify no persistent warning is rendered.

## 4. Validation

- [x] 4.1 [qa] Run a browser smoke check on the dashboard and /pending flow to verify the empty month hides the top CTA, the card button remains available, the CTA appears after a manual expense, and no-templates feedback is toast-only.
23 changes: 23 additions & 0 deletions openspec/specs/dashboard-experience/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,26 @@ The dashboard SHALL render pending expenses under the "Próximos por pagar" sect
- **GIVEN** a pending expense is listed under "Próximos por pagar"
- **WHEN** the user checks the checkbox
- **THEN** the expense status transitions to paid and all connected dashboard metrics update reactively

### Requirement: Month initialization CTA is hidden on an empty dashboard
The dashboard SHALL show the top-level "Iniciar mes" action only when the current period already has at least one MonthlyExpense. When the current period has no expenses, the dashboard SHALL show only the empty-state card for that month and SHALL NOT show the top-level initialization action.

#### Scenario: Empty month hides the top CTA
- **GIVEN** the current period has no MonthlyExpense documents
- **WHEN** the user opens the dashboard
- **THEN** the dashboard shows the empty-state card for the month
- **AND** the top-level "Iniciar mes" action is not shown

#### Scenario: Any expense makes the top CTA visible
- **GIVEN** the current period has at least one MonthlyExpense document
- **WHEN** the user opens the dashboard
- **THEN** the dashboard shows the top-level "Iniciar mes" action

### Requirement: Dashboard warns only when starting without templates
The dashboard SHALL notify the user only when they attempt to start the month and no ExpenseTemplate documents exist. The dashboard SHALL NOT show a permanent no-templates notice in the header or empty state.

#### Scenario: Clicking Iniciar mes without templates shows a warning
- **GIVEN** the user has no ExpenseTemplate documents
- **WHEN** they click "Iniciar mes" from the dashboard
- **THEN** the dashboard shows a warning that there are no templates created
- **AND** the dashboard does not start the month
22 changes: 22 additions & 0 deletions openspec/specs/monthly-expenses/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,25 @@ Historial SHALL show the same search field used on Gastos (`ListSearchBar` behav
- **WHEN** they want a different month
- **THEN** they change it in the calendar popover
- **AND** there is no dedicated month scroller with back and forward buttons

### Requirement: Gastos keeps month initialization accessible once the period has expenses
The Gastos view SHALL keep the initialization action available after the first load when the current period already has at least one MonthlyExpense document. When the current period has no expenses, the view SHALL not show that top-level initialization action.

#### Scenario: Empty month hides the action
- **GIVEN** the current period has no MonthlyExpense documents
- **WHEN** the user opens Gastos
- **THEN** the top-level "Iniciar mes" action is not shown

#### Scenario: Any expense makes the action visible
- **GIVEN** the current period has at least one MonthlyExpense document
- **WHEN** the user opens Gastos
- **THEN** the top-level "Iniciar mes" action is visible

### Requirement: Gastos warns only when starting without templates
The Gastos view SHALL notify the user only when they attempt to start the month and no ExpenseTemplate documents exist. The view SHALL NOT show a permanent no-templates notice in the header or the empty state.

#### Scenario: Clicking Iniciar mes without templates shows a warning
- **GIVEN** the user has no ExpenseTemplate documents
- **WHEN** they click "Iniciar mes" from Gastos
- **THEN** the view shows a warning that there are no templates created
- **AND** the month is not started
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createApp } from 'vue'
import { createPinia } from 'pinia'
import router from './router'
import { useAuthStore } from '@/stores/authStore'
import 'vue-sonner/style.css'
import './style.css'
import App from './App.vue'

Expand Down
5 changes: 5 additions & 0 deletions src/stores/expenseStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export const useExpenseStore = defineStore('expenses', () => {

const pendingExpenses = computed(() => expenses.value.filter((e) => e.status === 'pending'))

const hasMonthInitializedFromTemplates = computed(() =>
expenses.value.some((e) => e.source !== 'manual'),
)

const summary = computed<PeriodSummary>(() => {
const paid = paidExpenses.value
const pending = pendingExpenses.value
Expand Down Expand Up @@ -110,6 +114,7 @@ export const useExpenseStore = defineStore('expenses', () => {
currentPeriod,
paidExpenses,
pendingExpenses,
hasMonthInitializedFromTemplates,
summary,
fetchExpenses,
toggleStatus,
Expand Down
Loading