# 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*