diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..c3ea7db --- /dev/null +++ b/.env.development @@ -0,0 +1,26 @@ +# Development Environment Variables +# Este archivo contiene las variables de entorno para desarrollo local + +# Django Environment +DJANGO_ENV=development + +# Debug mode (True for development) +DEBUG=True + +# Django Secret Key (insecure key for development only) +SECRET_KEY=django-insecure-development-key-zh6rinl@8y7g(cf781snisx2j%p^c#d&b2@@9cqe!v@4yv8x=v + +# Allowed hosts (comma-separated) +ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0 + +# CORS allowed origins (comma-separated) +CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:7001,http://localhost:5173 + +# Database (SQLite by default in development) +# No additional DB configuration needed for SQLite + +# Tryton ERP Configuration +TRYTON_HOST=localhost +TRYTON_DATABASE=tryton +TRYTON_USERNAME=admin +TRYTON_PASSWORD=admin diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 0000000..eb9f5c1 --- /dev/null +++ b/.env.production.example @@ -0,0 +1,56 @@ +# Production Environment Variables - EXAMPLE +# ¡IMPORTANTE! Copia este archivo a .env.production y completa con valores reales +# NO commitees .env.production con valores sensibles a git + +# Django Environment +DJANGO_ENV=production + +# Debug mode (MUST be False in production) +DEBUG=False + +# Django Secret Key +# ¡IMPORTANTE! Genera una clave única y segura para producción +# Puedes generar una con: python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())' +SECRET_KEY=CHANGE-ME-TO-A-SECURE-RANDOM-SECRET-KEY-IN-PRODUCTION + +# Allowed hosts (comma-separated domains) +# Ejemplo: ALLOWED_HOSTS=tiendailusion.com,www.tiendailusion.com,api.tiendailusion.com +ALLOWED_HOSTS=tiendailusion.com,www.tiendailusion.com + +# CORS allowed origins (comma-separated URLs) +# Ejemplo: CORS_ALLOWED_ORIGINS=https://tiendailusion.com,https://www.tiendailusion.com +CORS_ALLOWED_ORIGINS=https://tiendailusion.com,https://www.tiendailusion.com + +# CSRF Trusted Origins (comma-separated URLs) +# Debe incluir el protocolo (https://) +CSRF_TRUSTED_ORIGINS=https://tiendailusion.com,https://www.tiendailusion.com + +# PostgreSQL Database Configuration +DB_NAME=tienda_ilusion_prod +DB_USER=tienda_ilusion_user +DB_PASSWORD=CHANGE-ME-TO-A-SECURE-DATABASE-PASSWORD +DB_HOST=postgres +DB_PORT=5432 + +# Email Configuration (para notificaciones y recuperación de contraseñas) +EMAIL_HOST=smtp.gmail.com +EMAIL_PORT=587 +EMAIL_USE_TLS=True +EMAIL_HOST_USER=noreply@tiendailusion.com +EMAIL_HOST_PASSWORD=CHANGE-ME-TO-YOUR-EMAIL-PASSWORD +DEFAULT_FROM_EMAIL=noreply@tiendailusion.com + +# Admin notifications +ADMIN_EMAIL=admin@tiendailusion.com + +# Tryton ERP Configuration (Production) +TRYTON_HOST=tryton-production-server +TRYTON_DATABASE=tryton_production +TRYTON_USERNAME=tienda_ilusion_integration +TRYTON_PASSWORD=CHANGE-ME-TO-TRYTON-PASSWORD + +# Optional: Redis URL for caching (if using Redis) +# REDIS_URL=redis://redis:6379/1 + +# Optional: Sentry DSN for error tracking +# SENTRY_DSN=https://your-sentry-dsn@sentry.io/project-id diff --git a/.env_example b/.env_example deleted file mode 100644 index 0472382..0000000 --- a/.env_example +++ /dev/null @@ -1,4 +0,0 @@ -TRYTON_HOST=localhost -TRYTON_DATABASE=tryton -TRYTON_USERNAME=admin -TRYTON_PASSWORD=admin diff --git a/.gitignore b/.gitignore index ff2fa59..9b40779 100644 --- a/.gitignore +++ b/.gitignore @@ -327,9 +327,39 @@ pip-selfcheck.json # End of https://www.toptal.com/developers/gitignore/api/emacs,python,django,venv +# Project-specific ignores /tienda_ilusion/don_confiao/static/frontend/ /tienda_ilusion/don_confiao/frontend/don-confiao/.vite/ /tienda_ilusion/don_confiao/frontend/don-confiao/.eslintrc.js /tienda_ilusion/don_confiao/frontend/don-confiao/.eslintrc-auto-import.json /tienda_ilusion/don_confiao/frontend/don-confiao/.editorconfig /tienda_ilusion/don_confiao/frontend/don-confiao/.browserslistrc + +# Environment files with sensitive data +.env.production +.env.local +.env.*.local + +# Static files collected by Django +/tienda_ilusion/staticfiles/ +staticfiles/ + +# Media files uploaded by users +/tienda_ilusion/media/ + +# Application logs +/tienda_ilusion/logs/ +*.log + +# Database backups +backups/ +*.sql +*.dump + +# Docker volumes and data +postgres_data/ +pgdata/ + +# IDE-specific +.vscode/ +.idea/ diff --git a/AGENTS.md b/AGENTS.md index 52d6f4d..b04940f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,19 +7,21 @@ Backend Django con Django REST Framework ``` don_confiao_backend/ ├── requirements.txt # Dependencias Python -├── docker-compose.yml # Configuración Docker -├── django.Dockerfile # Dockerfile Django -├── .env # Variables de entorno -├── .env_example # Ejemplo de variables de entorno -├── README.rst # Documentación básica -├── Rakefile # Tareas rake -├── doc/ # Documentación adicional +├── docker-compose.dev.yml # Docker Compose para desarrollo +├── docker-compose.prod.yml # Docker Compose para producción +├── django.Dockerfile # Dockerfile Django +├── .env.development # Variables de entorno desarrollo +├── .env.production.example # Ejemplo variables de entorno producción +├── .env_example # Ejemplo de variables de entorno +├── README.rst # Documentación básica +├── Rakefile # Tareas rake +├── doc/ # Documentación adicional │ └── requests.org -└── tienda_ilusion/ # Proyecto Django +└── tienda_ilusion/ # Proyecto Django ├── manage.py - ├── db.sqlite3 # Base de datos SQLite - ├── don_confiao/ # App principal - │ ├── models.py # Modelos: Customer, Product, Sale, SaleLine, Payment, ReconciliationJar, AdminCode + ├── db.sqlite3 # Base de datos SQLite (desarrollo) + ├── don_confiao/ # App principal + │ ├── models.py # Modelos: Customer, Product, Sale, SaleLine, Payment, ReconciliationJar, AdminCode │ ├── views.py │ ├── api_views.py │ ├── serializers.py @@ -27,16 +29,20 @@ don_confiao_backend/ │ ├── admin.py │ ├── urls.py │ ├── export_csv.py - │ ├── tests/ # Tests + │ ├── tests/ # Tests │ └── migrations/ - ├── users/ # App de usuarios + ├── users/ # App de usuarios │ ├── models.py │ ├── views.py │ ├── serializers.py │ ├── urls.py │ └── tests/ - └── tienda_ilusion/ # Configuración Django - ├── settings.py + └── config/ # Configuración Django + ├── settings/ # Settings por ambiente + │ ├── __init__.py # Detección automática de ambiente + │ ├── base.py # Configuración compartida + │ ├── development.py # Configuración desarrollo + │ └── production.py # Configuración producción ├── urls.py ├── wsgi.py └── asgi.py @@ -48,6 +54,9 @@ don_confiao_backend/ - django-cors-headers - djangorestframework-simplejwt - sabatron-tryton-rpc-client==7.4.0 (integración con Tryton ERP) +- psycopg2-binary (driver PostgreSQL para producción) +- gunicorn (servidor WSGI para producción) +- python-decouple (gestión de variables de entorno) ## Modelos Principales (don_confiao/models.py) - **Customer**: Clientes (name, address, email, phone, external_id) @@ -73,22 +82,50 @@ don_confiao_backend/ El proyecto se ejecuta con docker-compose. Todos los comandos `manage.py` deben ejecutarse dentro del contenedor: +### Desarrollo (Development) + ```bash # Ejecutar tests -docker-compose run --rm django python manage.py test +docker-compose -f docker-compose.dev.yml run --rm django python manage.py test # Migraciones -docker-compose run --rm django python manage.py makemigrations -docker-compose run --rm django python manage.py migrate +docker-compose -f docker-compose.dev.yml run --rm django python manage.py makemigrations +docker-compose -f docker-compose.dev.yml run --rm django python manage.py migrate # Servidor desarrollo -docker-compose up +docker-compose -f docker-compose.dev.yml up # Shell Django -docker-compose run --rm django python manage.py shell +docker-compose -f docker-compose.dev.yml run --rm django python manage.py shell # Crear superuser -docker-compose run --rm django python manage.py createsuperuser +docker-compose -f docker-compose.dev.yml run --rm django python manage.py createsuperuser +``` + +### Producción (Production) + +```bash +# Primero, copiar .env.production.example a .env.production y configurar variables +cp .env.production.example .env.production +# Editar .env.production con valores reales + +# Iniciar servicios (PostgreSQL + Django con Gunicorn) +docker-compose -f docker-compose.prod.yml up -d + +# Migraciones +docker-compose -f docker-compose.prod.yml run --rm django python manage.py migrate + +# Colectar archivos estáticos +docker-compose -f docker-compose.prod.yml run --rm django python manage.py collectstatic --noinput + +# Crear superuser +docker-compose -f docker-compose.prod.yml run --rm django python manage.py createsuperuser + +# Ver logs +docker-compose -f docker-compose.prod.yml logs -f django + +# Detener servicios +docker-compose -f docker-compose.prod.yml down ``` Nota: El volumen monta `tienda_ilusion/` en `/app/`, por lo que el path correcto es `python manage.py` (no `python tienda_ilusion/manage.py`). @@ -96,13 +133,255 @@ Nota: El volumen monta `tienda_ilusion/` en `/app/`, por lo que el path correcto ## Tests - Framework: Django unittest - Directorio: don_confiao/tests/ -- Ejecutar: `docker-compose run --rm django python manage.py test` +- Ejecutar: `docker-compose -f docker-compose.dev.yml run --rm django python manage.py test` ## Comandos Útiles (dentro del contenedor) -- Migraciones: `docker-compose run --rm django python manage.py makemigrations && docker-compose run --rm django python manage.py migrate` -- Servidor desarrollo: `docker-compose up` -- Shell Django: `docker-compose run --rm django python manage.py shell` -- Superuser: `docker-compose run --rm django python manage.py createsuperuser` +- Migraciones: `docker-compose -f docker-compose.dev.yml run --rm django python manage.py makemigrations && docker-compose -f docker-compose.dev.yml run --rm django python manage.py migrate` +- Servidor desarrollo: `docker-compose -f docker-compose.dev.yml up` +- Shell Django: `docker-compose -f docker-compose.dev.yml run --rm django python manage.py shell` +- Superuser: `docker-compose -f docker-compose.dev.yml run --rm django python manage.py createsuperuser` + +## Configuración de Ambientes + +El proyecto soporta dos ambientes diferentes mediante la variable `DJANGO_ENV`: + +### Development (desarrollo local) +- **Variable de ambiente**: `DJANGO_ENV=development` +- **Archivo de configuración**: `.env.development` +- **Base de datos**: SQLite (db.sqlite3) +- **DEBUG**: True +- **CORS**: Permisivo para desarrollo local +- **Docker Compose**: `docker-compose.dev.yml` + +### Production (producción) +- **Variable de ambiente**: `DJANGO_ENV=production` +- **Archivo de configuración**: `.env.production` (crear desde `.env.production.example`) +- **Base de datos**: PostgreSQL +- **DEBUG**: False +- **Seguridad**: HTTPS, HSTS, secure cookies, CSRF protections +- **Docker Compose**: `docker-compose.prod.yml` +- **Servidor**: Gunicorn con 4 workers + +### Cambiar entre ambientes + +La detección de ambiente es automática mediante la variable `DJANGO_ENV`. Docker Compose configura esta variable automáticamente según el archivo usado: + +- `docker-compose.dev.yml` → usa `.env.development` → carga `settings/development.py` +- `docker-compose.prod.yml` → usa `.env.production` → carga `settings/production.py` + +### Variables de entorno requeridas + +**Desarrollo** (`.env.development`): +- `DJANGO_ENV=development` +- `DEBUG=True` +- `TRYTON_HOST`, `TRYTON_DATABASE`, `TRYTON_USERNAME`, `TRYTON_PASSWORD` + +**Producción** (`.env.production`): +- `DJANGO_ENV=production` +- `DEBUG=False` +- `SECRET_KEY` (generar una nueva y segura) +- `ALLOWED_HOSTS` (dominios separados por comas) +- `CORS_ALLOWED_ORIGINS` (URLs separadas por comas) +- `CSRF_TRUSTED_ORIGINS` (URLs separadas por comas) +- `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `DB_HOST`, `DB_PORT` +- `EMAIL_HOST`, `EMAIL_PORT`, `EMAIL_HOST_USER`, `EMAIL_HOST_PASSWORD` +- `TRYTON_HOST`, `TRYTON_DATABASE`, `TRYTON_USERNAME`, `TRYTON_PASSWORD` + +Ver `.env.production.example` para todos los detalles. + +## Scripts Útiles + +El proyecto incluye scripts para facilitar operaciones comunes: + +### Health Check +Verifica el estado de todos los servicios: +```bash +# Verificar ambiente de desarrollo +./scripts/health-check.sh dev + +# Verificar ambiente de producción +./scripts/health-check.sh prod +``` + +### Backup de Base de Datos (Producción) +Crea un backup comprimido de la base de datos PostgreSQL: +```bash +./scripts/backup-db.sh +``` + +Los backups se guardan en `backups/` y se mantienen por 7 días automáticamente. + +### Restore de Base de Datos (Producción) +Restaura un backup de la base de datos: +```bash +./scripts/restore-backup.sh +``` + +**ADVERTENCIA**: Esto reemplazará la base de datos actual. Se crea un backup de seguridad antes de restaurar. + +## Troubleshooting + +### Errores Comunes + +#### 1. Error: "database 'tienda_ilusion_user' does not exist" +**Síntoma**: Aparece en los logs de PostgreSQL +``` +FATAL: database "tienda_ilusion_user" does not exist +``` + +**Causa**: Este es un comportamiento normal de PostgreSQL. Cuando te conectas sin especificar una base de datos, PostgreSQL intenta conectarse a una base con el mismo nombre del usuario. + +**Solución**: Este error NO afecta el funcionamiento de Django. Para verificar que la base de datos correcta existe: +```bash +docker exec tienda_ilusion_postgres_prod psql -U tienda_ilusion_user -d tienda_ilusion_prod -c '\l' +``` + +#### 2. Error: "required variable DB_PASSWORD is missing a value" +**Síntoma**: Docker Compose falla al iniciar con error de variable faltante + +**Causa**: El archivo `.env.production` no existe o tiene valores placeholder + +**Solución**: +1. Copiar el archivo de ejemplo: `cp .env.production.example .env.production` +2. Editar `.env.production` y reemplazar todos los valores `CHANGE-ME-...` con valores reales +3. Generar SECRET_KEY segura: + ```bash + python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())' + ``` + +#### 3. Error: "ModuleNotFoundError" o importación fallida +**Síntoma**: Django no puede importar módulos después de cambiar configuración + +**Causa**: Dependencias no instaladas o contenedor con caché viejo + +**Solución**: +```bash +# Reconstruir contenedores +docker-compose -f docker-compose.dev.yml build --no-cache +docker-compose -f docker-compose.dev.yml up + +# Para producción +docker-compose -f docker-compose.prod.yml build --no-cache +docker-compose -f docker-compose.prod.yml up -d +``` + +#### 4. Error: "django.db.utils.OperationalError: could not connect to server" +**Síntoma**: Django no puede conectarse a PostgreSQL + +**Causa**: PostgreSQL aún no está listo cuando Django intenta conectar + +**Solución**: El healthcheck en `docker-compose.prod.yml` maneja esto automáticamente. Si el problema persiste: +```bash +# Verificar que PostgreSQL esté corriendo +docker ps | grep postgres + +# Ver logs de PostgreSQL +docker logs tienda_ilusion_postgres_prod + +# Reiniciar servicios en orden +docker-compose -f docker-compose.prod.yml restart postgres +docker-compose -f docker-compose.prod.yml restart django +``` + +#### 5. Error: "CSRF verification failed" +**Síntoma**: Errores CSRF en producción + +**Causa**: `CSRF_TRUSTED_ORIGINS` no configurado correctamente + +**Solución**: En `.env.production`, asegurar que `CSRF_TRUSTED_ORIGINS` incluya el protocolo: +```bash +CSRF_TRUSTED_ORIGINS=https://tudominio.com,https://www.tudominio.com +``` + +#### 6. Static files no se sirven en producción +**Síntoma**: CSS/JS no cargan, errores 404 para archivos estáticos + +**Causa**: `collectstatic` no ejecutado o configuración de nginx incorrecta + +**Solución**: +```bash +# Ejecutar collectstatic +docker-compose -f docker-compose.prod.yml exec django python manage.py collectstatic --noinput + +# Verificar que los archivos existen +docker exec tienda_ilusion_django_prod ls -la /app/staticfiles +``` + +#### 7. Migraciones pendientes después de deployment +**Síntoma**: Errores de base de datos o tablas faltantes + +**Causa**: Migraciones no aplicadas en producción + +**Solución**: +```bash +# Ver estado de migraciones +docker-compose -f docker-compose.prod.yml exec django python manage.py showmigrations + +# Aplicar migraciones pendientes +docker-compose -f docker-compose.prod.yml exec django python manage.py migrate +``` + +### Comandos de Diagnóstico + +```bash +# Ver todos los contenedores +docker ps -a + +# Ver logs en tiempo real +docker-compose -f docker-compose.prod.yml logs -f + +# Ver logs solo de Django +docker logs -f tienda_ilusion_django_prod + +# Ver logs solo de PostgreSQL +docker logs -f tienda_ilusion_postgres_prod + +# Ejecutar shell en contenedor Django +docker-compose -f docker-compose.prod.yml exec django bash + +# Ejecutar Django shell +docker-compose -f docker-compose.prod.yml exec django python manage.py shell + +# Conectar a PostgreSQL +docker exec -it tienda_ilusion_postgres_prod psql -U tienda_ilusion_user -d tienda_ilusion_prod + +# Verificar variables de entorno en contenedor +docker-compose -f docker-compose.prod.yml exec django env | grep DJANGO +``` + +### Performance y Optimización + +Si experimentas problemas de rendimiento: + +1. **Verificar recursos del contenedor**: + ```bash + docker stats + ``` + +2. **Ajustar workers de Gunicorn** (editar `docker-compose.prod.yml`): + ```yaml + command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 8 --timeout 120 + ``` + Regla general: `workers = (2 x CPU cores) + 1` + +3. **Habilitar persistent connections**: Ya configurado en `settings/production.py` con `CONN_MAX_AGE = 600` + +4. **Considerar agregar Redis para caché**: Descomentar sección de Redis en `settings/production.py` + +### Seguridad + +Antes de ir a producción, verificar: + +- [ ] `DEBUG=False` en `.env.production` +- [ ] `SECRET_KEY` único y seguro generado +- [ ] `ALLOWED_HOSTS` configurado con dominios reales +- [ ] `CORS_ALLOWED_ORIGINS` limitado a orígenes confiables +- [ ] `CSRF_TRUSTED_ORIGINS` configurado correctamente +- [ ] Contraseñas de base de datos seguras +- [ ] `.env.production` NO commiteado a git +- [ ] HTTPS habilitado (certificados SSL/TLS configurados) +- [ ] Firewall configurado (solo puertos necesarios abiertos) +- [ ] Backups automáticos configurados ## Integraciones - **Tryton ERP**: Integración mediante sabatron-tryton-rpc-client para sincronización de clientes, productos y ventas diff --git a/README.md b/README.md new file mode 100644 index 0000000..be683d8 --- /dev/null +++ b/README.md @@ -0,0 +1,269 @@ +# Don Confiao Backend - Tienda Ilusion + +Backend Django con Django REST Framework para el sistema de punto de venta Tienda Ilusion. + +## Características + +- 🔐 Autenticación JWT con djangorestframework-simplejwt +- 🗄️ Soporte multi-ambiente (desarrollo/producción) +- 🐘 PostgreSQL para producción, SQLite para desarrollo +- 🔄 Integración con Tryton ERP +- 📦 Docker Compose para fácil deployment +- 🛡️ Configuración de seguridad completa para producción +- 📊 API REST completa para gestión de ventas, productos y clientes + +## Requisitos Previos + +- Docker & Docker Compose +- Python 3.11+ (para desarrollo local sin Docker) +- Git + +## Inicio Rápido + +### Desarrollo Local + +1. **Clonar el repositorio** + ```bash + git clone + cd don_confiao_backend + ``` + +2. **Iniciar servicios de desarrollo** + ```bash + docker-compose -f docker-compose.dev.yml up + ``` + +3. **Aplicar migraciones** + ```bash + docker-compose -f docker-compose.dev.yml run --rm django python manage.py migrate + ``` + +4. **Crear superusuario** + ```bash + docker-compose -f docker-compose.dev.yml run --rm django python manage.py createsuperuser + ``` + +5. **Acceder a la aplicación** + - API: http://localhost:7000 + - Admin: http://localhost:7000/admin + +### Producción + +1. **Configurar variables de entorno** + ```bash + cp .env.production.example .env.production + # Editar .env.production con valores reales + ``` + +2. **Generar SECRET_KEY segura** + ```bash + python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())' + ``` + +3. **Iniciar servicios** + ```bash + docker-compose -f docker-compose.prod.yml up -d + ``` + +4. **Las migraciones y collectstatic se ejecutan automáticamente** + - Si necesitas ejecutarlas manualmente: + ```bash + docker-compose -f docker-compose.prod.yml exec django python manage.py migrate + docker-compose -f docker-compose.prod.yml exec django python manage.py collectstatic --noinput + ``` + +5. **Crear superusuario** + ```bash + docker-compose -f docker-compose.prod.yml exec django python manage.py createsuperuser + ``` + +## Estructura del Proyecto + +``` +don_confiao_backend/ +├── tienda_ilusion/ # Proyecto Django +│ ├── config/ # Configuración principal +│ │ └── settings/ # Settings por ambiente +│ │ ├── base.py # Configuración compartida +│ │ ├── development.py # Desarrollo +│ │ └── production.py # Producción +│ ├── don_confiao/ # App principal +│ └── users/ # App de usuarios +├── scripts/ # Scripts de utilidad +│ ├── health-check.sh # Verificación de salud +│ ├── backup-db.sh # Backup de base de datos +│ └── restore-backup.sh # Restore de backup +├── docker-compose.dev.yml # Docker Compose desarrollo +├── docker-compose.prod.yml # Docker Compose producción +└── requirements.txt # Dependencias Python +``` + +## Ambientes + +### Development +- Base de datos: SQLite +- Debug: Habilitado +- CORS: Permisivo +- Server: Django development server +- Puerto: 7000 + +### Production +- Base de datos: PostgreSQL +- Debug: Deshabilitado +- CORS: Configurado por dominio +- Server: Gunicorn (4 workers) +- Puerto: 8000 +- Seguridad: HTTPS, HSTS, secure cookies + +## Comandos Útiles + +### Desarrollo + +```bash +# Ejecutar tests +docker-compose -f docker-compose.dev.yml run --rm django python manage.py test + +# Shell de Django +docker-compose -f docker-compose.dev.yml run --rm django python manage.py shell + +# Crear migraciones +docker-compose -f docker-compose.dev.yml run --rm django python manage.py makemigrations + +# Ver logs +docker-compose -f docker-compose.dev.yml logs -f +``` + +### Producción + +```bash +# Ver logs +docker-compose -f docker-compose.prod.yml logs -f django + +# Backup de base de datos +./scripts/backup-db.sh + +# Restore de backup +./scripts/restore-backup.sh backups/tienda_ilusion_backup_YYYYMMDD_HHMMSS.sql.gz + +# Health check +./scripts/health-check.sh prod + +# Reiniciar servicios +docker-compose -f docker-compose.prod.yml restart + +# Detener servicios +docker-compose -f docker-compose.prod.yml down +``` + +## API Endpoints + +La API REST está disponible en `/api/`. Principales endpoints: + +- `/api/token/` - Obtener token JWT +- `/api/token/refresh/` - Refrescar token JWT +- `/api/customers/` - Gestión de clientes +- `/api/products/` - Gestión de productos +- `/api/sales/` - Gestión de ventas +- `/admin/` - Panel de administración Django + +## Integración con Tryton ERP + +El sistema se integra con Tryton ERP para sincronización de: +- Clientes +- Productos +- Ventas + +Configurar las variables de entorno de Tryton en `.env.development` o `.env.production`: +```bash +TRYTON_HOST=your-tryton-server +TRYTON_DATABASE=your-database +TRYTON_USERNAME=your-username +TRYTON_PASSWORD=your-password +``` + +## Backup y Restore + +### Crear Backup +```bash +./scripts/backup-db.sh +``` +Los backups se guardan en `backups/` y se mantienen por 7 días. + +### Restaurar Backup +```bash +./scripts/restore-backup.sh backups/backup_file.sql.gz +``` + +## Troubleshooting + +Ver la sección de Troubleshooting en [AGENTS.md](AGENTS.md) para soluciones a problemas comunes. + +### Problemas Comunes + +1. **Error de conexión a base de datos**: Verificar que PostgreSQL esté corriendo +2. **CSRF errors**: Verificar `CSRF_TRUSTED_ORIGINS` en `.env.production` +3. **Static files no cargan**: Ejecutar `collectstatic` +4. **Errores de migración**: Verificar estado con `showmigrations` + +## Seguridad + +### Checklist de Producción + +Antes de desplegar a producción: + +- [ ] `DEBUG=False` en `.env.production` +- [ ] `SECRET_KEY` única y segura generada +- [ ] `ALLOWED_HOSTS` configurado correctamente +- [ ] `CORS_ALLOWED_ORIGINS` limitado a dominios confiables +- [ ] Contraseñas de base de datos seguras +- [ ] HTTPS habilitado con certificados SSL/TLS válidos +- [ ] Firewall configurado +- [ ] Backups automáticos configurados +- [ ] Monitoreo de logs configurado + +## Desarrollo + +### Agregar nuevas dependencias + +```bash +# Agregar a requirements.txt +echo "nueva-dependencia==version" >> requirements.txt + +# Reconstruir contenedor +docker-compose -f docker-compose.dev.yml build --no-cache +``` + +### Tests + +```bash +# Ejecutar todos los tests +docker-compose -f docker-compose.dev.yml run --rm django python manage.py test + +# Ejecutar tests de una app específica +docker-compose -f docker-compose.dev.yml run --rm django python manage.py test don_confiao + +# Con coverage +docker-compose -f docker-compose.dev.yml run --rm django coverage run --source='.' manage.py test +docker-compose -f docker-compose.dev.yml run --rm django coverage report +``` + +## Contribuir + +1. Fork el proyecto +2. Crear branch de feature (`git checkout -b feature/AmazingFeature`) +3. Commit cambios (`git commit -m 'Add some AmazingFeature'`) +4. Push al branch (`git push origin feature/AmazingFeature`) +5. Abrir Pull Request + +## Licencia + +[Especificar licencia] + +## Contacto + +[Información de contacto] + +## Documentación Adicional + +- [AGENTS.md](AGENTS.md) - Contexto completo del proyecto y troubleshooting +- [.env.production.example](.env.production.example) - Ejemplo de variables de producción diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..1778fff --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,24 @@ +version: '3.8' + +services: + django: + build: + context: ./ + dockerfile: django.Dockerfile + container_name: tienda_ilusion_django_dev + env_file: + - .env.development + environment: + - DJANGO_ENV=development + volumes: + - ./tienda_ilusion:/app/ + ports: + - "7000:9090" + networks: + - tienda_network_dev + command: python manage.py runserver 0.0.0.0:9090 + +networks: + tienda_network_dev: + driver: bridge + diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..1e71b79 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,87 @@ +version: "3.8" + +services: + postgres: + image: postgres:15-alpine + container_name: tienda_ilusion_postgres_prod + environment: + - POSTGRES_USER=${DB_USER:-tienda_ilusion_user} + - POSTGRES_DB=${DB_NAME:-tienda_ilusion_prod} + - POSTGRES_PASSWORD=${DB_PASSWORD:-tienda_ilusion_pass} + env_file: + - .env.production + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + networks: + - tienda_network + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-tienda_ilusion_user}"] + interval: 10s + timeout: 5s + retries: 5 + + django: + build: + context: ./ + dockerfile: django.Dockerfile + container_name: tienda_ilusion_django_prod + env_file: + - .env.production + environment: + - DJANGO_ENV=production + - DB_HOST=postgres + - DB_USER=${DB_USER:-tienda_ilusion_user} + - DB_NAME=${DB_NAME:-tienda_ilusion_prod} + - DB_PASSWORD=${DB_PASSWORD:-tienda_ilusion_pass} + volumes: + - ./tienda_ilusion:/app/ + - static_volume:/app/staticfiles + - media_volume:/app/media + - logs_volume:/app/logs + ports: + - "8000:8000" + depends_on: + postgres: + condition: service_healthy + networks: + - tienda_network + restart: unless-stopped + command: > + sh -c "python manage.py migrate && + python manage.py collectstatic --noinput && + gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4 --timeout 120" + + # Optional: Nginx reverse proxy para servir archivos estáticos + # nginx: + # image: nginx:alpine + # container_name: tienda_ilusion_nginx + # ports: + # - "80:80" + # - "443:443" + # volumes: + # - ./nginx.conf:/etc/nginx/nginx.conf:ro + # - static_volume:/var/www/static:ro + # - media_volume:/var/www/media:ro + # - ./ssl:/etc/nginx/ssl:ro + # depends_on: + # - django + # networks: + # - tienda_network + # restart: unless-stopped + +volumes: + postgres_data: + driver: local + static_volume: + driver: local + media_volume: + driver: local + logs_volume: + driver: local + +networks: + tienda_network: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index b8623e8..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,12 +0,0 @@ -services: - django: - build: - context: ./ - dockerfile: django.Dockerfile - env_file: - - .env - volumes: - - ./tienda_ilusion:/app/ - ports: - - "7000:9090" - diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..4d0f185 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,157 @@ +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. + +[[package]] +name = "asgiref" +version = "3.11.1" +description = "ASGI specs, helper code, and adapters" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133"}, + {file = "asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce"}, +] + +[package.extras] +tests = ["mypy (>=1.14.0)", "pytest", "pytest-asyncio"] + +[[package]] +name = "django" +version = "6.0.5" +description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." +optional = false +python-versions = ">=3.12" +groups = ["main"] +files = [ + {file = "django-6.0.5-py3-none-any.whl", hash = "sha256:9d58a7cb49244e74c8e161d5e403a46d6209f1009ba40f5a66d6aa0d0786a8f0"}, + {file = "django-6.0.5.tar.gz", hash = "sha256:bc6d6872e98a2864c836e42edd644b362db311147dd5aa8d5b82ba7a032f5269"}, +] + +[package.dependencies] +asgiref = ">=3.9.1" +sqlparse = ">=0.5.0" +tzdata = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +argon2 = ["argon2-cffi (>=23.1.0)"] +bcrypt = ["bcrypt (>=4.1.1)"] + +[[package]] +name = "django-cors-headers" +version = "4.9.0" +description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "django_cors_headers-4.9.0-py3-none-any.whl", hash = "sha256:15c7f20727f90044dcee2216a9fd7303741a864865f0c3657e28b7056f61b449"}, + {file = "django_cors_headers-4.9.0.tar.gz", hash = "sha256:fe5d7cb59fdc2c8c646ce84b727ac2bca8912a247e6e68e1fb507372178e59e8"}, +] + +[package.dependencies] +asgiref = ">=3.6" +django = ">=4.2" + +[[package]] +name = "djangorestframework" +version = "3.17.1" +description = "Web APIs for Django, made easy." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "djangorestframework-3.17.1-py3-none-any.whl", hash = "sha256:c3c74dd3e83a5a3efc37b3c18d92bd6f86a6791c7b7d4dff62bb068500e76457"}, + {file = "djangorestframework-3.17.1.tar.gz", hash = "sha256:a6def5f447fe78ff853bff1d47a3c59bf38f5434b031780b351b0c73a62db1a5"}, +] + +[package.dependencies] +django = ">=4.2" + +[[package]] +name = "djangorestframework-simplejwt" +version = "5.5.1" +description = "A minimal JSON Web Token authentication plugin for Django REST Framework" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "djangorestframework_simplejwt-5.5.1-py3-none-any.whl", hash = "sha256:2c30f3707053d384e9f315d11c2daccfcb548d4faa453111ca19a542b732e469"}, + {file = "djangorestframework_simplejwt-5.5.1.tar.gz", hash = "sha256:e72c5572f51d7803021288e2057afcbd03f17fe11d484096f40a460abc76e87f"}, +] + +[package.dependencies] +django = ">=4.2" +djangorestframework = ">=3.14" +pyjwt = ">=1.7.1" + +[package.extras] +crypto = ["cryptography (>=3.3.1)"] +dev = ["Sphinx", "cryptography", "freezegun", "ipython", "pre-commit", "pytest", "pytest-cov", "pytest-django", "pytest-watch", "pytest-xdist", "python-jose (==3.3.0)", "pyupgrade", "ruff", "sphinx_rtd_theme (>=0.1.9)", "tox", "twine", "wheel", "yesqa"] +doc = ["Sphinx", "sphinx_rtd_theme (>=0.1.9)"] +lint = ["pre-commit", "pyupgrade", "ruff", "yesqa"] +python-jose = ["python-jose (==3.3.0)"] +test = ["cryptography", "freezegun", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "tox"] + +[[package]] +name = "pyjwt" +version = "2.12.1" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c"}, + {file = "pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==7.10.7)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=8.4.2,<9.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==7.10.7)", "pytest (>=8.4.2,<9.0.0)"] + +[[package]] +name = "sabatron-tryton-rpc-client" +version = "7.4.0" +description = "Python RPC Client for Tryton" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "sabatron_tryton_rpc_client-7.4.0-py3-none-any.whl", hash = "sha256:131d8d9d6a1dc1d556d4806fa7750ca637247f021881dca5c2855d8a44e4bd45"}, + {file = "sabatron_tryton_rpc_client-7.4.0.tar.gz", hash = "sha256:3d3ededd1a8488463d6338cd7d8b2a271834ba42fc220ebb8e15e983c0e5b19d"}, +] + +[[package]] +name = "sqlparse" +version = "0.5.5" +description = "A non-validating SQL parser." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba"}, + {file = "sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e"}, +] + +[package.extras] +dev = ["build"] +doc = ["sphinx"] + +[[package]] +name = "tzdata" +version = "2026.2" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +groups = ["main"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7"}, + {file = "tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10"}, +] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.14" +content-hash = "f477382ae6c0dca55653b5c1b6784e0988a757c44c8d4e5d08417295c666cbc0" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..39786d3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[project] +name = "don-confiao-backend" +version = "0.1.0" +description = "Shop for Recreo store" +authors = [ + {name = "aserrador",email = "alejandro.ayala@onecluster.org"} +] +license = {text = "GPL-3.0-or-later"} +requires-python = ">=3.14" +dependencies = [ + "django (>=6.0.5,<7.0.0)", + "djangorestframework (>=3.17.1,<4.0.0)", + "django-cors-headers (>=4.9.0,<5.0.0)", + "djangorestframework-simplejwt (>=5.5.1,<6.0.0)", + "sabatron-tryton-rpc-client (>=7.4.0,<8.0.0)" +] + + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt index 1926c0e..7620b29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,15 @@ djangorestframework django-cors-headers djangorestframework-simplejwt sabatron-tryton-rpc-client==7.4.0 + +# Database drivers +psycopg2-binary # PostgreSQL driver for production + +# Production server +gunicorn # WSGI HTTP Server for production + +# Environment variable management +python-decouple # Manage environment variables and settings + +# Optional: Static files serving in production +# whitenoise # Serve static files efficiently diff --git a/tienda_ilusion/tienda_ilusion/__init__.py b/tienda_ilusion/config/__init__.py similarity index 100% rename from tienda_ilusion/tienda_ilusion/__init__.py rename to tienda_ilusion/config/__init__.py diff --git a/tienda_ilusion/tienda_ilusion/asgi.py b/tienda_ilusion/config/asgi.py similarity index 81% rename from tienda_ilusion/tienda_ilusion/asgi.py rename to tienda_ilusion/config/asgi.py index 8832126..58642c7 100644 --- a/tienda_ilusion/tienda_ilusion/asgi.py +++ b/tienda_ilusion/config/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tienda_ilusion.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") application = get_asgi_application() diff --git a/tienda_ilusion/config/settings/__init__.py b/tienda_ilusion/config/settings/__init__.py new file mode 100644 index 0000000..340446a --- /dev/null +++ b/tienda_ilusion/config/settings/__init__.py @@ -0,0 +1,36 @@ +""" +Settings module for tienda_ilusion project. + +This module automatically loads the appropriate settings based on the DJANGO_ENV +environment variable. Valid values are: +- 'development' (default): Development settings +- 'production': Production settings + +Usage: + Set DJANGO_ENV environment variable before running Django: + + Development: + export DJANGO_ENV=development + python manage.py runserver + + Production: + export DJANGO_ENV=production + gunicorn config.wsgi:application +""" + +import os + +# Determine which environment settings to load +DJANGO_ENV = os.environ.get("DJANGO_ENV", "development") + +if DJANGO_ENV == "production": + from .production import * +elif DJANGO_ENV == "development": + from .development import * +else: + # Fallback to development if unknown environment + from .development import * + + print( + f"Warning: Unknown DJANGO_ENV '{DJANGO_ENV}', falling back to development settings" + ) diff --git a/tienda_ilusion/config/settings/base.py b/tienda_ilusion/config/settings/base.py new file mode 100644 index 0000000..ae7ea4e --- /dev/null +++ b/tienda_ilusion/config/settings/base.py @@ -0,0 +1,175 @@ +""" +Django settings for tienda_ilusion project. + +Generated by 'django-admin startproject' using Django 5.0.6. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.0/ref/settings/ +""" + +import os + +from datetime import timedelta +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +# BASE_DIR apunta a tienda_ilusion/ (3 niveles arriba desde settings/base.py) +BASE_DIR = Path(__file__).resolve().parent.parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +# This will be overridden in production settings +SECRET_KEY = os.environ.get( + "SECRET_KEY", + "django-insecure-zh6rinl@8y7g(cf781snisx2j%p^c#d&b2@@9cqe!v@4yv8x=v", +) + +# SECURITY WARNING: don't run with debug turned on in production! +# This will be overridden in each environment +DEBUG = True + +# This will be overridden in each environment +ALLOWED_HOSTS = [] + +# This will be overridden in each environment +CORS_ALLOWED_ORIGINS = [] +# Application definition + +INSTALLED_APPS = [ + "don_confiao.apps.DonConfiaoConfig", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "rest_framework", + "rest_framework.authtoken", + "corsheaders", + "users", + # 'don_confiao' +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "corsheaders.middleware.CorsMiddleware", +] + +ROOT_URLCONF = "config.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "tienda_ilusion/templates")], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "config.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases +# This will be overridden in each environment + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.0/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = "static/" +STATIC_ROOT = BASE_DIR / "staticfiles" + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +FIXTURE_DIRS = ["don_confiao/tests/Fixtures"] + +REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": [ + "rest_framework_simplejwt.authentication.JWTAuthentication", + ], + "DEFAULT_PERMISSION_CLASSES": [ + "rest_framework.permissions.IsAuthenticated", + ], +} + +SIMPLE_JWT = { + "ACCESS_TOKEN_LIFETIME": timedelta(minutes=30), + "REFRESH_TOKEN_LIFETIME": timedelta(days=1), + "AUTH_HEADER_TYPES": ("Bearer",), +} + +# Logging configuration (can be overridden in environment settings) +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "console": { + "class": "logging.StreamHandler", + }, + }, + "root": { + "handlers": ["console"], + "level": "INFO", + }, +} diff --git a/tienda_ilusion/config/settings/development.py b/tienda_ilusion/config/settings/development.py new file mode 100644 index 0000000..ef0705c --- /dev/null +++ b/tienda_ilusion/config/settings/development.py @@ -0,0 +1,92 @@ +""" +Development settings for tienda_ilusion project. + +This file contains settings specific to local development environment. +Uses SQLite database and permissive CORS settings for easier development. +""" + +import os +from .base import * + +# SECURITY WARNING: don't use this in production! +DEBUG = True + +# SECRET_KEY for development (insecure, but convenient for development) +SECRET_KEY = os.environ.get( + "SECRET_KEY", + "django-insecure-development-key-zh6rinl@8y7g(cf781snisx2j%p^c#d&b2@@9cqe!v@4yv8x=v", +) + +# Allow all hosts in development for easier testing +ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost,127.0.0.1,0.0.0.0").split( + "," +) + +# CORS settings for development +CORS_ALLOWED_ORIGINS = os.environ.get( + "CORS_ALLOWED_ORIGINS", + "http://localhost:3000,http://localhost:7001,http://localhost:5173", +).split(",") + +# Allow credentials in CORS for development +CORS_ALLOW_CREDENTIALS = True + +# Database - SQLite for development +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + +# Email backend for development (prints to console) +EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + +# Enhanced logging for development +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "[{levelname}] {asctime} {module} {message}", + "style": "{", + }, + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "verbose", + }, + }, + "root": { + "handlers": ["console"], + "level": "DEBUG", + }, + "loggers": { + "django": { + "handlers": ["console"], + "level": "INFO", + "propagate": False, + }, + "django.db.backends": { + "handlers": ["console"], + "level": "DEBUG", + "propagate": False, + }, + }, +} + +# Development-only apps (optional) +# Uncomment to add django-debug-toolbar or other dev tools +# INSTALLED_APPS += [ +# 'debug_toolbar', +# ] + +# MIDDLEWARE += [ +# 'debug_toolbar.middleware.DebugToolbarMiddleware', +# ] + +# INTERNAL_IPS for debug toolbar +# INTERNAL_IPS = [ +# '127.0.0.1', +# ] diff --git a/tienda_ilusion/config/settings/production.py b/tienda_ilusion/config/settings/production.py new file mode 100644 index 0000000..66c965e --- /dev/null +++ b/tienda_ilusion/config/settings/production.py @@ -0,0 +1,180 @@ +""" +Production settings for tienda_ilusion project. + +This file contains settings specific to production environment. +Uses PostgreSQL database and strict security settings. + +IMPORTANT: All sensitive values MUST be provided via environment variables. +""" + +import os +from .base import * + +# SECURITY WARNING: DEBUG must be False in production! +DEBUG = False + +# SECRET_KEY must be provided via environment variable in production +SECRET_KEY = os.environ.get("SECRET_KEY") +if not SECRET_KEY: + raise ValueError("SECRET_KEY environment variable must be set in production!") + +# ALLOWED_HOSTS must be provided via environment variable +ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "").split(",") +if not ALLOWED_HOSTS or ALLOWED_HOSTS == [""]: + raise ValueError("ALLOWED_HOSTS environment variable must be set in production!") + +# CORS settings for production +CORS_ALLOWED_ORIGINS = os.environ.get("CORS_ALLOWED_ORIGINS", "").split(",") +if not CORS_ALLOWED_ORIGINS or CORS_ALLOWED_ORIGINS == [""]: + raise ValueError( + "CORS_ALLOWED_ORIGINS environment variable must be set in production!" + ) + +CORS_ALLOW_CREDENTIALS = True + +# Database - PostgreSQL for production +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": os.environ.get("DB_NAME", "tienda_ilusion"), + "USER": os.environ.get("DB_USER", "postgres"), + "PASSWORD": os.environ.get("DB_PASSWORD"), + "HOST": os.environ.get("DB_HOST", "localhost"), + "PORT": os.environ.get("DB_PORT", "5432"), + "CONN_MAX_AGE": 600, # Persistent connections + } +} + +if not DATABASES["default"]["PASSWORD"]: + raise ValueError("DB_PASSWORD environment variable must be set in production!") + +# Security settings for HTTPS +SECURE_SSL_REDIRECT = True +SESSION_COOKIE_SECURE = True +CSRF_COOKIE_SECURE = True +SECURE_BROWSER_XSS_FILTER = True +SECURE_CONTENT_TYPE_NOSNIFF = True +X_FRAME_OPTIONS = "DENY" + +# HTTP Strict Transport Security (HSTS) +SECURE_HSTS_SECONDS = 31536000 # 1 year +SECURE_HSTS_INCLUDE_SUBDOMAINS = True +SECURE_HSTS_PRELOAD = True + +# Proxy headers for deployment behind reverse proxy (nginx, etc.) +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") + +# Email configuration for production +# Adjust based on your email service +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" +EMAIL_HOST = os.environ.get("EMAIL_HOST", "smtp.gmail.com") +EMAIL_PORT = int(os.environ.get("EMAIL_PORT", "587")) +EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", "True") == "True" +EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", "") +EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", "") +DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", "noreply@tiendailusion.com") + +# Static files configuration for production +# Serve static files using WhiteNoise or CDN +STATIC_ROOT = BASE_DIR / "staticfiles" +STATIC_URL = "/static/" + +# Media files configuration +MEDIA_ROOT = BASE_DIR / "media" +MEDIA_URL = "/media/" + +# Optional: WhiteNoise configuration for serving static files +# Uncomment if using WhiteNoise +# MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware') +# STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' + +# Production logging - log to file and console +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "[{levelname}] {asctime} {name} {message}", + "style": "{", + }, + "simple": { + "format": "[{levelname}] {message}", + "style": "{", + }, + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "simple", + }, + "file": { + "class": "logging.handlers.RotatingFileHandler", + "filename": BASE_DIR / "logs" / "django.log", + "maxBytes": 1024 * 1024 * 15, # 15MB + "backupCount": 10, + "formatter": "verbose", + }, + }, + "root": { + "handlers": ["console", "file"], + "level": "INFO", + }, + "loggers": { + "django": { + "handlers": ["console", "file"], + "level": "INFO", + "propagate": False, + }, + "django.security": { + "handlers": ["console", "file"], + "level": "WARNING", + "propagate": False, + }, + }, +} + +# Create logs directory if it doesn't exist +import pathlib + +logs_dir = BASE_DIR / "logs" +pathlib.Path(logs_dir).mkdir(parents=True, exist_ok=True) + +# Admin email notifications for errors (optional) +ADMINS = [ + ("Admin", os.environ.get("ADMIN_EMAIL", "admin@tiendailusion.com")), +] +MANAGERS = ADMINS + +# Cache configuration (optional - adjust based on your setup) +# Using local memory cache by default +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "unique-snowflake", + } +} + +# For Redis cache (recommended for production): +# CACHES = { +# "default": { +# "BACKEND": "django_redis.cache.RedisCache", +# "LOCATION": os.environ.get("REDIS_URL", "redis://127.0.0.1:6379/1"), +# "OPTIONS": { +# "CLIENT_CLASS": "django_redis.client.DefaultClient", +# } +# } +# } + +# Session configuration +SESSION_ENGINE = "django.contrib.sessions.backends.db" +SESSION_COOKIE_AGE = 86400 # 24 hours +SESSION_COOKIE_HTTPONLY = True +SESSION_COOKIE_SAMESITE = "Lax" + +# CSRF configuration +CSRF_COOKIE_HTTPONLY = True +CSRF_COOKIE_SAMESITE = "Lax" +CSRF_TRUSTED_ORIGINS = os.environ.get("CSRF_TRUSTED_ORIGINS", "").split(",") + +# Performance optimizations +CONN_MAX_AGE = 600 # Database connection pooling diff --git a/tienda_ilusion/tienda_ilusion/urls.py b/tienda_ilusion/config/urls.py similarity index 100% rename from tienda_ilusion/tienda_ilusion/urls.py rename to tienda_ilusion/config/urls.py diff --git a/tienda_ilusion/tienda_ilusion/wsgi.py b/tienda_ilusion/config/wsgi.py similarity index 81% rename from tienda_ilusion/tienda_ilusion/wsgi.py rename to tienda_ilusion/config/wsgi.py index 90542ab..25eed8d 100644 --- a/tienda_ilusion/tienda_ilusion/wsgi.py +++ b/tienda_ilusion/config/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tienda_ilusion.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") application = get_wsgi_application() diff --git a/tienda_ilusion/manage.py b/tienda_ilusion/manage.py index 4b84f87..aabb818 100755 --- a/tienda_ilusion/manage.py +++ b/tienda_ilusion/manage.py @@ -1,12 +1,13 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" + import os import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tienda_ilusion.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +19,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/tienda_ilusion/tienda_ilusion/settings.py b/tienda_ilusion/tienda_ilusion/settings.py deleted file mode 100644 index 6678e0b..0000000 --- a/tienda_ilusion/tienda_ilusion/settings.py +++ /dev/null @@ -1,157 +0,0 @@ -""" -Django settings for tienda_ilusion project. - -Generated by 'django-admin startproject' using Django 5.0.6. - -For more information on this file, see -https://docs.djangoproject.com/en/5.0/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/5.0/ref/settings/ -""" -import os - -from datetime import timedelta -from pathlib import Path - -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ.get( - "SECRET_KEY", - "django-insecure-zh6rinl@8y7g(cf781snisx2j%p^c#d&b2@@9cqe!v@4yv8x=v" -) - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True if os.environ.get("DEBUG", 'False') in ['True', '1'] else False -ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost').split(',') - -CORS_ALLOWED_ORIGINS = os.environ.get( - 'CORS_ALLOWED_ORIGINS', - 'http://localhost:3000,http://localhost:7001').split(',') -# Application definition - -INSTALLED_APPS = [ - 'don_confiao.apps.DonConfiaoConfig', - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'rest_framework', - 'rest_framework.authtoken', - 'corsheaders', - 'users', - # 'don_confiao' -] - -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'corsheaders.middleware.CorsMiddleware', -] - -ROOT_URLCONF = 'tienda_ilusion.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'tienda_ilusion/templates')], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - -WSGI_APPLICATION = 'tienda_ilusion.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/5.0/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', - } -} - - -# Password validation -# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] - - -# Internationalization -# https://docs.djangoproject.com/en/5.0/topics/i18n/ - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/5.0/howto/static-files/ - -STATIC_URL = 'static/' - -# Default primary key field type -# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field - -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' - -FIXTURE_DIRS = ['don_confiao/tests/Fixtures'] - -REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': [ - "rest_framework_simplejwt.authentication.JWTAuthentication", - ], - 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.IsAuthenticated', - ], -} - -SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": timedelta(minutes=30), - "REFRESH_TOKEN_LIFETIME": timedelta(days=1), - "AUTH_HEADER_TYPES": ("Bearer",), -} - -# CORS_ALLOWED_ORIGINS = [ -# "http://localhost:5173", -# ]