Mi Tienda

Manual del sistema: cómo funciona, guía técnica y de pruebas

Reescritura del e-commerce en Laravel 12

Generado el {{ now()->format('d/m/Y H:i') }}

1. Qué es este proyecto

Este proyecto es una reescritura completa, desde cero, del e-commerce que antes corría en PHP plano sin framework (carpeta mi_tienda, servido por XAMPP). El sistema anterior tenía deuda técnica y de seguridad real: sin llaves foráneas en la base de datos, credenciales de pago en texto plano, contraseñas con salt compartido, y un carrito que vivía solo en el navegador. El proyecto nuevo (mi_tienda_v2) corrige todo eso sobre una arquitectura moderna: Laravel 12, con panel de administración y tienda pública completamente funcionales, pensado específicamente para operar un negocio en Perú (facturación electrónica SUNAT, pasarelas de pago locales, WhatsApp) y para poder reutilizarse en clientes nuevos (carga masiva de catálogo vía Excel y una herramienta para dejar la base de datos lista antes de poblarla).

2. Cómo funciona el sistema, de principio a fin

Esta sección explica el funcionamiento completo en lenguaje simple, sin tecnicismos, siguiendo el recorrido real de un cliente y luego el de un administrador. Las secciones siguientes del manual retoman cada punto en detalle técnico.

2.1 El recorrido del cliente

1Explorar la tienda. El visitante entra a la tienda sin necesidad de tener cuenta. Puede navegar por categorías y subcategorías, usar el buscador, filtrar por atributos (por ejemplo, talla o color) y ordenar los resultados. Cada producto muestra su precio, y si tiene una oferta activa, el precio anterior aparece tachado automáticamente.
2Elegir un producto. En la ficha del producto, si tiene variantes (por ejemplo, distintas tallas o colores), el cliente elige la combinación exacta y el sistema le muestra el stock disponible de esa combinación en particular, no del producto en general.
3Agregar al carrito. El carrito no vive solo en el navegador (como en el sistema anterior): queda guardado en el servidor, así que si el cliente cierra la pestaña o cambia de dispositivo después de iniciar sesión, no lo pierde. Si estaba comprando como invitado y luego inicia sesión, su carrito de invitado se fusiona automáticamente con el de su cuenta.
4Ir al checkout. El cliente ingresa (o confirma) su dirección de envío, tipo de comprobante (boleta con DNI o factura con RUC) y, si tiene un cupón, lo aplica ahí mismo. El sistema vuelve a calcular el precio, el envío, el impuesto y el descuento del cupón directamente desde la base de datos — nunca confía en los montos que el navegador le muestra al cliente, precisamente para evitar que alguien manipule el precio desde su propio navegador.
5Elegir cómo pagar. Hay seis formas de pagar, para cubrir distintos tipos de cliente: En cualquier caso, el pedido queda registrado en el sistema en estado "Pendiente" desde el primer instante, y el stock del producto queda reservado (apartado) para que otro cliente no se lo lleve mientras se confirma el pago — pero todavía no se descuenta del inventario ni se cobra nada hasta que el pago se confirme de verdad.
6Confirmación del pago. Cuando el pago se confirma (al instante con tarjeta/Yape/Izipay, apenas Culqi o PayPal avisan que se completó con Yape/Plin QR o PayPal, o cuando el administrador confirma el cobro en efectivo de un pedido contra entrega), el sistema recién ahí descuenta el stock real, emite el comprobante electrónico (boleta o factura) automáticamente ante SUNAT, y le envía un correo de confirmación al cliente.
7Después de la compra. Desde "Mi cuenta → Mis pedidos" el cliente puede ver el historial completo, descargar su comprobante, y — una vez que el administrador registra el envío — ver el courier y el número de seguimiento de su paquete (recibe un correo automático la primera vez que esa información queda disponible). Si compró un producto, puede dejar una reseña. Si necesita contactar a la tienda, tiene un botón flotante de WhatsApp en toda la tienda, y en cada producto un enlace de WhatsApp específico para consultar por ese producto en particular. También puede consultar en cualquier momento la política de devoluciones, la política de privacidad y los términos y condiciones, enlazados desde el pie de página.
8Lista de deseos con avisos automáticos. Si agrega un producto agotado a su lista de deseos, queda suscrito solo a ese producto para recibir un correo automático apenas vuelva a haber stock (y se cancela si lo quita de la lista). Si el producto ya en su lista baja de precio más adelante (nueva oferta, o el admin edita el precio), también recibe un aviso — nunca por el precio que ya vio al agregarlo, solo por bajadas reales desde ese momento.
9Si algo sale mal. Si el cliente no completa el pago a tiempo, la reserva de stock se libera sola pasados 30 minutos (o 3 días si eligió contra entrega, ya que ahí no hay una pasarela que avise de inmediato) y el pedido queda cancelado automáticamente. Si ya pagó y necesita una devolución, el administrador puede reembolsarlo: el dinero se revierte de verdad en la pasarela que corresponda, se repone el stock si el producto se puede revender, y se emite automáticamente la nota de crédito ante SUNAT que corresponde a la boleta/factura original.

2.2 El recorrido del administrador

1Preparar la tienda. El administrador carga el catálogo — a mano desde el panel, o de una sola vez subiendo un archivo Excel con todos los productos (con sus variantes) o todas las categorías/subcategorías. Si está reutilizando el sistema para un cliente nuevo, puede usar "Limpiar catálogo" para dejar la tienda vacía y lista para poblarla desde cero, sin tocar administradores, pedidos ni configuración. Si además va a entregar la instancia sin ningún dato de prueba (pedidos, clientes, reclamos, bitácora), la cuenta del desarrollador cuenta con "Preparar entrega" para eso.
2Configurar la tienda. Define el impuesto y las tarifas de envío, el número de WhatsApp de atención, y — si la tienda va a usar analítica — sus IDs de Google Analytics 4 y Meta Pixel. También puede personalizar el texto de la política de devoluciones, la política de privacidad y los términos y condiciones (todos con un contenido de partida ya redactado, pensado como base a revisar, no como asesoría legal definitiva).
3Gestionar pedidos día a día. Ve el listado completo de pedidos, filtra por estado o por comprobantes que fallaron al emitirse, cambia el estado de un pedido (procesando, enviado, entregado, cancelado), y registra el courier y número de seguimiento cuando lo envía (el cliente recibe un correo automático apenas se registra). Para pedidos contra entrega o pagos coordinados manualmente, confirma el cobro con un botón.
4Reembolsar cuando haga falta. Si un cliente necesita una devolución, el administrador la procesa desde el detalle del pedido: escribe el motivo, decide si el producto vuelve al stock, y el sistema revierte el cargo real en la pasarela (Culqi o PayPal — Yape/Plin por QR todavía se reembolsa manualmente desde el panel de Culqi), notifica al cliente por correo, y genera la nota de crédito SUNAT automáticamente.
5Medir el negocio. Desde Reportes puede ver ventas por período, el crecimiento de ventas día a día (con el porcentaje de variación contra el día anterior, pensado para seguimiento comercial), productos más vendidos, stock bajo (se actualiza solo), carritos abandonados (con el valor total en juego), y pedidos pendientes por antigüedad — todo exportable a CSV, Excel o PDF. Si configuró Google Analytics/Meta Pixel, también puede ver el comportamiento de los visitantes fuera del sistema, en esas mismas plataformas.
6Trabajar más rápido con varios productos a la vez. En el listado de productos puede seleccionar varios con casillas y activar, desactivar o eliminar todos de una sola vez, en vez de repetir la acción producto por producto.
7Proteger su propia cuenta. Desde "Mi seguridad" cada administrador puede activar verificación en dos pasos (2FA): además de su contraseña, se le pide un código de su app de autenticación (Google Authenticator, Authy, etc.) al iniciar sesión, con códigos de recuperación de un solo uso por si pierde el teléfono. Es opcional y la gestiona cada uno para su propia cuenta.
8Saber quién hizo qué. La "Bitácora de actividad" deja un registro de las acciones sensibles del panel — reembolsos, cambios de estado de pedido, alta/edición/baja de administradores, y "Limpiar catálogo" — con quién la hizo y cuándo, filtrable por administrador o por tipo de acción.

3. Accesos

3.1 Tienda pública

URLhttp://127.0.0.1:8000
Cliente de pruebatest@example.com / password
RegistroCualquier visitante puede crear su propia cuenta desde "Crear cuenta"

3.2 Panel de administración

URLhttp://127.0.0.1:8000/admin/login
Super administradoradmin@mitienda.test / password

El super administrador tiene acceso a todo. Existe también un rol editor con acceso restringido: Catálogo (categorías, productos, ofertas, cupones), Marketing (banners), Clientes y Reportes — sin acceso a Pedidos, Configuración de tienda, Perfiles de administrador, Reseñas, Libro de Reclamaciones ni a "Limpiar catálogo" (esta última reservada exclusivamente al super administrador por lo irreversible de la acción). Los roles se gestionan desde Perfiles en el panel.

La cuenta admin@mitienda.test tiene además dos permisos individuales (manual.view y delivery.reset) que nunca se asignan a ningún rol y por lo tanto no los hereda ningún super administrador nuevo que se cree después. Esos permisos son lo único que decide quién ve los enlaces "Manual (PDF)" y "Preparar entrega" y puede usarlos. Por la misma razón, esta cuenta no aparece en el listado de Perfiles ni puede editarse o eliminarse desde ahí, ni siquiera por otro super administrador — se administra únicamente a sí misma desde Mi cuenta → Mi perfil, donde cualquier administrador (de cualquier rol) puede cambiar su propio nombre, correo y contraseña confirmando la contraseña actual.

3.3 Cómo levantar el servidor

Desde una terminal, dentro de la carpeta del proyecto:

cd C:\xampp\htdocs\mi_tienda_v2

php artisan serve — levanta la tienda en http://127.0.0.1:8000

php artisan queue:worken otra terminal, obligatorio: sin esto, la confirmación de pedido, el aviso de contra entrega, la emisión de comprobantes y notas de crédito SUNAT, el aviso de envío, y el recordatorio de carrito abandonado se quedan encolados sin procesarse nunca (se guardan en la tabla jobs, no se pierden, pero tampoco se envían hasta que corra este proceso).

npm run dev — (opcional, en otra terminal) recompila el CSS/JS automáticamente si vas a editar vistas. Para dejar los assets listos de forma definitiva: npm run build.

4. Arquitectura técnica

4.1 Stack

ComponenteTecnología
FrameworkLaravel 12 (PHP 8.2)
Base de datosMariaDB 10.4 (BD: mi_tienda_v2)
Frontend interactivoLivewire 3 + Volt (componentes reactivos sin escribir una API aparte)
EstilosTailwind CSS, compilado con Vite; interactividad ligera puntual con Alpine.js
AutenticaciónLaravel Breeze (clientes, en español) + guard separado admin a medida (administradores)
Roles y permisosSpatie Laravel-Permission, granulares por módulo
ImágenesIntervention Image (recorte manteniendo proporción, sin deformar)
Pagos - tarjeta/Yape (Culqi)Culqi: cargo directo con tarjeta, Yape por teléfono+OTP, y Yape/Plin por QR (Órdenes de Culqi, confirmación asíncrona vía webhook/polling)
Pagos - IzipayPasarela independiente (motor Lyra/"Micuentaweb"), confirmación por firma HMAC-SHA256
Pagos - internacionalPayPal vía srmklive/paypal (API Orders v2)
Pagos - contra entregaSin pasarela: pedido queda pendiente hasta que el repartidor cobra y un admin lo confirma
Facturación electrónicaNubefact (OSE) — emite boleta/factura y nota de crédito (en reembolsos), y las declara ante SUNAT
AnalíticaGoogle Analytics 4 + Meta Pixel (opcionales, se activan solo si se configuran): eventos de ver producto, iniciar checkout y compra (una sola vez por pedido)
ContactoWhatsApp click-to-chat (botón flotante, pie de página, y enlace por producto)
Zona horariaAmerica/Lima (configurable vía APP_TIMEZONE) — todas las fechas se guardan en hora local de Perú
Carga masiva / Reportesmaatwebsite/excel (import de catálogo y export de reportes), CSV nativo, PDF vía barryvdh/laravel-dompdf
Verificación en dos pasospragmarx/google2fa + bacon/bacon-qr-code (TOTP, QR generado localmente, sin llamar a ningún servicio externo)
Backups automáticosspatie/laravel-backup: dump de la base de datos + storage/app/public (no todo el código, ya vive en git), programado a diario vía routes/console.php, con limpieza y monitoreo de salud; avisa por correo solo si algo falla
Integración continuaGitHub Actions: corre Pest, análisis estático (Larastan) y formato de código (Pint) en cada push/PR a main
TestingPest (sobre PHPUnit) — 434 tests automatizados en verde a la fecha de este manual, más 9 pruebas E2E con Playwright (navegador real)
Calidad de códigoLarastan/PHPStan (nivel 5, con baseline para deuda preexistente) y Laravel Pint, ambos como gate en el CI

4.2 Patrón de la aplicación

Controladores delgados que delegan la lógica de negocio a servicios reutilizables, en vez de mezclar todo en el controlador (como hacía el legacy):

Las partes interactivas (carrito, selector de variantes, formulario de productos con variantes dinámicas, wishlist, checkout) usan Livewire o Alpine.js para pequeños estados de UI (deshabilitar un botón al enviar, confirmaciones de texto, polling del estado de un pago QR); el resto son vistas Blade tradicionales con controladores.

Los webhooks (avisos automáticos de Culqi e Izipay cuando se confirma un pago) son el único tipo de ruta que no pasa por la protección CSRF habitual de Laravel — por ser peticiones servidor-a-servidor que no pueden llevar el token de la aplicación. Para compensar, el código nunca confía ciegamente en lo que el webhook dice: para Culqi, siempre re-consulta el estado real de la orden con la propia API; para Izipay, valida la firma HMAC-SHA256 antes de procesar cualquier dato.

4.3 Base de datos

38 tablas (incluyendo autenticación, roles/permisos y cola de trabajos de Laravel), todas con llaves foráneas reales (el legacy no tenía ninguna). Las más relevantes:

TablaContenido
categories, subcategories, productsTaxonomía del catálogo
attributes, attribute_values, product_variantsVariantes de producto (Talla/Color) con stock real por combinación
offersOfertas polimórficas: aplican a categoría, subcategoría o producto
carts, cart_itemsCarrito persistente en servidor (invitado o cliente)
orders, order_items, payments, order_status_historiesPedidos con líneas (con snapshot de precio/título), pagos (incluye intentos fallidos y reembolsos) y auditoría de cambios de estado; los pedidos también guardan courier y número/enlace de seguimiento del envío
invoicesComprobantes SUNAT por pedido: boleta, factura, o nota de crédito (en un reembolso) — un pedido puede tener como máximo un comprobante original y una nota de crédito
couponsCupones de descuento (porcentaje o monto fijo, con tope de usos y vigencia)
reviews, wishlistsReseñas (solo compradores) y lista de deseos — cada fila de wishlists guarda además el último precio efectivo que se le avisó al cliente
stock_alertsSuscripciones de "avísame cuando haya stock" por variante y correo (invitado o cliente registrado)
bannersSlides del carrusel de la página de inicio
users / adminsClientes y administradores en tablas y guards de autenticación separados; admins también guarda el secreto y los códigos de recuperación de la verificación en dos pasos, si el admin la activó
roles, permissionsRoles granulares del panel (Spatie)
admin_activity_logsBitácora de auditoría: quién hizo qué acción sensible del panel y cuándo
store_settingsImpuesto, tarifas de envío, número de WhatsApp, textos de política de devoluciones/privacidad/términos, e IDs de Google Analytics 4/Meta Pixel (configuración única de la tienda)

5. Módulos construidos

5.1 Tienda pública

5.2 Panel de administración

6. Seguridad

6.1 Correcciones frente al sistema anterior

LegacyAhora
Salt de contraseña compartido y hardcodeadoBcrypt con salt único por usuario (Laravel Hash)
Recuperar contraseña la reenviaba en texto plano por correoLink de reseteo firmado y con expiración
Token de verificación de email = md5(email), sin expirarLink de verificación firmado y expirable
Sin llaves foráneas en la base de datosFKs reales en las 38 tablas
Credenciales de PayPal/PayU en la base de datos, en texto planoSolo en .env, nunca en BD (igual para Culqi, Izipay y Nubefact)
Roles como texto libre (if ($perfil == "administrador"))Roles y permisos granulares (Spatie), incluyendo un permiso aparte para acciones irreversibles ("Limpiar catálogo")
Carrito solo en localStorageCarrito persistente en servidor, con validación de stock real
Precio/stock/cupón confiados al navegador en el checkoutTodo se recalcula server-side antes de crear el pedido
No existía el concepto de webhookLos avisos de Culqi/Izipay nunca se procesan "a ciegas": siempre se re-verifican (re-consulta a la API o validación de firma HMAC) antes de tocar un pedido

6.2 Auditoría de seguridad exhaustiva

Además de la comparación con el legacy, el proyecto ya en marcha pasó por una auditoría propia, archivo por archivo, agrupada por dimensión (autenticación, IDOR, inyección, pagos/webhooks, archivos, XSS, CSRF/carreras, datos sensibles, panel admin), donde cada hallazgo se verificó de forma adversarial antes de aplicarlo, para descartar falsos positivos. Esa auditoría corrigió 20 hallazgos; junto con una corrección aparte de la misma naturaleza (formularios sin method="POST"), estos son los más relevantes:

SeveridadHallazgoCorrección
CríticoUn pedido caro propio podía marcarse como pagado reutilizando (replay) la confirmación firmada de un pedido barato propio en IzipaySe compara el order_number y el monto de la respuesta firmada contra el pedido real de la ruta antes de aceptarla
AltoCondición de carrera permitía ejecutar markAsPaid()/refund() dos veces sobre el mismo pedido (doble descuento de stock, doble comprobante/nota de crédito)Se bloquea la fila del pedido y se re-valida su estado dentro de la misma transacción
AltoXSS en el checkout: un valor dinámico se interpolaba sin escapar dentro de un atributo x-data de Alpine.jsSe usa @@js() para cualquier valor dentro de contexto JS de Alpine
AltoSin límite de intentos en /admin/loginBloqueo de 5 intentos por correo+IP, igual que ya tenía el login de clientes
AltoDesactivar un administrador no revocaba su sesión ni su cookie "recordarme" ya activaMiddleware que revisa el estado activo en cada petición, no solo al iniciar sesión
AltoNingún formulario Livewire declaraba method: si el JS de Livewire no llegaba a cargar, el navegador caía a un submit nativo por GET, exponiendo la contraseña en la URLmethod="POST" explícito como respaldo en los 10 formularios Livewire
MedioEnumeración de usuarios en "olvidé mi contraseña" (la respuesta variaba según si el correo existía)Mismo mensaje de éxito exista o no la cuenta, salvo por límite de intentos
MedioSin límite de peticiones en webhooks/checkout/buscador; el webhook de Culqi logueaba el payload completoRate limiting agregado; el log ahora solo registra metadatos, no el cuerpo crudo
MedioCondición de carrera en el tope de usos de un cupónIncremento atómico condicionado al tope, en vez de leer-y-luego-escribir
MedioEl dashboard y el manual del panel admin no respetaban permisos granulares (el rol editor veía datos fuera de su alcance documentado)Cada sección del dashboard y la ruta del manual ahora exigen el permiso correspondiente
BajoSe podía reasignar una variante de otro producto manipulando su id (IDOR)La búsqueda de la variante se restringe siempre al producto actual
BajoImportación de Excel sin límite de tamaño de archivoLímite de tamaño agregado a la validación de subida
BajoCookie de sesión sin Secure forzado en producciónSecure activado automáticamente cuando APP_ENV=production

Aparte de la auditoría, dos correcciones más de esta misma naturaleza: los enlaces de navegación y los assets (CSS/JS/imágenes) usaban rutas absolutas armadas con el host interno, rompiéndose al compartir la tienda por un túnel HTTPS (devtunnels/ngrok) — se corrige generando rutas relativas y configurando trustProxies para reconocer el dominio público real vía cabeceras X-Forwarded-*.

6.3 Verificación en dos pasos para administradores

Cada admin puede activar 2FA (TOTP) por su cuenta desde "Mi seguridad" — no es obligatoria para todos, cada uno decide si la activa. El QR se genera de forma local (pragmarx/google2fa + bacon/bacon-qr-code), sin llamar a ningún servicio externo que expondría el secreto por la red. Al iniciar sesión con 2FA activado, la contraseña correcta no deja entrar directamente: el admin queda en un estado intermedio (sin sesión de guard todavía) hasta que confirma un código de su app o uno de sus códigos de recuperación de un solo uso, con el mismo límite de 5 intentos por admin+IP que ya protege el login. Desactivarla exige volver a confirmar la contraseña actual.

6.4 Trazabilidad de acciones sensibles

La bitácora de actividad (admin_activity_logs) registra automáticamente quién hizo qué en las acciones más sensibles del panel: reembolsos (con el motivo y si repuso stock), cambios de estado de pedido, confirmaciones manuales de pago, alta/edición/baja de administradores (incluyendo cambios de rol y activar/desactivar), acciones masivas sobre productos, y "Limpiar catálogo". Antes de esto, esas acciones no dejaban ningún rastro de quién las había hecho.

6.5 Segunda auditoría de seguridad

Con el proyecto ya más avanzado (autoservicio de "Mi perfil", exportación de la bitácora, protección de la cuenta del desarrollador), una segunda auditoría — 6 dimensiones en paralelo (autenticación, inyección, pagos/webhooks, archivos, CSRF/configuración, datos sensibles) — confirmó que las correcciones de la auditoría anterior seguían vigentes y buscó superficie nueva. Detalle completo, incluyendo lo evaluado y descartado por no aplicar (un índice único en payments que habría roto un flujo legítimo de Culqi QR), en docs/auditoria-seguridad.md. Hallazgos corregidos:

SeveridadHallazgoCorrección
AltoInyección de fórmulas (CSV/Excel) en la exportación de la bitácora: cualquier admin podía sembrar el payload cambiando su propio nombre en "Mi perfil", sin permisos especialesSe neutraliza cualquier celda que empiece con =+-@ antes de exportar
AltoHost Header Injection: sin trustHosts(), un Host falso podía envenenar el enlace de "olvidé mi contraseña"trustHosts() (Laravel ya lo desactiva solo en entorno local y en tests, sin afectar el desarrollo)
AltoSin límite de intentos sobre current_password en Mi perfil, 2FA, cambio de contraseña y borrado de cuenta: una sesión comprometida podía adivinarla por fuerza bruta y tomar la cuenta de forma permanenteBloqueo de 5 intentos por cuenta, mismo patrón que el login
Medio-AltoSin límite de intentos en el registro de cuentas (creación masiva + spam de verificación de email hacia terceros)Límite de 5 registros por minuto por IP
Medio-AltoSin límite de pedidos contra entrega pendientes por cliente (congelamiento de inventario / fraude de "no-show")Máximo 2 pedidos contra entrega pendientes por cliente
MedioLivewire no revalidaba el permiso ni el estado activo en las peticiones AJAX del formulario de productos: revocarle el permiso o desactivar a un admin a mitad de sesión no le impedía seguir guardando cambiosChequeo explícito en cada acción que modifica datos, no solo al cargar la página
MedioSin headers de seguridad HTTP (X-Frame-Options, etc.)Middleware global; el Content-Security-Policy queda pendiente de calibrar contra los widgets de Culqi/Izipay/GA4/Meta Pixel antes de activarlo
MedioPagos síncronos (Culqi tarjeta/Yape) sin timeout ni manejo de fallo de conexión: una llamada colgada terminaba en un error sin capturar, sin saber si el cargo sí se había ejecutadoTimeout explícito y mensaje cauteloso ("si se realizó un cargo, contáctanos") en vez de un error genérico
MedioEl backup podía correr sin cifrar si faltaba BACKUP_ARCHIVE_PASSWORD en producciónSe salta el backup en vez de correrlo inseguro; backup:monitor avisa por correo que falta uno reciente
BajoUn super-admin podía cambiar su propio rol/estado desde "Perfiles"Bloqueado ahí; solo puede hacerlo desde "Mi perfil"

7. Pendientes para un uso en producción real

Checklist completa de lanzamiento, paso a paso, en docs/checklist-lanzamiento.md. Resumen de lo más importante:

DOCUMENT_LOOKUP_TOKEN: opcional, no bloqueante para lanzar. Autocompleta el nombre/razón social del cliente a partir de su DNI/RUC contra un servicio externo (RENIEC/SUNAT no tienen API pública gratuita para terceros). El código ya está construido y probado — sin este token, el sistema funciona exactamente igual que antes (el cliente escribe su nombre a mano). Detalle completo, incluyendo proveedores candidatos a contratar, en docs/diferenciadores-comercio-local.md.

8. Calidad de código y automatización

Además de la suite de tests, el CI corre dos herramientas de calidad en cada push/PR a main:

Aparte del CI, hay una suite E2E con Playwright (e2e/, se corre con npm run test:e2e) que maneja un navegador real contra la app corriendo de verdad — a diferencia de Pest, que solo prueba a nivel HTTP, esta sí ejecuta JS/Livewire/Alpine tal como lo haría un cliente real. Cubre: catálogo y buscador, login de cliente (válido e inválido) a través del formulario Livewire real, agregar al carrito con la actualización reactiva del contador del header (o el aviso de stock si el producto está agotado), selección masiva de productos en el panel admin, y el flujo completo de 2FA —activar, cerrar sesión, volver a entrar pasando por el desafío con un código TOTP real, y desactivarlo de nuevo—. No corre en GitHub Actions (para no alentar cada push); usa la base de datos local y cuentas ya sembradas, con acciones pensadas para no ensuciar el catálogo de desarrollo.

9. Checklist de pruebas

9.1 Tienda pública

El home carga y muestra categorías y productos destacados
Navegar categoría → subcategoría → producto
El buscador devuelve resultados relevantes
Registro de una cuenta nueva (en español)
Iniciar y cerrar sesión
Agregar un producto con variantes (elegir Talla/Color) al carrito
Ver el carrito, cambiar cantidad, quitar un producto
Aplicar un cupón de descuento válido en el checkout
Completar el checkout con tarjeta/Yape (Culqi), Izipay o PayPal: totales correctos (subtotal + envío + impuesto)
Elegir Yape/Plin (QR) en un pedido dentro de S/ 6–500, y confirmar que la opción se oculta fuera de ese rango
Completar el checkout con pago contra entrega en un carrito ≤ S/ 500, y confirmar que no aparece esa opción si el carrito supera S/ 500
Confirmar que el pedido queda "Pendiente" y reserva el stock (no lo descuenta aún)
Ver el pedido en "Mi cuenta → Mis pedidos" y descargar el comprobante una vez pagado
Una vez que el admin registra courier/tracking, verlo en el detalle del pedido
Dejar una reseña en un producto ya comprado
Agregar y quitar un producto de la lista de deseos
Agregar a la lista de deseos un producto agotado y confirmar que queda suscrito al aviso de restock; quitarlo y confirmar que se cancela
En un producto agotado, dejar el correo en "avísame cuando haya stock" y confirmar que se registra la suscripción
Ver "Productos relacionados" al final de una ficha de producto
Ver el botón de WhatsApp flotante, el enlace del pie de página, y el enlace específico en una ficha de producto
Abrir las páginas de política de devoluciones, privacidad y términos desde el pie de página

9.2 Panel de administración

Iniciar sesión como super administrador
El dashboard muestra las métricas correctamente
Crear, editar y eliminar una categoría (y verificar que bloquea el borrado si tiene subcategorías/productos)
Crear un producto con variantes (Talla/Color) e imágenes
Descargar la plantilla de importación de productos, llenarla (un producto simple y uno con variantes) y subirla; confirmar que se crean correctamente y que una fila con error no bloquea el resto
Descargar la plantilla de importación de categorías/subcategorías y subirla; confirmar que no duplica una categoría que ya existía
Crear una oferta y verificar que el precio tachado aparece en la tienda pública
Crear un cupón y aplicarlo en un pedido de prueba
Ver el listado de pedidos, filtrar por estado y por "solo con comprobante fallido"
Confirmar manualmente el pago de un pedido pendiente, y "Confirmar cobro en efectivo" en uno contra entrega
Registrar courier y número de seguimiento en un pedido pagado
Reembolsar un pedido pagado: confirmar que repone stock (si corresponde), notifica al cliente, y genera la nota de crédito
Emitir/reintentar el comprobante SUNAT de un pedido pagado y descargar el PDF/XML
Cancelar un pedido pendiente y confirmar que libera el stock
Cambiar la configuración de impuesto/envío/WhatsApp/políticas/analítica de la tienda
Crear un administrador con rol "editor" y confirmar que NO accede a Pedidos/Configuración/Perfiles/Reseñas/Libro de Reclamaciones/"Limpiar catálogo" (debe dar 403) pero SÍ a Categorías/Productos/Ofertas/Cupones/Banners/Clientes/Reportes
(Solo super-admin) Desde "Reseñas", aprobar una reseña oculta, ocultar una aprobada, y eliminar una — confirmar que cada acción queda en la bitácora de actividad
Desde /libro-de-reclamaciones (sin iniciar sesión), registrar un reclamo y confirmar que llega el código y la copia al correo; desde el panel, responderlo y confirmar que el cliente recibe la respuesta y que queda en la bitácora de actividad
Sin DOCUMENT_LOOKUP_TOKEN configurado (el estado por defecto), escribir un DNI/RUC en el checkout o en el Libro de Reclamaciones y confirmar que no pasa nada raro — el nombre se sigue escribiendo a mano, sin errores
En el checkout, elegir Departamento/Provincia/Distrito y confirmar que el envío/total se recalculan solos; con una tarifa de zona configurada en Configuración → Envío por zona, confirmar que el pedido se crea con esa tarifa (no la nacional plana)
Generar el reporte de ventas y el de crecimiento diario, y exportar ambos en CSV, Excel y PDF
Revisar el reporte de productos más vendidos y de stock bajo
Revisar el reporte de pedidos pendientes por antigüedad y el de carritos abandonados
Seleccionar varios productos con las casillas y activar/desactivar/eliminar en masa
Activar 2FA desde "Mi seguridad", cerrar sesión, y volver a entrar confirmando el código de la app; probar también un código de recuperación
Revisar la "Bitácora de actividad" después de un reembolso o de editar un administrador, confirmar que quedó registrado, y probar la exportación a CSV/Excel
Correr php artisan activity-log:prune a mano una vez y confirmar que no borra nada reciente (solo lo más antiguo que ACTIVITY_LOG_RETENTION_MONTHS) y que deja el CSV en storage/app/activity-log-archives/
Desde "Mi cuenta → Mi perfil", cambiar el propio nombre/correo/contraseña confirmando la contraseña actual
Crear un segundo super administrador y confirmar que NO ve la cuenta del desarrollador en "Perfiles" ni puede editarla/eliminarla (ni por URL directa)
(Solo super-admin) Entrar a "Limpiar catálogo", confirmar que el botón sigue deshabilitado hasta escribir la frase exacta, y que tras confirmarlo el catálogo queda vacío sin afectar admins/pedidos/configuración
(Solo la cuenta del desarrollador) Entrar a "Preparar entrega", confirmar que un administrador sin el permiso delivery.reset recibe 403, que no deja confirmar sin elegir al menos un administrador activo con rol "super-admin" para conservar, y que tras confirmarlo se borran pedidos/clientes/reclamos/reseñas/bitácora sin afectar catálogo/cupones/configuración
Escribir mal la contraseña actual 6 veces seguidas en "Mi perfil" y confirmar que la 6ª ya no deja intentar, ni con la contraseña correcta
Crear 3 pedidos contra entrega seguidos con la misma cuenta y confirmar que el 3ro se rechaza (tope de 2 pendientes)

10. Ubicación de archivos clave

QuéDónde
Modelosapp/Models/
Lógica de negocio (servicios)app/Services/ (pasarelas de pago en app/Services/Payments/)
Carga masiva desde Excelapp/Imports/ (lectura) + app/Exports/ (plantillas y reportes)
Controladores de tienda públicaapp/Http/Controllers/Catalog/, CheckoutController.php, Checkout/, ComplaintController.php (Libro de Reclamaciones), DocumentLookupController.php (autocompletado y validez de DNI/RUC), UbigeoController.php (combos en cascada de dirección)
Webhooks de pasarelasapp/Http/Controllers/Webhooks/
Controladores del panel adminapp/Http/Controllers/Admin/
Componentes interactivos (Livewire)app/Livewire/
Notificaciones por correoapp/Notifications/ (confirmación de pedido, contra entrega, reembolso, envío, falla de comprobante, carrito abandonado, restock, bajada de precio, reclamo/queja recibido y respondido) — plantilla con marca propia en resources/views/vendor/mail/
Jobs en colaapp/Jobs/ (emisión de comprobante/nota de crédito, liberación de reserva, recordatorio de carrito, aviso de restock)
Comandos artisan propiosapp/Console/Commands/ (activity-log:prune, programado en routes/console.php junto con los backups)
Vistas de la tiendaresources/views/catalog/, checkout/, account/, policies/
Vistas del panel adminresources/views/admin/ (bitácora en admin/activity-log/, seguridad/2FA en admin/security/)
Traducciones al españollang/es/ y lang/es.json
Rutasroutes/web.php (tienda, incluye webhooks), routes/admin.php (panel)
Migraciones (esquema de BD)database/migrations/
Tests automatizadostests/Feature/ (434 tests Pest en verde a la fecha de este manual) + e2e/ (9 pruebas Playwright)
Integración continua.github/workflows/tests.yml (Pest, Larastan y Pint)
Análisis estáticophpstan.neon (config) + phpstan-baseline.neon (deuda congelada) + _ide_helper_models.php (docblocks de modelos)
Checklist de lanzamiento a produccióndocs/checklist-lanzamiento.md (credenciales, pasarelas, dominio/HTTPS, correo, backups...)
Checklist de credenciales pendientes (Culqi QR / Izipay)docs/produccion-pagos-pendiente.md
Diferenciadores locales para la línea de tesis (Libro de Reclamaciones, autocompletado DNI/RUC)docs/diferenciadores-comercio-local.md
Análisis del sistema legacymi_tienda/_analisis-legacy/ (esquema de BD y reglas de negocio del sistema anterior)

Manual generado automáticamente desde el panel de administración de Mi Tienda.