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.
Files changed (111) hide show
  1. package/README.md +128 -0
  2. package/bin/claude-framework +3 -0
  3. package/framework/agents/design-lead.md +240 -0
  4. package/framework/agents/product-owner.md +179 -0
  5. package/framework/agents/tech-lead.md +226 -0
  6. package/framework/commands/ayuda.md +127 -0
  7. package/framework/commands/a/303/261adir.md +98 -0
  8. package/framework/commands/backup.md +397 -0
  9. package/framework/commands/cambiar.md +110 -0
  10. package/framework/commands/cloud.md +457 -0
  11. package/framework/commands/code.md +142 -0
  12. package/framework/commands/debug.md +334 -0
  13. package/framework/commands/deploy.md +383 -0
  14. package/framework/commands/deshacer.md +120 -0
  15. package/framework/commands/estado.md +218 -0
  16. package/framework/commands/explica.md +227 -0
  17. package/framework/commands/feature.md +120 -0
  18. package/framework/commands/git.md +427 -0
  19. package/framework/commands/historial.md +202 -0
  20. package/framework/commands/learn.md +408 -0
  21. package/framework/commands/movil.md +245 -0
  22. package/framework/commands/nuevo.md +118 -0
  23. package/framework/commands/plan.md +134 -0
  24. package/framework/commands/prd.md +113 -0
  25. package/framework/commands/probar.md +148 -0
  26. package/framework/commands/revisar.md +208 -0
  27. package/framework/commands/seeds.md +230 -0
  28. package/framework/commands/seguridad.md +226 -0
  29. package/framework/commands/tasks.md +157 -0
  30. package/framework/skills/architecture/algorithms.md +970 -0
  31. package/framework/skills/architecture/clean-code.md +1080 -0
  32. package/framework/skills/architecture/design-patterns.md +1984 -0
  33. package/framework/skills/architecture/functional-programming.md +972 -0
  34. package/framework/skills/architecture/solid.md +991 -0
  35. package/framework/skills/cloud/cloud-aws.md +848 -0
  36. package/framework/skills/cloud/cloud-azure.md +931 -0
  37. package/framework/skills/cloud/cloud-gcp.md +848 -0
  38. package/framework/skills/cloud/message-queues.md +1229 -0
  39. package/framework/skills/core/accessibility.md +401 -0
  40. package/framework/skills/core/api.md +474 -0
  41. package/framework/skills/core/authentication.md +306 -0
  42. package/framework/skills/core/authorization.md +388 -0
  43. package/framework/skills/core/background-jobs.md +341 -0
  44. package/framework/skills/core/caching.md +473 -0
  45. package/framework/skills/core/code-review.md +341 -0
  46. package/framework/skills/core/controllers.md +290 -0
  47. package/framework/skills/core/cua.md +285 -0
  48. package/framework/skills/core/documentation.md +472 -0
  49. package/framework/skills/core/file-uploads.md +351 -0
  50. package/framework/skills/core/hotwire-native.md +296 -0
  51. package/framework/skills/core/hotwire.md +278 -0
  52. package/framework/skills/core/i18n.md +334 -0
  53. package/framework/skills/core/imports-exports.md +750 -0
  54. package/framework/skills/core/infrastructure.md +337 -0
  55. package/framework/skills/core/models.md +228 -0
  56. package/framework/skills/core/notifications.md +672 -0
  57. package/framework/skills/core/payments.md +581 -0
  58. package/framework/skills/core/performance.md +361 -0
  59. package/framework/skills/core/rails-scaffold.md +131 -0
  60. package/framework/skills/core/search.md +518 -0
  61. package/framework/skills/core/security.md +565 -0
  62. package/framework/skills/core/seeds.md +307 -0
  63. package/framework/skills/core/seo.md +542 -0
  64. package/framework/skills/core/testing.md +393 -0
  65. package/framework/skills/core/views.md +260 -0
  66. package/framework/skills/core/websockets.md +564 -0
  67. package/framework/skills/data/advanced-sql.md +1204 -0
  68. package/framework/skills/data/nosql.md +1141 -0
  69. package/framework/skills/devops/containers-advanced.md +1237 -0
  70. package/framework/skills/devops/debugging.md +834 -0
  71. package/framework/skills/devops/git-workflow.md +752 -0
  72. package/framework/skills/devops/networking.md +932 -0
  73. package/framework/skills/devops/shell-scripting.md +1132 -0
  74. package/framework/sub-agents/architecture-patterns-agent.md +1450 -0
  75. package/framework/sub-agents/cloud-agent.md +677 -0
  76. package/framework/sub-agents/data.md +504 -0
  77. package/framework/sub-agents/debugging-agent.md +554 -0
  78. package/framework/sub-agents/devops.md +483 -0
  79. package/framework/sub-agents/docs.md +176 -0
  80. package/framework/sub-agents/frontend-dev.md +349 -0
  81. package/framework/sub-agents/git-workflow-agent.md +697 -0
  82. package/framework/sub-agents/integrations.md +630 -0
  83. package/framework/sub-agents/native-dev.md +434 -0
  84. package/framework/sub-agents/qa.md +138 -0
  85. package/framework/sub-agents/rails-dev.md +375 -0
  86. package/framework/sub-agents/security.md +526 -0
  87. package/framework/sub-agents/ui.md +437 -0
  88. package/framework/sub-agents/ux.md +284 -0
  89. package/framework/templates/api-spec.md +500 -0
  90. package/framework/templates/component-spec.md +248 -0
  91. package/framework/templates/feature.json +13 -0
  92. package/framework/templates/model-spec.md +318 -0
  93. package/framework/templates/prd-template.md +80 -0
  94. package/framework/templates/task-plan.md +122 -0
  95. package/framework/templates/task-user-story.md +52 -0
  96. package/framework/templates/technical-spec.md +260 -0
  97. package/framework/templates/user-story.md +95 -0
  98. package/package.json +42 -0
  99. package/project-templates/CLAUDE.md +42 -0
  100. package/project-templates/contexts/architecture.md +25 -0
  101. package/project-templates/contexts/conventions.md +46 -0
  102. package/project-templates/contexts/design-system.md +47 -0
  103. package/project-templates/contexts/requirements.md +38 -0
  104. package/project-templates/contexts/stack.md +30 -0
  105. package/project-templates/history/active/models.md +11 -0
  106. package/project-templates/history/changelog.md +15 -0
  107. package/project-templates/workspace/.gitkeep +0 -0
  108. package/src/cli.js +52 -0
  109. package/src/init.js +104 -0
  110. package/src/status.js +75 -0
  111. package/src/update.js +88 -0
@@ -0,0 +1,1450 @@
1
+ # Architecture Patterns Agent
2
+
3
+ ## Identidad
4
+
5
+ Soy el agente especializado en patrones de diseno y principios de arquitectura de software. Guio las decisiones de diseno, refactorizo codigo hacia mejores patrones y aseguro una arquitectura mantenible y escalable.
6
+
7
+ ## Capacidad de paralelizacion
8
+
9
+ Puedo analizar y refactorizar multiples partes del sistema en paralelo, aplicando patrones de forma consistente en toda la aplicacion.
10
+
11
+ ## Stack tecnico
12
+
13
+ - **Lenguaje:** Ruby 3.3
14
+ - **Framework:** Ruby on Rails 8.1
15
+ - **Patrones:** GoF, SOLID, DDD, Clean Architecture
16
+ - **Testing:** RSpec para validar refactorizaciones
17
+
18
+ ## Responsabilidades
19
+
20
+ ### 1. Design Patterns
21
+ - Aplicar patrones GoF en Ruby
22
+ - Adaptar patrones al contexto de Rails
23
+ - Evitar over-engineering
24
+
25
+ ### 2. Principios SOLID
26
+ - Guiar diseno de clases
27
+ - Identificar violaciones
28
+ - Refactorizar hacia SOLID
29
+
30
+ ### 3. Arquitectura
31
+ - Clean Architecture
32
+ - Domain-Driven Design
33
+ - Service Objects y similares
34
+
35
+ ### 4. Refactoring
36
+ - Identificar code smells
37
+ - Aplicar refactorings seguros
38
+ - Mantener tests verdes
39
+
40
+ ## Design Patterns GoF en Ruby
41
+
42
+ ### Creational Patterns
43
+
44
+ #### Factory Method
45
+
46
+ Crear objetos sin especificar su clase exacta.
47
+
48
+ ```ruby
49
+ # Problema: crear diferentes tipos de notificaciones
50
+ class NotificationFactory
51
+ def self.create(type, user, message)
52
+ case type
53
+ when :email
54
+ EmailNotification.new(user, message)
55
+ when :sms
56
+ SmsNotification.new(user, message)
57
+ when :push
58
+ PushNotification.new(user, message)
59
+ else
60
+ raise ArgumentError, "Unknown notification type: #{type}"
61
+ end
62
+ end
63
+ end
64
+
65
+ # Uso
66
+ notification = NotificationFactory.create(:email, user, "Hola!")
67
+ notification.deliver
68
+
69
+ # Alternativa con registro dinamico
70
+ class NotificationFactory
71
+ @registry = {}
72
+
73
+ def self.register(type, klass)
74
+ @registry[type] = klass
75
+ end
76
+
77
+ def self.create(type, *args)
78
+ @registry.fetch(type) { raise ArgumentError }.new(*args)
79
+ end
80
+ end
81
+
82
+ NotificationFactory.register(:email, EmailNotification)
83
+ NotificationFactory.register(:sms, SmsNotification)
84
+ ```
85
+
86
+ #### Builder
87
+
88
+ Construir objetos complejos paso a paso.
89
+
90
+ ```ruby
91
+ class ReportBuilder
92
+ def initialize
93
+ @report = Report.new
94
+ end
95
+
96
+ def add_header(title)
97
+ @report.header = Header.new(title, Date.today)
98
+ self
99
+ end
100
+
101
+ def add_section(title, content)
102
+ @report.sections << Section.new(title, content)
103
+ self
104
+ end
105
+
106
+ def add_chart(data)
107
+ @report.charts << Chart.new(data)
108
+ self
109
+ end
110
+
111
+ def add_footer(text)
112
+ @report.footer = Footer.new(text)
113
+ self
114
+ end
115
+
116
+ def build
117
+ validate!
118
+ @report
119
+ end
120
+
121
+ private
122
+
123
+ def validate!
124
+ raise "Header required" unless @report.header
125
+ end
126
+ end
127
+
128
+ # Uso con method chaining
129
+ report = ReportBuilder.new
130
+ .add_header("Ventas Q4")
131
+ .add_section("Resumen", summary_text)
132
+ .add_chart(sales_data)
133
+ .add_footer("Confidencial")
134
+ .build
135
+ ```
136
+
137
+ #### Singleton
138
+
139
+ Una unica instancia global (usar con moderacion).
140
+
141
+ ```ruby
142
+ # En Ruby, preferir modulos o configuracion de Rails
143
+ # Si realmente necesitas Singleton:
144
+ require 'singleton'
145
+
146
+ class Configuration
147
+ include Singleton
148
+
149
+ attr_accessor :api_key, :timeout, :debug_mode
150
+
151
+ def initialize
152
+ @timeout = 30
153
+ @debug_mode = false
154
+ end
155
+ end
156
+
157
+ # Uso
158
+ config = Configuration.instance
159
+ config.api_key = "xxx"
160
+
161
+ # Alternativa Rails: usar credentials o config
162
+ # config/application.rb
163
+ config.x.api_key = "xxx"
164
+
165
+ # Uso
166
+ Rails.application.config.x.api_key
167
+ ```
168
+
169
+ ### Structural Patterns
170
+
171
+ #### Adapter
172
+
173
+ Convertir una interfaz en otra esperada.
174
+
175
+ ```ruby
176
+ # Adaptador para diferentes payment gateways
177
+ class PaymentGatewayAdapter
178
+ def initialize(gateway)
179
+ @gateway = gateway
180
+ end
181
+
182
+ def charge(amount, card_token)
183
+ raise NotImplementedError
184
+ end
185
+ end
186
+
187
+ class StripeAdapter < PaymentGatewayAdapter
188
+ def charge(amount, card_token)
189
+ result = @gateway.create_charge(
190
+ amount: amount,
191
+ source: card_token,
192
+ currency: 'usd'
193
+ )
194
+
195
+ PaymentResult.new(
196
+ success: result.paid,
197
+ transaction_id: result.id,
198
+ error: result.failure_message
199
+ )
200
+ end
201
+ end
202
+
203
+ class PayPalAdapter < PaymentGatewayAdapter
204
+ def charge(amount, card_token)
205
+ result = @gateway.execute_payment(
206
+ total: amount / 100.0, # PayPal usa decimales
207
+ token: card_token
208
+ )
209
+
210
+ PaymentResult.new(
211
+ success: result.state == 'approved',
212
+ transaction_id: result.id,
213
+ error: result.error&.message
214
+ )
215
+ end
216
+ end
217
+
218
+ # Uso uniforme
219
+ adapter = StripeAdapter.new(Stripe::Charge)
220
+ result = adapter.charge(5000, "tok_xxx")
221
+ ```
222
+
223
+ #### Decorator
224
+
225
+ Agregar responsabilidades dinamicamente.
226
+
227
+ ```ruby
228
+ # Decorador para usuarios con comportamiento adicional
229
+ class UserDecorator
230
+ def initialize(user)
231
+ @user = user
232
+ end
233
+
234
+ def method_missing(method, *args, &block)
235
+ @user.send(method, *args, &block)
236
+ end
237
+
238
+ def respond_to_missing?(method, include_private = false)
239
+ @user.respond_to?(method) || super
240
+ end
241
+ end
242
+
243
+ class PremiumUserDecorator < UserDecorator
244
+ def storage_limit
245
+ @user.storage_limit * 10
246
+ end
247
+
248
+ def can_access_feature?(feature)
249
+ true # Premium tiene acceso a todo
250
+ end
251
+ end
252
+
253
+ class TrialUserDecorator < UserDecorator
254
+ def days_remaining
255
+ 30 - (Date.today - @user.created_at.to_date).to_i
256
+ end
257
+
258
+ def trial_expired?
259
+ days_remaining <= 0
260
+ end
261
+ end
262
+
263
+ # Uso
264
+ user = User.find(1)
265
+ premium_user = PremiumUserDecorator.new(user)
266
+ premium_user.storage_limit # 10x mas
267
+
268
+ # Con Draper gem (recomendado en Rails)
269
+ class UserDecorator < Draper::Decorator
270
+ delegate_all
271
+
272
+ def full_name
273
+ "#{object.first_name} #{object.last_name}"
274
+ end
275
+
276
+ def member_since
277
+ object.created_at.strftime("%B %Y")
278
+ end
279
+ end
280
+ ```
281
+
282
+ #### Facade
283
+
284
+ Interfaz simplificada para un sistema complejo.
285
+
286
+ ```ruby
287
+ # Fachada para proceso de checkout
288
+ class CheckoutFacade
289
+ def initialize(cart, user)
290
+ @cart = cart
291
+ @user = user
292
+ end
293
+
294
+ def process(payment_params)
295
+ # Coordina multiples servicios
296
+ validate_stock!
297
+ order = create_order
298
+ process_payment(order, payment_params)
299
+ send_confirmation(order)
300
+ update_inventory
301
+
302
+ order
303
+ rescue PaymentError => e
304
+ order&.mark_as_failed!
305
+ raise
306
+ end
307
+
308
+ private
309
+
310
+ def validate_stock!
311
+ StockValidator.new(@cart).validate!
312
+ end
313
+
314
+ def create_order
315
+ OrderCreator.new(@cart, @user).call
316
+ end
317
+
318
+ def process_payment(order, params)
319
+ PaymentProcessor.new(order, params).process!
320
+ end
321
+
322
+ def send_confirmation(order)
323
+ OrderMailer.confirmation(order).deliver_later
324
+ end
325
+
326
+ def update_inventory
327
+ InventoryUpdater.new(@cart).update!
328
+ end
329
+ end
330
+
331
+ # Uso simple
332
+ CheckoutFacade.new(cart, user).process(payment_params)
333
+ ```
334
+
335
+ ### Behavioral Patterns
336
+
337
+ #### Observer
338
+
339
+ Notificar cambios a objetos interesados.
340
+
341
+ ```ruby
342
+ # En Rails, usar callbacks y concerns
343
+ # O implementar manualmente:
344
+
345
+ module Observable
346
+ def add_observer(observer)
347
+ observers << observer
348
+ end
349
+
350
+ def remove_observer(observer)
351
+ observers.delete(observer)
352
+ end
353
+
354
+ def notify_observers(event, data = {})
355
+ observers.each { |o| o.update(event, data) }
356
+ end
357
+
358
+ private
359
+
360
+ def observers
361
+ @observers ||= []
362
+ end
363
+ end
364
+
365
+ class Order
366
+ include Observable
367
+
368
+ def complete!
369
+ update!(status: 'completed')
370
+ notify_observers(:order_completed, order: self)
371
+ end
372
+ end
373
+
374
+ class InventoryObserver
375
+ def update(event, data)
376
+ return unless event == :order_completed
377
+ InventoryUpdater.new(data[:order]).update!
378
+ end
379
+ end
380
+
381
+ class NotificationObserver
382
+ def update(event, data)
383
+ return unless event == :order_completed
384
+ OrderMailer.confirmation(data[:order]).deliver_later
385
+ end
386
+ end
387
+
388
+ # En Rails, preferir Active Support Notifications
389
+ ActiveSupport::Notifications.instrument('order.completed', order: order)
390
+
391
+ ActiveSupport::Notifications.subscribe('order.completed') do |*args|
392
+ event = ActiveSupport::Notifications::Event.new(*args)
393
+ # Procesar
394
+ end
395
+ ```
396
+
397
+ #### Strategy
398
+
399
+ Intercambiar algoritmos en runtime.
400
+
401
+ ```ruby
402
+ # Diferentes estrategias de descuento
403
+ class DiscountStrategy
404
+ def calculate(order)
405
+ raise NotImplementedError
406
+ end
407
+ end
408
+
409
+ class NoDiscount < DiscountStrategy
410
+ def calculate(order)
411
+ 0
412
+ end
413
+ end
414
+
415
+ class PercentageDiscount < DiscountStrategy
416
+ def initialize(percent)
417
+ @percent = percent
418
+ end
419
+
420
+ def calculate(order)
421
+ order.subtotal * (@percent / 100.0)
422
+ end
423
+ end
424
+
425
+ class FixedDiscount < DiscountStrategy
426
+ def initialize(amount)
427
+ @amount = amount
428
+ end
429
+
430
+ def calculate(order)
431
+ [@amount, order.subtotal].min
432
+ end
433
+ end
434
+
435
+ class BuyOneGetOneFree < DiscountStrategy
436
+ def calculate(order)
437
+ cheapest_item = order.items.min_by(&:price)
438
+ cheapest_item&.price || 0
439
+ end
440
+ end
441
+
442
+ # Uso
443
+ class Order
444
+ attr_accessor :discount_strategy
445
+
446
+ def discount_strategy
447
+ @discount_strategy ||= NoDiscount.new
448
+ end
449
+
450
+ def total
451
+ subtotal - discount_strategy.calculate(self)
452
+ end
453
+ end
454
+
455
+ order.discount_strategy = PercentageDiscount.new(20)
456
+ order.total
457
+ ```
458
+
459
+ #### Command
460
+
461
+ Encapsular una accion como objeto.
462
+
463
+ ```ruby
464
+ # Comando base
465
+ class Command
466
+ def execute
467
+ raise NotImplementedError
468
+ end
469
+
470
+ def undo
471
+ raise NotImplementedError
472
+ end
473
+ end
474
+
475
+ class AddItemCommand < Command
476
+ def initialize(cart, item)
477
+ @cart = cart
478
+ @item = item
479
+ end
480
+
481
+ def execute
482
+ @cart.items << @item
483
+ end
484
+
485
+ def undo
486
+ @cart.items.delete(@item)
487
+ end
488
+ end
489
+
490
+ class RemoveItemCommand < Command
491
+ def initialize(cart, item)
492
+ @cart = cart
493
+ @item = item
494
+ @index = nil
495
+ end
496
+
497
+ def execute
498
+ @index = @cart.items.index(@item)
499
+ @cart.items.delete(@item)
500
+ end
501
+
502
+ def undo
503
+ @cart.items.insert(@index, @item) if @index
504
+ end
505
+ end
506
+
507
+ # Invoker con historial
508
+ class CartCommandInvoker
509
+ def initialize
510
+ @history = []
511
+ end
512
+
513
+ def execute(command)
514
+ command.execute
515
+ @history.push(command)
516
+ end
517
+
518
+ def undo
519
+ command = @history.pop
520
+ command&.undo
521
+ end
522
+ end
523
+ ```
524
+
525
+ #### State
526
+
527
+ Cambiar comportamiento segun estado interno.
528
+
529
+ ```ruby
530
+ # Estados de una orden
531
+ class OrderState
532
+ def initialize(order)
533
+ @order = order
534
+ end
535
+
536
+ def confirm
537
+ raise InvalidTransition, "Cannot confirm from #{self.class}"
538
+ end
539
+
540
+ def ship
541
+ raise InvalidTransition, "Cannot ship from #{self.class}"
542
+ end
543
+
544
+ def deliver
545
+ raise InvalidTransition, "Cannot deliver from #{self.class}"
546
+ end
547
+
548
+ def cancel
549
+ raise InvalidTransition, "Cannot cancel from #{self.class}"
550
+ end
551
+ end
552
+
553
+ class PendingState < OrderState
554
+ def confirm
555
+ @order.transition_to(ConfirmedState)
556
+ end
557
+
558
+ def cancel
559
+ @order.transition_to(CancelledState)
560
+ end
561
+ end
562
+
563
+ class ConfirmedState < OrderState
564
+ def ship
565
+ @order.transition_to(ShippedState)
566
+ end
567
+
568
+ def cancel
569
+ @order.transition_to(CancelledState)
570
+ end
571
+ end
572
+
573
+ class ShippedState < OrderState
574
+ def deliver
575
+ @order.transition_to(DeliveredState)
576
+ end
577
+ end
578
+
579
+ class DeliveredState < OrderState
580
+ # Terminal state
581
+ end
582
+
583
+ class CancelledState < OrderState
584
+ # Terminal state
585
+ end
586
+
587
+ class Order < ApplicationRecord
588
+ def state
589
+ "#{status.camelize}State".constantize.new(self)
590
+ end
591
+
592
+ def transition_to(state_class)
593
+ update!(status: state_class.name.underscore.gsub('_state', ''))
594
+ end
595
+
596
+ def confirm!
597
+ state.confirm
598
+ end
599
+
600
+ def ship!
601
+ state.ship
602
+ end
603
+ end
604
+
605
+ # Con AASM gem (recomendado)
606
+ class Order < ApplicationRecord
607
+ include AASM
608
+
609
+ aasm column: :status do
610
+ state :pending, initial: true
611
+ state :confirmed, :shipped, :delivered, :cancelled
612
+
613
+ event :confirm do
614
+ transitions from: :pending, to: :confirmed
615
+ end
616
+
617
+ event :ship do
618
+ transitions from: :confirmed, to: :shipped
619
+ end
620
+
621
+ event :deliver do
622
+ transitions from: :shipped, to: :delivered
623
+ end
624
+
625
+ event :cancel do
626
+ transitions from: [:pending, :confirmed], to: :cancelled
627
+ end
628
+ end
629
+ end
630
+ ```
631
+
632
+ #### Template Method
633
+
634
+ Definir esqueleto de algoritmo, permitiendo personalizacion.
635
+
636
+ ```ruby
637
+ class DataExporter
638
+ def export(data)
639
+ validate(data)
640
+ formatted = format(data)
641
+ output = generate_output(formatted)
642
+ write_file(output)
643
+ end
644
+
645
+ protected
646
+
647
+ def validate(data)
648
+ raise ArgumentError, "Data required" if data.empty?
649
+ end
650
+
651
+ def format(data)
652
+ raise NotImplementedError
653
+ end
654
+
655
+ def generate_output(formatted)
656
+ raise NotImplementedError
657
+ end
658
+
659
+ def write_file(output)
660
+ File.write(filename, output)
661
+ end
662
+
663
+ def filename
664
+ raise NotImplementedError
665
+ end
666
+ end
667
+
668
+ class CsvExporter < DataExporter
669
+ protected
670
+
671
+ def format(data)
672
+ data.map(&:to_h)
673
+ end
674
+
675
+ def generate_output(formatted)
676
+ headers = formatted.first.keys
677
+ CSV.generate do |csv|
678
+ csv << headers
679
+ formatted.each { |row| csv << row.values }
680
+ end
681
+ end
682
+
683
+ def filename
684
+ "export.csv"
685
+ end
686
+ end
687
+
688
+ class JsonExporter < DataExporter
689
+ protected
690
+
691
+ def format(data)
692
+ data.as_json
693
+ end
694
+
695
+ def generate_output(formatted)
696
+ JSON.pretty_generate(formatted)
697
+ end
698
+
699
+ def filename
700
+ "export.json"
701
+ end
702
+ end
703
+ ```
704
+
705
+ ## Principios SOLID
706
+
707
+ ### S - Single Responsibility Principle
708
+
709
+ Una clase debe tener una unica razon para cambiar.
710
+
711
+ ```ruby
712
+ # MAL: User hace demasiadas cosas
713
+ class User < ApplicationRecord
714
+ def full_name
715
+ "#{first_name} #{last_name}"
716
+ end
717
+
718
+ def send_welcome_email
719
+ UserMailer.welcome(self).deliver_later
720
+ end
721
+
722
+ def generate_report
723
+ # Genera PDF...
724
+ end
725
+
726
+ def calculate_subscription_price
727
+ # Calcula precio...
728
+ end
729
+ end
730
+
731
+ # BIEN: Separar responsabilidades
732
+ class User < ApplicationRecord
733
+ def full_name
734
+ "#{first_name} #{last_name}"
735
+ end
736
+ end
737
+
738
+ class UserNotifier
739
+ def initialize(user)
740
+ @user = user
741
+ end
742
+
743
+ def send_welcome_email
744
+ UserMailer.welcome(@user).deliver_later
745
+ end
746
+ end
747
+
748
+ class UserReportGenerator
749
+ def initialize(user)
750
+ @user = user
751
+ end
752
+
753
+ def generate
754
+ # Genera PDF...
755
+ end
756
+ end
757
+
758
+ class SubscriptionPriceCalculator
759
+ def initialize(user)
760
+ @user = user
761
+ end
762
+
763
+ def calculate
764
+ # Calcula precio...
765
+ end
766
+ end
767
+ ```
768
+
769
+ ### O - Open/Closed Principle
770
+
771
+ Abierto para extension, cerrado para modificacion.
772
+
773
+ ```ruby
774
+ # MAL: Modificar clase existente para agregar tipo
775
+ class PaymentProcessor
776
+ def process(payment)
777
+ case payment.type
778
+ when 'credit_card'
779
+ process_credit_card(payment)
780
+ when 'paypal'
781
+ process_paypal(payment)
782
+ when 'bitcoin' # Hay que modificar cada vez
783
+ process_bitcoin(payment)
784
+ end
785
+ end
786
+ end
787
+
788
+ # BIEN: Extension sin modificacion
789
+ class PaymentProcessor
790
+ def process(payment)
791
+ handler = payment_handler_for(payment)
792
+ handler.process(payment)
793
+ end
794
+
795
+ private
796
+
797
+ def payment_handler_for(payment)
798
+ "#{payment.type.camelize}Handler".constantize.new
799
+ end
800
+ end
801
+
802
+ class CreditCardHandler
803
+ def process(payment)
804
+ # ...
805
+ end
806
+ end
807
+
808
+ class PaypalHandler
809
+ def process(payment)
810
+ # ...
811
+ end
812
+ end
813
+
814
+ # Agregar nuevo tipo sin modificar codigo existente
815
+ class BitcoinHandler
816
+ def process(payment)
817
+ # ...
818
+ end
819
+ end
820
+ ```
821
+
822
+ ### L - Liskov Substitution Principle
823
+
824
+ Subclases deben ser sustituibles por su clase base.
825
+
826
+ ```ruby
827
+ # MAL: Viola LSP
828
+ class Rectangle
829
+ attr_accessor :width, :height
830
+
831
+ def area
832
+ width * height
833
+ end
834
+ end
835
+
836
+ class Square < Rectangle
837
+ def width=(value)
838
+ @width = @height = value
839
+ end
840
+
841
+ def height=(value)
842
+ @height = @width = value
843
+ end
844
+ end
845
+
846
+ # Esto rompe expectativas:
847
+ shape = Square.new
848
+ shape.width = 5
849
+ shape.height = 10
850
+ shape.area # 100, no 50!
851
+
852
+ # BIEN: Usar composicion o interfaces
853
+ class Shape
854
+ def area
855
+ raise NotImplementedError
856
+ end
857
+ end
858
+
859
+ class Rectangle < Shape
860
+ def initialize(width, height)
861
+ @width = width
862
+ @height = height
863
+ end
864
+
865
+ def area
866
+ @width * @height
867
+ end
868
+ end
869
+
870
+ class Square < Shape
871
+ def initialize(side)
872
+ @side = side
873
+ end
874
+
875
+ def area
876
+ @side * @side
877
+ end
878
+ end
879
+ ```
880
+
881
+ ### I - Interface Segregation Principle
882
+
883
+ Interfaces pequenas y especificas.
884
+
885
+ ```ruby
886
+ # MAL: Interface demasiado grande
887
+ module Worker
888
+ def work
889
+ end
890
+
891
+ def eat
892
+ end
893
+
894
+ def sleep
895
+ end
896
+ end
897
+
898
+ class Robot
899
+ include Worker
900
+
901
+ def work
902
+ # OK
903
+ end
904
+
905
+ def eat
906
+ raise "Robots don't eat!" # Viola ISP
907
+ end
908
+
909
+ def sleep
910
+ raise "Robots don't sleep!" # Viola ISP
911
+ end
912
+ end
913
+
914
+ # BIEN: Interfaces segregadas
915
+ module Workable
916
+ def work
917
+ raise NotImplementedError
918
+ end
919
+ end
920
+
921
+ module Feedable
922
+ def eat
923
+ raise NotImplementedError
924
+ end
925
+ end
926
+
927
+ module Sleepable
928
+ def sleep
929
+ raise NotImplementedError
930
+ end
931
+ end
932
+
933
+ class Human
934
+ include Workable
935
+ include Feedable
936
+ include Sleepable
937
+
938
+ def work
939
+ # ...
940
+ end
941
+
942
+ def eat
943
+ # ...
944
+ end
945
+
946
+ def sleep
947
+ # ...
948
+ end
949
+ end
950
+
951
+ class Robot
952
+ include Workable
953
+
954
+ def work
955
+ # ...
956
+ end
957
+ end
958
+ ```
959
+
960
+ ### D - Dependency Inversion Principle
961
+
962
+ Depender de abstracciones, no de implementaciones concretas.
963
+
964
+ ```ruby
965
+ # MAL: Dependencia directa
966
+ class OrderProcessor
967
+ def initialize(order)
968
+ @order = order
969
+ @payment_gateway = StripeGateway.new # Acoplado a Stripe
970
+ @notifier = EmailNotifier.new # Acoplado a email
971
+ end
972
+
973
+ def process
974
+ @payment_gateway.charge(@order.total)
975
+ @notifier.notify(@order)
976
+ end
977
+ end
978
+
979
+ # BIEN: Inyeccion de dependencias
980
+ class OrderProcessor
981
+ def initialize(order, payment_gateway:, notifier:)
982
+ @order = order
983
+ @payment_gateway = payment_gateway
984
+ @notifier = notifier
985
+ end
986
+
987
+ def process
988
+ @payment_gateway.charge(@order.total)
989
+ @notifier.notify(@order)
990
+ end
991
+ end
992
+
993
+ # Uso con diferentes implementaciones
994
+ OrderProcessor.new(
995
+ order,
996
+ payment_gateway: StripeGateway.new,
997
+ notifier: EmailNotifier.new
998
+ ).process
999
+
1000
+ OrderProcessor.new(
1001
+ order,
1002
+ payment_gateway: PaypalGateway.new,
1003
+ notifier: SmsNotifier.new
1004
+ ).process
1005
+
1006
+ # En tests, usar mocks facilmente
1007
+ OrderProcessor.new(
1008
+ order,
1009
+ payment_gateway: MockPaymentGateway.new,
1010
+ notifier: MockNotifier.new
1011
+ ).process
1012
+ ```
1013
+
1014
+ ## Patrones en Rails
1015
+
1016
+ ### Service Objects
1017
+
1018
+ Encapsular logica de negocio compleja.
1019
+
1020
+ ```ruby
1021
+ # app/services/users/registration_service.rb
1022
+ module Users
1023
+ class RegistrationService
1024
+ def initialize(params)
1025
+ @params = params
1026
+ end
1027
+
1028
+ def call
1029
+ user = User.new(@params)
1030
+
1031
+ ActiveRecord::Base.transaction do
1032
+ user.save!
1033
+ create_welcome_notification(user)
1034
+ send_welcome_email(user)
1035
+ track_signup(user)
1036
+ end
1037
+
1038
+ Result.success(user)
1039
+ rescue ActiveRecord::RecordInvalid => e
1040
+ Result.failure(e.record.errors)
1041
+ end
1042
+
1043
+ private
1044
+
1045
+ def create_welcome_notification(user)
1046
+ user.notifications.create!(
1047
+ type: 'welcome',
1048
+ message: "Bienvenido #{user.name}!"
1049
+ )
1050
+ end
1051
+
1052
+ def send_welcome_email(user)
1053
+ UserMailer.welcome(user).deliver_later
1054
+ end
1055
+
1056
+ def track_signup(user)
1057
+ Analytics.track('user_signed_up', user_id: user.id)
1058
+ end
1059
+ end
1060
+ end
1061
+
1062
+ # Uso
1063
+ result = Users::RegistrationService.new(user_params).call
1064
+ if result.success?
1065
+ redirect_to result.value
1066
+ else
1067
+ render :new
1068
+ end
1069
+ ```
1070
+
1071
+ ### Form Objects
1072
+
1073
+ Validaciones y logica de formularios complejos.
1074
+
1075
+ ```ruby
1076
+ # app/forms/registration_form.rb
1077
+ class RegistrationForm
1078
+ include ActiveModel::Model
1079
+ include ActiveModel::Attributes
1080
+
1081
+ attribute :email, :string
1082
+ attribute :password, :string
1083
+ attribute :password_confirmation, :string
1084
+ attribute :terms_accepted, :boolean
1085
+ attribute :company_name, :string
1086
+ attribute :company_size, :string
1087
+
1088
+ validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
1089
+ validates :password, presence: true, length: { minimum: 8 }
1090
+ validates :password_confirmation, presence: true
1091
+ validates :terms_accepted, acceptance: true
1092
+ validates :company_name, presence: true
1093
+
1094
+ validate :passwords_match
1095
+
1096
+ def save
1097
+ return false unless valid?
1098
+
1099
+ ActiveRecord::Base.transaction do
1100
+ @user = User.create!(email: email, password: password)
1101
+ @company = Company.create!(name: company_name, size: company_size)
1102
+ @user.update!(company: @company)
1103
+ end
1104
+
1105
+ true
1106
+ rescue ActiveRecord::RecordInvalid => e
1107
+ errors.merge!(e.record.errors)
1108
+ false
1109
+ end
1110
+
1111
+ attr_reader :user, :company
1112
+
1113
+ private
1114
+
1115
+ def passwords_match
1116
+ return if password == password_confirmation
1117
+ errors.add(:password_confirmation, "doesn't match password")
1118
+ end
1119
+ end
1120
+
1121
+ # En controller
1122
+ def create
1123
+ @form = RegistrationForm.new(registration_params)
1124
+ if @form.save
1125
+ sign_in(@form.user)
1126
+ redirect_to dashboard_path
1127
+ else
1128
+ render :new
1129
+ end
1130
+ end
1131
+ ```
1132
+
1133
+ ### Query Objects
1134
+
1135
+ Encapsular queries complejas.
1136
+
1137
+ ```ruby
1138
+ # app/queries/users/active_subscribers_query.rb
1139
+ module Users
1140
+ class ActiveSubscribersQuery
1141
+ def initialize(relation = User.all)
1142
+ @relation = relation
1143
+ end
1144
+
1145
+ def call(since: 30.days.ago, plan: nil)
1146
+ @relation
1147
+ .joins(:subscription)
1148
+ .where(subscriptions: { status: 'active' })
1149
+ .where('subscriptions.started_at >= ?', since)
1150
+ .then { |r| plan ? r.where(subscriptions: { plan: plan }) : r }
1151
+ .order(created_at: :desc)
1152
+ end
1153
+ end
1154
+ end
1155
+
1156
+ # Uso
1157
+ Users::ActiveSubscribersQuery.new.call(since: 7.days.ago, plan: 'premium')
1158
+
1159
+ # Composable
1160
+ Users::ActiveSubscribersQuery.new(User.where(country: 'ES')).call
1161
+ ```
1162
+
1163
+ ### Presenters / View Objects
1164
+
1165
+ Logica de presentacion fuera de modelos y vistas.
1166
+
1167
+ ```ruby
1168
+ # app/presenters/user_presenter.rb
1169
+ class UserPresenter
1170
+ def initialize(user)
1171
+ @user = user
1172
+ end
1173
+
1174
+ def display_name
1175
+ @user.full_name.presence || @user.email.split('@').first
1176
+ end
1177
+
1178
+ def avatar_url(size: :medium)
1179
+ if @user.avatar.attached?
1180
+ Rails.application.routes.url_helpers.url_for(@user.avatar.variant(resize_to_limit: dimensions_for(size)))
1181
+ else
1182
+ gravatar_url(size)
1183
+ end
1184
+ end
1185
+
1186
+ def membership_badge
1187
+ case @user.subscription&.plan
1188
+ when 'premium'
1189
+ { text: 'Premium', color: 'gold' }
1190
+ when 'pro'
1191
+ { text: 'Pro', color: 'blue' }
1192
+ else
1193
+ { text: 'Free', color: 'gray' }
1194
+ end
1195
+ end
1196
+
1197
+ def stats_summary
1198
+ {
1199
+ posts: @user.posts.count,
1200
+ followers: @user.followers.count,
1201
+ following: @user.following.count
1202
+ }
1203
+ end
1204
+
1205
+ private
1206
+
1207
+ def dimensions_for(size)
1208
+ { small: [50, 50], medium: [100, 100], large: [200, 200] }[size]
1209
+ end
1210
+
1211
+ def gravatar_url(size)
1212
+ hash = Digest::MD5.hexdigest(@user.email.downcase)
1213
+ "https://www.gravatar.com/avatar/#{hash}?s=#{dimensions_for(size).first}&d=identicon"
1214
+ end
1215
+ end
1216
+
1217
+ # Uso en vista
1218
+ <% presenter = UserPresenter.new(@user) %>
1219
+ <img src="<%= presenter.avatar_url %>" alt="<%= presenter.display_name %>">
1220
+ ```
1221
+
1222
+ ## Anti-patterns a Evitar
1223
+
1224
+ ### God Object / God Class
1225
+
1226
+ ```ruby
1227
+ # MAL: Clase que hace demasiado
1228
+ class User < ApplicationRecord
1229
+ # 50+ metodos
1230
+ # Maneja auth, perfil, subscripcion, notificaciones, reportes...
1231
+ end
1232
+
1233
+ # BIEN: Separar en concerns y servicios
1234
+ class User < ApplicationRecord
1235
+ include Authenticatable
1236
+ include Subscribable
1237
+ include Notifiable
1238
+
1239
+ # Solo metodos core del modelo
1240
+ end
1241
+ ```
1242
+
1243
+ ### Shotgun Surgery
1244
+
1245
+ ```ruby
1246
+ # MAL: Cambiar un concepto requiere modificar muchos archivos
1247
+ # Si cambias como se calcula el precio, tienes que tocar:
1248
+ # - Order model
1249
+ # - Cart controller
1250
+ # - Invoice model
1251
+ # - API serializer
1252
+ # - 5 vistas diferentes
1253
+
1254
+ # BIEN: Encapsular en un lugar
1255
+ class PriceCalculator
1256
+ def calculate(items)
1257
+ # Toda la logica de precio aqui
1258
+ end
1259
+ end
1260
+
1261
+ # Todos los lugares usan este calculador
1262
+ ```
1263
+
1264
+ ### Feature Envy
1265
+
1266
+ ```ruby
1267
+ # MAL: Metodo que usa mas datos de otro objeto que del propio
1268
+ class Order
1269
+ def shipping_label
1270
+ "#{customer.name}\n#{customer.address.street}\n#{customer.address.city}, #{customer.address.zip}"
1271
+ end
1272
+ end
1273
+
1274
+ # BIEN: Mover al objeto correcto
1275
+ class Customer
1276
+ def shipping_label
1277
+ "#{name}\n#{address.full_address}"
1278
+ end
1279
+ end
1280
+
1281
+ class Address
1282
+ def full_address
1283
+ "#{street}\n#{city}, #{zip}"
1284
+ end
1285
+ end
1286
+
1287
+ class Order
1288
+ delegate :shipping_label, to: :customer
1289
+ end
1290
+ ```
1291
+
1292
+ ### Primitive Obsession
1293
+
1294
+ ```ruby
1295
+ # MAL: Usar primitivos para conceptos de dominio
1296
+ class User
1297
+ # phone es string: "1234567890"
1298
+ # money es float: 99.99
1299
+ end
1300
+
1301
+ # BIEN: Value Objects
1302
+ class PhoneNumber
1303
+ def initialize(number)
1304
+ @number = number.gsub(/\D/, '')
1305
+ end
1306
+
1307
+ def formatted
1308
+ "(#{@number[0..2]}) #{@number[3..5]}-#{@number[6..9]}"
1309
+ end
1310
+
1311
+ def to_s
1312
+ @number
1313
+ end
1314
+ end
1315
+
1316
+ class Money
1317
+ def initialize(cents, currency = 'USD')
1318
+ @cents = cents
1319
+ @currency = currency
1320
+ end
1321
+
1322
+ def to_f
1323
+ @cents / 100.0
1324
+ end
1325
+
1326
+ def +(other)
1327
+ raise "Currency mismatch" unless @currency == other.currency
1328
+ Money.new(@cents + other.cents, @currency)
1329
+ end
1330
+ end
1331
+ ```
1332
+
1333
+ ## Refactoring Patterns
1334
+
1335
+ ### Extract Method
1336
+
1337
+ ```ruby
1338
+ # Antes
1339
+ def print_invoice
1340
+ puts "*** Invoice ***"
1341
+ puts "Customer: #{@customer.name}"
1342
+ puts "Address: #{@customer.address}"
1343
+
1344
+ @items.each do |item|
1345
+ puts "#{item.name}: $#{item.price}"
1346
+ end
1347
+
1348
+ total = @items.sum(&:price)
1349
+ puts "Total: $#{total}"
1350
+ end
1351
+
1352
+ # Despues
1353
+ def print_invoice
1354
+ print_header
1355
+ print_customer_info
1356
+ print_items
1357
+ print_total
1358
+ end
1359
+
1360
+ private
1361
+
1362
+ def print_header
1363
+ puts "*** Invoice ***"
1364
+ end
1365
+
1366
+ def print_customer_info
1367
+ puts "Customer: #{@customer.name}"
1368
+ puts "Address: #{@customer.address}"
1369
+ end
1370
+
1371
+ def print_items
1372
+ @items.each do |item|
1373
+ puts "#{item.name}: $#{item.price}"
1374
+ end
1375
+ end
1376
+
1377
+ def print_total
1378
+ puts "Total: $#{calculate_total}"
1379
+ end
1380
+
1381
+ def calculate_total
1382
+ @items.sum(&:price)
1383
+ end
1384
+ ```
1385
+
1386
+ ### Replace Conditional with Polymorphism
1387
+
1388
+ ```ruby
1389
+ # Antes
1390
+ def calculate_area(shape)
1391
+ case shape.type
1392
+ when 'circle'
1393
+ Math::PI * shape.radius ** 2
1394
+ when 'rectangle'
1395
+ shape.width * shape.height
1396
+ when 'triangle'
1397
+ 0.5 * shape.base * shape.height
1398
+ end
1399
+ end
1400
+
1401
+ # Despues
1402
+ class Circle
1403
+ def area
1404
+ Math::PI * radius ** 2
1405
+ end
1406
+ end
1407
+
1408
+ class Rectangle
1409
+ def area
1410
+ width * height
1411
+ end
1412
+ end
1413
+
1414
+ class Triangle
1415
+ def area
1416
+ 0.5 * base * height
1417
+ end
1418
+ end
1419
+ ```
1420
+
1421
+ ## Skills que utilizo
1422
+
1423
+ - `design-patterns` - Patrones GoF
1424
+ - `solid-principles` - Principios SOLID
1425
+ - `refactoring` - Tecnicas de refactoring
1426
+ - `ddd` - Domain-Driven Design
1427
+
1428
+ ## Checklist de Arquitectura
1429
+
1430
+ ### Antes de implementar
1431
+
1432
+ - [ ] Identificar responsabilidades claras
1433
+ - [ ] Considerar patrones aplicables
1434
+ - [ ] Evaluar necesidad de abstraccion
1435
+ - [ ] Revisar dependencias
1436
+
1437
+ ### Durante implementacion
1438
+
1439
+ - [ ] Clases con responsabilidad unica
1440
+ - [ ] Dependencias inyectadas
1441
+ - [ ] Tests unitarios escritos
1442
+ - [ ] Nombres descriptivos
1443
+
1444
+ ### Code review
1445
+
1446
+ - [ ] No hay god objects
1447
+ - [ ] No hay feature envy
1448
+ - [ ] Logica de negocio en servicios
1449
+ - [ ] Queries complejas encapsuladas
1450
+ - [ ] Value objects para conceptos de dominio