Sistema de gestión de eventos desarrollado con Ruby on Rails 8, diseñado para facilitar la creación, gestión y descubrimiento de eventos de todo tipo.
- Stack Tecnológico
- Arquitectura del Sistema
- Autenticación y Flujo de Registro
- Requisitos Previos
- Instalación por Sistema Operativo
- Configuración Inicial
- Base de Datos
- Ejecutar el Proyecto
- Tailwind CSS
- Mapas con Leaflet
- Endpoints y Rutas
- Seed Inicial
- Comandos Útiles
- Testing
- Estructura del Proyecto
- Rate Limiting
- Convenciones Git
- Deployment
- Seguridad
- Recursos Adicionales
- Ruby 3.3.1 - Lenguaje de programación
- Rails 8.1.3 - Framework web
- PostgreSQL - Base de datos relacional
- Puma - Servidor web
- TailwindCSS 4.4 - Framework CSS
- Hotwire (Turbo + Stimulus) - JavaScript moderno sin build step
- Leaflet - Mapas interactivos (CSS vía CDN unpkg.com)
- Importmap - Gestión de módulos JavaScript
- Devise 5.0 - Autenticación de usuarios
- Pundit 2.5 - Autorización basada en políticas
- omniauth-google-oauth2 ~> 1.2 - Autenticación con Google OAuth
- Kaminari 1.2 - Paginación
- Geocoder ~> 1.8 - Geocodificación de direcciones de eventos
- Dotenv ~> 3.2 - Variables de entorno
- rails-i18n ~> 8.0 - Internacionalización
- RSpec 8.0 - Framework de testing
- Capybara + Selenium - Testing de sistema
- Culqi - Pasarela de pagos (API v4)
- PaymentGateway - Patrón Gateway abstracto (MockGateway + CulqiGateway)
- Solid Queue recurring jobs - Expiración automática de reservas
- Letter Opener - Vista previa de emails en desarrollo
- Web Console - Consola interactiva en páginas de error
- Solid Cache - Caching con backing de base de datos
- Solid Queue - Job queue con backing de base de datos
- Solid Cable - Action Cable con backing de base de datos
- Kamal - Deployment en containers
- Docker - Contenedores
- Thruster - HTTP caching y compression
- Brakeman - Análisis de seguridad estático
- Bundler-audit - Auditoría de gems vulnerables
- Rubocop Rails Omakase - Linting y estilo
Lógica de negocio compleja encapsulada en servicios:
ConfirmationCodeService- Manejo de códigos de verificación email:- Genera código de 6 dígitos, almacena hash SHA256
- Expira a los 15 minutos
- Máximo 3 intentos de verificación
- Reintento con cooldown de 60 segundos
Events::CreateService- Creación de eventos con imágenes (Active Storage + URL legacy)Events::UpdateService- Actualización de eventos con reemplazo de imágenesComments::CreateService- Creación de comentarios con validaciónExpireBookingsJob(recurring via Solid Queue cada 5 min):- Expira reservas
pendingcuyoexpires_atvenció - Excluye reservas con un Payment en estado
pending(protege pagos en curso) - Usa
update_allatómico (única sentencia SQL)
- Expira reservas
BookingService- Creación de reservas con control de capacidad:- Bloqueo pesimista (
lock!) sobre evento y ticket_type - Verifica
#available?y#remaining_capacity - Transacción atómica
- Bloqueo pesimista (
PaymentGateway(Module + Base Class) — Patrón Gateway abstracto:Base#charge(amount_cents:, currency_code:, description:, email:, source_id:)— interfazinstance— singleton resuelto viaENV["PAYMENT_GATEWAY"]MockGateway— para dev/test, simula aprobado/declinado según prefijo de tarjeta
Payments::ChargeService— Procesamiento de pago:- Bloqueo pesimista (
with_lock) sobre booking (previene doble cobro) - Monto recalculado del lado servidor (
quantity * unit_price * 100) - Re-chequea
expired_unpaid?yconfirmed?dentro del lock - Crea Payment y actualiza Booking status atómicamente
- Bloqueo pesimista (
Consultas complejas encapsuladas:
Events::SearchQuery- Búsqueda avanzada de eventos por: city, category, query (texto), fechas (start_date, end_date), price (min/max)
Autorización por recurso:
ApplicationPolicy- Política baseEventPolicy- Autorización de eventosUserPolicy- Autorización de usuariosCategoryPolicy- Autorización de categoríasCommentPolicy- Autorización de comentarios
User (Devise + Omniauth)
├── has_many :user_roles
├── has_many :roles, through: :user_roles
├── has_many :organized_events (as: organizer)
├── has_many :comments
├── has_many :bookings
├── confirmation_code (SHA256 hashed)
├── confirmation_sent_at
├── confirmation_attempts
└── confirmed_at
Role
├── has_many :user_roles
└── has_many :users, through: :user_roles
UserRole (Join Table)
├── belongs_to :user
├── belongs_to :role
└── belongs_to :assigned_by (User, opcional)
Category
└── has_many :events
Event
├── belongs_to :organizer (User)
├── belongs_to :category
├── has_many :event_images
├── has_many :comments
├── has_many :ticket_types
├── has_many :bookings
└── enum status: draft(0), published(1), canceled(2), finished(3)
TicketType
├── belongs_to :event
├── has_many :bookings
├── price, quantity_total, max_per_order
├── sales_start_at / sales_end_at
└── #available? / #remaining_capacity
Booking
├── belongs_to :user
├── belongs_to :event
├── belongs_to :ticket_type
├── has_one :payment, dependent: :destroy
├── quantity, unit_price
├── booked_at, expires_at
├── enum status: pending(0), confirmed(1), cancelled(2), expired(3)
├── before_create :set_booked_at
├── before_create :set_expiration (15 min, if pending)
└── #expired_unpaid?
Payment
├── belongs_to :booking
├── provider (MockGateway / CulqiGateway, etc.)
├── provider_charge_id (unique, nullable)
├── status enum: pending(0), approved(1), declined(2), refunded(3)
├── raw_response (JSONB)
├── UNIQUE index on booking_id (protege doble cobro)
├── UNIQUE partial index on provider_charge_id WHERE NOT NULL
└── State machine: ALLOWED_TRANSITIONS con #can_transition_to?
EventImage
├── belongs_to :event
└── has_one_attached :file (Active Storage)
Comment
├── belongs_to :event
└── belongs_to :user
- admin - Acceso total al sistema, gestión de usuarios y eventos
- organizer - Puede crear y gestionar sus propios eventos
- registered_user - Usuario regular que puede ver eventos y comentar
- Gestión de eventos propios
- CRUD completo de eventos
- Solo ve sus eventos
- Dashboard de administración
- Gestión de usuarios (asignación de roles)
- Gestión de eventos (todos)
- Gestión de categorías
enum status: {
draft: 0, # Borrador (solo visible para organizer/admin)
published: 1, # Publicado (visible para todos)
canceled: 2, # Cancelado
finished: 3 # Finalizado
}enum status: {
pending: 0, # Esperando pago
confirmed: 1, # Pagado / confirmado
cancelled: 2, # Cancelado por usuario o sistema
expired: 3 # Expiró antes de pagar
}enum status: {
pending: 0, # Pago iniciado (procesando)
approved: 1, # Pago aprobado
declined: 2, # Pago rechazado
refunded: 3 # Reembolsado
}
# Transiciones permitidas (protege contra webhooks out-of-order):
# pending → approved, declined
# approved → declined, refunded
# declined → (ninguna — terminal)
# refunded → (ninguna — terminal)El sistema implementa confirmación de email custom (NO usa Devise :confirmable).
- El usuario selecciona su rol (Asistir / Organizar)
- Completa el formulario de registro con email y contraseña
- Se envía un código de verificación de 6 dígitos vía
UserMailer#confirmation_code - El código se almacena como hash SHA256 con expiración de 15 minutos y máximo 3 intentos
- El usuario ingresa el código en
/confirmation/verify - Si es correcto, se confirma la cuenta (
confirmed_atse setea) y se redirige al dashboard
- El usuario selecciona su rol (Asistir / Organizar)
- Hace clic en "Continuar con Google"
- Se envía una solicitud
POST /users/auth/google_oauth2/with_roleque guardasession[:pending_role]y redirige a Google - Google callback en
/users/auth/google_oauth2/callback - Si el email ya existe, se asigna el rol pendiente y se inicia sesión
- Si es nuevo, se crea el usuario con datos de Google, se asigna el rol y se confirma automáticamente
Usuario → selecciona rol → clic "Continuar con Google"
→ POST /users/auth/google_oauth2/with_role
→ session[:pending_role] = rol_seleccionado
→ redirect_to /users/auth/google_oauth2
→ Google callback → Users::OmniauthCallbacksController
→ asigna pending_role al usuario
→ inicia sesión
Antes de comenzar, asegúrate de tener instalado:
- Git - Control de versiones
- Ruby 3.3.1 - Lenguaje de programación
- PostgreSQL - Base de datos
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"brew install rbenv ruby-build
echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc
source ~/.zshrc
rbenv install 3.3.1
rbenv global 3.3.1Verificar instalación:
ruby -v # Debe mostrar ruby 3.3.1brew install postgresql@16
brew services start postgresql@16sudo apt update
sudo apt install -y git curl build-essential libssl-dev libreadline-dev zlib1g-dev \
libpq-dev postgresql postgresql-contribcurl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init - bash)"' >> ~/.bashrc
source ~/.bashrc
rbenv install 3.3.1
rbenv global 3.3.1Verificar instalación:
ruby -vsudo service postgresql start
sudo -u postgres createuser --superuser $USER
createdbSigue la guía oficial de Microsoft: https://docs.microsoft.com/windows/wsl/install
# Actualizar sistema
sudo apt update && sudo apt upgrade -y
# Instalar dependencias
sudo apt install -y git curl build-essential libssl-dev libreadline-dev zlib1g-dev \
libpq-dev postgresql postgresql-contrib
# Instalar rbenv y Ruby
curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init - bash)"' >> ~/.bashrc
source ~/.bashrc
rbenv install 3.3.1
rbenv global 3.3.1
# Configurar PostgreSQL
sudo service postgresql start
sudo -u postgres createuser --superuser $USER
createdbgit clone <URL_DEL_REPOSITORIO>
cd event-managementbundle installtouch .envEditar .env con tus credenciales:
# Database
DB_USERNAME=postgres
DB_PASSWORD=tu_password_postgres
# Rails
RAILS_MAX_THREADS=5
# Google OAuth
GOOGLE_CLIENT_ID=tu_google_client_id
GOOGLE_CLIENT_SECRET=tu_google_client_secret
# SMTP (Gmail)
SMTP_ADDRESS=smtp.gmail.com
SMTP_PORT=587
SMTP_DOMAIN=gmail.com
SMTP_USERNAME=tu_email@gmail.com
SMTP_PASSWORD=tu_app_password
MAILER_SENDER=SGE <noreply@tudominio.com>
# App
APP_HOST=actify.qd.je
APP_PROTOCOL=http
# Pagos (Culqi)
CULQI_PUBLIC_KEY=pk_test_tu_public_key
PAYMENT_GATEWAY=MockGateway # Cambiar a CulqiGateway para integración real
# Production (opcional)
EVENT_MANAGEMENT_DATABASE_PASSWORD=tu_password_productionEl archivo config/database.yml ya está configurado para usar variables de entorno. Verifica que coincidan con tu .env:
default: &default
adapter: postgresql
encoding: unicode
username: <%= ENV["DB_USERNAME"] %>
password: <%= ENV["DB_PASSWORD"] %>
host: localhost
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>rails db:createrails db:migraterails db:seedrails db:resetrails db:schema:dump
cat db/schema.rbbin/devEsto levanta el servidor Rails y el watcher de Tailwind CSS simultáneamente.
rails server
# o
rails sAbrir en navegador: https://actify.qd.je
curl https://actify.qd.je/up
# Debe retornar: OKEl proyecto usa TailwindCSS v4 con @import "tailwindcss" en el archivo CSS principal. No existe archivo tailwind.config.js — la configuración se maneja directamente en CSS.
Las clases que se togglean desde JavaScript (como hidden, flex, colores dinámicos) se safelistean con @source inline() directamente en el CSS:
@import "tailwindcss";
@source inline(".hidden");
@source inline(".flex");Esto evita que PurgeCSS elimine clases usadas solo en runtime por Stimulus controllers.
bin/rails tailwindcss:buildbin/dev ejecuta el Procfile.dev que corre bin/rails tailwindcss:watch en paralelo al servidor, recompilando automáticamente al detectar cambios.
Los mapas interactivos se renderizan en la página de detalle del evento usando Leaflet con CSS cargado desde CDN (unpkg.com).
- La inicialización del mapa se maneja mediante Stimulus controllers
event_map_controller.js- Renderiza el mapa con marcador en la ubicación del evento- Las coordenadas se obtienen via Geocoder al crear/actualizar un evento
| Método | Ruta | Controlador | Acción | Descripción |
|---|---|---|---|---|
| GET | / |
home#index |
Página principal | Landing page |
| GET | /events |
events#index |
Listado de eventos | Explorar eventos (con filtros) |
| GET | /events/:id |
events#show |
Detalle de evento | Ver evento publicado |
| GET | /up |
rails/health#show |
Health check | Verificar estado del servidor |
| Método | Ruta | Descripción |
|---|---|---|
| GET/POST | /users/sign_in |
Iniciar sesión |
| GET/POST | /users/sign_up |
Registrarse |
| GET/POST | /users/password/new |
Recuperar contraseña |
| DELETE | /users/sign_out |
Cerrar sesión |
| PATCH | /users |
Actualizar usuario (Devise RegistrationsController#update) |
| DELETE | /users |
Cancelar cuenta (Devise RegistrationsController#destroy) |
| Método | Ruta | Controlador | Acción | Descripción |
|---|---|---|---|---|
| GET | /confirmation/new |
confirmations#new |
Formulario de confirmación | Ingresar código |
| POST | /confirmation |
confirmations#create |
Enviar código | Enviar código al email |
| POST | /confirmation/verify |
confirmations#verify |
Verificar código | Validar código de 6 dígitos |
| POST | /confirmation/resend |
confirmations#resend |
Reenviar código | Reenviar con cooldown de 60s |
| Método | Ruta | Controlador | Descripción |
|---|---|---|---|
| GET | /users/auth/google_oauth2/callback |
users/omniauth_callbacks#google_oauth2 |
Callback de Google |
| POST | /users/auth/google_oauth2/with_role |
users/omniauth_callbacks#authorize_with_role |
Inicia OAuth con rol en sesión |
| Método | Ruta | Controlador | Acción | Descripción |
|---|---|---|---|---|
| GET | /profile |
profiles#show |
Ver perfil | Mi perfil |
| GET | /profile/edit |
profiles#edit |
Editar perfil | Formulario de edición |
| PATCH/PUT | /profile |
profiles#update |
Actualizar perfil | Guardar cambios |
| Método | Ruta | Controlador | Acción | Descripción |
|---|---|---|---|---|
| POST | /events/:event_id/comments |
comments#create |
Crear comentario | Comentar en evento |
| DELETE | /comments/:id |
comments#destroy |
Eliminar comentario | Borrar comentario propio |
| Método | Ruta | Controlador | Acción | Descripción |
|---|---|---|---|---|
| GET | /events/:event_id/bookings/new |
bookings#new |
Nueva reserva | Formulario de reserva |
| POST | /events/:event_id/bookings |
bookings#create |
Crear reserva | Crear reserva con lock pesimista |
| GET | /bookings |
bookings#index |
Mis reservas | Listado de reservas del usuario |
| GET | /bookings/:id |
bookings#show |
Ver reserva | Detalle de reserva |
| GET | /bookings/:booking_id/payments/new |
payments#new |
Checkout | Página de pago con Culqi |
| POST | /bookings/:booking_id/payments |
payments#create |
Procesar pago | Cobro con lock + validación |
| Método | Ruta | Controlador | Acción | Descripción |
|---|---|---|---|---|
| POST | /webhooks/payments/receive |
webhooks/payments#receive |
Webhook | Recibe eventos del proveedor de pagos |
El endpoint de webhook valida transiciones de estado vía máquina de estados (ALLOWED_TRANSITIONS) para ignorar eventos out-of-order (ej. "approved" que llega después de "refunded").
| Método | Ruta | Controlador | Acción | Descripción |
|---|---|---|---|---|
| GET | /organizer/events |
organizer/events#index |
Mis eventos | Listado de mis eventos |
| GET | /organizer/events/new |
organizer/events#new |
Nuevo evento | Formulario de creación |
| POST | /organizer/events |
organizer/events#create |
Crear evento | Guardar nuevo evento |
| GET | /organizer/events/:id |
organizer/events#show |
Ver evento | Detalle de mi evento |
| GET | /organizer/events/:id/edit |
organizer/events#edit |
Editar evento | Formulario de edición |
| PATCH/PUT | /organizer/events/:id |
organizer/events#update |
Actualizar evento | Guardar cambios |
| DELETE | /organizer/events/:id |
organizer/events#destroy |
Eliminar evento | Borrar evento |
| Método | Ruta | Controlador | Acción | Descripción |
|---|---|---|---|---|
| GET | /admin/dashboard |
admin/dashboard#index |
Dashboard | Panel de administración |
| GET | /admin/users |
admin/users#index |
Usuarios | Listado de usuarios |
| GET | /admin/users/:id |
admin/users#show |
Ver usuario | Detalle de usuario |
| GET | /admin/users/:id/edit |
admin/users#edit |
Editar usuario | Formulario con roles |
| PATCH/PUT | /admin/users/:id |
admin/users#update |
Actualizar usuario | Guardar cambios y roles |
| GET | /admin/events |
admin/events#index |
Eventos | Listado de todos los eventos |
| GET | /admin/events/:id |
admin/events#show |
Ver evento | Detalle de evento |
| GET | /admin/events/:id/edit |
admin/events#edit |
Editar evento | Formulario de edición |
| PATCH/PUT | /admin/events/:id |
admin/events#update |
Actualizar evento | Guardar cambios |
| GET | /admin/categories |
admin/categories#index |
Categorías | Listado de categorías |
| GET | /admin/categories/new |
admin/categories#new |
Nueva categoría | Formulario de creación |
| POST | /admin/categories |
admin/categories#create |
Crear categoría | Guardar categoría |
| GET | /admin/categories/:id/edit |
admin/categories#edit |
Editar categoría | Formulario de edición |
| PATCH/PUT | /admin/categories/:id |
admin/categories#update |
Actualizar categoría | Guardar cambios |
| DELETE | /admin/categories/:id |
admin/categories#destroy |
Eliminar categoría | Borrar categoría |
El seed crea datos de prueba para desarrollo y testing:
admin- Administrador del sistemaorganizer- Organizador de eventosregistered_user- Usuario regular
- Tecnología
- Música
- Deportes
- Arte y Cultura
- Gastronomía
| Rol | Password | Nombre | |
|---|---|---|---|
| Admin | admin@eventos.com |
admin123 |
Admin Principal |
| Admin | admin@admin.com |
admin123 |
Admin General |
| Organizer | organizer@eventos.com |
organizer123 |
Organizador Oficial |
| User | user@eventos.com |
user123 |
Juan Pérez |
| Users extra | user0@eventos.com ... user4@eventos.com |
password123 |
Usuario Falso N |
- Rails World 2026 - Lima, $350 USD (Publicado)
- Festival de Rock Latino - Arequipa, $120 PEN (Publicado)
- Hackathon SGE 2026 - Lima, GRATIS (Publicado)
- Taller de Pintura Óleo - Cusco, $150 PEN (Borrador)
- Cada evento publicado tiene 3 comentarios aleatorios con ratings 4-5
rails db:seedrails db:reset
# Equivalente a:
rails db:drop db:create db:migrate db:seedrails generate model NombreModelo campo1:tipo campo2:tipo
# Ejemplo:
rails generate model Product name:string price:decimal description:textrails generate controller NombreControlador accion1 accion2
# Ejemplo:
rails generate controller Products index showrails generate migration NombreMigracion
# Ejemplo:
rails generate migration AddStatusToProducts status:integerrails generate scaffold NombreModelo campo1:tipo campo2:tiporails db:migrate # Ejecutar migraciones pendientes
rails db:rollback # Revertir última migración
rails db:rollback STEP=3 # Revertir últimas 3 migraciones
rails db:migrate:status # Ver estado de migraciones
rails db:version # Ver versión actualrails db:seed # Cargar datos iniciales
rails db:seed:replant # Recargar seed (borra y recrea)rails db:reset # Drop, create, migrate, seed
rails db:drop # Eliminar base de datos
rails db:create # Crear base de datosrails db:schema:dump # Generar schema.rb desde DB
rails db:schema:load # Cargar DB desde schema.rbrails console
# o
rails crails console production
rails console test# Crear usuario
User.create!(name: "Juan", email: "juan@test.com", password: "password123")
# Buscar usuario
user = User.find_by(email: "admin@eventos.com")
# Asignar rol
user.roles << Role.find_by(name: "admin")
# Ver eventos
Event.published_only
Event.upcoming
Event.by_city("Lima")
# Crear evento
Event.create!(name: "Mi Evento", description: "Descripción", city: "Lima",
address: "Dirección", start_date: 1.week.from_now,
category: Category.first, organizer: User.first)265 tests — 0 failures — Modelos, requests, servicios, jobs, policies y mailers.
Configurados en config/recurring.yml:
| Job | Schedule | Descripción |
|---|---|---|
ExpireBookingsJob |
Cada 5 minutos | Expira reservas pending cuyo timer venció (excluye con pagos en curso) |
bundle exec rspecbundle exec rspec spec/models/user_spec.rb
bundle exec rspec spec/requests/events_spec.rbbundle exec rspec --format documentationbundle exec rspec --format documentation --format html --out coverage.htmlbundle exec rubocopbundle exec rubocop -abundle exec rubocop --only Style,Layoutbundle exec brakemanbundle exec brakeman -o brakeman-report.htmlbundle exec bundler-audit checkbundle exec bundler-audit updatetail -f log/development.lograils log:cleartail -f log/production.lograils assets:precompilerails assets:clean
rails assets:clobberrails tmp:clear
rails cache:clearrails routesrails routes | grep admin
rails routes | grep eventsrails routes --compactbin/rails tailwindcss:buildbin/rails tailwindcss:watchEl proyecto usa RSpec 8.0 para testing.
spec/
├── models/ # Tests de modelos
│ ├── user_spec.rb
│ ├── event_spec.rb
│ ├── booking_spec.rb
│ ├── payment_spec.rb
│ ├── comment_spec.rb
│ ├── category_spec.rb
│ ├── event_image_spec.rb
│ ├── event_map_spec.rb
│ ├── role_spec.rb
│ ├── ticket_type_spec.rb
│ └── user_role_spec.rb
├── requests/ # Tests de requests
│ ├── events_spec.rb
│ ├── bookings_spec.rb
│ ├── payments/
│ │ └── payments_spec.rb
│ ├── webhooks/
│ │ └── payments_spec.rb
│ ├── omniauth_callbacks_spec.rb
│ ├── confirmations_spec.rb
│ ├── home_spec.rb
│ ├── location_spec.rb
│ └── organizer/
├── policies/ # Tests de políticas
│ ├── event_policy_spec.rb
│ ├── user_policy_spec.rb
│ ├── category_policy_spec.rb
│ ├── booking_policy_spec.rb
│ └── comment_policy_spec.rb
├── services/ # Tests de servicios
│ ├── confirmation_code_service_spec.rb
│ ├── payment_gateway_spec.rb
│ └── payments/
│ └── charge_service_spec.rb
├── jobs/ # Tests de jobs
│ └── expire_bookings_job_spec.rb
├── mailers/ # Tests de mailers
│ └── user_mailer_spec.rb
├── support/ # Soporte para tests
│ └── omniauth.rb
├── rails_helper.rb # Configuración de Rails
└── spec_helper.rb # Configuración de RSpec
# Todos los tests
bundle exec rspec
# Tests de un archivo específico
bundle exec rspec spec/models/user_spec.rb
# Tests de una línea específica
bundle exec rspec spec/models/user_spec.rb:15
# Tests con formato documentation
bundle exec rspec --format documentation
# Tests con fallas primero
bundle exec rspec --order failedevent-management/
├── app/
│ ├── assets/ # Assets estáticos (imágenes, fuentes)
│ ├── controllers/ # Controladores
│ │ ├── admin/ # Namespace admin
│ │ │ ├── base_controller.rb
│ │ │ ├── dashboard_controller.rb
│ │ │ ├── users_controller.rb
│ │ │ ├── events_controller.rb
│ │ │ └── categories_controller.rb
│ │ ├── organizer/ # Namespace organizer
│ │ │ ├── base_controller.rb
│ │ │ └── events_controller.rb
│ │ ├── users/ # Devise overrides + Omniauth
│ │ │ ├── omniauth_callbacks_controller.rb
│ │ │ └── registrations_controller.rb
│ │ ├── webhooks/ # Webhooks de proveedores externos
│ │ │ └── payments_controller.rb
│ │ ├── application_controller.rb
│ │ ├── bookings_controller.rb
│ │ ├── confirmations_controller.rb
│ │ ├── events_controller.rb
│ │ ├── comments_controller.rb
│ │ ├── home_controller.rb
│ │ ├── payments_controller.rb
│ │ └── profiles_controller.rb
│ ├── helpers/ # View helpers
│ ├── javascript/ # JavaScript (Stimulus controllers)
│ │ ├── controllers/
│ │ │ ├── application.js
│ │ │ ├── index.js
│ │ │ ├── carousel_controller.js
│ │ │ ├── confirm_controller.js
│ │ │ ├── event_detail_controller.js
│ │ │ ├── event_form_controller.js
│ │ │ ├── event_map_controller.js
│ │ │ ├── explore_controller.js
│ │ │ ├── flash_controller.js
│ │ │ ├── location_controller.js
│ │ │ ├── navbar_controller.js
│ │ │ └── organizer_dashboard_controller.js
│ │ └── application.js
│ ├── jobs/ # Active Job jobs
│ │ ├── application_job.rb
│ │ └── expire_bookings_job.rb # Recurre cada 5 min vía Solid Queue
│ ├── mailers/ # Mailers
│ │ └── user_mailer.rb
│ ├── models/ # Modelos ActiveRecord
│ │ ├── application_record.rb
│ │ ├── user.rb
│ │ ├── role.rb
│ │ ├── user_role.rb
│ │ ├── category.rb
│ │ ├── event.rb
│ │ ├── event_image.rb
│ │ ├── comment.rb
│ │ ├── ticket_type.rb
│ │ ├── booking.rb
│ │ └── payment.rb
│ ├── policies/ # Pundit policies
│ │ ├── application_policy.rb
│ │ ├── event_policy.rb
│ │ ├── user_policy.rb
│ │ ├── category_policy.rb
│ │ └── comment_policy.rb
│ ├── queries/ # Query objects
│ │ └── events/
│ │ └── search_query.rb
│ ├── services/ # Service objects
│ │ ├── booking_service.rb
│ │ ├── confirmation_code_service.rb
│ │ ├── payment_gateway.rb # Module + Base class
│ │ ├── payment_gateway/
│ │ │ └── mock_gateway.rb # Mock para dev/test
│ │ ├── payments/
│ │ │ └── charge_service.rb # Cobro con lock pesimista
│ │ ├── events/
│ │ │ ├── create_service.rb
│ │ │ └── update_service.rb
│ │ └── comments/
│ │ └── create_service.rb
│ └── views/ # Vistas ERB
│ ├── layouts/
│ ├── admin/
│ ├── organizer/
│ ├── events/
│ ├── devise/
│ ├── confirmations/
│ ├── profiles/
│ ├── payments/
│ │ └── new.html.erb # Checkout con Culqi + timer
│ └── shared/
├── bin/ # Ejecutables Rails
│ ├── rails
│ ├── rake
│ ├── setup
│ ├── dev
│ └── ...
├── config/ # Configuración
│ ├── application.rb
│ ├── database.yml
│ ├── routes.rb
│ ├── environments/
│ ├── initializers/
│ └── locales/
├── db/ # Base de datos
│ ├── migrate/ # Migraciones
│ ├── seeds.rb # Datos iniciales
│ └── schema.rb # Schema actual
├── lib/ # Librerías personalizadas
├── log/ # Logs
├── public/ # Archivos públicos
├── spec/ # Tests RSpec
│ ├── models/
│ ├── requests/
│ ├── policies/
│ ├── services/
│ ├── mailers/
│ ├── support/
│ ├── rails_helper.rb
│ └── spec_helper.rb
├── storage/ # Active Storage
├── test/ # Tests Rails (si existen)
├── tmp/ # Archivos temporales
├── vendor/ # Gems vendorizadas
├── .env # Variables de entorno (NO commitear)
├── .gitignore # Archivos ignorados por Git
├── .ruby-version # Versión de Ruby
├── Dockerfile # Configuración Docker
├── Gemfile # Dependencias Ruby
├── Gemfile.lock # Versiones bloqueadas
├── Procfile.dev # Procfile para desarrollo
├── Rakefile # Tareas Rake
├── README.md # Este archivo
└── config.ru # Configuración Rack
El proyecto usa los siguientes controladores Stimulus:
| Controller | Descripción |
|---|---|
explore_controller.js |
Búsqueda y filtros en exploración de eventos |
event_form_controller.js |
Formulario de creación/edición de eventos |
event_detail_controller.js |
Interacciones en detalle de evento |
event_map_controller.js |
Renderizado de mapa Leaflet |
flash_controller.js |
Auto-dismiss de mensajes flash |
organizer_dashboard_controller.js |
Dashboard del organizador |
confirm_controller.js |
Confirmación de email (código de 6 dígitos) |
carousel_controller.js |
Carrusel de imágenes de evento |
location_controller.js |
Selección de ubicación con autocompletado |
navbar_controller.js |
Comportamiento de la barra de navegación |
checkout_timer_controller.js |
Cuenta regresiva en checkout de pago (15 min) |
Actualmente no hay rate limiting implementado en la aplicación. Esto es una consideración futura para:
- Endpoints de confirmación de email (evitar abuso de envío de códigos)
- Creación de comentarios
- Intentos de login (Devise ya incluye
Lockableopcional pero no está activado)
Si el proyecto escala a producción, se recomienda implementar rate limiting via Rack::Attack o middleware similar.
main → Producción
develop → Desarrollo integrado
feature/* → Nuevas funcionalidades
bugfix/* → Corrección de bugs
hotfix/* → Correcciones urgentes en producción
-
Crear branch desde
developgit checkout develop git pull origin develop git checkout -b feature/nombre-feature
-
Hacer commits con mensajes descriptivos
git add . git commit -m "feat: agregar búsqueda de eventos por ciudad"
-
Push al branch
git push origin feature/nombre-feature
-
Crear Pull Request a
develop -
Code review y aprobación
-
Merge a
develop -
Deploy desde
developamain
feat: nueva funcionalidad
fix: corrección de bug
docs: cambios en documentación
style: cambios de formato (sin lógica)
refactor: refactorización de código
test: agregar o modificar tests
chore: tareas de mantenimiento
Ejemplos:
git commit -m "feat: agregar filtro de eventos por precio"
git commit -m "fix: corregir validación de fecha fin en eventos"
git commit -m "docs: actualizar README con nuevas instrucciones"
git commit -m "refactor: simplificar lógica de búsqueda de eventos"El proyecto está configurado para deployment con Kamal.
gem install kamalkamal initEl archivo ya existe. Verifica la configuración:
- Servidor de destino
- Imagen Docker
- Variables de entorno
- Comandos de pre-deployment
kamal deploykamal app logskamal app exec "rails console"
kamal app exec "rails db:migrate"docker build -t event-management .docker run -p 3000:3000 \
-e DB_USERNAME=postgres \
-e DB_PASSWORD=password \
event-management# Database
DB_USERNAME=postgres
DB_PASSWORD=tu_password_seguro
RAILS_MAX_THREADS=5
# Rails Secret
RAILS_MASTER_KEY=tu_master_key
SECRET_KEY_BASE=tu_secret_key_base
# Production Database
EVENT_MANAGEMENT_DATABASE_PASSWORD=tu_password_production
# Otros
RAILS_ENV=production
RAILS_SERVE_STATIC_FILES=trueNUNCA commitear:
.env- Usa la lista de variables más arriba como plantillaconfig/master.key- Ya está en.gitignore- Contraseñas hardcodeadas
- API keys
Ejecutar regularmente:
bundle exec brakeman
bundle exec bundler-audit checkEl proyecto incluye:
CSP (Content Security Policy)— COMENTADO enconfig/initializers/content_security_policy.rb. No está activo. Pendiente de configuración para producción.- CSRF protection — Activo via
protect_from_forgery with: :exception - Secure headers — Configurados por defecto en Rails 8
Mantener buenas prácticas:
- ✅ Pull Requests con descripción clara
- ✅ Código limpio y legible
- ✅ Convenciones de Rails
- ✅ Commits descriptivos (Conventional Commits)
- ✅ Variables sensibles en
.env - ✅ Tests para nuevas funcionalidades
- ✅ Code review antes de merge
- ✅ Documentar cambios complejos
[Agregar licencia del proyecto aquí]
- Fork el proyecto
- Crear branch para tu feature (
git checkout -b feature/AmazingFeature) - Commit tus cambios (
git commit -m 'feat: add AmazingFeature') - Push al branch (
git push origin feature/AmazingFeature) - Abrir Pull Request
Para preguntas o problemas:
- Crear issue en el repositorio
- Contactar al equipo de desarrollo
- Revisar documentación oficial
Última actualización: Junio 2026