QA · Cabizz · Auditoría de código
Cobertura de los dos planes pendientes de ejecutar — Flujo Operativo end-to-end y Escenarios de Excepción— verificada leyendo el código real de las 3 apps de cliente, la app de conductor, los 3 paneles admin y el backend app-api-v2 (la API en producción).
El camino feliz está prácticamente todo implementado. Los 67 casos del Flujo Operativo se pueden ejecutar: home→carrito→checkout Stripe→panel admin→máquina de estados→despacho a conductores→pickup→entrega→notificaciones existen en código, con evidencia. Fallarán al ejecutar solo detalles puntuales (botón "Calificar", contador del dashboard del conductor, historial de estados en admin).
Los escenarios de excepción son el terreno flojo. De 39, hay 13 sólidos, 20 parciales y 6 completamente ausentes. El hueco es sistemático y está concentrado en dinero (no hay reembolsos de Stripe en ningún lado) y en fallos del conductor en ruta (sin timeout de pickup ni reasignación). Esto coincide con la propia autoevaluación de la v2: Fase 1 y 2 aplicadas, Fase 3 (dinero/despacho avanzado) y 4 pendientes.
Ojo con el vocabulario de estados. El plan de pruebas original usaba nombres en inglés que no coinciden con tu DB. El pipeline real (tal como aparece en la app) es Pendiente(1) → En proceso(2) → En camino al punto(3) → En ruta(4) → Confirma entrega(8) → Completado(5), con Cancelado(6) / Rechazado(7). Punto clave: no hay un estado "Confirmado"/"Preparando" separado — aceptar el pedido y prepararlo caen ambos en En proceso(2). Los archivos de prueba ya fueron reetiquetados a estos nombres.
Las 17 fases del camino feliz (hoja DESEMPEÑO). Las 3 apps de cliente comparten plantilla y los 3 admin también; se anota la divergencia relevante.
| Fase | Actor | Estado | Evidencia / matiz |
|---|---|---|---|
| 1 · Cliente realiza pedido (home, carrito, checkout, Stripe → Pendiente) | Cliente | Contemplado | Home/carrito/checkout/Stripe en las 3 apps. Matiz: el pedido se crea antes del pago (createorder); en web Stripe es redirect en la misma pestaña, no nueva. |
| 2 · Pedido aparece en panel Admin (tiempo real Pusher) | Sistema | Contemplado | Evento order-created + useOrdersRealtime (Pusher + poll 60s de respaldo) en los 3 admin. |
| 3 · Admin acepta (Pendiente → En proceso) | Admin | Contemplado | Sin botón "Accept" dedicado: cambio vía q-select de estado. No existe "Confirmado"/"Preparando" aparte → aceptar y preparar son el mismo estado En proceso(2). |
| 3b · Admin rechaza pedido | Admin | Contemplado | POST /orders/{id}/reject con motivo obligatorio. Sin reembolso Stripe (ver excepciones). |
| 4 · Notificación al cliente (confirmado) | Sistema | Contemplado | order-status Pusher+FCM (estados 2–5, 8). Matiz: el handler FCM in-app solo loguea; la UI se actualiza por Pusher/poll, no por el tap del push. |
| 5 · Admin marca preparación (sigue En proceso) | Admin | Contemplado | Máquina de estados real con guardas de transición. "Start Collection" de byebye viene de la API, no del front. Nota: no cambia de estado (ya está en En proceso(2)). |
| 6 · Admin marca listo (→ En camino al punto, 3) | Admin | Contemplado | La transición a En camino al punto(3) habilita el despacho a conductores. |
7 · Conductor ACTIVO recibe order-offered | Driver | Contemplado | Modal AppEvents.vue + OrderDispatchService. Matiz: la oferta la emite el cron cli/events.php, no la transición; si el cron no corre, no hay oferta. |
| 7b · Conductor INACTIVO NO recibe | Driver | Contemplado | Filtro en backend: vista orders_pending exige active=true AND approved=true. El front NO filtra — depende 100% del backend. |
| 8 · Driver ACEPTA → En ruta (4), aparece en pestaña "En Proceso" | Driver | Contemplado | POST /tracker/offerresponse + asignación atómica. Pestañas En Proceso/Completados en OrderSummary.vue. |
| 8b · Cliente notificado (driver asignado) | Sistema | Contemplado | order-status al pasar a status 4. |
| 9 · Driver RECHAZA → vuelve al pool | Driver | Parcial | El rechazo existe (Offerresponse accepted:false) pero la re-oferta a otro conductor no es activa: depende de que el cron vuelva a encontrar candidatos. |
| 10 · Driver recoge (pickup, status 3 → 4) | Driver | Contemplado | Botón "Picked up" + navegación Google Maps a pickup y cliente. |
| 11 · Driver marca entrega (→ Confirma entrega 8 / Completado 5) | Driver | Parcial | El front envía Confirma entrega(8) (espera confirmación del cliente), no Completado(5) directo. La pestaña "Completados" cuenta 5 y 8. El contador del Dashboard del conductor está hardcodeado a 0 (Dashboard.vue es un stub). |
| 11b · Cliente notificado (entregado) + botón Calificar | Sistema | Parcial | Notificación OK. El botón "Calificar" no existe: es stub (console.log) en byebye/cheers e inexistente en fork. |
| 12 · Admin ve estado final Completado en panel | Admin | Parcial | Badge de estado con color OK. Sin historial/timeline de transiciones ni timestamp de entrega (solo createdAt). |
| 13 · Cancelación por Admin en cualquier estado | Admin | Parcial | Cambio a Cancelado vía select. Cancelar por select no pide motivo (solo el flujo "reject") y no reembolsa. |
| 12.1 · Geolocalización del conductor (GPS periódico) | Driver | Contemplado | POST /tracker/Geolocation + BackgroundGeolocation nativo, filtro 30m, permisos Info.plist. |
| 14.1 · Flujo completo en Web (PWA) | Cliente | Contemplado | Funciona. Matiz: en web Stripe usa window.location.assign (misma pestaña), no window.open. |
El complemento al camino feliz. Aquí es donde el código tiene huecos reales — concentrados en dinero (reembolsos) y fallos del conductor en ruta.
| ID | Escenario | Estado | Evidencia / qué falta |
|---|---|---|---|
| E1.1 | Negocio rechaza en Pendiente + reembolso | Parcial | Reject con motivo OK; reembolso Stripe AUSENTE. El cliente queda cobrado. |
| E1.2 | Rechaza/cancela tras confirmar, con motivo | Parcial | Transición controlada OK; reverso de cobro AUSENTE. |
| E1.3 | Cliente cancela antes de asignar driver | Parcial | Endpoint /myorders/{id}/cancel + flag can_cancel OK; sin reembolso si estaba pagado. |
| E1.4 | Cliente cancela con conductor en camino | Parcial | Depende de que el backend ponga can_cancel=false; política de cancelación tardía AUSENTE. |
| E1.5 | Admin cancela con conductor en tránsito | Parcial | Notifica driver+cliente y lo desasigna (order_unassigned); sin reembolso. |
| E1.6 | Doble cancelación (idempotente) | Contemplado | Estados terminales no re-actualizan; fork filtra transiciones. Backend idempotente. |
| ID | Escenario | Estado | Evidencia / qué falta |
|---|---|---|---|
| E2.1 | Dos conductores aceptan el mismo pedido (race) | Contemplado | acceptOfferAtomically: SELECT … FOR UPDATE + WHERE driver IS NULL. Asignación atómica a uno solo. |
| E2.2 | Dos conductores en misma ubicación | Parcial | Vista con distancia existe; criterio de desempate no documentado ni probado. |
| E2.3 | Ningún conductor activo → pedido en cola | Parcial | Señal awaiting_driver_count + banner admin; sin escalamiento ni alerta por tiempo. |
| E2.4 | Todos rechazan (pool agotado) | Parcial | Rechazo OK; sin re-oferta activa ni estado final cuando el pool se agota. |
| E2.5 | Aceptar oferta ya tomada / expirada | Contemplado | Validación hasPendingOffer → 404 + toast "ya tomado"; expiración cron a 60s. |
| E2.6 | Oferta con app en background (push abre modal) | Parcial | FCM en order-offered se envía; pero el front NO abre el modal desde el push (handler solo loguea, sin deep-link). |
| ID | Escenario | Estado | Evidencia / qué falta |
|---|---|---|---|
| E3.1 | Conductor desaparece buscando el pedido | Ausente | No hay timeout de búsqueda ni reasignación (auto/manual). |
| E3.2 | Abandona con el pedido ya recogido | Ausente | Se guarda última posición; sin workflow de escalamiento / re-entrega. |
| E3.3 | Pierde señal GPS en tránsito | Parcial | La app no crashea y retoma al recuperar; falta "ubicación no disponible" explícito vs mapa congelado. |
| E3.4 | Cierra la app a mitad de la entrega | Contemplado | Al reabrir, /order_summary restaura el pedido activo (estado server-side). |
| E3.5 | Se queda sin batería / se apaga | Parcial | driver_status conserva última posición; sin acción del admin ante dispositivo apagado. |
| E3.6 | Acepta pero nunca recoge (timeout pickup) | Ausente | No existe deadline ni alerta de pickup. |
| E3.7 | Reasignación tras abandono del conductor | Ausente | No hay UI ni endpoint de reasignar a otro conductor. |
| ID | Escenario | Estado | Evidencia / qué falta |
|---|---|---|---|
| E4.1 | Pago rechazado → no crea pedido | Parcial | El pedido se crea antes del pago; un decline lo deja vivo hasta el cleanup a 24h. Sin estado awaiting_payment. |
| E4.2 | Callback /success no llega, pedido igual se registra | Contemplado | Webhook checkout.session.completed con firma HMAC; independiente del redirect. |
| E4.3 | Doble submit del checkout (doble cobro) | Parcial | Bloqueo de doble-click en UI OK; idempotencia server-side AUSENTE (sin Idempotency-Key). |
| E4.4 | Reembolso tras cancelación / rechazo | Ausente | Cero referencias a refund en el backend. Cancelar/rechazar nunca devuelve la plata. |
| E4.5 | Cierra la app con pago "en proceso" | Parcial | Cleanup de no-pagados a 24h; sin estado propio de "en proceso". |
| ID | Escenario | Estado | Evidencia / qué falta |
|---|---|---|---|
| E5.1 | Caída de Pusher/Soketi | Contemplado | Banner "desconectado" + poll 60s + refresh manual en admin/user/driver. |
| E5.2 | Push FCM no llega → estado se actualiza al abrir | Contemplado | onMounted → Start() recarga desde API + poll. Falta refresh de token expirado. |
| E5.3 | Cliente pierde internet en el seguimiento | Parcial | No rompe; sin banner explícito "sin conexión" ni navigator.onLine. |
| E5.4 | Reconexión: recuperar eventos perdidos | Contemplado | validatePendingOffer + /tracker/offerstatus descarta ofertas viejas al reconectar. |
| E5.5 | Evento duplicado (llega dos veces) | Parcial | Dedupe en cliente (pusherDedup.js) OK; webhook Stripe procesa aunque el evento esté duplicado (sin guard). |
| ID | Escenario | Estado | Evidencia / qué falta |
|---|---|---|---|
| E6.1 | Cambio de estado fuera de orden (Pendiente→En camino al punto) | Contemplado | OrderStatusPolicy::assertTransition → 422. El front de fork solo ofrece la transición válida (byebye/cheers dependen del backend). |
| E6.2 | Pedido huérfano En camino al punto sin conductor | Parcial | Conteo awaiting_driver; sin escalamiento por antigüedad. |
| E6.3 | Marcado ENTREGADO pero cliente no recibió (disputa) | Parcial | Status 8 "confirmación del cliente" + Confirm/Dispute en fork-user + tabla order_delivery_disputes; falta canal completo con evidencia. |
| E6.4 | Geolocalización con permisos denegados | Contemplado | Permisos nativos + mensajes i18n; degrada sin crashear. |
| E6.5 | Sesión expira a mitad del flujo | Parcial | Refresh de token transparente OK; device_id obligatorio / 409 AUSENTE (es opcional en el backend). |
| E6.6 | Múltiples pedidos simultáneos del mismo cliente | Contemplado | Pedidos por order_id independientes; sin estado global compartido. |
| ID | Escenario | Estado | Evidencia / qué falta |
|---|---|---|---|
| E7.1 | Producto agotado tras el pago | Parcial | Chequeo de stock en checkout (no vende agotado); sin sustitución/reembolso tras pago. (El stock además no se decrementa — bug conocido.) |
| E7.2 | Dirección fuera de zona de cobertura | Contemplado | assertDeliveryWithinUserDistanceLimit + validación en el front antes de cobrar. |
| E7.3 | Negocio cierra con un pedido activo | Ausente | Sin política al desactivar el negocio con pedidos en curso. |
| E7.4 | Pedido de monto cero / cupón 100% | Contemplado | Total $0 crea payment.received status=2 sin pasar por Stripe. |
Priorizado. Todo lo demás debería pasar (con los matices de nomenclatura de estados y dependencia del cron).
refund en el backend. Todo caso que espere "se devuelve la plata" falla. Es el hueco de dinero más grande.console.log, no hay flujo de rating.Dashboard.vue está hardcodeado a 0.order-offered) la emite php app-api-v2/cli/events.php, no la transición de estado. Sin cron activo, ningún conductor recibe pedidos y toda la fase 7–11 queda bloqueada.Pendiente(1) → En proceso(2) → En camino al punto(3) → En ruta(4) → Confirma entrega(8) → Completado(5). Aceptar y preparar caen ambos en En proceso(2) (no hay un estado "Preparando" aparte). Los archivos de prueba ya usan estos nombres.