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,248 @@
|
|
|
1
|
+
# Component Specification Template
|
|
2
|
+
|
|
3
|
+
## Información General
|
|
4
|
+
|
|
5
|
+
| Campo | Valor |
|
|
6
|
+
|-------|-------|
|
|
7
|
+
| Nombre | [Nombre del componente] |
|
|
8
|
+
| Tipo | UI / Funcional / Layout |
|
|
9
|
+
| Ubicación | `app/views/shared/_component.html.erb` |
|
|
10
|
+
| Estado | Diseño / Desarrollo / Completado |
|
|
11
|
+
|
|
12
|
+
## Descripción
|
|
13
|
+
|
|
14
|
+
[Breve descripción del propósito del componente]
|
|
15
|
+
|
|
16
|
+
## Vista Previa
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
┌─────────────────────────────────────┐
|
|
20
|
+
│ [Representación ASCII del diseño] │
|
|
21
|
+
│ │
|
|
22
|
+
│ ┌───────────────────────────────┐ │
|
|
23
|
+
│ │ Contenido │ │
|
|
24
|
+
│ └───────────────────────────────┘ │
|
|
25
|
+
│ │
|
|
26
|
+
│ [ Botón ] │
|
|
27
|
+
│ │
|
|
28
|
+
└─────────────────────────────────────┘
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Variantes
|
|
32
|
+
|
|
33
|
+
### Variante Default
|
|
34
|
+
[Descripción y cuándo usar]
|
|
35
|
+
|
|
36
|
+
### Variante [Nombre]
|
|
37
|
+
[Descripción y cuándo usar]
|
|
38
|
+
|
|
39
|
+
### Variante [Nombre]
|
|
40
|
+
[Descripción y cuándo usar]
|
|
41
|
+
|
|
42
|
+
## Props / Parámetros
|
|
43
|
+
|
|
44
|
+
| Nombre | Tipo | Requerido | Default | Descripción |
|
|
45
|
+
|--------|------|-----------|---------|-------------|
|
|
46
|
+
| `title` | String | Sí | - | Título principal |
|
|
47
|
+
| `description` | String | No | nil | Texto descriptivo |
|
|
48
|
+
| `variant` | Symbol | No | :default | Estilo visual |
|
|
49
|
+
| `data` | Hash | No | {} | Data attributes adicionales |
|
|
50
|
+
|
|
51
|
+
## Uso
|
|
52
|
+
|
|
53
|
+
### Básico
|
|
54
|
+
```erb
|
|
55
|
+
<%= render "shared/component", title: "Mi Título" %>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Con todas las opciones
|
|
59
|
+
```erb
|
|
60
|
+
<%= render "shared/component",
|
|
61
|
+
title: "Mi Título",
|
|
62
|
+
description: "Descripción opcional",
|
|
63
|
+
variant: :outlined,
|
|
64
|
+
data: { controller: "my-controller" } %>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Con bloque
|
|
68
|
+
```erb
|
|
69
|
+
<%= render "shared/component", title: "Mi Título" do %>
|
|
70
|
+
<p>Contenido personalizado</p>
|
|
71
|
+
<% end %>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Implementación
|
|
75
|
+
|
|
76
|
+
### ERB Template
|
|
77
|
+
```erb
|
|
78
|
+
<%# app/views/shared/_component.html.erb %>
|
|
79
|
+
<%# locals: (title:, description: nil, variant: :default, data: {}) %>
|
|
80
|
+
<%
|
|
81
|
+
base_classes = "rounded-lg shadow"
|
|
82
|
+
variant_classes = case variant
|
|
83
|
+
when :outlined then "border-2 border-gray-200"
|
|
84
|
+
when :filled then "bg-gray-100"
|
|
85
|
+
else "bg-white"
|
|
86
|
+
end
|
|
87
|
+
classes = "#{base_classes} #{variant_classes}"
|
|
88
|
+
%>
|
|
89
|
+
|
|
90
|
+
<div class="<%= classes %>"
|
|
91
|
+
<%= tag.attributes(data) %>>
|
|
92
|
+
<h3 class="text-lg font-semibold text-gray-900">
|
|
93
|
+
<%= title %>
|
|
94
|
+
</h3>
|
|
95
|
+
|
|
96
|
+
<% if description.present? %>
|
|
97
|
+
<p class="mt-2 text-gray-600">
|
|
98
|
+
<%= description %>
|
|
99
|
+
</p>
|
|
100
|
+
<% end %>
|
|
101
|
+
|
|
102
|
+
<% if block_given? %>
|
|
103
|
+
<div class="mt-4">
|
|
104
|
+
<%= yield %>
|
|
105
|
+
</div>
|
|
106
|
+
<% end %>
|
|
107
|
+
</div>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Stimulus Controller (si aplica)
|
|
111
|
+
```javascript
|
|
112
|
+
// app/javascript/controllers/component_controller.js
|
|
113
|
+
import { Controller } from "@hotwired/stimulus"
|
|
114
|
+
|
|
115
|
+
export default class extends Controller {
|
|
116
|
+
static targets = ["content"]
|
|
117
|
+
static values = {
|
|
118
|
+
expanded: { type: Boolean, default: false }
|
|
119
|
+
}
|
|
120
|
+
static classes = ["hidden"]
|
|
121
|
+
|
|
122
|
+
toggle() {
|
|
123
|
+
this.expandedValue = !this.expandedValue
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
expandedValueChanged() {
|
|
127
|
+
this.contentTarget.classList.toggle(this.hiddenClass, !this.expandedValue)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Estilos
|
|
133
|
+
|
|
134
|
+
### Clases de Tailwind utilizadas
|
|
135
|
+
|
|
136
|
+
| Clase | Propósito |
|
|
137
|
+
|-------|-----------|
|
|
138
|
+
| `rounded-lg` | Bordes redondeados |
|
|
139
|
+
| `shadow` | Sombra sutil |
|
|
140
|
+
| `p-4` | Padding interno |
|
|
141
|
+
| `text-lg` | Tamaño de texto título |
|
|
142
|
+
| `font-semibold` | Peso de fuente título |
|
|
143
|
+
|
|
144
|
+
### Customización
|
|
145
|
+
```erb
|
|
146
|
+
<%# Añadir clases adicionales %>
|
|
147
|
+
<%= render "shared/component",
|
|
148
|
+
title: "Título",
|
|
149
|
+
class: "my-custom-class" %>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Estados
|
|
153
|
+
|
|
154
|
+
### Default
|
|
155
|
+
[Descripción del estado normal]
|
|
156
|
+
|
|
157
|
+
### Hover
|
|
158
|
+
[Comportamiento al pasar el mouse]
|
|
159
|
+
|
|
160
|
+
### Focus
|
|
161
|
+
[Comportamiento al recibir foco]
|
|
162
|
+
|
|
163
|
+
### Disabled
|
|
164
|
+
[Comportamiento cuando está deshabilitado]
|
|
165
|
+
|
|
166
|
+
### Loading
|
|
167
|
+
[Comportamiento durante carga]
|
|
168
|
+
|
|
169
|
+
## Accesibilidad
|
|
170
|
+
|
|
171
|
+
| Aspecto | Implementación |
|
|
172
|
+
|---------|---------------|
|
|
173
|
+
| Rol ARIA | `role="[rol]"` |
|
|
174
|
+
| Labels | `aria-label="[descripción]"` |
|
|
175
|
+
| Focus | Orden lógico, visible |
|
|
176
|
+
| Keyboard | [Teclas soportadas] |
|
|
177
|
+
|
|
178
|
+
### Ejemplo accesible
|
|
179
|
+
```erb
|
|
180
|
+
<div role="region"
|
|
181
|
+
aria-labelledby="component-title-<%= id %>">
|
|
182
|
+
<h3 id="component-title-<%= id %>">
|
|
183
|
+
<%= title %>
|
|
184
|
+
</h3>
|
|
185
|
+
</div>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Responsive
|
|
189
|
+
|
|
190
|
+
| Breakpoint | Comportamiento |
|
|
191
|
+
|------------|----------------|
|
|
192
|
+
| Mobile (< 640px) | [Descripción] |
|
|
193
|
+
| Tablet (640-1024px) | [Descripción] |
|
|
194
|
+
| Desktop (> 1024px) | [Descripción] |
|
|
195
|
+
|
|
196
|
+
## Tests
|
|
197
|
+
|
|
198
|
+
```ruby
|
|
199
|
+
# spec/views/shared/_component.html.erb_spec.rb
|
|
200
|
+
require "rails_helper"
|
|
201
|
+
|
|
202
|
+
RSpec.describe "shared/_component", type: :view do
|
|
203
|
+
it "renders title" do
|
|
204
|
+
render partial: "shared/component", locals: { title: "Test" }
|
|
205
|
+
expect(rendered).to have_text("Test")
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it "renders description when provided" do
|
|
209
|
+
render partial: "shared/component",
|
|
210
|
+
locals: { title: "Test", description: "Desc" }
|
|
211
|
+
expect(rendered).to have_text("Desc")
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it "renders block content" do
|
|
215
|
+
render partial: "shared/component", locals: { title: "Test" } do
|
|
216
|
+
"Custom content"
|
|
217
|
+
end
|
|
218
|
+
expect(rendered).to have_text("Custom content")
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Ejemplos de Uso Real
|
|
224
|
+
|
|
225
|
+
### En página de artículos
|
|
226
|
+
```erb
|
|
227
|
+
<%= render "shared/component",
|
|
228
|
+
title: @article.title,
|
|
229
|
+
description: @article.excerpt %>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### En dashboard
|
|
233
|
+
```erb
|
|
234
|
+
<%= render "shared/component", title: "Estadísticas" do %>
|
|
235
|
+
<%= render "dashboard/stats_chart" %>
|
|
236
|
+
<% end %>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Notas de Implementación
|
|
240
|
+
|
|
241
|
+
- [Nota importante 1]
|
|
242
|
+
- [Nota importante 2]
|
|
243
|
+
|
|
244
|
+
## Changelog
|
|
245
|
+
|
|
246
|
+
| Versión | Fecha | Cambios |
|
|
247
|
+
|---------|-------|---------|
|
|
248
|
+
| 1.0 | [Fecha] | Versión inicial |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0",
|
|
3
|
+
"id": "{YYYY-MM-DD-hhmmss}-{slug}",
|
|
4
|
+
"title": "{título inferido}",
|
|
5
|
+
"description": "{descripción}",
|
|
6
|
+
"original_request": "{texto exacto del usuario}",
|
|
7
|
+
"created_at": "{ISO timestamp}",
|
|
8
|
+
"updated_at": "{ISO timestamp}",
|
|
9
|
+
"status": "created",
|
|
10
|
+
"progress": 0,
|
|
11
|
+
"current_phase": "initial",
|
|
12
|
+
"tasks": []
|
|
13
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# Model Specification Template
|
|
2
|
+
|
|
3
|
+
## Información General
|
|
4
|
+
|
|
5
|
+
| Campo | Valor |
|
|
6
|
+
|-------|-------|
|
|
7
|
+
| Nombre | [NombreModelo] |
|
|
8
|
+
| Tabla | [nombre_modelos] |
|
|
9
|
+
| Archivo | `app/models/nombre_modelo.rb` |
|
|
10
|
+
| Estado | Diseño / Implementado |
|
|
11
|
+
|
|
12
|
+
## Descripción
|
|
13
|
+
|
|
14
|
+
[Descripción del propósito del modelo y su rol en el dominio]
|
|
15
|
+
|
|
16
|
+
## Diagrama de Relaciones
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
┌─────────────┐
|
|
20
|
+
│ User │
|
|
21
|
+
└──────┬──────┘
|
|
22
|
+
│
|
|
23
|
+
│ has_many
|
|
24
|
+
▼
|
|
25
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
26
|
+
│ Category │◄────│ Article │────►│ Comment │
|
|
27
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
28
|
+
│
|
|
29
|
+
│ has_many_attached
|
|
30
|
+
▼
|
|
31
|
+
┌─────────────┐
|
|
32
|
+
│ Images │
|
|
33
|
+
└─────────────┘
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Atributos
|
|
37
|
+
|
|
38
|
+
| Nombre | Tipo | Null | Default | Descripción |
|
|
39
|
+
|--------|------|------|---------|-------------|
|
|
40
|
+
| `id` | bigint | No | auto | Primary key |
|
|
41
|
+
| `field_name` | string | No | - | Descripción del campo |
|
|
42
|
+
| `description` | text | Sí | nil | Descripción larga |
|
|
43
|
+
| `status` | string | No | 'draft' | Estado del registro |
|
|
44
|
+
| `published` | boolean | No | false | Si está publicado |
|
|
45
|
+
| `amount` | decimal | Sí | nil | Valor monetario |
|
|
46
|
+
| `user_id` | bigint | No | - | FK a users |
|
|
47
|
+
| `created_at` | datetime | No | auto | Fecha creación |
|
|
48
|
+
| `updated_at` | datetime | No | auto | Fecha actualización |
|
|
49
|
+
|
|
50
|
+
## Migración
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
class CreateModelNames < ActiveRecord::Migration[8.0]
|
|
54
|
+
def change
|
|
55
|
+
create_table :model_names do |t|
|
|
56
|
+
t.string :field_name, null: false
|
|
57
|
+
t.text :description
|
|
58
|
+
t.string :status, null: false, default: 'draft'
|
|
59
|
+
t.boolean :published, null: false, default: false
|
|
60
|
+
t.decimal :amount, precision: 10, scale: 2
|
|
61
|
+
t.references :user, null: false, foreign_key: true
|
|
62
|
+
|
|
63
|
+
t.timestamps
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
add_index :model_names, :status
|
|
67
|
+
add_index :model_names, :published
|
|
68
|
+
add_index :model_names, [:user_id, :status]
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Asociaciones
|
|
74
|
+
|
|
75
|
+
| Tipo | Modelo | Opciones | Descripción |
|
|
76
|
+
|------|--------|----------|-------------|
|
|
77
|
+
| `belongs_to` | `:user` | - | Usuario propietario |
|
|
78
|
+
| `has_many` | `:comments` | `dependent: :destroy` | Comentarios |
|
|
79
|
+
| `has_many` | `:tags` | `through: :taggings` | Tags asociadas |
|
|
80
|
+
| `has_one_attached` | `:image` | - | Imagen principal |
|
|
81
|
+
| `has_many_attached` | `:documents` | - | Documentos adjuntos |
|
|
82
|
+
|
|
83
|
+
## Validaciones
|
|
84
|
+
|
|
85
|
+
| Campo | Validación | Opciones | Mensaje |
|
|
86
|
+
|-------|------------|----------|---------|
|
|
87
|
+
| `field_name` | `presence` | - | "no puede estar vacío" |
|
|
88
|
+
| `field_name` | `length` | `maximum: 255` | "máximo 255 caracteres" |
|
|
89
|
+
| `field_name` | `uniqueness` | `scope: :user_id` | "ya existe" |
|
|
90
|
+
| `status` | `inclusion` | `in: STATUSES` | "estado inválido" |
|
|
91
|
+
| `amount` | `numericality` | `greater_than: 0` | "debe ser mayor a 0" |
|
|
92
|
+
|
|
93
|
+
## Callbacks
|
|
94
|
+
|
|
95
|
+
| Tipo | Método | Descripción |
|
|
96
|
+
|------|--------|-------------|
|
|
97
|
+
| `before_validation` | `:normalize_data` | Limpia y normaliza datos |
|
|
98
|
+
| `before_save` | `:generate_slug` | Genera slug del título |
|
|
99
|
+
| `after_create` | `:send_notification` | Envía notificación |
|
|
100
|
+
| `after_commit` | `:update_search_index` | Actualiza índice (on create/update) |
|
|
101
|
+
|
|
102
|
+
## Scopes
|
|
103
|
+
|
|
104
|
+
| Nombre | Query | Descripción |
|
|
105
|
+
|--------|-------|-------------|
|
|
106
|
+
| `published` | `where(published: true)` | Solo publicados |
|
|
107
|
+
| `draft` | `where(published: false)` | Solo borradores |
|
|
108
|
+
| `recent` | `order(created_at: :desc)` | Ordenados por fecha |
|
|
109
|
+
| `by_status` | `where(status: status)` | Filtrar por status |
|
|
110
|
+
| `search` | `where("title LIKE ?", "%#{q}%")` | Búsqueda por título |
|
|
111
|
+
|
|
112
|
+
## Métodos de Clase
|
|
113
|
+
|
|
114
|
+
### `self.search(query)`
|
|
115
|
+
```ruby
|
|
116
|
+
def self.search(query)
|
|
117
|
+
return all if query.blank?
|
|
118
|
+
where("field_name ILIKE ?", "%#{query}%")
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### `self.statistics`
|
|
123
|
+
```ruby
|
|
124
|
+
def self.statistics
|
|
125
|
+
{
|
|
126
|
+
total: count,
|
|
127
|
+
published: published.count,
|
|
128
|
+
draft: draft.count
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Métodos de Instancia
|
|
134
|
+
|
|
135
|
+
### `#publish!`
|
|
136
|
+
```ruby
|
|
137
|
+
def publish!
|
|
138
|
+
update!(published: true, published_at: Time.current)
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
Publica el registro y guarda la fecha.
|
|
142
|
+
|
|
143
|
+
### `#owned_by?(user)`
|
|
144
|
+
```ruby
|
|
145
|
+
def owned_by?(user)
|
|
146
|
+
self.user == user
|
|
147
|
+
end
|
|
148
|
+
```
|
|
149
|
+
Verifica si el usuario es propietario.
|
|
150
|
+
|
|
151
|
+
### `#status_label`
|
|
152
|
+
```ruby
|
|
153
|
+
def status_label
|
|
154
|
+
I18n.t("model_name.status.#{status}")
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
Devuelve etiqueta traducida del status.
|
|
158
|
+
|
|
159
|
+
## Enums / Constantes
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
STATUSES = %w[draft pending published archived].freeze
|
|
163
|
+
|
|
164
|
+
# O usando enum de Rails (si aplica)
|
|
165
|
+
enum :status, {
|
|
166
|
+
draft: 'draft',
|
|
167
|
+
pending: 'pending',
|
|
168
|
+
published: 'published',
|
|
169
|
+
archived: 'archived'
|
|
170
|
+
}, default: :draft
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Implementación Completa
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
# app/models/model_name.rb
|
|
177
|
+
class ModelName < ApplicationRecord
|
|
178
|
+
# Constants
|
|
179
|
+
STATUSES = %w[draft pending published archived].freeze
|
|
180
|
+
|
|
181
|
+
# Associations
|
|
182
|
+
belongs_to :user
|
|
183
|
+
has_many :comments, dependent: :destroy
|
|
184
|
+
has_many :taggings, dependent: :destroy
|
|
185
|
+
has_many :tags, through: :taggings
|
|
186
|
+
has_one_attached :image
|
|
187
|
+
|
|
188
|
+
# Validations
|
|
189
|
+
validates :field_name, presence: true,
|
|
190
|
+
length: { maximum: 255 },
|
|
191
|
+
uniqueness: { scope: :user_id }
|
|
192
|
+
validates :status, inclusion: { in: STATUSES }
|
|
193
|
+
validates :amount, numericality: { greater_than: 0 }, allow_nil: true
|
|
194
|
+
|
|
195
|
+
# Callbacks
|
|
196
|
+
before_validation :normalize_data
|
|
197
|
+
before_save :generate_slug
|
|
198
|
+
|
|
199
|
+
# Scopes
|
|
200
|
+
scope :published, -> { where(published: true) }
|
|
201
|
+
scope :draft, -> { where(published: false) }
|
|
202
|
+
scope :recent, -> { order(created_at: :desc) }
|
|
203
|
+
scope :by_status, ->(status) { where(status: status) }
|
|
204
|
+
|
|
205
|
+
# Class methods
|
|
206
|
+
def self.search(query)
|
|
207
|
+
return all if query.blank?
|
|
208
|
+
where("field_name ILIKE ?", "%#{query}%")
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Instance methods
|
|
212
|
+
def publish!
|
|
213
|
+
update!(published: true, published_at: Time.current)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def owned_by?(user)
|
|
217
|
+
self.user == user
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
def normalize_data
|
|
223
|
+
self.field_name = field_name&.strip
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def generate_slug
|
|
227
|
+
self.slug = field_name.parameterize if slug.blank?
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Factory
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
# spec/factories/model_names.rb
|
|
236
|
+
FactoryBot.define do
|
|
237
|
+
factory :model_name do
|
|
238
|
+
user
|
|
239
|
+
field_name { Faker::Lorem.sentence(word_count: 3) }
|
|
240
|
+
description { Faker::Lorem.paragraph }
|
|
241
|
+
status { ModelName::STATUSES.sample }
|
|
242
|
+
published { false }
|
|
243
|
+
|
|
244
|
+
trait :published do
|
|
245
|
+
published { true }
|
|
246
|
+
published_at { Time.current }
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
trait :with_image do
|
|
250
|
+
after(:build) do |record|
|
|
251
|
+
record.image.attach(
|
|
252
|
+
io: File.open(Rails.root.join("spec/fixtures/files/test.jpg")),
|
|
253
|
+
filename: "test.jpg"
|
|
254
|
+
)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Tests
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# spec/models/model_name_spec.rb
|
|
265
|
+
require "rails_helper"
|
|
266
|
+
|
|
267
|
+
RSpec.describe ModelName, type: :model do
|
|
268
|
+
describe "validations" do
|
|
269
|
+
it { should validate_presence_of(:field_name) }
|
|
270
|
+
it { should validate_length_of(:field_name).is_at_most(255) }
|
|
271
|
+
it { should validate_inclusion_of(:status).in_array(ModelName::STATUSES) }
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
describe "associations" do
|
|
275
|
+
it { should belong_to(:user) }
|
|
276
|
+
it { should have_many(:comments).dependent(:destroy) }
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
describe "scopes" do
|
|
280
|
+
describe ".published" do
|
|
281
|
+
it "returns only published records" do
|
|
282
|
+
published = create(:model_name, :published)
|
|
283
|
+
draft = create(:model_name, published: false)
|
|
284
|
+
|
|
285
|
+
expect(ModelName.published).to include(published)
|
|
286
|
+
expect(ModelName.published).not_to include(draft)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
describe "#publish!" do
|
|
292
|
+
it "marks record as published" do
|
|
293
|
+
record = create(:model_name, published: false)
|
|
294
|
+
|
|
295
|
+
record.publish!
|
|
296
|
+
|
|
297
|
+
expect(record.reload).to be_published
|
|
298
|
+
expect(record.published_at).to be_present
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Consideraciones
|
|
305
|
+
|
|
306
|
+
### Seguridad
|
|
307
|
+
- [ ] Autorización implementada en controlador/policy
|
|
308
|
+
- [ ] Strong parameters definidos
|
|
309
|
+
- [ ] Validaciones de usuario
|
|
310
|
+
|
|
311
|
+
### Rendimiento
|
|
312
|
+
- [ ] Índices en columnas frecuentemente consultadas
|
|
313
|
+
- [ ] Eager loading configurado donde sea necesario
|
|
314
|
+
- [ ] Counter caches si hay conteos frecuentes
|
|
315
|
+
|
|
316
|
+
### Notas
|
|
317
|
+
- [Nota adicional 1]
|
|
318
|
+
- [Nota adicional 2]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# {Título del Feature}
|
|
2
|
+
|
|
3
|
+
## Metadata
|
|
4
|
+
feature_id: `{feature_id}`
|
|
5
|
+
created_at: `{timestamp}`
|
|
6
|
+
status: `prd_created`
|
|
7
|
+
|
|
8
|
+
## Análisis de PRDs Existentes
|
|
9
|
+
|
|
10
|
+
### PRDs Revisados
|
|
11
|
+
{Lista de PRDs existentes que se revisaron}
|
|
12
|
+
|
|
13
|
+
### Matriz de Solapamiento
|
|
14
|
+
|
|
15
|
+
| Aspecto | Este PRD | PRD Existente | Conflicto? |
|
|
16
|
+
|---------|----------|---------------|------------|
|
|
17
|
+
| {aspecto 1} | {RF-XX} | {feature/RF-XX} | SÍ/NO |
|
|
18
|
+
| {aspecto 2} | {RF-XX} | {feature/RF-XX} | SÍ/NO |
|
|
19
|
+
|
|
20
|
+
**Solapamientos Encontrados**: {count} (Si > 0, documentar resolución)
|
|
21
|
+
|
|
22
|
+
### Resolución de Solapamientos
|
|
23
|
+
{Si hay solapamientos, cómo se resuelven:
|
|
24
|
+
- Referenciar PRD existente en vez de duplicar
|
|
25
|
+
- Extender funcionalidad existente
|
|
26
|
+
- Coordinar con otro feature}
|
|
27
|
+
|
|
28
|
+
## Resumen
|
|
29
|
+
{Descripción clara de qué es este feature y por qué es valioso para los usuarios}
|
|
30
|
+
|
|
31
|
+
## Problema
|
|
32
|
+
{Describe el problema específico que este feature resuelve}
|
|
33
|
+
|
|
34
|
+
## Usuarios
|
|
35
|
+
{Quién usará esta funcionalidad y cómo les beneficia}
|
|
36
|
+
|
|
37
|
+
## Alcance
|
|
38
|
+
|
|
39
|
+
### Incluido
|
|
40
|
+
- {Funcionalidad 1 que SÍ se implementará}
|
|
41
|
+
- {Funcionalidad 2 que SÍ se implementará}
|
|
42
|
+
- {Funcionalidad 3 que SÍ se implementará}
|
|
43
|
+
|
|
44
|
+
### Excluido (por ahora)
|
|
45
|
+
- {Funcionalidad futura 1}
|
|
46
|
+
- {Funcionalidad futura 2}
|
|
47
|
+
|
|
48
|
+
## Requisitos
|
|
49
|
+
|
|
50
|
+
### Funcionales
|
|
51
|
+
- **RF-01**: {Requisito funcional 1}
|
|
52
|
+
- **RF-02**: {Requisito funcional 2}
|
|
53
|
+
- **RF-03**: {Requisito funcional 3}
|
|
54
|
+
|
|
55
|
+
### No Funcionales
|
|
56
|
+
- **RNF-01 Rendimiento**: {Requisitos de velocidad/capacidad}
|
|
57
|
+
- **RNF-02 Seguridad**: {Requisitos de seguridad}
|
|
58
|
+
- **RNF-03 Usabilidad**: {Requisitos de facilidad de uso}
|
|
59
|
+
|
|
60
|
+
## Flujo de Usuario
|
|
61
|
+
1. El usuario {acción 1}
|
|
62
|
+
2. El sistema {respuesta 1}
|
|
63
|
+
3. El usuario {acción 2}
|
|
64
|
+
4. El sistema {respuesta 2}
|
|
65
|
+
5. Resultado: {estado final}
|
|
66
|
+
|
|
67
|
+
## Dependencias
|
|
68
|
+
|
|
69
|
+
### Con Otros Features
|
|
70
|
+
{Lista de features relacionados y cómo interactúan}
|
|
71
|
+
- Feature X: {relación}
|
|
72
|
+
- Feature Y: {relación}
|
|
73
|
+
|
|
74
|
+
### Técnicas
|
|
75
|
+
- {Librerías o recursos necesarios}
|
|
76
|
+
- {APIs externas}
|
|
77
|
+
- {Modelos existentes a usar}
|
|
78
|
+
|
|
79
|
+
## Notas
|
|
80
|
+
{Cualquier consideración adicional, riesgos, o decisiones pendientes}
|