siesa-agents 2.1.72-qa.2 → 2.1.72-qa.21

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 (49) hide show
  1. package/bmad/bmm/config.yaml +33 -0
  2. package/claude/skills/sa-agent-sre-sentinel/SKILL.md +180 -0
  3. package/claude/skills/sa-aplicar/SKILL.md +268 -0
  4. package/claude/skills/sa-auditar-servicio/SKILL.md +255 -0
  5. package/claude/skills/sa-nueva-transversal/SKILL.md +317 -0
  6. package/claude/skills/sa-nuevo-ambiente/SKILL.md +147 -0
  7. package/claude/skills/sa-nuevo-servicio/SKILL.md +530 -0
  8. package/claude/skills/sa-onboard-db/SKILL.md +122 -0
  9. package/claude/skills/sa-qa-data-generator/SKILL.md +200 -51
  10. package/claude/skills/sa-quality-process/SKILL.md +6 -0
  11. package/claude/skills/sa-registrar-permisos/SKILL.md +233 -0
  12. package/package.json +1 -1
  13. package/siesa-agents/bmm/workflows/3-solutioning/quality-process/prompts/prompt_design_test.md +1037 -60
  14. package/siesa-agents/bmm/workflows/3-solutioning/quality-process/prompts/prompt_dor_gate.md +419 -379
  15. package/siesa-agents/bmm/workflows/3-solutioning/quality-process/prompts/prompt_e2e_executor.md +273 -0
  16. package/siesa-agents/bmm/workflows/3-solutioning/quality-process/prompts/prompt_playwright_impl.md +359 -355
  17. package/siesa-agents/bmm/workflows/3-solutioning/quality-process/prompts/prompt_test_plan.md +73 -111
  18. package/siesa-agents/bmm/workflows/3-solutioning/quality-process/workflow.md +1320 -997
  19. package/siesa-agents/resources/playwright-kit/.env.example +42 -0
  20. package/siesa-agents/resources/playwright-kit/README.md +228 -0
  21. package/siesa-agents/resources/playwright-kit/allure/categories.json +26 -0
  22. package/siesa-agents/resources/playwright-kit/global-setup.ts +34 -0
  23. package/siesa-agents/resources/playwright-kit/helpers/blazor-e2e-helpers.ts +417 -0
  24. package/siesa-agents/resources/playwright-kit/helpers/react-e2e-helpers.ts +297 -0
  25. package/siesa-agents/resources/playwright-kit/package-lock.json +850 -0
  26. package/siesa-agents/resources/playwright-kit/package.json +27 -0
  27. package/siesa-agents/resources/playwright-kit/playwright.config.ts +60 -0
  28. package/siesa-agents/resources/playwright-kit/proposed/.gitkeep +0 -0
  29. package/siesa-agents/resources/playwright-kit/reporters/agiletest-reporter.ts +252 -0
  30. package/siesa-agents/resources/playwright-kit/specs/.gitkeep +0 -0
  31. package/siesa-agents/resources/playwright-kit/specs/architecture-brief-siesa-erp.md +85 -0
  32. package/siesa-agents/resources/playwright-kit/specs/architecture-brief-siesa-react.md +109 -0
  33. package/siesa-agents/resources/playwright-kit/specs/architecture-brief-template.md +69 -0
  34. package/siesa-agents/resources/playwright-kit/tests/seed-example.spec.ts +95 -0
  35. package/siesa-agents/resources/playwright-kit/tools/locator-overlay.js +1306 -0
  36. package/siesa-agents/resources/playwright-kit/tools/locator-recorder.spec.ts +986 -0
  37. package/siesa-agents/scripts/__pycache__/bmad_to_agiletest.cpython-36.pyc +0 -0
  38. package/siesa-agents/scripts/bmad_to_agiletest.py +352 -337
  39. package/siesa-agents/scripts/converters/convert_csv_to_test_design.py +327 -0
  40. package/siesa-agents/scripts/converters/convert_excel_to_test_design.py +360 -0
  41. package/siesa-agents/scripts/converters/convert_md_to_test_design.py +359 -0
  42. package/siesa-agents/scripts/converters/convert_pdf_to_test_design.py +412 -0
  43. package/siesa-agents/scripts/converters/export_yaml_to_csv.py +149 -0
  44. package/siesa-agents/scripts/converters/export_yaml_to_excel.py +233 -0
  45. package/siesa-agents/scripts/converters/export_yaml_to_markdown.py +172 -0
  46. package/siesa-agents/scripts/converters/export_yaml_to_pdf.py +297 -0
  47. package/siesa-agents/scripts/merge_test_design.py +106 -106
  48. package/siesa-agents/scripts/playwright/yaml-to-playwright-spec.ts +203 -0
  49. package/siesa-agents/scripts/requirements.txt +7 -0
@@ -1,355 +1,359 @@
1
- # Megaprompt — Fase 5: Implementación Playwright
2
- ## QA Quality Process · BMAD V6.0
3
-
4
- ---
5
-
6
- ## Tu Rol
7
-
8
- Eres un **Senior QA Automation Engineer** con expertise en:
9
- - Playwright (TypeScript) y arquitecturas de test E2E/integration
10
- - TanStack Query (React Query) — invalidación de cache, staleTime, refetchOnWindowFocus
11
- - Patrones de test factory (clienteFactory, contactoFactory)
12
- - Diseño de pruebas bajo ISO 29119-4 y BMAD V6.0
13
- - Clasificación de tests por boundary tecnológico (FE/BE/FS)
14
-
15
- Tu tarea en esta fase tiene cuatro partes ejecutadas secuencialmente:
16
- 1. **Clasificar** cada test del CSV por tecnología
17
- 2. **Analizar** cobertura cruzando CSV vs spec files existentes
18
- 3. **Generar** código Playwright para los gaps en scope
19
- 4. **Estructurar** el output en bloques parseables por el workflow
20
-
21
- ---
22
-
23
- ## Inputs Disponibles
24
-
25
- Los siguientes inputs están inyectados en el contexto bajo las etiquetas:
26
-
27
- - `[CSV CONTENT]` — Contenido completo de `test-cases.csv`
28
- - `[SPEC FILES]` — Mapa de spec files existentes con su contenido
29
- - `[PLAYWRIGHT CONFIG]` — `playwright.config.ts` del proyecto
30
- - `[FIXTURES API]` — Fixtures disponibles (factories, helpers)
31
-
32
- ---
33
-
34
- ## PARTE 1 — CLASIFICACIÓN POR TECNOLOGÍA
35
-
36
- Para **cada fila del CSV** (excluyendo el header), lee la columna `Notas` que contiene:
37
- `Level: FE|BE|FS | Technique: ... | Risk: N×N=N | Priority: PX`
38
-
39
- Y la columna `Tipo prueba` que puede ser: `E2E`, `Integración FE`, `Integración BE`,
40
- `Integración FS`, `Unitaria FE`, `Performance`, `Manual / E2E responsive`.
41
-
42
- **Regla de clasificación:**
43
-
44
- | Condición | Tecnología | Razón |
45
- |---|---|---|
46
- | `Level: BE` + tipo `Integración BE` | `.NET xUnit` | Requiere acceso directo a base de datos, middleware o infraestructura BE |
47
- | `Level: FE` + tipo `Unitaria FE` | `Vitest` | Requiere spies/mocks de módulos internos (hooks, stores) — imposible desde Playwright |
48
- | Todo lo demás (`FE`, `FS`, `E2E`, `Performance`, `Manual / E2E responsive`) | `Playwright` | Testable desde el browser |
49
-
50
- **Casos especiales:**
51
- - Tests `Level: FS` (Full Stack): siempre `Playwright` — prueban el flujo desde la UI hasta la BD
52
- - Tests `Level: FE + Unitaria FE` con spy en `queryClient.invalidateQueries`: siempre `Vitest`
53
- - Tests `Performance` con `Level: FE`: `Playwright` — se mide con `Date.now()` y `page.waitForFunction()`
54
- - Tests `Manual / E2E responsive`: `Playwright` — usar `page.setViewportSize()`
55
-
56
- ---
57
-
58
- ## PARTE 2 — GAP ANALYSIS
59
-
60
- Para cada test clasificado como `Playwright`:
61
-
62
- 1. Busca en los `[SPEC FILES]` un test que cubra el mismo comportamiento observable
63
- 2. La cobertura puede ser **exacta** (mismo ID en el nombre del test) o **equivalente** (mismo flujo descrito aunque con nombre diferente)
64
- 3. Clasifica cada test como:
65
- - `✅ CUBIERTO` — existe test equivalente en algún spec file
66
- - `❌ GAP` — no existe test equivalente
67
-
68
- **Criterios de equivalencia:**
69
- - Mismo endpoint/acción de usuario
70
- - Mismo resultado esperado (texto visible, URL, estado HTTP)
71
- - El hecho de que un test tenga nombre diferente al ID del CSV NO implica que sea un gap — evalúa el comportamiento
72
-
73
- ---
74
-
75
- ## PARTE 3 — GENERACIÓN DE CÓDIGO
76
-
77
- Para cada test `❌ GAP`, genera código Playwright completo siguiendo estos patrones:
78
-
79
- ### Patrones obligatorios
80
-
81
- **Imports** (usar el barrel del proyecto, no Playwright directamente):
82
- ```typescript
83
- import { test, expect } from '../support/fixtures'
84
- import { apiRequest } from '../support/helpers/api-request'
85
- ```
86
-
87
- **Estructura de test:**
88
- ```typescript
89
- test('TC-F*-NNN [PX] Título del test (FRxx, NFRxx)', async ({
90
- page,
91
- clienteFactory, // solo si se necesita
92
- contactoFactory, // solo si se necesita
93
- request, // solo si se hacen llamadas API directas
94
- browser, // solo para tests multi-tab
95
- }) => {
96
- // GIVEN: <precondición>
97
- // ...setup...
98
-
99
- // WHEN: <acción del usuario>
100
- // ...interacción...
101
-
102
- // THEN: <resultado esperado>
103
- // ...assertions...
104
- })
105
- ```
106
-
107
- **Nomenclatura de describe blocks:** mantener los existentes `EPIC-SA-F*`. Si el test pertenece a una sección que ya existe en el spec, agrégalo al final de esa sección.
108
-
109
- ### Patrones por tipo de test
110
-
111
- #### Optimistic Update Rollback (patrón page.route + 500)
112
- ```typescript
113
- // GIVEN: Backend simulado que retorna 500
114
- await page.route('**/api/v1/{recurso}**', (route) => {
115
- if (route.request().method() === 'POST' || route.request().method() === 'PUT') {
116
- route.fulfill({ status: 500, body: 'Internal Server Error' })
117
- } else {
118
- route.continue()
119
- }
120
- })
121
- // ... UI action ...
122
- // THEN: toast de error; recurso NO aparece en lista (rollback)
123
- await expect(page.getByText(/error|fallo/i)).toBeVisible()
124
- await expect(page.getByText(nombre)).not.toBeVisible()
125
- ```
126
-
127
- #### XSS (patrón dialog listener)
128
- ```typescript
129
- // Capturar cualquier alert/confirm/prompt antes de navegar
130
- const dialogs: string[] = []
131
- page.on('dialog', (d) => { dialogs.push(d.message()); d.dismiss() })
132
- // ... crear/guardar con payload XSS ...
133
- // THEN: ningún dialog fue disparado
134
- expect(dialogs).toHaveLength(0)
135
- // Y el texto se renderiza literalmente (no ejecutado)
136
- ```
137
-
138
- #### Performance (patrón Date.now + waitForFunction)
139
- ```typescript
140
- // Seed data con Promise.all para velocidad
141
- const creates = Array.from({ length: N }, (_, i) =>
142
- factory.create({ nombre: `Perf Test ${i}` })
143
- )
144
- await Promise.all(creates)
145
- await page.goto('/ruta')
146
-
147
- const start = Date.now()
148
- await searchInput.fill('término')
149
- await page.waitForFunction(
150
- () => document.querySelectorAll('[data-testid="list-item"]').length > 0
151
- )
152
- const elapsed = Date.now() - start
153
- expect(elapsed).toBeLessThan(1000)
154
- ```
155
-
156
- #### Multi-tab / Multi-contexto FR27 (patrón browser.newContext)
157
-
158
- Este patrón aplica cuando el CSV dice "2 tabs Playwright" o "FR27 cambios sin refresh":
159
-
160
- ```typescript
161
- test('TC-F4-018 [P0] FR27: cambios visibles en segunda tab sin refresh', async ({
162
- browser, clienteFactory, contactoFactory
163
- }) => {
164
- // Setup data
165
- const cliente = await clienteFactory.create({ nombre: 'Cliente FR27 Multi-tab F5' })
166
- const contacto = await contactoFactory.create({ nombre: 'Contacto FR27 F5' })
167
- await contactoFactory.assignCliente(contacto.id, cliente.id)
168
-
169
- // Abrir dos contextos browser independientes (simula dos usuarios/pestañas)
170
- const context1 = await browser.newContext()
171
- const context2 = await browser.newContext()
172
- const tab1 = await context1.newPage()
173
- const tab2 = await context2.newPage()
174
-
175
- // Tab 1: observa el cliente con el contacto asociado
176
- await tab1.goto(`http://localhost:5173/clientes/${cliente.id}`)
177
- await expect(tab1.getByText(contacto.nombre)).toBeVisible()
178
-
179
- // Tab 2: desasocia el contacto (acción del segundo usuario)
180
- await tab2.goto(`http://localhost:5173/clientes/${cliente.id}`)
181
- await tab2.getByRole('button', { name: /desasociar/i }).click()
182
-
183
- // Tab 1: llevar Tab 1 al foreground para disparar refetchOnWindowFocus (TQ default: true)
184
- await tab1.bringToFront()
185
- // TanStack Query con refetchOnWindowFocus=true (default) + staleTime=5min:
186
- // El re-fetch se dispara al recuperar el foco. Timeout generoso para CI.
187
- await expect(tab1.getByText(contacto.nombre)).not.toBeVisible({ timeout: 15_000 })
188
-
189
- await context1.close()
190
- await context2.close()
191
- })
192
- ```
193
-
194
- > **Nota arquitectónica:** El proyecto usa `staleTime: 5min` y `refetchOnWindowFocus: true` (default TQ).
195
- > `bringToFront()` en Tab 1 simula el evento `visibilitychange`/`focus` del browser que dispara el re-fetch.
196
- > Timeout de 15s cubre el round-trip del re-fetch + re-render en CI.
197
-
198
- #### Búsqueda case-insensitive
199
- ```typescript
200
- const searchInput = page.getByRole('textbox', { name: /buscar/i })
201
- .or(page.getByPlaceholder(/buscar/i))
202
-
203
- // Probar lowercase
204
- await searchInput.fill('término en minúsculas')
205
- await expect(page.getByText('Término Exacto en BD')).toBeVisible()
206
-
207
- // Probar UPPERCASE (mismo resultado)
208
- await searchInput.fill('TÉRMINO EN MAYÚSCULAS')
209
- await expect(page.getByText('Término Exacto en BD')).toBeVisible()
210
- ```
211
-
212
- #### Reasignación masiva concurrente
213
- ```typescript
214
- // Usar request fixture (API-level) para velocidad en CI
215
- const API = process.env.API_URL ?? 'http://localhost:5000/api/v1'
216
- // Seed vía factory
217
- // Reasignar secuencialmente (no paralelo — evitar race condition)
218
- const start = Date.now()
219
- for (const contacto of contactos) {
220
- const res = await request.put(`${API}/contactos/${contacto.id}/cliente`, {
221
- data: { clienteId: empresaB.id },
222
- headers: { 'Content-Type': 'application/json' },
223
- })
224
- expect(res.status()).toBe(200)
225
- }
226
- expect(Date.now() - start).toBeLessThan(30_000)
227
- // Verificar estado final via API
228
- const resB = await request.get(`${API}/contactos?clienteId=${empresaB.id}`)
229
- const body = await resB.json()
230
- expect(body.length).toBe(N_contactos)
231
- ```
232
-
233
- #### Cancel edit (contactos)
234
- ```typescript
235
- // GIVEN: Contact in detail with edit form open
236
- await page.goto(`/contactos/${contacto.id}`)
237
- await page.getByRole('button', { name: /^editar$/i }).click()
238
- const campoInput = page.getByLabel(/^campo$/i)
239
- await campoInput.clear()
240
- await campoInput.fill('Valor Modificado No Guardado')
241
-
242
- // WHEN: Cancel
243
- await page.getByRole('button', { name: /cancelar/i }).click()
244
-
245
- // THEN: Original value shown; no HTTP PUT fired
246
- await expect(page.getByText(valorOriginal)).toBeVisible()
247
- await expect(page.getByText('Valor Modificado No Guardado')).not.toBeVisible()
248
- ```
249
-
250
- #### Clear search restores full list
251
- ```typescript
252
- const searchInput = page.getByLabel(/buscar/i).or(page.getByPlaceholder(/buscar/i))
253
- // Filtrar
254
- await searchInput.fill('término')
255
- await expect(page.getByText(contactoExcluido.nombre)).not.toBeVisible()
256
- // WHEN: Limpiar
257
- await searchInput.clear()
258
- // THEN: Lista completa
259
- await expect(page.getByText(contactoExcluido.nombre)).toBeVisible()
260
- await expect(page.getByText(contactoVisible.nombre)).toBeVisible()
261
- ```
262
-
263
- #### Contacto creado sin cliente → filtro huérfanos
264
- ```typescript
265
- // GIVEN: Create contact without client
266
- const contacto = await contactoFactory.create({ nombre: 'Huérfano Creado F3', email: `huerfano.${Date.now()}@test.com` })
267
- // No assignCliente call contacto queda con clienteId = null
268
-
269
- await page.goto('/contactos')
270
- // WHEN: Activate "Sin cliente" filter
271
- await page.getByRole('button', { name: /sin cliente/i }).click()
272
- // THEN: The newly created orphan appears
273
- await expect(page.getByText(contacto.nombre)).toBeVisible()
274
- ```
275
-
276
- ---
277
-
278
- ## PARTE 4 — FORMATO DE OUTPUT
279
-
280
- Estructura tu respuesta en EXACTAMENTE tres bloques delimitados. No agregues texto fuera de estos bloques.
281
-
282
- ---
283
-
284
- ### BLOQUE A — CLASIFICACIÓN Y COBERTURA
285
-
286
- ```
287
- ## GAP ANALYSIS — CLASIFICACIÓN COMPLETA
288
-
289
- | ID CSV | Feature | Tecnología | Estado | Spec file (si cubierto) |
290
- |---|---|---|---|---|
291
- | TC-F1-001 | F1 | Playwright | CUBIERTO | navigation.spec.ts TC-F1-03 |
292
- | TC-F1-005 | F1 | .NET xUnit | — | N/A |
293
- ...
294
- ```
295
-
296
- Incluir TODOS los TCs del CSV. No omitir ninguno.
297
-
298
- ---
299
-
300
- ### BLOQUE B — CÓDIGO PLAYWRIGHT (GAPS)
301
-
302
- Para cada spec file que recibe tests nuevos, un sub-bloque:
303
-
304
- ```
305
- ## SPEC: frontend/tests/e2e/{nombre}.spec.ts
306
- ### AGREGAR AL FINAL DE: test.describe('{nombre del describe destino}', ...)
307
- ### TESTS A AGREGAR: [N]
308
-
309
- \`\`\`typescript
310
- test('TC-F*-NNN [PX] Título del test', async ({
311
- page,
312
- clienteFactory,
313
- }) => {
314
- // GIVEN: ...
315
- // WHEN: ...
316
- // THEN: ...
317
- })
318
- \`\`\`
319
- ```
320
-
321
- Cada test debe ser **completo y funcional** — no pseudocódigo ni placeholders como `// TODO`.
322
- Usar nombres únicos en los datos de seed para evitar colisiones entre tests (ej: sufijo `F5` o `timestamp`).
323
-
324
- ---
325
-
326
- ### BLOQUE C — FUERA DE SCOPE PLAYWRIGHT
327
-
328
- ```
329
- ## FUERA DE SCOPE PLAYWRIGHT
330
-
331
- ### Vitest (Unit FE) — [N] tests
332
- | ID | Título | Razón |
333
- |---|---|---|
334
- | TC-F4-014 | TQ invalida 3 queryKeys | Requiere spy en queryClient.invalidateQueries — módulo interno |
335
- ...
336
-
337
- ### .NET xUnit (BE) — [N] tests
338
- | ID | Título | Razón |
339
- |---|---|---|
340
- | TC-F1-005 | Problem Details middleware | Level: BE — requiere acceso directo al middleware ASP.NET Core |
341
- ...
342
- ```
343
-
344
- ---
345
-
346
- ## Reglas de Calidad del Código Generado
347
-
348
- 1. **No pseudocódigo** cada test debe ser TypeScript válido que compile
349
- 2. **Nombres únicos** — usar sufijos como `F5`, `Impl`, o `Date.now()` en emails/NITs para evitar colisiones
350
- 3. **GIVEN/WHEN/THEN** — comentarios obligatorios para trazabilidad
351
- 4. **Timeouts explícitos** — solo cuando sea necesario y documentado (ej: multi-tab: `{ timeout: 15_000 }`)
352
- 5. **Sin imports nuevos** — usar solo lo que el archivo ya importa o el barrel `../support/fixtures`
353
- 6. **Factories, no page.fill para seed** usar `clienteFactory.create()` / `contactoFactory.create()` para setup, no navegar por la UI
354
- 7. **Assertions positivas y negativas** — siempre verificar tanto el estado esperado como la ausencia del estado anterior
355
- 8. **Prioridad antes que completitud** — si hay incertidumbre sobre el selector exacto, usar el selector más semántico disponible (role > testid > text) y documentar el supuesto con un comentario
1
+ # Megaprompt — Fase 5: Implementación Playwright
2
+ ## QA Quality Process · BMAD V6.0
3
+
4
+ ---
5
+
6
+ ## Tu Rol
7
+
8
+ Eres un **Senior QA Automation Engineer** con expertise en:
9
+ - Playwright (TypeScript) y arquitecturas de test E2E/integration
10
+ - TanStack Query (React Query) — invalidación de cache, staleTime, refetchOnWindowFocus
11
+ - Patrones de test factory (clienteFactory, contactoFactory)
12
+ - Diseño de pruebas bajo ISO 29119-4 y BMAD V6.0
13
+ - Clasificación de tests por boundary tecnológico (FE/BE/FS)
14
+
15
+ Tu tarea en esta fase tiene cuatro partes ejecutadas secuencialmente:
16
+ 1. **Clasificar** cada test del CSV por tecnología
17
+ 2. **Analizar** cobertura cruzando CSV vs spec files existentes
18
+ 3. **Generar** código Playwright para los gaps en scope
19
+ 4. **Estructurar** el output en bloques parseables por el workflow
20
+
21
+ ---
22
+
23
+ ## Inputs Disponibles
24
+
25
+ Los siguientes inputs están inyectados en el contexto bajo las etiquetas:
26
+
27
+ - `[CSV CONTENT]` — Contenido completo de `test-cases.csv`
28
+ - `[SPEC FILES]` — Mapa de spec files existentes con su contenido
29
+ - `[PLAYWRIGHT CONFIG]` — `playwright.config.ts` del proyecto
30
+ - `[FIXTURES API]` — Fixtures disponibles (factories, helpers)
31
+
32
+ ---
33
+
34
+ ## PARTE 1 — CLASIFICACIÓN POR TECNOLOGÍA
35
+
36
+ Para **cada fila del CSV** (excluyendo el header), lee la columna `Notas` que contiene:
37
+ `Level: FE|BE|FS | Technique: ... | Risk: N×N=N | Priority: PX`
38
+
39
+ Y la columna `Tipo prueba` que puede ser: `E2E`, `Integración FE`, `Integración BE`,
40
+ `Integración FS`, `Unitaria FE`, `Performance`, `Manual / E2E responsive`.
41
+
42
+ **Regla de clasificación:**
43
+
44
+ | Condición | Tecnología | Razón |
45
+ |---|---|---|
46
+ | `Level: BE` + tipo `Integración BE` | `.NET xUnit` | Requiere acceso directo a base de datos, middleware o infraestructura BE |
47
+ | `Level: FE` + tipo `Unitaria FE` | `Vitest` | Requiere spies/mocks de módulos internos (hooks, stores) — imposible desde Playwright |
48
+ | Todo lo demás (`FE`, `FS`, `E2E`, `Performance`, `Manual / E2E responsive`) | `Playwright` | Testable desde el browser |
49
+
50
+ **Casos especiales:**
51
+ - Tests `Level: FS` (Full Stack): siempre `Playwright` — prueban el flujo desde la UI hasta la BD
52
+ - Tests `Level: FE + Unitaria FE` con spy en `queryClient.invalidateQueries`: siempre `Vitest`
53
+ - Tests `Performance` con `Level: FE`: `Playwright` — se mide con `Date.now()` y `page.waitForFunction()`
54
+ - Tests `Manual / E2E responsive`: `Playwright` — usar `page.setViewportSize()`
55
+
56
+ ---
57
+
58
+ ## PARTE 2 — GAP ANALYSIS
59
+
60
+ Para cada test clasificado como `Playwright`:
61
+
62
+ 1. Busca en los `[SPEC FILES]` un test que cubra el mismo comportamiento observable
63
+ 2. La cobertura puede ser **exacta** (mismo ID en el nombre del test) o **equivalente** (mismo flujo descrito aunque con nombre diferente)
64
+ 3. Clasifica cada test como:
65
+ - `✅ CUBIERTO` — existe test equivalente en algún spec file
66
+ - `❌ GAP` — no existe test equivalente
67
+
68
+ **Criterios de equivalencia:**
69
+ - Mismo endpoint/acción de usuario
70
+ - Mismo resultado esperado (texto visible, URL, estado HTTP)
71
+ - El hecho de que un test tenga nombre diferente al ID del CSV NO implica que sea un gap — evalúa el comportamiento
72
+
73
+ ---
74
+
75
+ ## PARTE 3 — GENERACIÓN DE CÓDIGO
76
+
77
+ Para cada test `❌ GAP`, genera código Playwright completo siguiendo estos patrones:
78
+
79
+ ### Patrones obligatorios
80
+
81
+ **Imports** (usar el barrel del proyecto, no Playwright directamente):
82
+ ```typescript
83
+ import { test, expect } from '../support/fixtures'
84
+ import { apiRequest } from '../support/helpers/api-request'
85
+ ```
86
+
87
+ **Estructura de test:** usa el `ID Caso de Prueba` del CSV como prefijo del título (en V7.8 tiene la forma `[FEATURE_CODE]-[NIVEL]-[NNN]`, ej. `VNT-FU-001`; en diseños clásicos `TC-F*-NNN`).
88
+ ```typescript
89
+ test('VNT-FU-001 [PX] Título del test (FRxx, NFRxx)', async ({
90
+ page,
91
+ clienteFactory, // solo si se necesita
92
+ contactoFactory, // solo si se necesita
93
+ request, // solo si se hacen llamadas API directas
94
+ browser, // solo para tests multi-tab
95
+ }) => {
96
+ // GIVEN: <precondición>
97
+ // ...setup...
98
+
99
+ // WHEN: <acción del usuario>
100
+ // ...interacción...
101
+
102
+ // THEN: <resultado esperado>
103
+ // ...assertions...
104
+ })
105
+ ```
106
+
107
+ **Nomenclatura de describe blocks:** mantener la convención de describe ya presente en el spec del proyecto (ej. `EPIC-SA-F*`). Agrupa el test por su feature (columna `ID Épica`/`Feature` del CSV). Si el test pertenece a una sección que ya existe en el spec, agrégalo al final de esa sección.
108
+
109
+ ### Patrones por tipo de test
110
+
111
+ #### Optimistic Update Rollback (patrón page.route + 500)
112
+ ```typescript
113
+ // GIVEN: Backend simulado que retorna 500
114
+ await page.route('**/api/v1/{recurso}**', (route) => {
115
+ if (route.request().method() === 'POST' || route.request().method() === 'PUT') {
116
+ route.fulfill({ status: 500, body: 'Internal Server Error' })
117
+ } else {
118
+ route.continue()
119
+ }
120
+ })
121
+ // ... UI action ...
122
+ // THEN: toast de error; recurso NO aparece en lista (rollback)
123
+ await expect(page.getByText(/error|fallo/i)).toBeVisible()
124
+ await expect(page.getByText(nombre)).not.toBeVisible()
125
+ ```
126
+
127
+ #### XSS (patrón dialog listener)
128
+ ```typescript
129
+ // Capturar cualquier alert/confirm/prompt antes de navegar
130
+ const dialogs: string[] = []
131
+ page.on('dialog', (d) => { dialogs.push(d.message()); d.dismiss() })
132
+ // ... crear/guardar con payload XSS ...
133
+ // THEN: ningún dialog fue disparado
134
+ expect(dialogs).toHaveLength(0)
135
+ // Y el texto se renderiza literalmente (no ejecutado)
136
+ ```
137
+
138
+ #### Performance (patrón Date.now + waitForFunction)
139
+ ```typescript
140
+ // Seed data con Promise.all para velocidad
141
+ const creates = Array.from({ length: N }, (_, i) =>
142
+ factory.create({ nombre: `Perf Test ${i}` })
143
+ )
144
+ await Promise.all(creates)
145
+ await page.goto('/ruta')
146
+
147
+ const start = Date.now()
148
+ await searchInput.fill('término')
149
+ await page.waitForFunction(
150
+ () => document.querySelectorAll('[data-testid="list-item"]').length > 0
151
+ )
152
+ const elapsed = Date.now() - start
153
+ expect(elapsed).toBeLessThan(1000)
154
+ ```
155
+
156
+ #### Multi-tab / Multi-contexto FR27 (patrón browser.newContext)
157
+
158
+ Este patrón aplica cuando el CSV dice "2 tabs Playwright" o "FR27 cambios sin refresh":
159
+
160
+ ```typescript
161
+ test('TC-F4-018 [P0] FR27: cambios visibles en segunda tab sin refresh', async ({
162
+ browser, clienteFactory, contactoFactory
163
+ }) => {
164
+ // Setup data
165
+ const cliente = await clienteFactory.create({ nombre: 'Cliente FR27 Multi-tab F5' })
166
+ const contacto = await contactoFactory.create({ nombre: 'Contacto FR27 F5' })
167
+ await contactoFactory.assignCliente(contacto.id, cliente.id)
168
+
169
+ // Abrir dos contextos browser independientes (simula dos usuarios/pestañas)
170
+ const context1 = await browser.newContext()
171
+ const context2 = await browser.newContext()
172
+ const tab1 = await context1.newPage()
173
+ const tab2 = await context2.newPage()
174
+
175
+ // Tab 1: observa el cliente con el contacto asociado
176
+ // Ruta RELATIVA — respeta baseURL del playwright.config.ts (NUNCA hardcodear localhost ni la URL del ERP).
177
+ await tab1.goto(`/clientes/${cliente.id}`)
178
+ await expect(tab1.getByText(contacto.nombre)).toBeVisible()
179
+
180
+ // Tab 2: desasocia el contacto (acción del segundo usuario)
181
+ await tab2.goto(`/clientes/${cliente.id}`)
182
+ await tab2.getByRole('button', { name: /desasociar/i }).click()
183
+
184
+ // Tab 1: llevar Tab 1 al foreground para disparar refetchOnWindowFocus (TQ default: true)
185
+ await tab1.bringToFront()
186
+ // TanStack Query con refetchOnWindowFocus=true (default) + staleTime=5min:
187
+ // El re-fetch se dispara al recuperar el foco. Timeout generoso para CI.
188
+ await expect(tab1.getByText(contacto.nombre)).not.toBeVisible({ timeout: 15_000 })
189
+
190
+ await context1.close()
191
+ await context2.close()
192
+ })
193
+ ```
194
+
195
+ > **Nota arquitectónica:** El proyecto usa `staleTime: 5min` y `refetchOnWindowFocus: true` (default TQ).
196
+ > `bringToFront()` en Tab 1 simula el evento `visibilitychange`/`focus` del browser que dispara el re-fetch.
197
+ > Timeout de 15s cubre el round-trip del re-fetch + re-render en CI.
198
+
199
+ #### Búsqueda case-insensitive
200
+ ```typescript
201
+ const searchInput = page.getByRole('textbox', { name: /buscar/i })
202
+ .or(page.getByPlaceholder(/buscar/i))
203
+
204
+ // Probar lowercase
205
+ await searchInput.fill('término en minúsculas')
206
+ await expect(page.getByText('Término Exacto en BD')).toBeVisible()
207
+
208
+ // Probar UPPERCASE (mismo resultado)
209
+ await searchInput.fill('TÉRMINO EN MAYÚSCULAS')
210
+ await expect(page.getByText('Término Exacto en BD')).toBeVisible()
211
+ ```
212
+
213
+ #### Reasignación masiva concurrente
214
+ ```typescript
215
+ // Usar request fixture (API-level) para velocidad en CI
216
+ // Base RELATIVA por defecto — el request fixture resuelve contra baseURL; override con API_URL solo si el API vive en otro host.
217
+ const API = process.env.API_URL ?? '/api/v1'
218
+ // Seed vía factory
219
+ // Reasignar secuencialmente (no paralelo evitar race condition)
220
+ const start = Date.now()
221
+ for (const contacto of contactos) {
222
+ const res = await request.put(`${API}/contactos/${contacto.id}/cliente`, {
223
+ data: { clienteId: empresaB.id },
224
+ headers: { 'Content-Type': 'application/json' },
225
+ })
226
+ expect(res.status()).toBe(200)
227
+ }
228
+ expect(Date.now() - start).toBeLessThan(30_000)
229
+ // Verificar estado final via API
230
+ const resB = await request.get(`${API}/contactos?clienteId=${empresaB.id}`)
231
+ const body = await resB.json()
232
+ expect(body.length).toBe(N_contactos)
233
+ ```
234
+
235
+ #### Cancel edit (contactos)
236
+ ```typescript
237
+ // GIVEN: Contact in detail with edit form open
238
+ await page.goto(`/contactos/${contacto.id}`)
239
+ await page.getByRole('button', { name: /^editar$/i }).click()
240
+ const campoInput = page.getByLabel(/^campo$/i)
241
+ await campoInput.clear()
242
+ await campoInput.fill('Valor Modificado No Guardado')
243
+
244
+ // WHEN: Cancel
245
+ await page.getByRole('button', { name: /cancelar/i }).click()
246
+
247
+ // THEN: Original value shown; no HTTP PUT fired
248
+ await expect(page.getByText(valorOriginal)).toBeVisible()
249
+ await expect(page.getByText('Valor Modificado No Guardado')).not.toBeVisible()
250
+ ```
251
+
252
+ #### Clear search restores full list
253
+ ```typescript
254
+ const searchInput = page.getByLabel(/buscar/i).or(page.getByPlaceholder(/buscar/i))
255
+ // Filtrar
256
+ await searchInput.fill('término')
257
+ await expect(page.getByText(contactoExcluido.nombre)).not.toBeVisible()
258
+ // WHEN: Limpiar
259
+ await searchInput.clear()
260
+ // THEN: Lista completa
261
+ await expect(page.getByText(contactoExcluido.nombre)).toBeVisible()
262
+ await expect(page.getByText(contactoVisible.nombre)).toBeVisible()
263
+ ```
264
+
265
+ #### Contacto creado sin cliente → filtro huérfanos
266
+ ```typescript
267
+ // GIVEN: Create contact without client
268
+ const contacto = await contactoFactory.create({ nombre: 'Huérfano Creado F3', email: `huerfano.${Date.now()}@test.com` })
269
+ // No assignCliente call — contacto queda con clienteId = null
270
+
271
+ await page.goto('/contactos')
272
+ // WHEN: Activate "Sin cliente" filter
273
+ await page.getByRole('button', { name: /sin cliente/i }).click()
274
+ // THEN: The newly created orphan appears
275
+ await expect(page.getByText(contacto.nombre)).toBeVisible()
276
+ ```
277
+
278
+ ---
279
+
280
+ ## PARTE 4 FORMATO DE OUTPUT
281
+
282
+ Estructura tu respuesta en EXACTAMENTE tres bloques delimitados. No agregues texto fuera de estos bloques.
283
+
284
+ ---
285
+
286
+ ### BLOQUE A — CLASIFICACIÓN Y COBERTURA
287
+
288
+ ```
289
+ ## GAP ANALYSIS CLASIFICACIÓN COMPLETA
290
+
291
+ | ID CSV | Feature | Tecnología | Estado | Spec file (si cubierto) |
292
+ |---|---|---|---|---|
293
+ | TC-F1-001 | F1 | Playwright | ✅ CUBIERTO | navigation.spec.ts — TC-F1-03 |
294
+ | TC-F1-005 | F1 | .NET xUnit | — | N/A |
295
+ ...
296
+ ```
297
+
298
+ Incluir TODOS los TCs del CSV. No omitir ninguno.
299
+
300
+ ---
301
+
302
+ ### BLOQUE B CÓDIGO PLAYWRIGHT (GAPS)
303
+
304
+ Para cada spec file que recibe tests nuevos, un sub-bloque:
305
+
306
+ ```
307
+ ## SPEC: frontend/tests/e2e/{nombre}.spec.ts
308
+ ### AGREGAR AL FINAL DE: test.describe('{nombre del describe destino}', ...)
309
+ ### TESTS A AGREGAR: [N]
310
+
311
+ \`\`\`typescript
312
+ test('TC-F*-NNN [PX] Título del test', async ({
313
+ page,
314
+ clienteFactory,
315
+ }) => {
316
+ // GIVEN: ...
317
+ // WHEN: ...
318
+ // THEN: ...
319
+ })
320
+ \`\`\`
321
+ ```
322
+
323
+ Cada test debe ser **completo y funcional** — no pseudocódigo ni placeholders como `// TODO`.
324
+ Usar nombres únicos en los datos de seed para evitar colisiones entre tests (ej: sufijo `F5` o `timestamp`).
325
+
326
+ ---
327
+
328
+ ### BLOQUE C — FUERA DE SCOPE PLAYWRIGHT
329
+
330
+ ```
331
+ ## FUERA DE SCOPE PLAYWRIGHT
332
+
333
+ ### Vitest (Unit FE) — [N] tests
334
+ | ID | Título | Razón |
335
+ |---|---|---|
336
+ | TC-F4-014 | TQ invalida 3 queryKeys | Requiere spy en queryClient.invalidateQueries — módulo interno |
337
+ ...
338
+
339
+ ### .NET xUnit (BE) — [N] tests
340
+ | ID | Título | Razón |
341
+ |---|---|---|
342
+ | TC-F1-005 | Problem Details middleware | Level: BE — requiere acceso directo al middleware ASP.NET Core |
343
+ ...
344
+ ```
345
+
346
+ ---
347
+
348
+ ## Reglas de Calidad del Código Generado
349
+
350
+ 1. **No pseudocódigo** — cada test debe ser TypeScript válido que compile
351
+ 2. **Nombres únicos** — usar sufijos como `F5`, `Impl`, o `Date.now()` en emails/NITs para evitar colisiones
352
+ 3. **GIVEN/WHEN/THEN** — comentarios obligatorios para trazabilidad
353
+ 4. **Timeouts explícitos** solo cuando sea necesario y documentado (ej: multi-tab: `{ timeout: 15_000 }`)
354
+ 5. **Sin imports nuevos** — usar solo lo que el archivo ya importa o el barrel `../support/fixtures`
355
+ 6. **Factories, no page.fill para seed** — usar `clienteFactory.create()` / `contactoFactory.create()` para setup, no navegar por la UI
356
+ 7. **Assertions positivas y negativas** — siempre verificar tanto el estado esperado como la ausencia del estado anterior
357
+ 8. **Prioridad antes que completitud** — si hay incertidumbre sobre el selector exacto, usar el selector más semántico disponible (role > testid > text) y documentar el supuesto con un comentario
358
+ 9. **Assertions DURAS, nunca blandas (R-CI-01)** — toda verificación DEBE usar `expect()` de Playwright. **PROHIBIDO** usar `console.log`, `if/else`, `try/catch` silencioso o flags booleanas como sustituto de una assertion: un paso que solo loguea "pasa" aunque no haya hecho nada y produce **falsos 100% pass**. Si una condición debe cumplirse para que el test sea válido, va dentro de un `expect(...)` que falle el test cuando no se cumple (incluido el login: `await expect(page.getByRole(...)).toBeVisible()` tras autenticar, no un `console.log('login ok')`).
359
+ 10. **URLs relativas** — `page.goto('/ruta')` y `request` resuelven contra `baseURL`; **nunca** hardcodear `http://localhost:*` ni la URL del ERP en los specs.