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