claude-agent-framework 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -0
- package/bin/claude-framework +3 -0
- package/framework/agents/design-lead.md +240 -0
- package/framework/agents/product-owner.md +179 -0
- package/framework/agents/tech-lead.md +226 -0
- package/framework/commands/ayuda.md +127 -0
- package/framework/commands/a/303/261adir.md +98 -0
- package/framework/commands/backup.md +397 -0
- package/framework/commands/cambiar.md +110 -0
- package/framework/commands/cloud.md +457 -0
- package/framework/commands/code.md +142 -0
- package/framework/commands/debug.md +334 -0
- package/framework/commands/deploy.md +383 -0
- package/framework/commands/deshacer.md +120 -0
- package/framework/commands/estado.md +218 -0
- package/framework/commands/explica.md +227 -0
- package/framework/commands/feature.md +120 -0
- package/framework/commands/git.md +427 -0
- package/framework/commands/historial.md +202 -0
- package/framework/commands/learn.md +408 -0
- package/framework/commands/movil.md +245 -0
- package/framework/commands/nuevo.md +118 -0
- package/framework/commands/plan.md +134 -0
- package/framework/commands/prd.md +113 -0
- package/framework/commands/probar.md +148 -0
- package/framework/commands/revisar.md +208 -0
- package/framework/commands/seeds.md +230 -0
- package/framework/commands/seguridad.md +226 -0
- package/framework/commands/tasks.md +157 -0
- package/framework/skills/architecture/algorithms.md +970 -0
- package/framework/skills/architecture/clean-code.md +1080 -0
- package/framework/skills/architecture/design-patterns.md +1984 -0
- package/framework/skills/architecture/functional-programming.md +972 -0
- package/framework/skills/architecture/solid.md +991 -0
- package/framework/skills/cloud/cloud-aws.md +848 -0
- package/framework/skills/cloud/cloud-azure.md +931 -0
- package/framework/skills/cloud/cloud-gcp.md +848 -0
- package/framework/skills/cloud/message-queues.md +1229 -0
- package/framework/skills/core/accessibility.md +401 -0
- package/framework/skills/core/api.md +474 -0
- package/framework/skills/core/authentication.md +306 -0
- package/framework/skills/core/authorization.md +388 -0
- package/framework/skills/core/background-jobs.md +341 -0
- package/framework/skills/core/caching.md +473 -0
- package/framework/skills/core/code-review.md +341 -0
- package/framework/skills/core/controllers.md +290 -0
- package/framework/skills/core/cua.md +285 -0
- package/framework/skills/core/documentation.md +472 -0
- package/framework/skills/core/file-uploads.md +351 -0
- package/framework/skills/core/hotwire-native.md +296 -0
- package/framework/skills/core/hotwire.md +278 -0
- package/framework/skills/core/i18n.md +334 -0
- package/framework/skills/core/imports-exports.md +750 -0
- package/framework/skills/core/infrastructure.md +337 -0
- package/framework/skills/core/models.md +228 -0
- package/framework/skills/core/notifications.md +672 -0
- package/framework/skills/core/payments.md +581 -0
- package/framework/skills/core/performance.md +361 -0
- package/framework/skills/core/rails-scaffold.md +131 -0
- package/framework/skills/core/search.md +518 -0
- package/framework/skills/core/security.md +565 -0
- package/framework/skills/core/seeds.md +307 -0
- package/framework/skills/core/seo.md +542 -0
- package/framework/skills/core/testing.md +393 -0
- package/framework/skills/core/views.md +260 -0
- package/framework/skills/core/websockets.md +564 -0
- package/framework/skills/data/advanced-sql.md +1204 -0
- package/framework/skills/data/nosql.md +1141 -0
- package/framework/skills/devops/containers-advanced.md +1237 -0
- package/framework/skills/devops/debugging.md +834 -0
- package/framework/skills/devops/git-workflow.md +752 -0
- package/framework/skills/devops/networking.md +932 -0
- package/framework/skills/devops/shell-scripting.md +1132 -0
- package/framework/sub-agents/architecture-patterns-agent.md +1450 -0
- package/framework/sub-agents/cloud-agent.md +677 -0
- package/framework/sub-agents/data.md +504 -0
- package/framework/sub-agents/debugging-agent.md +554 -0
- package/framework/sub-agents/devops.md +483 -0
- package/framework/sub-agents/docs.md +176 -0
- package/framework/sub-agents/frontend-dev.md +349 -0
- package/framework/sub-agents/git-workflow-agent.md +697 -0
- package/framework/sub-agents/integrations.md +630 -0
- package/framework/sub-agents/native-dev.md +434 -0
- package/framework/sub-agents/qa.md +138 -0
- package/framework/sub-agents/rails-dev.md +375 -0
- package/framework/sub-agents/security.md +526 -0
- package/framework/sub-agents/ui.md +437 -0
- package/framework/sub-agents/ux.md +284 -0
- package/framework/templates/api-spec.md +500 -0
- package/framework/templates/component-spec.md +248 -0
- package/framework/templates/feature.json +13 -0
- package/framework/templates/model-spec.md +318 -0
- package/framework/templates/prd-template.md +80 -0
- package/framework/templates/task-plan.md +122 -0
- package/framework/templates/task-user-story.md +52 -0
- package/framework/templates/technical-spec.md +260 -0
- package/framework/templates/user-story.md +95 -0
- package/package.json +42 -0
- package/project-templates/CLAUDE.md +42 -0
- package/project-templates/contexts/architecture.md +25 -0
- package/project-templates/contexts/conventions.md +46 -0
- package/project-templates/contexts/design-system.md +47 -0
- package/project-templates/contexts/requirements.md +38 -0
- package/project-templates/contexts/stack.md +30 -0
- package/project-templates/history/active/models.md +11 -0
- package/project-templates/history/changelog.md +15 -0
- package/project-templates/workspace/.gitkeep +0 -0
- package/src/cli.js +52 -0
- package/src/init.js +104 -0
- package/src/status.js +75 -0
- package/src/update.js +88 -0
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
# Skill: Debugging
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Técnicas y herramientas de depuración para encontrar y solucionar bugs en aplicaciones Ruby on Rails de forma eficiente.
|
|
6
|
+
|
|
7
|
+
## Herramientas de Debugging
|
|
8
|
+
|
|
9
|
+
### Debug Gem (Rails 7+)
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# Gemfile (incluido por defecto en Rails 7+)
|
|
13
|
+
group :development, :test do
|
|
14
|
+
gem "debug", platforms: %i[mri mingw x64_mingw]
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
# En cualquier parte del código
|
|
20
|
+
def process_order(order)
|
|
21
|
+
debugger # Pausa ejecución aquí
|
|
22
|
+
|
|
23
|
+
total = calculate_total(order)
|
|
24
|
+
# ...
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Byebug
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
# Gemfile
|
|
32
|
+
group :development, :test do
|
|
33
|
+
gem "byebug"
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
# Uso
|
|
39
|
+
def problematic_method
|
|
40
|
+
byebug # Punto de parada
|
|
41
|
+
|
|
42
|
+
result = complex_calculation
|
|
43
|
+
result
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Pry
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
# Gemfile
|
|
51
|
+
group :development, :test do
|
|
52
|
+
gem "pry-rails"
|
|
53
|
+
gem "pry-byebug" # Combina pry con debugging
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
# Uso
|
|
59
|
+
def analyze_data(data)
|
|
60
|
+
binding.pry # Abre consola interactiva
|
|
61
|
+
|
|
62
|
+
processed = data.map { |d| transform(d) }
|
|
63
|
+
processed
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Binding.irb (Sin gems adicionales)
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# Disponible en Ruby 2.5+
|
|
71
|
+
def investigate_bug
|
|
72
|
+
binding.irb # Abre IRB en este punto
|
|
73
|
+
|
|
74
|
+
# código a investigar
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Comandos del Debugger
|
|
79
|
+
|
|
80
|
+
### Navegación
|
|
81
|
+
|
|
82
|
+
| Comando | Alias | Descripción |
|
|
83
|
+
|---------|-------|-------------|
|
|
84
|
+
| `step` | `s` | Entrar en el método |
|
|
85
|
+
| `next` | `n` | Siguiente línea (sin entrar) |
|
|
86
|
+
| `finish` | `fin` | Salir del método actual |
|
|
87
|
+
| `continue` | `c` | Continuar ejecución |
|
|
88
|
+
| `up` | | Subir en el stack frame |
|
|
89
|
+
| `down` | | Bajar en el stack frame |
|
|
90
|
+
|
|
91
|
+
### Inspección
|
|
92
|
+
|
|
93
|
+
| Comando | Descripción |
|
|
94
|
+
|---------|-------------|
|
|
95
|
+
| `where` / `bt` | Ver backtrace completo |
|
|
96
|
+
| `list` / `l` | Ver código alrededor |
|
|
97
|
+
| `info locals` | Ver variables locales |
|
|
98
|
+
| `info args` | Ver argumentos del método |
|
|
99
|
+
| `p expression` | Evaluar expresión |
|
|
100
|
+
| `pp object` | Pretty print de objeto |
|
|
101
|
+
|
|
102
|
+
### Breakpoints
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
# En debug gem
|
|
106
|
+
break app/models/user.rb:25 # Línea específica
|
|
107
|
+
break User#validate # Método específico
|
|
108
|
+
break if user.admin? # Breakpoint condicional
|
|
109
|
+
delete 1 # Eliminar breakpoint 1
|
|
110
|
+
info breakpoints # Listar breakpoints
|
|
111
|
+
|
|
112
|
+
# En byebug
|
|
113
|
+
break 25 # Línea 25 del archivo actual
|
|
114
|
+
break User#save # Método save de User
|
|
115
|
+
condition 1 user.email.nil? # Condición en breakpoint 1
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Ejemplo de sesión de debugging
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
# app/models/order.rb
|
|
122
|
+
class Order < ApplicationRecord
|
|
123
|
+
def calculate_total
|
|
124
|
+
debugger
|
|
125
|
+
|
|
126
|
+
subtotal = line_items.sum(&:total)
|
|
127
|
+
tax = subtotal * tax_rate
|
|
128
|
+
shipping = calculate_shipping
|
|
129
|
+
|
|
130
|
+
subtotal + tax + shipping
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# En el debugger:
|
|
135
|
+
(rdbg) p subtotal # => 100.0
|
|
136
|
+
(rdbg) p tax_rate # => 0.21
|
|
137
|
+
(rdbg) p tax # => 21.0
|
|
138
|
+
(rdbg) n # Siguiente línea
|
|
139
|
+
(rdbg) p shipping # => nil # Aquí está el bug!
|
|
140
|
+
(rdbg) s # Entrar en calculate_shipping
|
|
141
|
+
(rdbg) where # Ver stack trace
|
|
142
|
+
(rdbg) c # Continuar
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Rails Console
|
|
146
|
+
|
|
147
|
+
### Comandos útiles
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
# Iniciar consola
|
|
151
|
+
rails console
|
|
152
|
+
rails c
|
|
153
|
+
|
|
154
|
+
# Consola en sandbox (rollback al salir)
|
|
155
|
+
rails console --sandbox
|
|
156
|
+
|
|
157
|
+
# Consola en producción (cuidado!)
|
|
158
|
+
RAILS_ENV=production rails console
|
|
159
|
+
|
|
160
|
+
# Recargar código modificado
|
|
161
|
+
reload!
|
|
162
|
+
|
|
163
|
+
# Acceder al último valor retornado
|
|
164
|
+
_
|
|
165
|
+
|
|
166
|
+
# Ver SQL generado
|
|
167
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
|
168
|
+
|
|
169
|
+
# O para una query específica
|
|
170
|
+
User.where(active: true).to_sql
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Helper y app
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
# Acceder a helpers de vistas
|
|
177
|
+
helper.number_to_currency(1234.50)
|
|
178
|
+
helper.time_ago_in_words(3.days.ago)
|
|
179
|
+
helper.link_to("Home", "/")
|
|
180
|
+
|
|
181
|
+
# Hacer requests HTTP
|
|
182
|
+
app.get "/"
|
|
183
|
+
app.response.status # => 200
|
|
184
|
+
app.response.body # HTML
|
|
185
|
+
|
|
186
|
+
app.post "/users", params: { user: { name: "Test" } }
|
|
187
|
+
|
|
188
|
+
# Acceder a rutas
|
|
189
|
+
app.users_path
|
|
190
|
+
app.root_url
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Inspección de objetos
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
# Ver métodos de un objeto
|
|
197
|
+
User.new.methods.sort
|
|
198
|
+
User.new.methods.grep(/name/)
|
|
199
|
+
|
|
200
|
+
# Ver source de un método
|
|
201
|
+
User.instance_method(:full_name).source_location
|
|
202
|
+
User.method(:find).source_location
|
|
203
|
+
|
|
204
|
+
# Con pry
|
|
205
|
+
$ User#full_name # Ver source
|
|
206
|
+
? User#full_name # Ver documentación
|
|
207
|
+
|
|
208
|
+
# Ver ancestros
|
|
209
|
+
User.ancestors
|
|
210
|
+
|
|
211
|
+
# Ver asociaciones
|
|
212
|
+
User.reflect_on_all_associations.map(&:name)
|
|
213
|
+
|
|
214
|
+
# Ver columnas
|
|
215
|
+
User.column_names
|
|
216
|
+
User.columns_hash["email"]
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Logging
|
|
220
|
+
|
|
221
|
+
### Niveles de log
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
# config/environments/development.rb
|
|
225
|
+
config.log_level = :debug # :debug, :info, :warn, :error, :fatal
|
|
226
|
+
|
|
227
|
+
# En código
|
|
228
|
+
Rails.logger.debug "Valor de x: #{x}"
|
|
229
|
+
Rails.logger.info "Usuario creado: #{user.id}"
|
|
230
|
+
Rails.logger.warn "Parámetro deprecado usado"
|
|
231
|
+
Rails.logger.error "Fallo en payment: #{error.message}"
|
|
232
|
+
Rails.logger.fatal "Sistema no puede continuar"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Tagged Logging
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
# config/application.rb
|
|
239
|
+
config.log_tags = [:request_id, :remote_ip]
|
|
240
|
+
|
|
241
|
+
# Uso manual
|
|
242
|
+
Rails.logger.tagged("OrderProcessor", "User:#{user.id}") do
|
|
243
|
+
Rails.logger.info "Procesando orden"
|
|
244
|
+
# [OrderProcessor] [User:123] Procesando orden
|
|
245
|
+
end
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Custom Logger
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
# lib/debug_logger.rb
|
|
252
|
+
class DebugLogger
|
|
253
|
+
def self.log(context, data = {})
|
|
254
|
+
return unless Rails.env.development?
|
|
255
|
+
|
|
256
|
+
message = {
|
|
257
|
+
timestamp: Time.current.iso8601,
|
|
258
|
+
context: context,
|
|
259
|
+
caller: caller[0],
|
|
260
|
+
**data
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
Rails.logger.debug message.to_json
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Uso
|
|
268
|
+
DebugLogger.log("PaymentService", amount: 100, user_id: user.id)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Silenciar logs temporalmente
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
# Silenciar ActiveRecord
|
|
275
|
+
ActiveRecord::Base.logger.silence do
|
|
276
|
+
# Queries aquí no se loguean
|
|
277
|
+
User.find_each { |u| process(u) }
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Log a archivo específico
|
|
281
|
+
debug_log = Logger.new(Rails.root.join("log/debug.log"))
|
|
282
|
+
debug_log.info "Investigando bug..."
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Stack Traces
|
|
286
|
+
|
|
287
|
+
### Leer un stack trace
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
NoMethodError: undefined method 'total' for nil:NilClass
|
|
291
|
+
|
|
292
|
+
app/models/order.rb:25:in `calculate_total'
|
|
293
|
+
app/services/checkout_service.rb:15:in `process'
|
|
294
|
+
app/controllers/orders_controller.rb:10:in `create'
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Leer de abajo hacia arriba:**
|
|
298
|
+
1. El error empezó en `orders_controller.rb:10`
|
|
299
|
+
2. Llamó a `checkout_service.rb:15`
|
|
300
|
+
3. Que llamó a `order.rb:25` donde ocurrió el error
|
|
301
|
+
|
|
302
|
+
### Filtrar stack trace
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
# config/initializers/backtrace_silencers.rb
|
|
306
|
+
Rails.backtrace_cleaner.add_silencer { |line| line.include?("/gems/") }
|
|
307
|
+
|
|
308
|
+
# Ver backtrace completo
|
|
309
|
+
Rails.backtrace_cleaner.remove_silencers!
|
|
310
|
+
|
|
311
|
+
# En una excepción
|
|
312
|
+
begin
|
|
313
|
+
risky_operation
|
|
314
|
+
rescue => e
|
|
315
|
+
puts e.message
|
|
316
|
+
puts e.backtrace.first(10).join("\n")
|
|
317
|
+
|
|
318
|
+
# Con Rails cleaner
|
|
319
|
+
puts Rails.backtrace_cleaner.clean(e.backtrace).join("\n")
|
|
320
|
+
end
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Capturar contexto
|
|
324
|
+
|
|
325
|
+
```ruby
|
|
326
|
+
# Guardar contexto cuando ocurre un error
|
|
327
|
+
class ApplicationController < ActionController::Base
|
|
328
|
+
rescue_from StandardError do |exception|
|
|
329
|
+
context = {
|
|
330
|
+
user_id: current_user&.id,
|
|
331
|
+
params: params.to_unsafe_h,
|
|
332
|
+
session: session.to_h,
|
|
333
|
+
url: request.url,
|
|
334
|
+
method: request.method
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
Rails.logger.error "Error: #{exception.message}"
|
|
338
|
+
Rails.logger.error "Context: #{context.to_json}"
|
|
339
|
+
Rails.logger.error exception.backtrace.first(20).join("\n")
|
|
340
|
+
|
|
341
|
+
raise exception
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Estrategias de Debugging
|
|
347
|
+
|
|
348
|
+
### Divide y vencerás
|
|
349
|
+
|
|
350
|
+
```ruby
|
|
351
|
+
# Bug: el resultado es incorrecto
|
|
352
|
+
def complex_calculation(data)
|
|
353
|
+
# Dividir en pasos y verificar cada uno
|
|
354
|
+
step1 = transform_data(data)
|
|
355
|
+
Rails.logger.debug "After step1: #{step1.inspect}"
|
|
356
|
+
|
|
357
|
+
step2 = filter_data(step1)
|
|
358
|
+
Rails.logger.debug "After step2: #{step2.inspect}"
|
|
359
|
+
|
|
360
|
+
step3 = aggregate_data(step2)
|
|
361
|
+
Rails.logger.debug "After step3: #{step3.inspect}"
|
|
362
|
+
|
|
363
|
+
step3
|
|
364
|
+
end
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Rubber Duck Debugging
|
|
368
|
+
|
|
369
|
+
```ruby
|
|
370
|
+
# Explicar el código línea por línea:
|
|
371
|
+
def find_discount(user, cart)
|
|
372
|
+
# 1. Obtengo todos los cupones activos del usuario
|
|
373
|
+
coupons = user.coupons.active
|
|
374
|
+
|
|
375
|
+
# 2. Filtro los que aplican a los productos del carrito
|
|
376
|
+
applicable = coupons.select { |c| c.applies_to?(cart) }
|
|
377
|
+
|
|
378
|
+
# 3. Ordeno por descuento y tomo el mejor
|
|
379
|
+
# ESPERA... ¿qué pasa si applicable está vacío?
|
|
380
|
+
best = applicable.max_by(&:discount_percent)
|
|
381
|
+
|
|
382
|
+
# 4. Retorno el descuento
|
|
383
|
+
best.discount_percent # NoMethodError si best es nil!
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Fix:
|
|
387
|
+
best&.discount_percent || 0
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Binary Search (para bugs de regresión)
|
|
391
|
+
|
|
392
|
+
```ruby
|
|
393
|
+
# Si algo funcionaba antes y ahora no:
|
|
394
|
+
|
|
395
|
+
# 1. Usar git bisect
|
|
396
|
+
# git bisect start
|
|
397
|
+
# git bisect bad HEAD
|
|
398
|
+
# git bisect good v1.0.0
|
|
399
|
+
# git bisect run bundle exec rspec spec/models/order_spec.rb
|
|
400
|
+
|
|
401
|
+
# 2. Manual: comentar mitad del código
|
|
402
|
+
def process_order(order)
|
|
403
|
+
validate_order(order)
|
|
404
|
+
# calculate_taxes(order)
|
|
405
|
+
# apply_discounts(order)
|
|
406
|
+
# process_payment(order)
|
|
407
|
+
# send_confirmation(order)
|
|
408
|
+
end
|
|
409
|
+
# Si funciona, el bug está en la mitad comentada
|
|
410
|
+
# Seguir dividiendo hasta encontrarlo
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Añadir assertions temporales
|
|
414
|
+
|
|
415
|
+
```ruby
|
|
416
|
+
def transfer_money(from, to, amount)
|
|
417
|
+
# Assertions para encontrar estado inesperado
|
|
418
|
+
raise "from is nil!" if from.nil?
|
|
419
|
+
raise "to is nil!" if to.nil?
|
|
420
|
+
raise "amount must be positive: #{amount}" unless amount.positive?
|
|
421
|
+
raise "insufficient funds: #{from.balance} < #{amount}" if from.balance < amount
|
|
422
|
+
|
|
423
|
+
from.balance -= amount
|
|
424
|
+
to.balance += amount
|
|
425
|
+
|
|
426
|
+
# Verificar invariantes
|
|
427
|
+
raise "from balance negative!" if from.balance.negative?
|
|
428
|
+
end
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Profiling
|
|
432
|
+
|
|
433
|
+
### Rack Mini Profiler
|
|
434
|
+
|
|
435
|
+
```ruby
|
|
436
|
+
# Gemfile
|
|
437
|
+
group :development do
|
|
438
|
+
gem "rack-mini-profiler"
|
|
439
|
+
gem "memory_profiler"
|
|
440
|
+
gem "stackprof"
|
|
441
|
+
end
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
```ruby
|
|
445
|
+
# En cualquier request, aparece badge con timing
|
|
446
|
+
# Añadir ?pp=flamegraph para flamegraph
|
|
447
|
+
# Añadir ?pp=analyze-memory para memoria
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Memory Profiler
|
|
451
|
+
|
|
452
|
+
```ruby
|
|
453
|
+
# En consola o código
|
|
454
|
+
require "memory_profiler"
|
|
455
|
+
|
|
456
|
+
report = MemoryProfiler.report do
|
|
457
|
+
# Código a analizar
|
|
458
|
+
1000.times { User.all.to_a }
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
report.pretty_print(to_file: "tmp/memory_report.txt")
|
|
462
|
+
|
|
463
|
+
# Métricas clave:
|
|
464
|
+
# - Total allocated: memoria usada
|
|
465
|
+
# - Total retained: memoria no liberada (posible leak)
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### StackProf (CPU profiling)
|
|
469
|
+
|
|
470
|
+
```ruby
|
|
471
|
+
require "stackprof"
|
|
472
|
+
|
|
473
|
+
# Profile bloque de código
|
|
474
|
+
StackProf.run(mode: :cpu, out: "tmp/stackprof.dump") do
|
|
475
|
+
# Código lento
|
|
476
|
+
heavy_computation
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
# Analizar resultados
|
|
480
|
+
# stackprof tmp/stackprof.dump --text
|
|
481
|
+
# stackprof tmp/stackprof.dump --method 'Object#slow_method'
|
|
482
|
+
|
|
483
|
+
# Profile request completo
|
|
484
|
+
# En config/initializers/stackprof.rb
|
|
485
|
+
if Rails.env.development?
|
|
486
|
+
require "stackprof"
|
|
487
|
+
use StackProf::Middleware, enabled: true,
|
|
488
|
+
mode: :cpu,
|
|
489
|
+
interval: 1000,
|
|
490
|
+
save_every: 5
|
|
491
|
+
end
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### Benchmark
|
|
495
|
+
|
|
496
|
+
```ruby
|
|
497
|
+
require "benchmark"
|
|
498
|
+
|
|
499
|
+
# Medir tiempo
|
|
500
|
+
time = Benchmark.measure do
|
|
501
|
+
User.all.each { |u| process(u) }
|
|
502
|
+
end
|
|
503
|
+
puts time # 0.234567
|
|
504
|
+
|
|
505
|
+
# Comparar alternativas
|
|
506
|
+
Benchmark.bm(20) do |x|
|
|
507
|
+
x.report("each:") { users.each { |u| process(u) } }
|
|
508
|
+
x.report("find_each:") { User.find_each { |u| process(u) } }
|
|
509
|
+
x.report("in_batches:") { User.in_batches { |batch| batch.each { |u| process(u) } } }
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
# Benchmark/ips para comparaciones más precisas
|
|
513
|
+
require "benchmark/ips"
|
|
514
|
+
|
|
515
|
+
Benchmark.ips do |x|
|
|
516
|
+
x.report("string +") { "hello" + " " + "world" }
|
|
517
|
+
x.report("string interpolation") { "hello #{'world'}" }
|
|
518
|
+
x.compare!
|
|
519
|
+
end
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## N+1 Queries
|
|
523
|
+
|
|
524
|
+
### Bullet Gem
|
|
525
|
+
|
|
526
|
+
```ruby
|
|
527
|
+
# Gemfile
|
|
528
|
+
group :development do
|
|
529
|
+
gem "bullet"
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# config/environments/development.rb
|
|
533
|
+
config.after_initialize do
|
|
534
|
+
Bullet.enable = true
|
|
535
|
+
Bullet.alert = true # Popup en browser
|
|
536
|
+
Bullet.bullet_logger = true # Log a bullet.log
|
|
537
|
+
Bullet.console = true # Console.log
|
|
538
|
+
Bullet.rails_logger = true # Rails logger
|
|
539
|
+
Bullet.add_footer = true # Footer en página
|
|
540
|
+
end
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Detectar manualmente
|
|
544
|
+
|
|
545
|
+
```ruby
|
|
546
|
+
# Activar logging de SQL
|
|
547
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
|
548
|
+
|
|
549
|
+
# En consola
|
|
550
|
+
User.all.each { |u| puts u.posts.count }
|
|
551
|
+
# Verás N+1 queries: 1 para users + N para posts
|
|
552
|
+
|
|
553
|
+
# Usar explain
|
|
554
|
+
User.includes(:posts).explain
|
|
555
|
+
# Muestra plan de ejecución
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### Solucionar N+1
|
|
559
|
+
|
|
560
|
+
```ruby
|
|
561
|
+
# ANTES (N+1)
|
|
562
|
+
@users = User.all
|
|
563
|
+
# En vista: user.posts.count genera query por usuario
|
|
564
|
+
|
|
565
|
+
# DESPUÉS (eager loading)
|
|
566
|
+
@users = User.includes(:posts)
|
|
567
|
+
# o
|
|
568
|
+
@users = User.preload(:posts)
|
|
569
|
+
# o para joins
|
|
570
|
+
@users = User.eager_load(:posts)
|
|
571
|
+
|
|
572
|
+
# Diferencias:
|
|
573
|
+
# includes: Rails decide si usar JOIN o queries separadas
|
|
574
|
+
# preload: Siempre queries separadas
|
|
575
|
+
# eager_load: Siempre LEFT OUTER JOIN
|
|
576
|
+
|
|
577
|
+
# Counter cache para counts
|
|
578
|
+
class Post < ApplicationRecord
|
|
579
|
+
belongs_to :user, counter_cache: true
|
|
580
|
+
end
|
|
581
|
+
# Requiere columna posts_count en users
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
## Error Tracking
|
|
585
|
+
|
|
586
|
+
### Sentry
|
|
587
|
+
|
|
588
|
+
```ruby
|
|
589
|
+
# Gemfile
|
|
590
|
+
gem "sentry-ruby"
|
|
591
|
+
gem "sentry-rails"
|
|
592
|
+
|
|
593
|
+
# config/initializers/sentry.rb
|
|
594
|
+
Sentry.init do |config|
|
|
595
|
+
config.dsn = Rails.application.credentials.sentry_dsn
|
|
596
|
+
config.environment = Rails.env
|
|
597
|
+
config.breadcrumbs_logger = [:active_support_logger, :http_logger]
|
|
598
|
+
|
|
599
|
+
# Sample rate (0.0 a 1.0)
|
|
600
|
+
config.traces_sample_rate = 0.5
|
|
601
|
+
|
|
602
|
+
# Filtrar datos sensibles
|
|
603
|
+
config.before_send = lambda do |event, hint|
|
|
604
|
+
event.extra.delete(:password)
|
|
605
|
+
event
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# Capturar errores manualmente
|
|
610
|
+
begin
|
|
611
|
+
risky_operation
|
|
612
|
+
rescue => e
|
|
613
|
+
Sentry.capture_exception(e, extra: { user_id: current_user.id })
|
|
614
|
+
raise
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# Añadir contexto
|
|
618
|
+
Sentry.set_user(id: user.id, email: user.email)
|
|
619
|
+
Sentry.set_tags(feature: "checkout")
|
|
620
|
+
Sentry.set_context("order", { id: order.id, total: order.total })
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Honeybadger
|
|
624
|
+
|
|
625
|
+
```ruby
|
|
626
|
+
# Gemfile
|
|
627
|
+
gem "honeybadger"
|
|
628
|
+
|
|
629
|
+
# config/honeybadger.yml
|
|
630
|
+
api_key: <%= Rails.application.credentials.honeybadger_api_key %>
|
|
631
|
+
env: <%= Rails.env %>
|
|
632
|
+
|
|
633
|
+
# Uso
|
|
634
|
+
Honeybadger.notify(exception, context: { user_id: user.id })
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### Rollbar
|
|
638
|
+
|
|
639
|
+
```ruby
|
|
640
|
+
# Gemfile
|
|
641
|
+
gem "rollbar"
|
|
642
|
+
|
|
643
|
+
# config/initializers/rollbar.rb
|
|
644
|
+
Rollbar.configure do |config|
|
|
645
|
+
config.access_token = Rails.application.credentials.rollbar_token
|
|
646
|
+
config.environment = Rails.env
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
# Uso
|
|
650
|
+
Rollbar.error(exception, user_id: user.id)
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
## Reproducir Bugs
|
|
654
|
+
|
|
655
|
+
### Seeds para estado específico
|
|
656
|
+
|
|
657
|
+
```ruby
|
|
658
|
+
# db/seeds/bug_reproduction.rb
|
|
659
|
+
# Crear datos que reproducen el bug
|
|
660
|
+
|
|
661
|
+
user = User.create!(
|
|
662
|
+
email: "test@example.com",
|
|
663
|
+
password: "password123"
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
# Estado específico que causa el bug
|
|
667
|
+
order = Order.create!(
|
|
668
|
+
user: user,
|
|
669
|
+
status: "pending",
|
|
670
|
+
total: 0 # Edge case que causa división por cero
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
puts "Bug reproduction data created"
|
|
674
|
+
puts "User ID: #{user.id}"
|
|
675
|
+
puts "Order ID: #{order.id}"
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### Fixtures de test
|
|
679
|
+
|
|
680
|
+
```yaml
|
|
681
|
+
# spec/fixtures/users.yml
|
|
682
|
+
bug_user:
|
|
683
|
+
email: user_with_bug@example.com
|
|
684
|
+
name: Bug User
|
|
685
|
+
|
|
686
|
+
# spec/fixtures/orders.yml
|
|
687
|
+
problematic_order:
|
|
688
|
+
user: bug_user
|
|
689
|
+
status: pending
|
|
690
|
+
total: 0
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### Script de reproducción
|
|
694
|
+
|
|
695
|
+
```ruby
|
|
696
|
+
#!/usr/bin/env ruby
|
|
697
|
+
# scripts/reproduce_bug_123.rb
|
|
698
|
+
|
|
699
|
+
require_relative "../config/environment"
|
|
700
|
+
|
|
701
|
+
puts "Reproduciendo Bug #123..."
|
|
702
|
+
|
|
703
|
+
# 1. Crear estado inicial
|
|
704
|
+
user = User.create!(email: "test#{Time.now.to_i}@test.com", password: "password")
|
|
705
|
+
order = user.orders.create!(status: "pending")
|
|
706
|
+
|
|
707
|
+
# 2. Ejecutar acción que causa el bug
|
|
708
|
+
begin
|
|
709
|
+
CheckoutService.new(order).process
|
|
710
|
+
rescue => e
|
|
711
|
+
puts "Bug reproducido!"
|
|
712
|
+
puts "Error: #{e.message}"
|
|
713
|
+
puts e.backtrace.first(5).join("\n")
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
# 3. Limpiar
|
|
717
|
+
user.destroy
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### Pasos claros
|
|
721
|
+
|
|
722
|
+
```markdown
|
|
723
|
+
## Bug #123: Error al procesar orden vacía
|
|
724
|
+
|
|
725
|
+
### Pasos para reproducir:
|
|
726
|
+
1. Crear usuario: `user = User.create!(email: "test@test.com", password: "pass")`
|
|
727
|
+
2. Crear orden sin items: `order = user.orders.create!()`
|
|
728
|
+
3. Intentar checkout: `CheckoutService.new(order).process`
|
|
729
|
+
|
|
730
|
+
### Resultado esperado:
|
|
731
|
+
Error claro indicando que la orden está vacía
|
|
732
|
+
|
|
733
|
+
### Resultado actual:
|
|
734
|
+
`ZeroDivisionError: divided by 0`
|
|
735
|
+
|
|
736
|
+
### Causa raíz:
|
|
737
|
+
En `order.rb:45`, se divide total entre número de items sin verificar que no sea cero.
|
|
738
|
+
|
|
739
|
+
### Fix:
|
|
740
|
+
```ruby
|
|
741
|
+
def average_item_price
|
|
742
|
+
return 0 if line_items.empty?
|
|
743
|
+
total / line_items.count
|
|
744
|
+
end
|
|
745
|
+
```
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
## Debugging en Producción
|
|
749
|
+
|
|
750
|
+
### Logs estructurados
|
|
751
|
+
|
|
752
|
+
```ruby
|
|
753
|
+
# config/environments/production.rb
|
|
754
|
+
config.log_formatter = proc do |severity, time, progname, msg|
|
|
755
|
+
{
|
|
756
|
+
timestamp: time.iso8601,
|
|
757
|
+
level: severity,
|
|
758
|
+
message: msg,
|
|
759
|
+
app: "myapp",
|
|
760
|
+
env: Rails.env
|
|
761
|
+
}.to_json + "\n"
|
|
762
|
+
end
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
### Investigar sin reproducir
|
|
766
|
+
|
|
767
|
+
```ruby
|
|
768
|
+
# Añadir logging temporal
|
|
769
|
+
class OrderProcessor
|
|
770
|
+
def process(order)
|
|
771
|
+
Rails.logger.info({
|
|
772
|
+
event: "order_processing_start",
|
|
773
|
+
order_id: order.id,
|
|
774
|
+
user_id: order.user_id,
|
|
775
|
+
items_count: order.line_items.count,
|
|
776
|
+
total: order.total
|
|
777
|
+
}.to_json)
|
|
778
|
+
|
|
779
|
+
result = do_processing(order)
|
|
780
|
+
|
|
781
|
+
Rails.logger.info({
|
|
782
|
+
event: "order_processing_complete",
|
|
783
|
+
order_id: order.id,
|
|
784
|
+
result: result
|
|
785
|
+
}.to_json)
|
|
786
|
+
|
|
787
|
+
result
|
|
788
|
+
rescue => e
|
|
789
|
+
Rails.logger.error({
|
|
790
|
+
event: "order_processing_error",
|
|
791
|
+
order_id: order.id,
|
|
792
|
+
error: e.message,
|
|
793
|
+
backtrace: e.backtrace.first(10)
|
|
794
|
+
}.to_json)
|
|
795
|
+
|
|
796
|
+
raise
|
|
797
|
+
end
|
|
798
|
+
end
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
### Console en producción (con cuidado)
|
|
802
|
+
|
|
803
|
+
```bash
|
|
804
|
+
# Conectar a producción
|
|
805
|
+
RAILS_ENV=production rails console
|
|
806
|
+
|
|
807
|
+
# SIEMPRE usar transacciones para investigar
|
|
808
|
+
ActiveRecord::Base.transaction do
|
|
809
|
+
# Investigar...
|
|
810
|
+
order = Order.find(123)
|
|
811
|
+
order.calculate_total
|
|
812
|
+
|
|
813
|
+
raise ActiveRecord::Rollback # No guardar cambios
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
# Solo lectura
|
|
817
|
+
Order.find(123).attributes
|
|
818
|
+
User.where(created_at: 1.day.ago..).count
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
## Checklist de Debugging
|
|
822
|
+
|
|
823
|
+
### Cuando encuentras un bug:
|
|
824
|
+
|
|
825
|
+
1. [ ] Reproducir el bug de forma consistente
|
|
826
|
+
2. [ ] Aislar el problema (quitar código hasta que desaparezca)
|
|
827
|
+
3. [ ] Añadir logging/breakpoints en puntos clave
|
|
828
|
+
4. [ ] Verificar entrada vs salida esperada
|
|
829
|
+
5. [ ] Revisar código reciente (git log, git blame)
|
|
830
|
+
6. [ ] Buscar patrones similares en el código
|
|
831
|
+
7. [ ] Escribir test que falle
|
|
832
|
+
8. [ ] Aplicar fix
|
|
833
|
+
9. [ ] Verificar que el test pase
|
|
834
|
+
10. [ ] Verificar que no haya regresiones
|