Files
plantillas-proyectos/frontend/e2e/FlujoCompleto.MD
Kevin_Ramirez bdd089954b
Some checks failed
Build Producción & Push a Harbor / test (push) Failing after 3s
Build Producción & Push a Harbor / build (push) Has been skipped
Aduanasoft/plantillas-proyectos/pipeline/head There was a failure building this commit
feat: plantilla base workspace SaaS
2026-07-21 13:59:00 -05:00

248 lines
9.3 KiB
Markdown

# Reporte de Pruebas E2E — Flujo de Factura
**Proyecto:** Anexo 76 — Sistema de Control de Operaciones Aduaneras
**Herramienta:** Playwright
**Archivo:** `frontend/e2e/invoice-flow.spec.ts`
**Fecha:** Abril 2026
**Estado:** 12/12 pruebas pasando ✅
**Tiempo de ejecución:** ~4.4 minutos
---
## Resumen
| Categoría | Pruebas | Estado |
|-----------|---------|--------|
| Prerrequisitos (proveedor, cliente, agente, TC) | 4 | ✅ |
| Pedimento | 1 | ✅ |
| Factura TEM | 3 | ✅ |
| Partidas | 2 | ✅ |
| Actualización final | 1 | ✅ |
| **Total** | **11** | **✅** |
> Nota: el test de setup de autenticación (`auth.setup.ts`) suma 1 prueba adicional, totalizando 12 en el runner.
---
## Flujo completo
### 1. Crear proveedor
Navega a `/dashboard/clients_and_providers`, abre el formulario de nuevo socio, llena RFC y nombre, selecciona tipo "Proveedor" y guarda. Verifica redirección a la lista y que el nombre aparece en la tabla.
### 2. Crear cliente
Mismo flujo que el proveedor pero con tipo "Cliente".
### 3. Crear agente aduanal
Navega a `/dashboard/customs_brokers`, abre el formulario, llena clave, patente y nombre. Verifica toast de éxito y redirección.
### 4. Crear tipo de cambio
Navega a `/dashboard/general_catalogs/exchange-rate`, abre el modal de nuevo tipo de cambio, llena fecha de hoy y valor `17.5`, confirma. Verifica toast de éxito.
### 5. Crear pedimento
Navega a `/dashboard/pedimentos/edit/new`, llena año (`26`), selecciona Aduana, Patente, Clave, Tipo de Operación y Régimen via bits-ui Select. Llena número de pedimento. Guarda y verifica redirección a `/dashboard/pedimentos`.
### 6. Crear factura de importación TEM
Navega a `/dashboard/invoices/edit/new?operation_type=imp&invoice_type=TEM`. Llena número de factura con `pressSequentially`, fecha, y en la pestaña General selecciona proveedor, sold-to, shipped-to, agente aduanal, aduana y tipo de documento. Guarda y verifica toast de éxito. Al finalizar guarda el número de factura en `.e2e-shared.json` para los tests posteriores.
### 7. Factura aparece en la lista
Filtra por número de factura en la lista de importación y verifica que la fila es visible.
### 8. Agregar partida a la factura
Abre la factura desde la lista, navega a la pestaña Partidas, abre el sheet de nueva partida. Selecciona Clase, U.M. y País de Origen (cada uno abre un dialog con tabla). Llena cantidad (`10`), costo unitario (`100`), peso neto (`5`), peso bruto (`6`) y descripción en español. Hace click en el botón "Crear" del sheet. Guarda la factura completa.
### 9. Editar factura existente
Lee el número de factura desde `.e2e-shared.json`, la busca en la lista, la abre en modo edición. En la pestaña General vuelve a seleccionar agente aduanal, aduana y tipo de documento. Guarda y verifica toast de éxito.
### 10. Editar partida existente
Lee el número desde shared, abre la factura, va a pestaña Partidas. Hace click en el ícono Pencil de la primera fila para abrir el sheet de edición. Modifica cantidad (`20`) y costo unitario (`200`). Hace click en "Actualizar" del sheet. Guarda la factura.
### 11. Actualizar factura — verificación final
Lee el número desde shared, busca la factura en la lista, selecciona la fila, hace click en el botón "Actualizar" del footer (ícono RefreshCw, clase `h-8`). Verifica el resultado con toast de éxito.
---
## Patrones técnicos establecidos
### fillInput — inputs reactivos de Svelte 5
Los inputs de Svelte 5 no responden a `page.fill()` ni `pressSequentially` de forma confiable. La solución es usar el native setter del prototipo:
```typescript
async function fillInput(page: Page, selector: string, value: string) {
await page.locator(selector).click()
await page.evaluate(({ sel, val }) => {
const el = document.querySelector(sel) as HTMLInputElement
const setter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, 'value'
)?.set
setter?.call(el, val)
el.dispatchEvent(new Event('input', { bubbles: true }))
el.dispatchEvent(new Event('change', { bubbles: true }))
}, { sel: selector, val: value })
await page.waitForTimeout(2000)
}
```
La excepción es `#invoice_number`, que sí responde a `pressSequentially` con delay:
```typescript
await page.locator('#invoice_number').click()
await page.keyboard.press('Control+A')
await page.locator('#invoice_number').pressSequentially(INVOICE_NUMBER, { delay: 1000 })
```
### bits-ui Select — selects con IDs dinámicos
Los selects de bits-ui generan IDs como `bits-s65` que cambian en cada render. La estrategia es seleccionarlos por el atributo `data-select-trigger` y posición:
```typescript
const triggers = page.locator('[data-select-trigger]')
await triggers.nth(0).click() // Aduana
await page.getByRole('option').first().click()
```
Para selects con IDs estables (facturas) se usa directamente:
```typescript
await page.locator('#provider_id').click()
await page.getByRole('option').first().click()
```
### Dialogs anidados — clase, U.M., país de origen
Los campos Clase, U.M. y País de Origen abren un dialog de búsqueda encima del sheet. Para evitar que el sheet intercepte los clicks, se scopea al último dialog abierto:
```typescript
await page.locator('#clase').click()
await page.waitForTimeout(3000)
const claseDialog = page.locator('[data-dialog-content]').last()
await claseDialog.locator('tbody tr').first().click()
```
### Botones dentro del sheet
El botón de guardar partida está dentro del sheet y puede ser interceptado. Se scopea explícitamente:
```typescript
const sheet = page.locator('[data-slot="sheet-content"]')
await sheet.getByRole('button', { name: /Crear/ }).click() // nueva partida
await sheet.getByRole('button', { name: /Actualizar/ }).click() // editar partida
```
### Distinguir botones ambiguos por clase CSS
Cuando hay múltiples botones con el mismo texto o ícono, se distinguen por clases CSS únicas:
```typescript
// Botón Actualizar del footer (tiene h-8, border, RefreshCw)
await page.locator('button.h-8:has([class*="lucide-refresh"])').click()
```
### Compartir estado entre tests
Playwright corre cada test en un worker separado, por lo que `Date.now()` se reevalúa. Para compartir el número de factura entre tests se usa un archivo JSON:
```typescript
// Al final del test 6
saveShared({ INVOICE_NUMBER })
// En tests 7-11
const shared = loadShared()
const invoiceNumber = shared.INVOICE_NUMBER || INVOICE_NUMBER
```
El archivo se guarda en `frontend/e2e/.e2e-shared.json`.
---
## Selectores de referencia
| Campo | Selector | Tipo |
|-------|----------|------|
| RFC | `#rfc` | input normal |
| Nombre | `#name` | input normal |
| Tipo de socio | `#type` | bits-ui Select |
| Año pedimento | `#year` | input normal |
| Número pedimento | `#pedimento_number` | input normal |
| Aduana pedimento | `[data-select-trigger]` nth(0) | bits-ui Select |
| Patente pedimento | `[data-select-trigger]` nth(1) | bits-ui Select |
| Clave pedimento | `[data-select-trigger]` nth(2) | bits-ui Select |
| Número factura | `#invoice_number` | input (pressSequentially) |
| Fecha factura | `#invoice_date` | date input |
| Proveedor | `#provider_id` | bits-ui Select |
| Sold-to | `#sold_to_id` | bits-ui Select |
| Shipped-to | `#shipped_to_id` | bits-ui Select |
| Agente aduanal | `#customs_broker_id` | bits-ui Select |
| Aduana factura | `#aduana` | bits-ui Select |
| Tipo documento | `#document_type` | bits-ui Select |
| Clase partida | `#clase` | input readonly → dialog |
| U.M. | `#um` | input readonly → dialog |
| País origen | `#pais_origen` | input readonly → dialog |
| Cantidad | `#cantidad` | input number |
| Costo unitario | `#costo_unitario` | input number |
| Peso neto | `#peso_neto` | input number |
| Peso bruto | `#peso_bruto` | input number |
| Descripción ES | `#desc_espanol` | textarea |
| Filtro número | `#filter-invoice-number` | input normal |
---
## Comandos
```bash
# Flujo completo
pnpm test:e2e --grep "Flujo completo"
# Test individual
pnpm test:e2e --grep "5. crear pedimento"
pnpm test:e2e --grep "8. agregar partida"
# Modo visual para debug
pnpm test:e2e --grep "Flujo completo" --headed --timeout 120000
```
---
## Estructura de archivos
```
frontend/e2e/
├── .auth/
│ └── user.json sesion de autenticacion
├── .e2e-shared.json estado compartido entre tests (generado)
├── auth.setup.ts 1 test — login y guardado de sesion
├── invoice-flow.spec.ts 11 tests — flujo completo de factura
├── full-flow.spec.ts 4 tests
├── login.spec.ts 3 tests
├── navigation.spec.ts 10 tests
└── modules.spec.ts 10 tests
```
---
## Conteo total actualizado
| Suite | Pruebas |
|-------|---------|
| auth.setup.ts | 1 |
| invoice-flow.spec.ts | 11 |
| full-flow.spec.ts | 4 |
| login.spec.ts | 3 |
| navigation.spec.ts | 10 |
| modules.spec.ts | 10 |
| **Total Playwright** | **39** |
---
*Anexo 76 — Reporte de Pruebas E2E v4.0 — invoice-flow — Abril 2026*