# Checklist de lanzamiento a producción

El sistema (catálogo, checkout, panel admin, seguridad, tests) ya está construido
y probado en local. Nada de esto es código por escribir — son cuentas que abrir,
un servidor donde desplegar, y variables de `.env` por completar antes de poder
cobrar pedidos reales. Para el detalle específico de Culqi QR (Yape/Plin) e
Izipay, ver [`docs/produccion-pagos-pendiente.md`](produccion-pagos-pendiente.md)
— esta checklist cubre todo lo demás y sirve de mapa general.

Marcar cada casilla a medida que se completa. El orden sugerido va de lo que
toma más tiempo en gestionarse (cuentas con terceros) a lo que se hace el mismo
día del despliegue.

## 1. Cuentas y credenciales de pasarelas/facturación

- [ ] **Culqi**: cuenta de comercio real (no de pruebas), obtener
      `CULQI_PUBLIC_KEY`/`CULQI_SECRET_KEY` de producción. Si se va a ofrecer
      Yape/Plin por QR, confirmar con soporte/ventas de Culqi que la cuenta
      tiene **billeteras móviles habilitadas** (no es automático).
- [ ] **Izipay**: afiliación como comercio (vía asesor comercial, no es
      autoservicio) — ver checklist detallada en
      [`produccion-pagos-pendiente.md`](produccion-pagos-pendiente.md).
- [ ] **PayPal**: cuenta Business verificada, credenciales **LIVE** (no
      sandbox) de `PAYPAL_LIVE_CLIENT_ID`/`PAYPAL_LIVE_CLIENT_SECRET`, y
      `PAYPAL_MODE=live`. Confirmar/actualizar `PAYPAL_PEN_USD_RATE` (no hay
      una fuente automática del tipo de cambio).
- [ ] **Nubefact**: contratar el servicio (OSE), obtener `NUBEFACT_TOKEN`, y
      confirmar con ellos las series reales autorizadas ante SUNAT
      (`NUBEFACT_SERIE_BOLETA`, `NUBEFACT_SERIE_FACTURA`, y las de nota de
      crédito) — las que trae `.env.example` son solo las de la cuenta de
      pruebas.
- [ ] Completar las 4 credenciales anteriores en el `.env` del servidor real
      (nunca en el repositorio ni en la base de datos).

## 2. Correo

- [ ] Elegir un proveedor SMTP real (Amazon SES, Mailgun, SendGrid, o el que
      dé el hosting) y completar `MAIL_MAILER`/`MAIL_HOST`/`MAIL_PORT`/
      `MAIL_USERNAME`/`MAIL_PASSWORD`/`MAIL_FROM_ADDRESS` en `.env` — hoy
      (`MAIL_MAILER=log`) los correos solo se registran en el log, no se
      envían de verdad.
- [ ] Configurar los registros DNS del dominio (SPF, DKIM, y idealmente DMARC)
      para que los correos no caigan en spam.
- [ ] Enviar un correo de prueba real (ej. un registro de cuenta nueva) y
      confirmar que llega a la bandeja de entrada, no a spam.

## 3. Dominio, hosting y HTTPS

- [ ] Dominio real apuntando al servidor de producción (hoy corre en
      `127.0.0.1:8000`).
- [ ] Certificado SSL/HTTPS válido (Let's Encrypt o el que dé el hosting) —
      **obligatorio**: Culqi e Izipay no pueden avisar sus webhooks a
      `localhost` ni a un sitio sin HTTPS.
- [ ] Servidor web (Apache/Nginx) apuntando el document root a `public/`, no a
      la raíz del proyecto.
- [ ] `APP_URL` en `.env` con el dominio real (no `http://localhost`).
- [ ] **Qué archivos subir**: `.gitignore` ya excluye lo pesado/específico de
      cada máquina (`vendor/`, `node_modules/`, `public/build/`, `.env`) — si
      se despliega vía `git clone`/`git pull`, ya llega solo lo necesario. En
      el servidor, generar lo que falta:
  - [ ] `composer install --no-dev --optimize-autoloader` — el `--no-dev` deja
        afuera Pest/PHPStan/Playwright, que no sirven en producción.
  - [ ] `npm install && npm run build` (o compilar en la máquina local y subir
        solo la carpeta `public/build/` resultante, sin instalar Node en el
        servidor).
  - [ ] `php artisan storage:link` — crea el symlink `public/storage →
        storage/app/public`; es nuevo por cada servidor, no se copia del
        entorno local.
  - Seguro de excluir (no rompen nada si igual se suben, solo ocupan espacio
    de más): `tests/`, `e2e/`, `docs/`, `.github/`, `Manual-MiTienda.pdf`,
    `phpunit.xml`, `phpstan.neon`/`phpstan-baseline.neon`.

## 4. Variables de entorno de producción (`.env`)

- [ ] `APP_ENV=production`.
- [ ] `APP_DEBUG=false` — hoy está en `true` para desarrollo y expone detalles
      internos (stack traces, rutas del servidor) si algo falla.
- [ ] `APP_KEY` generada de nuevo para producción (`php artisan key:generate`
      en el servidor real — **no reutilizar** la de desarrollo). Es la llave
      de cifrado de sesiones/cookies y del secreto 2FA de cada admin: se
      genera **una sola vez** al levantar ese servidor y nunca se vuelve a
      tocar después (cambiarla deja ilegibles los datos ya cifrados con la
      anterior). Si este sistema se reutiliza para varios clientes, cada
      instalación (su propio servidor + base de datos) necesita la suya
      propia, generada una vez al inicio de esa instalación — no es algo que
      se repita por cada pedido ni por cada vez que se entra al panel.
- [ ] `TRUSTED_PROXIES` restringido a la IP real del balanceador/reverse proxy
      que quede delante de la aplicación — hoy vale `*` (confía en cualquier
      proxy), razonable solo para desarrollo detrás de un túnel.
- [ ] `SESSION_SECURE_COOKIE` no necesita tocarse: ya se activa solo cuando
      `APP_ENV=production` (ver `config/session.php`).

## 5. Base de datos

- [ ] Crear la base de datos de producción y correr
      `php artisan migrate --force`.
- [ ] **No usar los datos de prueba del entorno de desarrollo** (catálogo con
      productos Lorem Ipsum, `admin@mitienda.test`/`password`,
      `test@example.com`/`password`). **Ojo**: `php artisan db:seed` (sin
      `--class`) corre los 5 seeders encadenados en `DatabaseSeeder`
      —incluido `CatalogSeeder` (el catálogo Lorem Ipsum), `AdminSeeder`
      (la cuenta con contraseña `password` conocida) y el usuario
      `test@example.com`— así que **no correrlo tal cual en producción**. En
      su lugar:
  - [ ] `php artisan db:seed --class=RolesAndPermissionsSeeder` —
        **obligatorio**, crea las filas de roles/permisos (`super-admin`,
        `editor`) de las que depende todo el panel admin.
  - [ ] `php artisan db:seed --class=UbigeoSeeder` — **obligatorio**, carga
        los ~1840 departamentos/provincias/distritos del Perú (desde
        `database/data/ubigeo/*.json`) de los que depende el selector de
        dirección del checkout y el envío por zona. Sin esto, el checkout
        no deja completar la dirección.
  - [ ] `php artisan db:seed --class=StoreSettingSeeder` — opcional pero
        recomendado: deja valores por defecto razonables (IGV 18%, tarifas
        de envío, políticas genéricas) en vez de dejar la tabla vacía.
        **Revisar y ajustar estos valores** desde "Configuración de la
        tienda" antes de anunciar el lanzamiento — son solo un punto de
        partida, no necesariamente los reales del negocio.
  - [ ] Crear el/los administrador(es) reales a mano (vía `php artisan
        tinker`, o desde el propio panel una vez exista uno con
        `admins.manage`) — **no correr `AdminSeeder`**.
  - [ ] **Reasignar el permiso `manual.view`** a la cuenta real del
        desarrollador: `Admin::where('email', '<email real>')->first()
        ->givePermissionTo('manual.view')`. Este permiso es el que activa
        toda la protección construida para la cuenta del desarrollador (no
        aparece en "Perfiles" para otros admins, nadie más puede
        editarla/eliminarla/verla, y es la única que ve el manual técnico en
        `/admin/manual`). Sin este paso, **en producción ningún admin queda
        protegido ni tiene acceso al manual**, porque el seeder solo se lo da
        a `admin@mitienda.test`, cuenta que no debe existir en producción.
  - [ ] Decidir qué rol (`super-admin` vs `editor`) le corresponde a cada
        cuenta de staff real antes de crearlas — `editor` no tiene acceso a
        `store-settings.manage`/`orders.manage`/`admins.manage`/
        `catalog.reset`/`reviews.manage`/`complaints.manage` (ver
        `RolesAndPermissionsSeeder`).
  - [ ] Cargar el catálogo real (a mano o vía la carga masiva por Excel) —
        no correr `CatalogSeeder` en producción.
  - [ ] Completar **Configuración → Datos de la empresa** (razón social, RUC,
        domicilio fiscal) — aparecen en cada reclamo/queja del **Libro de
        Reclamaciones Virtual** (`/libro-de-reclamaciones`, obligatorio por
        ley para cualquier negocio que atienda consumidores en Perú, D.S.
        N° 011-2011-PCM y su modificatoria D.S. N° 101-2022-PCM).
  - [ ] Opcional: en **Configuración → Envío por zona**, poner tarifas
        distintas para Lima Metropolitana / Costa-Sierra / Selva. Si se deja
        vacío, las tres zonas cobran la misma "Tarifa de envío nacional" de
        siempre (mismo comportamiento que antes de que existiera esto).
  - Si se está reutilizando el sistema para un cliente nuevo, "Limpiar
    catálogo" (zona de peligro, solo super-admin) deja la tienda vacía y
    lista para poblarla desde cero sin tocar administradores/pedidos/config.
- [ ] Activar 2FA en la(s) cuenta(s) de administrador reales desde "Mi
      seguridad" — es opcional en el sistema, pero recomendable activarla
      apenas se creen las cuentas de producción.
- [x] ~~Backups automáticos~~: ya están construidos (`spatie/laravel-backup`,
      programados en `routes/console.php` — dump de la BD + `storage/app/public`
      a diario, con limpieza y monitoreo de salud). Solo falta, antes de
      lanzar:
  - [ ] Confirmar `BACKUP_NOTIFICATION_EMAIL` en `.env` apuntando a un correo
        real que sí se revise (llega un aviso si un backup falla o queda en
        mal estado — nunca un correo diario de "todo bien").
  - [ ] **Obligatorio en producción**: `BACKUP_ARCHIVE_PASSWORD` en `.env`,
        larga y aleatoria. Sin esto, `backup:run` se salta solo (no corre sin
        cifrar) y `backup:monitor` terminará avisando por correo que no hay
        un backup reciente — es la señal de que falta configurar esto.
  - [ ] En Windows/XAMPP, `MYSQLDUMP_BINARY_PATH` si `mysqldump` no está en el
        PATH; en el servidor Linux real normalmente no hace falta.
  - [ ] Para producción real, agregar un disco remoto (S3 u otro) en
        `config/backup.php` → `destination.disks` — un backup que vive en el
        mismo servidor que falla no sirve de mucho.
  - [ ] Correr `php artisan backup:run` una vez a mano y confirmar con
        `php artisan backup:list` que aparece y queda "Healthy".

## 6. Procesos en segundo plano

- [ ] `php artisan queue:work` corriendo **de forma permanente** vía
      Supervisor (o systemd) en el servidor real, no en una terminal abierta
      — sin esto, la confirmación de pedido, los comprobantes/notas de
      crédito SUNAT, el aviso de envío, el recordatorio de carrito
      abandonado, y los avisos de restock/bajada de precio se quedan
      encolados sin procesarse nunca.
- [ ] Confirmar que Supervisor reinicia el worker solo si el proceso se cae.
- [ ] **Cron real** con `* * * * * php artisan schedule:run` — sin esto, los
      backups programados (y cualquier otra tarea que se agregue a
      `routes/console.php` más adelante) nunca se disparan solos.

## 7. Webhooks de las pasarelas

- [ ] Configurar en el panel de Culqi (Eventos → Webhooks) la URL
      `https://<tu-dominio>/webhooks/culqi`.
- [ ] Configurar en el panel de Izipay la URL de notificación IPN
      `https://<tu-dominio>/webhooks/izipay`.
- [ ] Confirmar que `bootstrap/app.php` sigue excluyendo `webhooks/*` y
      `checkout/izipay/*/confirmar` de la verificación CSRF (ya está hecho,
      solo no romperlo en cambios futuros).

## 8. Prueba de punta a punta con dinero real

- [ ] Un pedido de prueba real por un monto pequeño con **cada** pasarela que
      se vaya a ofrecer (tarjeta/Culqi, Yape/Plin QR si aplica, Izipay,
      PayPal), confirmando que el pedido pasa a "Pagado" solo por webhook
      (no solo por el polling del navegador).
- [ ] Confirmar que el comprobante electrónico se emite correctamente y
      aparece en el portal de SUNAT/Nubefact.
- [ ] Procesar un reembolso de prueba y confirmar que el dinero se revierte
      de verdad en la pasarela y que se emite la nota de crédito.
- [ ] Correr el checklist manual completo de la sección 9 del manual técnico
      (`/admin/manual`) contra el sitio real, con datos reales.

## 9. Últimas verificaciones de seguridad

- [ ] Ninguna credencial real quedó commiteada en el repositorio (revisar
      `.env` no está en git — ya está en `.gitignore`).
- [ ] `composer audit` y `npm audit` sin vulnerabilidades críticas pendientes
      justo antes de desplegar.
- [ ] El CI (`Pest` + `Larastan` + `Pint`) está en verde en el commit que se
      va a desplegar.
- [x] ~~Auditoría de seguridad~~: dos rondas completas ya corregidas, ver
      [`docs/auditoria-seguridad.md`](auditoria-seguridad.md) (tabla de estado al
      inicio del documento). Dos pendientes puramente operativos, no de código:
  - [ ] `TRUSTED_PROXIES` restringido a la IP real del balanceador (ya en la
        sección 4 de este checklist) — sin esto, el bloqueo de 5 intentos de
        login por IP se puede evadir falsificando `X-Forwarded-For`.
  - [ ] **Opcional, recomendado después del lanzamiento**: activar
        `Content-Security-Policy` (hoy no está activo a propósito). Desplegar
        primero en modo `Content-Security-Policy-Report-Only` 1-2 semanas para
        capturar qué necesitan los widgets de Culqi/Izipay y GA4/Meta Pixel
        antes de pasar a modo bloqueo — un CSP mal calibrado puede romper el
        checkout.

## 10. Monitoreo post-lanzamiento

- [ ] Revisar `storage/logs/laravel.log` periódicamente los primeros días (o
      configurar un servicio de monitoreo de errores tipo Sentry/Flare si el
      volumen de pedidos lo justifica — no está integrado todavía).
- [ ] Confirmar que `storage/` tiene los permisos correctos para que Laravel
      pueda escribir logs y assets subidos por el admin.
