Plataforma web oficial de la comunidad de desarrolladores de Salta, Argentina
Características • Tech Stack • Instalación • Configuración • Arquitectura • Despliegue • CI/CD • Monitoreo • Imágenes • Seguridad • SEO • Desarrollo
- Autenticación completa - Login, registro con verificación de email y recuperación de contraseña
- Dashboard de usuario - Panel personalizado con perfil, credencial digital y gestión de datos
- Credencial digital - Credencial verificable con QR code, descargable como PNG
- Sistema de roles - Miembro, Colaborador, Moderador y Administrador
- Eventos - Listado y gestión de eventos con workflow de aprobación
- Notificaciones - Sistema de notificaciones in-app para usuarios
- Colaboradores - Showcase de empresas y organizaciones aliadas
- Staff - Perfiles del equipo organizador
- Código de conducta - Reglamento de la comunidad
| Tecnología | Versión | Descripción |
|---|---|---|
| 3.12 | Lenguaje principal | |
| 5.2 | Framework web | |
| 15+ | Base de datos | |
| 7+ | Cache y rate limiting | |
| 23.0 | WSGI Server | |
| - | CDN de imágenes |
| Tecnología | Versión | Descripción |
|---|---|---|
| 4.x | CSS compilado via django-tailwind-cli | |
| ES6+ | Interactividad | |
| - | Templates Django |
| Tecnología | Descripción |
|---|---|
| Cloud hosting | |
| Containerización | |
| Reverse proxy | |
| CI/CD | |
| Package manager |
saltadev-website/
├── saltadev/ # Django project root
│ ├── manage.py
│ ├── tailwind.config.js # Configuración Tailwind CSS
│ ├── saltadev/ # Django settings package
│ │ ├── settings/
│ │ │ ├── base.py # Configuración base
│ │ │ ├── local.py # Desarrollo local
│ │ │ ├── development.py # Desarrollo
│ │ │ ├── staging.py # Staging
│ │ │ └── production.py # Producción
│ │ ├── urls.py
│ │ └── wsgi.py
│ │
│ ├── home/ # Landing page
│ ├── auth_login/ # Login
│ ├── auth_register/ # Registro + verificación email
│ ├── password_reset/ # Recuperación de contraseña
│ ├── users/ # Modelo de usuario y perfil
│ ├── dashboard/ # Panel de usuario
│ ├── events/ # Eventos
│ ├── benefits/ # Beneficios para miembros
│ ├── locations/ # Países y provincias
│ ├── content/ # Contenido (colaboradores, etc)
│ ├── code_of_conduct/ # Código de conducta
│ ├── user_notifications/ # Sistema de notificaciones
│ │
│ ├── static/ # Archivos estáticos
│ │ └── css/
│ │ └── source.css # CSS fuente para Tailwind
│ └── templates/ # Templates HTML
│
├── deploy/ # Configuración de despliegue
│ └── render.yaml # IaC para Render.com
├── build.sh # Script de build para Render
├── tests/ # Tests con pytest
├── docker/ # Configuración Docker
├── nginx/ # Configuración Nginx
├── scripts/ # Scripts de utilidad
│
├── pyproject.toml # Dependencias y configuración
├── uv.lock # Lockfile
├── pytest.ini # Configuración pytest
├── mypy.ini # Configuración mypy
├── biome.json # Configuración Biome
└── .pre-commit-config.yaml # Pre-commit hooks
- Python 3.12+
- uv (package manager)
- PostgreSQL 15+ (producción) o SQLite (desarrollo)
- Redis 7+ (opcional, para rate limiting)
# Clonar repositorio
git clone https://github.com/saltadev/saltadev-website.git
cd saltadev-website
# Crear entorno virtual e instalar dependencias
uv venv
source .venv/bin/activate
uv pip install -e .[dev]
# Configurar variables de entorno
cp .env.example .env.local
# Editar .env.local con tus valores
# Aplicar migraciones
cd saltadev
python manage.py migrate
# Crear superusuario (opcional)
python manage.py createsuperuser
# Ejecutar servidor de desarrollo
python manage.py runserverCrear archivo .env.local en la raíz del proyecto:
# Django
DJANGO_ENV=local
SECRET_KEY=tu-secret-key-muy-segura
DEBUG=True
# Base de datos (opcional para local, usa SQLite por defecto)
DATABASE_URL=postgres://user:pass@localhost:5432/saltadev
# Email - Opción 1: SMTP (Gmail)
EMAIL_HOST_USER=tu_email@gmail.com
EMAIL_HOST_PASSWORD=tu_app_password
# Email - Opción 2: Resend (requerido en Render.com free tier)
RESEND_API_KEY=re_xxx
DEFAULT_FROM_EMAIL=noreply@tudominio.com
# reCAPTCHA v2
RECAPTCHA_V2_SITE_KEY=tu-site-key
RECAPTCHA_V2_SECRET=tu-secret-key
# URL del sitio
SITE_URL=http://localhost:8000| Entorno | Archivo | Descripción |
|---|---|---|
local |
.env.local |
Desarrollo local con DEBUG=True |
development |
.env.development |
Servidor de desarrollo |
staging |
.env.staging |
Pre-producción |
production |
.env.production |
Producción |
flowchart TB
subgraph Public["Páginas Públicas"]
home[Home]
events[Eventos]
coc[Código de Conducta]
credential[Credencial Pública]
end
subgraph Auth["Autenticación"]
login[Login]
register[Registro]
verify[Verificación Email]
reset[Password Reset]
end
subgraph Private["Área Privada"]
dashboard[Dashboard]
profile[Perfil]
end
subgraph Core["Core"]
users[Users Model]
locations[Locations]
content[Content]
end
home --> login
home --> register
register --> verify
verify --> dashboard
login --> dashboard
login --> reset
reset --> login
dashboard --> profile
dashboard --> credential
users --> locations
users --> content
sequenceDiagram
participant U as Usuario
participant R as Registro
participant US as Users
participant M as Email
U->>R: Envía formulario de registro
R->>US: Crea usuario (email_confirmed=False)
US->>M: Envía código de verificación (6 dígitos)
M-->>U: Email con código
U->>US: Ingresa código
US->>US: Verifica código y confirma email
US->>U: Login automático → Dashboard
sequenceDiagram
participant U as Usuario
participant PR as Password Reset
participant M as Email
U->>PR: Solicita recuperación
PR->>PR: Genera token (válido 10 min)
PR->>M: Envía email con link
M-->>U: Email con link de reset
U->>PR: Abre link y envía nueva contraseña
PR->>PR: Valida token y actualiza password
PR->>U: Redirige a login con mensaje de éxito
El proyecto usa django-notifications-hq para notificaciones in-app:
from notifications.signals import notify
notify.send(sender, recipient=user, verb="aprobó tu evento", target=event)Endpoints disponibles:
| Endpoint | Descripción |
|---|---|
/notificaciones/ |
Lista de notificaciones del usuario |
/notificaciones/marcar-leidas/ |
Marcar todas como leídas |
/notificaciones/<id>/marcar-leida/ |
Marcar una como leída |
Los eventos tienen un workflow de aprobación con tres estados:
| Estado | Descripción |
|---|---|
PENDING |
Evento creado, esperando aprobación |
APPROVED |
Evento aprobado, visible públicamente |
REJECTED |
Evento rechazado |
Flujo:
- Usuario crea evento → estado
PENDING - Staff/Admin revisa en el admin
- Al aprobar/rechazar → se envía notificación automática al creador
El proyecto incluye configuración Infrastructure as Code (IaC) para Render.com en deploy/render.yaml.
Ver deploy/RENDER.md para la guía completa de despliegue.
| Servicio | Tipo | Plan | Descripción |
|---|---|---|---|
saltadev-db |
PostgreSQL | Free | Base de datos |
saltadev-redis |
Redis | Free | Cache y rate limiting |
saltadev-website |
Web Service | Free | Aplicación Django |
- Conectar repositorio en Render Dashboard
- Seleccionar "Blueprint" y apuntar a
deploy/render.yaml - Configurar variables secretas (Cloudinary, Email, reCAPTCHA)
- Deploy!
Las siguientes se generan automáticamente:
DATABASE_URL- Conexión a PostgreSQLREDIS_URL- Conexión a RedisSECRET_KEY- Generada automáticamente
Las siguientes requieren configuración manual:
CLOUDINARY_CLOUD_NAME,CLOUDINARY_API_KEY,CLOUDINARY_API_SECRETRESEND_API_KEY,DEFAULT_FROM_EMAILRECAPTCHA_V2_SITE_KEY,RECAPTCHA_V2_SECRETSITE_URL
El archivo build.sh ejecuta durante el deploy:
pip install -r requirements.txt
python manage.py tailwind build
python manage.py collectstatic --no-input
python manage.py migrate
python manage.py loaddata locationsEl proyecto incluye un workflow de CI que se ejecuta en cada push y PR a main:
| Check | Comando | Descripción |
|---|---|---|
| Tests | pytest --cov |
Tests con coverage |
| Linting | ruff check |
Verificación de código |
| Format | ruff format --check |
Verificación de formato |
| Types | mypy |
Chequeo de tipos |
| Security | bandit |
Análisis de seguridad |
El workflow usa PostgreSQL en un service container para tests de integración.
Ver .github/workflows/ci.yml para la configuración completa.
GET /health/
Verifica el estado de los servicios críticos:
- Django: Aplicación web
- PostgreSQL: Base de datos
- Redis: Cache
Respuesta exitosa (200 OK):
{
"status": "healthy",
"services": {
"django": "ok",
"postgres": "ok",
"redis": "ok"
}
}Respuesta con error (503 Service Unavailable):
{
"status": "unhealthy",
"services": {
"django": "ok",
"postgres": "error",
"redis": "ok"
}
}Este endpoint es utilizado por Render.com para verificar la salud del servicio.
El proyecto usa Cloudinary para almacenar y servir imágenes (avatares, etc.):
| Característica | Detalle |
|---|---|
| CDN Global | Servido desde edge locations mundiales |
| Formato automático | WebP/AVIF según el navegador |
| Calidad automática | Optimización sin pérdida visible |
| Transformaciones on-the-fly | No consume créditos de transformación |
Las imágenes se suben SIN transformación y se procesan on-the-fly via URL:
https://res.cloudinary.com/{cloud}/upload/w_400,h_400,c_fill,g_face,q_auto,f_auto/{path}
Esto evita consumir los 25 créditos/mes del free tier, ya que las transformaciones on-the-fly no consumen créditos de transformación.
En desarrollo local, las imágenes usan Django ImageField y se guardan en static/assets/img/:
| Tipo | Ruta |
|---|---|
| Colaboradores | static/assets/img/partners/ |
| Staff | static/assets/img/staff/ |
| Avatares | media/avatars/ |
Cloudinary es opcional en desarrollo. Si no se configuran las credenciales, las imágenes se guardan localmente.
| Medida | Descripción |
|---|---|
| CSRF Protection | Token CSRF en todos los formularios |
| reCAPTCHA v2 | Protección contra bots en login/registro |
| Rate Limiting | Límite de intentos por IP, email y fingerprint |
| Password Validation | Validación de fortaleza de contraseñas |
| Token Expiration | Tokens de recuperación expiran en 10 minutos |
| Single-use Tokens | Tokens de un solo uso para reset de contraseña |
| Email Verification | Verificación obligatoria de email |
| No User Enumeration | Misma respuesta para emails existentes y no existentes |
| Disposable Email Block | Rechaza emails de proveedores temporales (10minutemail, etc.) |
| CSP Headers | Content Security Policy via django-csp (producción) |
| Secure Headers | Headers de seguridad configurados |
| HTTPS Only | Forzado en producción |
Protección contra ataques de fuerza bruta:
- Bloqueo después de 5 intentos fallidos
- Bloqueo por IP y por nombre de usuario
- Desbloqueo automático después de 1 hora
| Característica | Descripción |
|---|---|
| robots.txt | Control de crawlers con reglas Disallow para áreas privadas |
| sitemap.xml | Sitemap generado dinámicamente con django.contrib.sitemaps |
| JSON-LD | Schema Organization en la homepage para rich snippets |
| Meta descriptions | Descripciones únicas y optimizadas por página |
| Canonical URLs | URLs canónicas en todas las páginas públicas |
| Open Graph | Meta tags para compartir en Facebook y LinkedIn |
| Twitter Cards | Meta tags para compartir en Twitter/X |
| Redirects 301 | Links sociales con redirect permanente para tracking |
saltadev/
├── saltadev/
│ ├── sitemaps.py # Configuración de sitemap.xml
│ └── urls.py # Rutas de robots.txt y sitemap
│
├── templates/
│ ├── robots.txt # Template de robots.txt
│ └── includes/
│ ├── head.html # Meta tags, OG y Twitter Cards
│ └── structured_data/
│ └── organization.html # JSON-LD Organization schema
│
└── content/
└── redirects.py # Redirects 301 para redes sociales
# Activar entorno virtual
source .venv/bin/activate
# Ejecutar servidor
python manage.py runserver
# Ejecutar tests
pytest -q
# Tests con coverage
pytest --cov=saltadev --cov-report=html
# Linting y formato
pre-commit run --all-files
# Solo ruff
ruff check .
ruff format .
# Solo mypy
mypy saltadev/
# Ejecutar checks de CI localmente
uv run pytest --cov=saltadev
uv run ruff check saltadev && uv run ruff format --check saltadev
uv run mypy saltadev
uv run bandit -r saltadev -x "**/tests/**"
# Crear migraciones
python manage.py makemigrations
# Aplicar migraciones
python manage.py migrate
# Limpiar tokens expirados
python manage.py cleanup_expired_tokensEl proyecto usa pre-commit con los siguientes hooks:
| Hook | Descripción |
|---|---|
| ruff | Linting y formato de Python |
| biome | Formato de JS/CSS/HTML y linting |
| bandit | Análisis de seguridad en Python |
| detect-secrets | Detección de secretos en el código |
| mypy | Chequeo de tipos estático |
# Instalar hooks
pre-commit install
# Ejecutar manualmente
pre-commit run --all-filestests/
├── conftest.py # Fixtures globales
├── test_password_validation.py # Validación de contraseñas
├── test_password_reset_validation.py
├── test_users/ # Tests del módulo users
│ ├── test_models.py
│ └── test_views.py
└── ...
El proyecto usa un Dockerfile unificado con docker-compose para cada ambiente.
docker/
├── Dockerfile # Dockerfile unificado (multi-stage)
├── entrypoint.sh # Script de entrada (migraciones, collectstatic)
├── docker-compose.local.yml # Desarrollo local con hot reload
├── docker-compose.dev.yml # Servidor de desarrollo
├── docker-compose.staging.yml # Staging con SSL
└── docker-compose.prod.yml # Producción con SSL
# Crear archivo .env.local (si no existe)
cp saltadev/.env.local.example saltadev/.env.local
# Iniciar todos los servicios (Django + PostgreSQL + Redis)
docker compose -f docker/docker-compose.local.yml up
# Ejecutar en background
docker compose -f docker/docker-compose.local.yml up -d
# Ver logs
docker compose -f docker/docker-compose.local.yml logs -f web
# Detener servicios
docker compose -f docker/docker-compose.local.yml down
# Eliminar volúmenes (reset de base de datos)
docker compose -f docker/docker-compose.local.yml down -vEl ambiente local incluye:
- Hot reload: los cambios en
saltadev/se reflejan sin rebuild - Migraciones automáticas: se ejecutan al iniciar el contenedor
- Fixtures: se cargan automáticamente (locations)
- PostgreSQL: puerto 5432 expuesto para acceso directo
- Redis: puerto 6379 expuesto para acceso directo
# Development (gunicorn + nginx)
docker compose -f docker/docker-compose.dev.yml up
# Staging (gunicorn + nginx + SSL)
docker compose -f docker/docker-compose.staging.yml up
# Production (gunicorn + nginx + SSL + 4 workers)
docker compose -f docker/docker-compose.prod.yml up| Ambiente | Servidor | Workers | Hot Reload | SSL |
|---|---|---|---|---|
| local | runserver | 1 | Sí | No |
| development | gunicorn | 2 | No | No |
| staging | gunicorn | 2 | No | Sí |
| production | gunicorn | 4 | No | Sí |
El proyecto usa django-tailwind-cli para compilar Tailwind CSS sin necesidad de Node.js.
# Compilar CSS una vez
cd saltadev
python manage.py tailwind build
# Watch mode (recompila automáticamente)
python manage.py tailwind watchEl CSS se compila automáticamente al iniciar los containers via entrypoint.sh.
| Archivo | Descripción |
|---|---|
saltadev/tailwind.config.js |
Colores, fuentes, sombras personalizadas |
saltadev/static/css/source.css |
Directivas @tailwind |
saltadev/static/css/tailwind.css |
Output (gitignored) |
GET /credencial/<public_id>/
Muestra la credencial pública de un usuario. Incluye:
- Foto de perfil
- Nombre completo
- Rol en la comunidad
- Estado de verificación
- DNI (si está configurado)
- ID de miembro
- Fecha de registro
- Ubicación
- Código QR verificable
La credencial se puede descargar como PNG o compartir.
| Proveedor | Backend | Cuándo usar |
|---|---|---|
| SMTP (Gmail) | django.core.mail.backends.smtp.EmailBackend |
Desarrollo local |
| Resend | anymail.backends.resend.EmailBackend |
Render.com (SMTP bloqueado) |
El sistema detecta automáticamente qué backend usar según RESEND_API_KEY:
- Si está configurado → usa Resend via HTTP API
- Si no → usa SMTP tradicional
Los templates HTML se encuentran en templates/emails/:
| Template | Uso |
|---|---|
verification.html |
Código de verificación de email |
password_reset.html |
Link de recuperación de contraseña |
Los emails incluyen fallback de texto plano y el logo de SaltaDev.
- Fork del repositorio
- Crear branch para feature (
git checkout -b feature/nueva-funcionalidad) - Commit de cambios (
git commit -m 'Agrega nueva funcionalidad') - Push al branch (
git push origin feature/nueva-funcionalidad) - Crear Pull Request
- Python: Seguir PEP 8, usar ruff para linting
- JavaScript: Formato con Biome
- Commits: Mensajes descriptivos en español o inglés
- Código: Código en inglés, UI en español
Este proyecto es propiedad de la comunidad SaltaDev.
Hecho con ❤️ en Salta, Argentina
salta.dev.ar • GitHub • LinkedIn • Instagram
