RF18: Usuario registra gasto
Descripción
Como usuario autenticado, quiero registrar un gasto puntual para llevar el control de a dónde se va mi dinero.
Es un movimiento de egreso no recurrente (a diferencia del gasto fijo, RF17). Puede crearse manualmente o derivarse de un ticket (RF20). El monto y la descripción se cifran a nivel de campo.
| Campo | Valor |
|---|---|
| Módulo | Finance Data Collection (FDC) Module |
| Actor | Usuario autenticado |
| Endpoint | POST /finance/transactions (type: expense) |
| Precondiciones | Sesión activa |
| Prioridad | Alta (MVP) |
| Etapa | MVP |
| Requisitos relacionados | RF19, RF20, RF23 |
Reglas de negocio
- RN-18.1 — Un gasto tiene monto, fecha, categoría y (opcional) comercio/descripción y adjunto (ticket).
- RN-18.2 —
amount,description,merchantse cifran a nivel de campo (AES-256-GCM, clave por usuario). - RN-18.3 — La fecha no puede ser futura.
- RN-18.4 — El gasto se categoriza para el análisis de IA (clasificación de gasto).
- RN-18.5 — El usuario puede editar o eliminar el gasto.
- RN-18.6 — El monto se maneja como decimal de precisión fija y lleva código de moneda (
MXNpor defecto en el MVP), conforme a RNF-05. - RN-18.7 — La creación acepta una idempotency key para que un reintento por fallo de red no duplique el gasto (RNF-09).
Validaciones de entrada
| Campo | Reglas | Mensaje de error |
|---|---|---|
amount | Obligatorio. Decimal > 0. Máx. 2 decimales. | "Ingresa un monto válido mayor a 0." |
currency | Opcional. ISO 4217; por defecto MXN. | "Moneda no válida." |
date | Obligatorio. No futura. | "La fecha no puede ser posterior a hoy." |
category | Obligatorio. Valor de catálogo. | "Selecciona una categoría válida." |
merchant/description | Opcional. Máx. 120 caracteres. | "El texto es demasiado largo." |
idempotencyKey | Opcional pero recomendado. Identificador único del intento. | — |
Validación de tipos/fecha y consultas parametrizadas; no se aceptan inyecciones SQL.
Criterios de aceptación
Escenario 1: Registro de gasto exitoso
Dado que ingreso monto positivo, fecha no futura y categoría,
Cuando guardo el gasto,
Entonces el sistema lo persiste (campos cifrados) y responde 201 Created,
Y se refleja en el dashboard y en el saldo del periodo.
Escenario 2: Monto inválido
Dado que ingreso un monto ≤ 0 o no numérico,
Cuando intento guardar,
Entonces el sistema responde 400 con "Ingresa un monto válido mayor a 0".
Escenario 3: Fecha futura
Dado que selecciono una fecha posterior a hoy,
Cuando intento guardar,
Entonces el sistema responde 400 con "La fecha no puede ser posterior a hoy".
Escenario 4: Entrada no esperada (seguridad)
Dado que ingreso texto excesivamente largo o una inyección SQL en la descripción, Cuando el backend valida, Entonces la entrada se rechaza por longitud/patrón y la inyección no se ejecuta.
Escenario 5: Reintento duplicado (consistencia)
Dado que reintento el mismo registro por un fallo de red con la misma idempotencyKey,
Cuando el backend lo procesa,
Entonces no se crea un gasto duplicado.
Criterios no funcionales
- Campos sensibles cifrados; respuesta < 1 s.
- Idempotencia en la creación (RNF-09); monto decimal + moneda (RNF-05).
- Comunicación TLS 1.2+.