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
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "playwright-agentes-ia-siesa",
3
+ "version": "1.0.0",
4
+ "description": "Kit Playwright + Agentes IA para automatización E2E — Siesa",
5
+ "scripts": {
6
+ "test": "npx playwright test",
7
+ "test:headed": "npx playwright test --headed",
8
+ "test:seed": "npx playwright test tests/seed-example.spec.ts --headed",
9
+ "recorder": "npx playwright test tools/locator-recorder.spec.ts --headed --timeout 3600000",
10
+ "test:proposed": "npx playwright test proposed/ --config playwright.config.ts",
11
+ "report": "npx playwright show-report",
12
+ "report:allure": "npx allure generate allure-results -o allure-report --clean && npx allure open allure-report",
13
+ "report:monocart": "npx monocart show-report monocart-report/index.html"
14
+ },
15
+ "keywords": ["playwright", "e2e", "siesa", "qa", "agentes-ia"],
16
+ "author": "Equipo QA I&D — Procesos y Calidad de Software, SIESA",
17
+ "license": "UNLICENSED",
18
+ "private": true,
19
+ "type": "commonjs",
20
+ "devDependencies": {
21
+ "@playwright/test": "^1.60.0",
22
+ "allure-commandline": "^2.42.0",
23
+ "allure-playwright": "^3.9.0",
24
+ "dotenv": "^16.4.5",
25
+ "monocart-reporter": "^2.11.2"
26
+ }
27
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Configuracion Playwright — Kit Agentes IA Siesa
3
+ * Reporters: list + HTML + Allure + Monocart
4
+ */
5
+ import { defineConfig } from '@playwright/test';
6
+ import { config as dotenvConfig } from 'dotenv';
7
+
8
+ dotenvConfig(); // carga .env personal del QA (gitignored); usa defaults si no existe
9
+
10
+ export default defineConfig({
11
+ globalSetup: './global-setup.ts',
12
+ testDir: '.',
13
+ timeout: 120_000,
14
+ expect: { timeout: 10_000 },
15
+ fullyParallel: false,
16
+ // R-CI-03: Blazor Server (SignalR) requiere reintentos ante fallos transitorios.
17
+ retries: process.env.CI ? 2 : 1,
18
+ workers: 1,
19
+ reporter: [
20
+ ['list'],
21
+ ['html', { open: 'never', outputFolder: 'playwright-report' }],
22
+ // R-CI-07: junit.xml para GitHub Actions / Jenkins (solo en CI).
23
+ ...(process.env.CI ? [['junit', { outputFile: 'test-results/junit.xml' }] as const] : []),
24
+ ['allure-playwright', {
25
+ outputFolder: 'allure-results',
26
+ detail: true,
27
+ suiteTitle: process.env.SUITE_TITLE || 'E2E — Pipeline Agentes IA',
28
+ }],
29
+ ['monocart-reporter', {
30
+ name: process.env.SUITE_TITLE || 'E2E — Pipeline Agentes IA',
31
+ outputFile: 'monocart-report/index.html',
32
+ }],
33
+ ],
34
+ use: {
35
+ baseURL: process.env.BASE_URL || 'https://erp-qa-beta.siesaerp.com',
36
+ // CI corre en runners Linux sin display → headless obligatorio.
37
+ headless: !!process.env.CI,
38
+ viewport: null,
39
+ // Timeouts parametrizables por entorno (CI lento puede subirlos sin tocar código).
40
+ actionTimeout: Number(process.env.ACTION_TIMEOUT ?? 30_000),
41
+ navigationTimeout: Number(process.env.NAVIGATION_TIMEOUT ?? 30_000),
42
+ // En CI solo capturar evidencia ante fallo para no saturar disco.
43
+ screenshot: process.env.CI ? 'only-on-failure' : 'on',
44
+ // En CI, trace solo en el primer reintento (archivos más pequeños que retain-on-failure).
45
+ trace: process.env.CI ? 'on-first-retry' : 'retain-on-failure',
46
+ video: process.env.CI ? 'off' : 'on',
47
+ },
48
+ projects: [
49
+ {
50
+ name: 'chromium',
51
+ use: {
52
+ browserName: 'chromium',
53
+ launchOptions: {
54
+ slowMo: 150,
55
+ args: ['--force-device-scale-factor=1', '--high-dpi-support=1', '--start-maximized'],
56
+ },
57
+ },
58
+ },
59
+ ],
60
+ });
@@ -0,0 +1,252 @@
1
+ /**
2
+ * AgileTest Playwright Reporter
3
+ *
4
+ * Sincroniza resultados de Playwright con una Ejecución de Pruebas en AgileTest.
5
+ * Se desactiva silenciosamente si las env vars no están definidas.
6
+ *
7
+ * Env vars requeridas:
8
+ * AGILETEST_CLIENT_ID, AGILETEST_CLIENT_SECRET
9
+ * AGILETEST_EXECUTION_ID — ID interno de la ejecución (ej: 115230)
10
+ * AGILETEST_TC_ID — ID interno del test case (ej: 1531352)
11
+ * AGILETEST_PROJECT_ID — ID del proyecto Jira (default: 10032)
12
+ * AGILETEST_TOTAL_STEPS — Total de pasos del TC (default: 17)
13
+ * Si se ejecutan menos, el TC queda "In Progress"
14
+ */
15
+ import type {
16
+ Reporter,
17
+ TestCase,
18
+ TestResult,
19
+ FullConfig,
20
+ Suite,
21
+ FullResult,
22
+ } from '@playwright/test/reporter';
23
+
24
+ const AGILETEST_BASE = 'https://jira.agiletest.app';
25
+
26
+ /** Status IDs para proyecto HCM (10032) */
27
+ const STATUS_IDS = {
28
+ todo: 15016,
29
+ pass: 15017,
30
+ fail: 15018,
31
+ blocked: 15019,
32
+ inProgress: 15020,
33
+ skipped: 15021,
34
+ retest: 15022,
35
+ } as const;
36
+
37
+ interface StepResult {
38
+ paso: string;
39
+ title: string;
40
+ status: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted';
41
+ duration: number;
42
+ error?: string;
43
+ }
44
+
45
+ interface AgileTestConfig {
46
+ clientId: string;
47
+ clientSecret: string;
48
+ executionId: string;
49
+ tcId: string;
50
+ projectId: string;
51
+ totalSteps: number;
52
+ }
53
+
54
+ function readConfig(): AgileTestConfig | null {
55
+ const clientId = process.env.AGILETEST_CLIENT_ID;
56
+ const clientSecret = process.env.AGILETEST_CLIENT_SECRET;
57
+ const executionId = process.env.AGILETEST_EXECUTION_ID;
58
+ const tcId = process.env.AGILETEST_TC_ID;
59
+
60
+ if (!clientId || !clientSecret || !executionId || !tcId) {
61
+ return null;
62
+ }
63
+
64
+ return {
65
+ clientId,
66
+ clientSecret,
67
+ executionId,
68
+ tcId,
69
+ projectId: process.env.AGILETEST_PROJECT_ID || '10032',
70
+ totalSteps: parseInt(process.env.AGILETEST_TOTAL_STEPS || '17', 10),
71
+ };
72
+ }
73
+
74
+ async function getToken(clientId: string, clientSecret: string): Promise<string> {
75
+ const res = await fetch(`${AGILETEST_BASE}/api/apikeys/authenticate`, {
76
+ method: 'POST',
77
+ headers: { 'Content-Type': 'application/json' },
78
+ body: JSON.stringify({ clientId, clientSecret }),
79
+ });
80
+
81
+ if (!res.ok) {
82
+ throw new Error(`AgileTest auth failed: ${res.status} ${await res.text()}`);
83
+ }
84
+
85
+ // La respuesta es el token como string con comillas
86
+ const raw = await res.text();
87
+ return raw.replace(/^"|"$/g, '');
88
+ }
89
+
90
+ class AgileTestReporter implements Reporter {
91
+ private results: StepResult[] = [];
92
+ private config: AgileTestConfig | null = null;
93
+ private enabled = false;
94
+
95
+ onBegin(_config: FullConfig, _suite: Suite): void {
96
+ this.config = readConfig();
97
+ this.enabled = this.config !== null;
98
+
99
+ if (!this.enabled) {
100
+ console.log('[AgileTest] Reporter desactivado — env vars no definidas');
101
+ } else {
102
+ console.log('[AgileTest] Reporter activo — sincronizará con ejecución', this.config!.executionId);
103
+ }
104
+ }
105
+
106
+ onTestEnd(test: TestCase, result: TestResult): void {
107
+ if (!this.enabled) return;
108
+
109
+ const match = test.title.match(/Paso\s+(\d+|manual)/i);
110
+ const paso = match ? match[1] : '?';
111
+
112
+ this.results.push({
113
+ paso,
114
+ title: test.title,
115
+ status: result.status,
116
+ duration: result.duration,
117
+ error: result.errors?.[0]?.message?.substring(0, 300),
118
+ });
119
+ }
120
+
121
+ async onEnd(_result: FullResult): Promise<void> {
122
+ if (!this.enabled || this.results.length === 0) return;
123
+
124
+ const config = this.config!;
125
+
126
+ // Determinar si la ejecución fue completa o parcial
127
+ const executedSteps = this.results.filter(r => r.paso !== '?').length;
128
+ const isPartial = executedSteps < config.totalSteps;
129
+
130
+ const hasFailed = this.results.some(
131
+ r => r.status === 'failed' || r.status === 'timedOut' || r.status === 'interrupted',
132
+ );
133
+ const hasSkipped = this.results.some(r => r.status === 'skipped');
134
+
135
+ let globalStatusId: number;
136
+ let statusLabel: string;
137
+
138
+ if (hasFailed) {
139
+ globalStatusId = STATUS_IDS.fail;
140
+ statusLabel = 'FAIL';
141
+ } else if (isPartial) {
142
+ globalStatusId = STATUS_IDS.inProgress;
143
+ statusLabel = `IN PROGRESS (${executedSteps}/${config.totalSteps})`;
144
+ } else if (hasSkipped) {
145
+ globalStatusId = STATUS_IDS.skipped;
146
+ statusLabel = 'SKIPPED';
147
+ } else {
148
+ globalStatusId = STATUS_IDS.pass;
149
+ statusLabel = 'PASS';
150
+ }
151
+
152
+ try {
153
+ // 1. Autenticar
154
+ console.log('[AgileTest] Autenticando...');
155
+ const token = await getToken(config.clientId, config.clientSecret);
156
+
157
+ const headers = {
158
+ 'Content-Type': 'application/json',
159
+ Authorization: `JWT ${token}`,
160
+ };
161
+
162
+ // 2. PUT resultado del TC en la ejecución
163
+ console.log(`[AgileTest] Actualizando TC ${config.tcId} → ${statusLabel}`);
164
+ const putUrl = `${AGILETEST_BASE}/ds/test-executions/${config.executionId}/test-case/${config.tcId}`;
165
+ const putRes = await fetch(putUrl, {
166
+ method: 'PUT',
167
+ headers,
168
+ body: JSON.stringify({
169
+ testExecutionItem: { result: globalStatusId },
170
+ projectId: config.projectId,
171
+ }),
172
+ });
173
+
174
+ if (!putRes.ok) {
175
+ console.error(`[AgileTest] PUT falló: ${putRes.status} ${await putRes.text()}`);
176
+ return;
177
+ }
178
+
179
+ const putData = await putRes.json();
180
+ const itemId = putData.id;
181
+ console.log(`[AgileTest] Resultado actualizado → ${statusLabel} (itemId: ${itemId})`);
182
+
183
+ // 3. POST comentario con tabla detallada vía test-execution-items
184
+ const comment = this.buildComment(statusLabel, isPartial, config.totalSteps);
185
+ const commentUrl = `${AGILETEST_BASE}/ds/test-execution-items/${itemId}/comments`;
186
+ const commentRes = await fetch(commentUrl, {
187
+ method: 'POST',
188
+ headers,
189
+ body: JSON.stringify({ text: comment, projectId: config.projectId }),
190
+ });
191
+
192
+ if (commentRes.ok) {
193
+ console.log('[AgileTest] Comentario agregado con detalle por paso');
194
+ } else {
195
+ console.warn(`[AgileTest] Comentario falló: ${commentRes.status} (no crítico)`);
196
+ }
197
+ } catch (err) {
198
+ console.error('[AgileTest] Error sincronizando resultados:', err);
199
+ }
200
+ }
201
+
202
+ private buildComment(globalStatus: string, isPartial: boolean, totalSteps: number): string {
203
+ const sorted = [...this.results].sort((a, b) => {
204
+ const na = a.paso === 'manual' ? 999 : a.paso === '?' ? 1000 : Number(a.paso);
205
+ const nb = b.paso === 'manual' ? 999 : b.paso === '?' ? 1000 : Number(b.paso);
206
+ return na - nb;
207
+ });
208
+
209
+ const totalDuration = this.results.reduce((sum, r) => sum + r.duration, 0);
210
+ const passed = this.results.filter(r => r.status === 'passed').length;
211
+ const failed = this.results.filter(
212
+ r => r.status === 'failed' || r.status === 'timedOut' || r.status === 'interrupted',
213
+ ).length;
214
+ const skipped = this.results.filter(r => r.status === 'skipped').length;
215
+ const pending = totalSteps - this.results.filter(r => r.paso !== '?').length;
216
+
217
+ const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
218
+
219
+ const lines = [
220
+ `*Playwright Automated Run — ${timestamp}*`,
221
+ `*Resultado: ${globalStatus}* | ${passed} passed, ${failed} failed, ${skipped} skipped${pending > 0 ? `, ${pending} pending` : ''} | ${this.formatDuration(totalDuration)}`,
222
+ '',
223
+ '||Paso||Nombre||Status||Duración||',
224
+ ];
225
+
226
+ for (const r of sorted) {
227
+ const icon = r.status === 'passed' ? '(/)' : r.status === 'skipped' ? '(i)' : '(x)';
228
+ const statusText = r.status === 'passed' ? 'PASS' : r.status === 'skipped' ? 'SKIP' : 'FAIL';
229
+ lines.push(`|${r.paso}|${r.title}|${icon} ${statusText}|${this.formatDuration(r.duration)}|`);
230
+ }
231
+
232
+ if (isPartial) {
233
+ lines.push(`|—|_Pasos ${this.results.length + 1}-${totalSteps} pendientes_|(//) TO DO|—|`);
234
+ }
235
+
236
+ if (failed > 0) {
237
+ lines.push('', '*Errores:*');
238
+ for (const r of sorted.filter(s => s.error)) {
239
+ lines.push(`* Paso ${r.paso}: {noformat}${r.error}{noformat}`);
240
+ }
241
+ }
242
+
243
+ return lines.join('\n');
244
+ }
245
+
246
+ private formatDuration(ms: number): string {
247
+ if (ms < 1000) return `${ms}ms`;
248
+ return `${(ms / 1000).toFixed(1)}s`;
249
+ }
250
+ }
251
+
252
+ export default AgileTestReporter;
@@ -0,0 +1,85 @@
1
+ # Architecture Brief — SIESA ERP (Blazor Server + DevExpress + Radzen)
2
+
3
+ > Brief lleno para la plataforma SIESA ERP. Los patrones aquí documentados son
4
+ > **transferibles entre módulos** porque todos comparten el mismo stack (SDK UI Kit).
5
+ > Lo que el agente aprende en un módulo le sirve en todos.
6
+
7
+ ---
8
+
9
+ ## Stack y Comportamiento
10
+
11
+ - **Framework UI:** Blazor Server con SignalR (conexión persistente)
12
+ - **Componentes:** DevExpress BlazorGrid + Radzen Dialogs + SDK UI Kit
13
+ - **Pre-carga requerida:** SÍ — navegar a la URL base (sin parámetros `dat=`) para inicializar
14
+ SignalR, esperar `networkidle` + 3s, y luego navegar a la URL real.
15
+ - **Cultura/Idioma:** ES con tildes. Algunos dropdowns (Género, Estado civil) pueden aparecer
16
+ en EN según config del servidor → **intentar ES primero, fallback EN**.
17
+ - **Auth:** MasterCrud + SSO (ERP) · Email+Password (Portal de Empleo).
18
+
19
+ ## Patrones de Interacción
20
+
21
+ ### Campos de texto (SDKCharField)
22
+ - **Selector:** `[data-automation-id="SDKCharField_{CAMPO}"] input.dxbl-text-edit-input`
23
+ - **Interacción:** click → `fill("")` → `pressSequentially(valor, { delay: 30 })`
24
+
25
+ ### Campos numéricos (SDKNumericField)
26
+ - **Selector:** `[data-automation-id="NumericField_{CAMPO}"] input.dxbl-text-edit-input`
27
+ - **Nota:** DevExpress formatea con decimales — assertion con regex `/^valor(\.00)?$/`.
28
+
29
+ ### Dropdowns simples (SDKSelectField — Radzen)
30
+ - **Selector:** `[data-automation-id="SDKSelectField_{CAMPO}"] span.rz-dropdown-label`
31
+ - **Interacción:** click → esperar opciones → click en option por texto (ES, fallback EN).
32
+
33
+ ### Entity fields (SDKEntityField — dropdown con búsqueda)
34
+ - **Input:** `[data-automation-id="SDKEntityField_{CAMPO}"] input`
35
+ - **Primera opción:** `[data-automation-id="SDKEntityField_{CAMPO}_1"]`
36
+ - **Lupa:** `[data-automation-id="SDKEntityField_{CAMPO}_SearchBtn"]`
37
+ - **Crear:** `[data-automation-id="SDKEntityField_{CAMPO}_CreateBtn"]`
38
+ - **Dropdown:** click input → esperar lista → click `_1`
39
+ - **Búsqueda:** click lupa → submit → dblclick en celda del grid
40
+ - **Crear inline:** click crear → llenar Id + Name + Status Activo
41
+
42
+ ### Textareas (SDKTextField) — descripción SIEMPRE es textarea
43
+ - **Selector:** `[data-automation-id="SDKTextField_{CAMPO}"] textarea`
44
+ - **Interacción:** click → Ctrl+A → `fill("")` → `fill(valor)`
45
+
46
+ ### Campos de fecha (Radzen DatePicker)
47
+ - **Interacción:** `click({ clickCount: 3 })` → `pressSequentially(MM/DD/YYYY)` → Tab
48
+
49
+ ### Botones toolbar (TopBarButton)
50
+ - **Editar:** `[data-automation-id="TopBarButton_Action.Edit"]`.last()
51
+ - **Guardar:** `[data-automation-id="TopBarButton_Action.Save"]`.last()
52
+ - **Cancelar:** `[data-automation-id="TopBarButton_Action.Cancel"]`.last()
53
+
54
+ ### Dialogs y Popups
55
+ - **Wrapper:** `.rz-dialog-wrapper` (puede interceptar clicks)
56
+ - **Cerrar:** JS click en `a.rz-dialog-titlebar-close`
57
+ - **Grid DevExpress:** `td` con `role="gridcell"`, dblclick para seleccionar
58
+
59
+ ## Problemas Conocidos
60
+
61
+ - [x] **Pre-load SignalR:** navegar a URL base sin `dat=` primero, luego URL real.
62
+ - [x] **Modal reconexión Blazor:** `#components-reconnect-modal` intercepta clicks → esperar hidden o force click.
63
+ - [x] **Loader SDK:** `.sdk-loader` bloquea interacción → esperar `state: hidden` (timeout 10s).
64
+ - [x] **Toolbar duplicada:** sidebar oculto duplica botones → usar `.last()`.
65
+ - [x] **Cultura dual:** dropdowns pueden mostrar ES o EN → intentar ES, fallback EN.
66
+ - [x] **Celda de filtro DevExpress:** tras `.fill()` presionar `Enter` para activar el binding.
67
+ - [x] **Listados dual-mode:** primera visita = formulario de búsqueda; después = grid.
68
+
69
+ ## Helpers disponibles (`helpers/blazor-e2e-helpers.ts`)
70
+
71
+ `login` · `fillSDKText` · `fillSDKTextArea` · `fillFechaRadzen` · `clickToolbar` ·
72
+ `closeOverlays` · `clickActivo` · `waitForSave` · `selectEntityPopup` · `searchInGrid` ·
73
+ `navigateTo` · `waitForGrid` · `getFechas` · `createObsCollector`
74
+
75
+ ## Aplicaciones registradas
76
+
77
+ | Aplicación | Stack | URL base | Auth |
78
+ |-----------|-------|----------|------|
79
+ | Portal de Empleo | Blazor + DevExpress + Radzen | beta-aws.siesadev.com/BLPortalLogin | Email+Password |
80
+ | ERP HCM | Blazor + DevExpress + SDK UI Kit | beta-aws.siesadev.com | MasterCrud + SSO |
81
+ | Encuestas | Blazor + DevExpress + SDK UI Kit | beta-aws.siesadev.com | SSO |
82
+ | Financiero | React 18 + siesa-ui-kit + .NET 8 | qa.finance.siesa.dev/app | SSO |
83
+
84
+ > **Patrones transferibles:** entity field, SDKSelectField, SDKCharField, TopBarButton,
85
+ > pre-load SignalR, modal reconexión, loader SDK, toolbar duplicada, cultura dual.
@@ -0,0 +1,109 @@
1
+ # Architecture Brief — SIESA React (Vite + TanStack + siesa-ui-kit)
2
+
3
+ > Brief para las apps SIESA construidas con el **stack corporativo React** (la arquitectura
4
+ > objetivo de los microservicios nuevos). Los patrones aquí son **transferibles entre módulos**
5
+ > porque todos comparten siesa-ui-kit + MasterCrud + LookupField.
6
+ >
7
+ > ⚠️ Este es el stack **distinto al ERP Blazor**. Si la app objetivo es Blazor Server / DevExpress /
8
+ > Radzen, usa `architecture-brief-siesa-erp.md`, NO este.
9
+
10
+ ---
11
+
12
+ ## Stack y Comportamiento
13
+
14
+ - **Framework UI:** React (componentes `.tsx`, hooks) · Vite 7+ · TanStack Router (file-based)
15
+ - **Componentes:** siesa-ui-kit (librería propia) + Radix UI + shadcn/ui. Orquestador de masters: `MasterCrud`.
16
+ - **Estado:** Zustand (cliente) + TanStack Query (servidor). Los datos llegan async → esperar fin de fetch.
17
+ - **Pre-carga requerida:** NO. No hay SignalR ni conexión persistente (a diferencia de Blazor).
18
+ Esperar a que TanStack Query resuelva (spinner/skeleton oculto) en vez de `networkidle` fijo.
19
+ - **Cultura/Idioma:** ES por defecto; i18n por `react-i18next`/locale. Confirmar en el brief del módulo.
20
+ - **Auth:** SSO corporativo (JWT con TenantId). `Company-ID` en headers para operaciones multi-compañía.
21
+ - **Microfrontends:** Single-SPA (default). La app se monta en `#single-spa-application`.
22
+
23
+ ## Ambiente
24
+
25
+ - **URL base:** {URL — ej: qa.finance.siesa.dev/app}
26
+ - **Usuario de prueba:** {referencia a env var}
27
+ - **Password:** {referencia a env var, NUNCA literal en el repo}
28
+
29
+ ## Estrategia de selectores (orden de preferencia)
30
+
31
+ > En React, prioriza selectores **semánticos y estables** sobre CSS. Si el componente no expone
32
+ > rol/label accesible, pide `data-testid` al equipo y documéntalo aquí; no uses clases de Tailwind
33
+ > como selector (cambian con el estilo).
34
+
35
+ 1. `getByRole('button', { name: 'Guardar' })` / `getByRole('textbox', { name: 'Código' })`
36
+ 2. `getByLabel('Nombre')` (campos asociados a `<label>`)
37
+ 3. `getByTestId('...')` (`data-testid` acordado con el equipo)
38
+ 4. CSS solo como último recurso, documentado abajo.
39
+
40
+ ## Patrones de Interacción (completar con lo confirmado por módulo/dev)
41
+
42
+ ### Campos de texto / número (React Hook Form)
43
+ - **Selector:** `getByLabel('{CAMPO}')` o `getByRole('textbox', { name: '{CAMPO}' })`
44
+ - **Interacción:** `click` → `fill('')` → `fill(valor)`. La validación Zod aparece on-blur/submit.
45
+ - **Nota:** assertion sobre el valor con `toHaveValue`; para errores, `getByText` del mensaje Zod.
46
+
47
+ ### Dropdowns / Select (siesa-ui-kit / Radix Select)
48
+ - **Selector trigger:** `getByRole('combobox', { name: '{CAMPO}' })`
49
+ - **Interacción:** click trigger → `getByRole('option', { name: '{VALOR}' })` → click.
50
+
51
+ ### LookupField (selección de registros maestros — patrón R-LF-001)
52
+ - **Input:** `getByRole('combobox', { name: '{CAMPO}' })` o `getByTestId('lookup-{campo}')`
53
+ - **Interacción:** escribir término → esperar lista (TanStack Query) → click opción.
54
+ - **Dependientes (cascada):** seleccionar el padre primero; el hijo se habilita por `dependencies`.
55
+
56
+ ### MasterCrud (orquestador de masters)
57
+ - **Crear:** botón `getByRole('button', { name: 'Nuevo' })` → llenar form → `Guardar`.
58
+ - **Editar:** seleccionar fila de la grilla → `Editar` → modificar → `Guardar`.
59
+ - **Navegación:** modal / sidebar / page según prop `navigationType` (confirmar en brief del módulo).
60
+
61
+ ### Formularios (React Hook Form + Zod)
62
+ - **Submit:** `getByRole('button', { name: 'Guardar' })`.
63
+ - **Validación:** los errores Zod se renderizan como texto cerca del campo → `getByText(/mensaje/)`.
64
+
65
+ ### Botones / Toolbar
66
+ - **Guardar / Cancelar / Eliminar:** `getByRole('button', { name: '{ACCIÓN}' })`.
67
+
68
+ ### Dialogs y Popups (Radix Dialog)
69
+ - **Wrapper:** `getByRole('dialog')` · **Cerrar:** `getByRole('button', { name: /cerrar|close/i })`.
70
+
71
+ ## Estados de carga y espera (clave en React)
72
+
73
+ - **No** uses esperas fijas. Espera a que el spinner/skeleton de TanStack Query desaparezca:
74
+ `await expect(page.getByRole('progressbar')).toBeHidden()` o el `data-testid` del loader del kit.
75
+ - Tras guardar, espera el toast de éxito o la invalidación de query (la grilla se refresca sola).
76
+
77
+ ## Problemas Conocidos (marcar los que apliquen por módulo)
78
+
79
+ - [ ] **Carga async TanStack Query:** esperar fin de fetch antes de interactuar/aseverar.
80
+ - [ ] **Toast de confirmación:** aparece y se auto-oculta → capturarlo rápido o assert por rol `status`.
81
+ - [ ] **Single-SPA mount:** la app vive bajo `#single-spa-application`; esperar montaje.
82
+ - [ ] **Dark mode (class-based):** no afecta selectores semánticos; sí selectores por color.
83
+ - [ ] **Otro:** {describir}
84
+
85
+ ## Datos de Prueba de referencia
86
+
87
+ | Campo | Valor | Patrón | Notas |
88
+ |-------|-------|--------|-------|
89
+ | {campo} | {valor} | texto/select/lookup/fecha | {nota} |
90
+
91
+ ## Helpers disponibles (`helpers/react-e2e-helpers.ts`)
92
+
93
+ `createObsCollector` · `login` · `waitForQuery` · `fillField` · `selectOption` · `selectLookup` ·
94
+ `setToggle` · `submitForm` · `clickAction` · `expectToast` · `expectFieldError` · `navigateTo` ·
95
+ `waitForGrid` · `getFechas` · `step` · `generateRunId`
96
+
97
+ > ⚠️ **Scaffold:** los selectores concretos de siesa-ui-kit no están documentados en el repo,
98
+ > así que varios helpers traen suposiciones marcadas con `TODO[brief]` (rol del LookupField,
99
+ > loader, ruta de login SSO). Cada helper acepta `byTestId`/overrides — confírmalos contra la
100
+ > app real (vía dev o grabador) antes de promover specs a `tests/`.
101
+
102
+ ## Aplicaciones registradas (React)
103
+
104
+ | Aplicación | Stack | URL base | Auth |
105
+ |-----------|-------|----------|------|
106
+ | Financiero | React 18 + siesa-ui-kit + .NET 8 | qa.finance.siesa.dev/app | SSO |
107
+
108
+ > **Retroalimentación del Healer:** cada patrón/quirk nuevo que descubra el Healer se agrega aquí
109
+ > con prefijo `[HEALER]` (validar que se repita en ≥2 corridas antes de marcarlo estable).
@@ -0,0 +1,69 @@
1
+ # Architecture Brief — {NOMBRE_APLICACIÓN}
2
+
3
+ > **Qué es:** el insumo que le da al Agente Ejecutor E2E el conocimiento del stack, los
4
+ > selectores y los patrones de interacción de la app objetivo. Con brief, el agente genera
5
+ > specs correctos al primer intento (~85-90% autonomía); sin él, descubre por prueba y error.
6
+ >
7
+ > **Cómo llenarlo:** (1) un dev lo completa en ~5 min, o (2) QA usa el grabador
8
+ > (`tools/locator-recorder.spec.ts`) en una sesión exploratoria y lo llena.
9
+
10
+ ---
11
+
12
+ ## Stack y Comportamiento
13
+
14
+ - **Framework UI:** {ej: Blazor Server con SignalR / React 18 / Angular}
15
+ - **Componentes:** {ej: DevExpress BlazorGrid + Radzen Dialogs + SDK UI Kit}
16
+ - **Pre-carga requerida:** {SI/NO — si SI, describir el patrón. Ej: "navegar a URL base sin parámetros para inicializar SignalR, luego navegar a la URL real"}
17
+ - **Cultura/Idioma:** {ej: "ES con tildes; algunos dropdowns aparecen en EN según config del servidor — intentar ES primero"}
18
+ - **Auth:** {ej: Email+Password / SSO corporativo / MasterCrud}
19
+
20
+ ## Ambiente
21
+
22
+ - **URL base:** {URL}
23
+ - **Usuario de prueba:** {usuario o referencia a env var}
24
+ - **Password:** {referencia a env var, NUNCA literal en el repo}
25
+
26
+ ## Patrones de Interacción (completar solo los que apliquen)
27
+
28
+ ### Campos de texto
29
+ - **Selector:** `{ej: [data-automation-id="SDKCharField_{CAMPO}"] input.dxbl-text-edit-input}`
30
+ - **Interacción:** {ej: click → fill("") → pressSequentially(valor, { delay: 30 })}
31
+
32
+ ### Campos numéricos
33
+ - **Selector:** `{...}`
34
+ - **Nota:** {ej: DevExpress formatea con decimales — "70" se muestra como "70.00"}
35
+
36
+ ### Dropdowns simples
37
+ - **Selector:** `{ej: [data-automation-id="SDKSelectField_{CAMPO}"] span.rz-dropdown-label}`
38
+ - **Interacción:** {click → esperar opciones → click en option por texto}
39
+
40
+ ### Entity fields (dropdown con búsqueda/lupa)
41
+ - **Input:** `{...}` · **Primera opción:** `{..._1}` · **Lupa:** `{..._SearchBtn}` · **Crear:** `{..._CreateBtn}`
42
+ - **Interacción:** {dropdown: click input → esperar lista → click `_1`; búsqueda: click lupa → submit → dblclick celda}
43
+
44
+ ### Campos de fecha
45
+ - **Selector:** `{...}` · **Interacción:** {ej: click({clickCount:3}) → pressSequentially(MM/DD/YYYY) → Tab}
46
+
47
+ ### Botones toolbar
48
+ - **Editar / Guardar / Cancelar:** `{...}`
49
+
50
+ ### Dialogs y Popups
51
+ - **Wrapper / cerrar / grid:** `{...}`
52
+
53
+ ## Problemas Conocidos (marcar los que apliquen)
54
+
55
+ - [ ] **Pre-load:** {descripción}
56
+ - [ ] **Modal de reconexión:** {selector + cómo manejarlo}
57
+ - [ ] **Loader bloqueante:** {selector + esperar hidden}
58
+ - [ ] **Toolbar duplicada:** {usar `.last()`}
59
+ - [ ] **Cultura dual ES/EN:** {intentar ES, fallback EN}
60
+ - [ ] **Otro:** {describir}
61
+
62
+ ## Datos de Prueba de referencia
63
+
64
+ | Campo | Valor | Patrón | Notas |
65
+ |-------|-------|--------|-------|
66
+ | {campo} | {valor} | texto/dropdown/entity/fecha | {nota} |
67
+
68
+ > **Retroalimentación del Healer:** cada vez que el Healer descubra un patrón/quirk nuevo,
69
+ > agregarlo aquí con prefijo `[HEALER]` para que los próximos módulos se beneficien.