215 lines
6.1 KiB
Markdown
215 lines
6.1 KiB
Markdown
# expenses_manager
|
|
|
|
Aplicación web de gestión de finanzas personales (gastos, ingresos, cuentas,
|
|
categorías, objetivos y repostajes de vehículo), construida con **Django** y
|
|
desplegada en un NAS Synology mediante **Docker** + **PostgreSQL**.
|
|
|
|
## Características
|
|
|
|
- Registro de **gastos** e **ingresos**, con categorías jerárquicas y etiquetas.
|
|
- **Cuentas** con cálculo de saldo (soft-delete para preservar históricos).
|
|
- **Dashboard** con gráficos por categoría, evolución de saldo y comparativas de periodo.
|
|
- **Objetivos** de tres tipos: pago/deuda, presupuesto (mensual/anual) y ahorro.
|
|
- Módulo de **repostajes** con cálculo de consumo, precio por litro y km recorridos.
|
|
|
|
## Stack
|
|
|
|
- Python 3.11 · Django 5.2
|
|
- PostgreSQL (producción) / SQLite (desarrollo local por defecto)
|
|
- Gunicorn + WhiteNoise
|
|
- Docker (despliegue) · Jenkins (CI)
|
|
|
|
---
|
|
|
|
## Desarrollo local
|
|
|
|
Por defecto, en local la aplicación usa **SQLite**, así que no necesitas
|
|
levantar ninguna base de datos para empezar.
|
|
|
|
### Requisitos
|
|
|
|
- Python 3.11 (estos pasos asumen un entorno **conda**, pero sirve cualquier
|
|
entorno virtual).
|
|
|
|
### Pasos
|
|
|
|
1. Clona el repositorio:
|
|
|
|
```sh
|
|
git clone https://gitea.kuijper.es/jkuijperm/expenses_manager.git
|
|
cd expenses_manager
|
|
```
|
|
|
|
2. Crea y activa el entorno conda:
|
|
|
|
```sh
|
|
conda create -n gastos python=3.11
|
|
conda activate gastos
|
|
```
|
|
|
|
3. Instala las dependencias:
|
|
|
|
```sh
|
|
pip install -r expenses_manager/requirements.txt
|
|
```
|
|
|
|
4. Crea el archivo `.env` (ver [Variables de entorno](#variables-de-entorno)).
|
|
Para desarrollo basta con:
|
|
|
|
```env
|
|
DEBUG=True
|
|
SECRET_KEY=cualquier-clave-para-desarrollo
|
|
```
|
|
|
|
Con `DEBUG=True` y sin variables `DB_*`, se usa SQLite automáticamente.
|
|
|
|
5. Aplica las migraciones y arranca el servidor:
|
|
|
|
```sh
|
|
cd expenses_manager
|
|
python manage.py migrate
|
|
python manage.py runserver
|
|
```
|
|
|
|
6. Abre `http://127.0.0.1:8000/` en el navegador.
|
|
|
|
> **Datos de demostración (opcional):** con `DEBUG=True` puedes poblar la base
|
|
> de datos con un usuario y datos de ejemplo:
|
|
>
|
|
> ```sh
|
|
> python manage.py seed_demo
|
|
> ```
|
|
>
|
|
> El comando está protegido para no ejecutarse si `DEBUG=False`.
|
|
|
|
---
|
|
|
|
## Variables de entorno
|
|
|
|
La configuración se lee de un archivo `.env` (mediante `python-dotenv`) situado
|
|
en `expenses_manager/` (junto a `manage.py`). **Este archivo no se versiona.**
|
|
|
|
| Variable | Obligatoria | Por defecto | Descripción |
|
|
|---|---|---|---|
|
|
| `SECRET_KEY` | Sí en producción | — | Clave secreta de Django. Con `DEBUG=False` es obligatoria (si falta, la app no arranca). |
|
|
| `DEBUG` | No | `False` | `True` en desarrollo. Activa SQLite por defecto y desactiva el hardening. |
|
|
| `ALLOWED_HOSTS` | No | `localhost,127.0.0.1` | Lista separada por comas. |
|
|
| `CSRF_TRUSTED_ORIGINS` | No | (vacío) | Lista separada por comas (p. ej. `https://midominio.com`). |
|
|
| `DB_ENGINE` | No | `sqlite3` | `postgresql` para usar Postgres; cualquier otro valor usa SQLite. |
|
|
| `DB_NAME` | Con Postgres | — | Nombre de la base de datos. |
|
|
| `DB_USER` | Con Postgres | — | Usuario. |
|
|
| `DB_PASSWORD` | Con Postgres | — | Contraseña. |
|
|
| `DB_HOST` | No | `localhost` | Host de la base de datos (`db` en Docker Compose). |
|
|
| `DB_PORT` | No | `5432` | Puerto. |
|
|
|
|
En el repositorio hay un archivo **`.env.example`** con estas variables como
|
|
plantilla. Cópialo a `.env` y rellena los valores.
|
|
|
|
---
|
|
|
|
## Despliegue con Docker
|
|
|
|
El despliegue en producción usa Docker con dos servicios: la aplicación Django
|
|
(servida por Gunicorn) y una base de datos PostgreSQL.
|
|
|
|
### Dockerfile
|
|
|
|
El `Dockerfile` está en el repositorio (`app/Dockerfile` en el árbol de
|
|
despliegue) y hace lo esencial: parte de `python:3.11-slim`, instala las
|
|
dependencias de `requirements.txt` y copia el código. A grandes rasgos:
|
|
|
|
```dockerfile
|
|
FROM python:3.11-slim
|
|
|
|
ENV PYTHONUNBUFFERED=1 \
|
|
PYTHONDONTWRITEBYTECODE=1
|
|
|
|
WORKDIR /app/expenses_manager
|
|
|
|
COPY expenses_manager/requirements.txt .
|
|
RUN pip install --no-cache-dir -r requirements.txt
|
|
|
|
COPY . .
|
|
|
|
EXPOSE 8000
|
|
```
|
|
|
|
### docker-compose (no versionado)
|
|
|
|
El `docker-compose.yml` **no está en el repositorio**: es específico de la
|
|
infraestructura del NAS y vive fuera del proyecto. A continuación se documenta
|
|
qué debe contener, para poder reproducirlo. **Ningún secreto va escrito en el
|
|
archivo**: todos los valores sensibles se leen del `.env`.
|
|
|
|
```yaml
|
|
services:
|
|
db:
|
|
image: postgres:15
|
|
restart: always
|
|
environment:
|
|
POSTGRES_DB: ${DB_NAME}
|
|
POSTGRES_USER: ${DB_USER}
|
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
volumes:
|
|
- ./postgres:/var/lib/postgresql/data
|
|
|
|
web:
|
|
build: ./app
|
|
container_name: finanzas_web
|
|
command: gunicorn expenses_manager.wsgi:application --bind 0.0.0.0:8000
|
|
volumes:
|
|
- ./app:/app
|
|
ports:
|
|
- "8001:8000"
|
|
depends_on:
|
|
- db
|
|
env_file:
|
|
- ./app/expenses_manager/.env
|
|
```
|
|
|
|
Notas:
|
|
|
|
- Las credenciales de Postgres (`DB_NAME`, `DB_USER`, `DB_PASSWORD`) las toma
|
|
Docker Compose de un `.env` situado **junto al `docker-compose.yml`**. Es un
|
|
archivo distinto del `.env` de Django (que va dentro del proyecto y lo carga
|
|
la propia app vía `env_file`).
|
|
- El servicio `web` no repite las variables de base de datos en un bloque
|
|
`environment`: las hereda del `.env` de Django a través de `env_file`.
|
|
|
|
### Puesta en marcha y actualizaciones
|
|
|
|
Desde el directorio que contiene el `docker-compose.yml`:
|
|
|
|
```sh
|
|
git -C app pull # traer los últimos cambios al repo montado
|
|
docker-compose down
|
|
docker-compose up -d --build
|
|
docker-compose exec web python manage.py migrate
|
|
docker-compose exec web python manage.py collectstatic --noinput
|
|
```
|
|
|
|
- `migrate` solo es necesario si hay migraciones nuevas.
|
|
- `collectstatic` solo si han cambiado archivos estáticos (CSS/JS).
|
|
- **No** ejecutes `makemigrations` en producción: las migraciones se generan en
|
|
desarrollo y llegan versionadas con el `git pull`.
|
|
|
|
---
|
|
|
|
## Tests
|
|
|
|
La suite usa `pytest` + `pytest-django`:
|
|
|
|
```sh
|
|
cd expenses_manager
|
|
pytest
|
|
```
|
|
|
|
La integración continua (Jenkins) ejecuta los tests en cada cambio de la rama
|
|
`main`.
|
|
|
|
---
|
|
|
|
## Ramas
|
|
|
|
- **`main`** — solo recibe cambios ya probados (vía merge desde `dev`).
|
|
- **`dev`** — rama de trabajo activa. |