{ "feature_id": "F-152", "agent": "qa", "stage": "qa_gate", "verdict": "APPROVED", "summary": "F-152 verificado: welcome email se envía al email registrado (account_created) de forma best-effort con warning logueado si SMTP falla o no está configurado; el registro sigue retornando 2xx. La confirmación de orden se dispara en PaymentSucceeded con outcome.kind==='processed' (idempotente ante duplicados) y la orden pasa a PAID. Suite tsc/prettier/eslint/boundaries/vitest(197) y verify.sh verdes.", "evidence": [ "SettingsWelcomeMailer.test.ts: nodemailer mockeado -> sendMail llamado una sola vez con to==email registrado y subject=='¡Bienvenido a Mercado de Vida!' (plantilla account_created)", "SettingsWelcomeMailer.test.ts: readSmtpOptions lanza 'SMTP is not configured' cuando falta SMTP; sendWelcome no propaga -> register handler no falla", "identity.routes register: welcome dispatch es fire-and-forget .catch(request.log.warn) -> warning logueado y respuesta 201 con payload de usuario", "payments.routes webhook: gate event.type==='PaymentSucceeded' && outcome.kind==='processed' -> sendOrderStatusEmail({state:'PAID'}); PaymentsService.test cubre transición a PAID", "outcome.kind==='processed' garantiza un solo email ante webhooks duplicados (PaymentsService devuelve kind:'duplicate' antes de transición)", "order-status-mailer.test.ts cubre buildOrderStatusEmail/PAID (preexistente, sin tocar)", "orders-index.test.ts verifica barrel re-exporta sendOrderStatusEmail/ORDER_STATE_LABELS/buildOrderStatusEmail", "Regresión: verify.sh exit 0; tsc 0 errors; prettier+eslint clean; lint:boundaries sin nuevas violaciones; 197 tests pass; git diff --check clean" ], "checks": [ {"item": "AC: welcome email enviado al email registrado", "ok": true, "evidence": "SettingsWelcomeMailer.test.ts: mail.to === 'ada@example.com' (input email), subject === account_created"}, {"item": "AC: registro responde 2xx aunque SMTP falle/no-config", "ok": true, "evidence": "dispatch fire-and-forget .catch(request.log.warn); RegisterUser sin depender de email"}, {"item": "AC: warning logueado ante fallo SMTP welcome", "ok": true, "evidence": ".catch(request.log.warn) en identity.routes register handler"}, {"item": "AC: confirmación enviada en PaymentSucceeded", "ok": true, "evidence": "payments.routes: sendOrderStatusEmail({state:'PAID'}) tras outcome.kind==='processed'"}, {"item": "AC: orden pasa a PAID", "ok": true, "evidence": "PaymentsService.handleWebhook transition + payments-service.test.ts (existente)"}, {"item": "AC: webhook responde 2xx", "ok": true, "evidence": "reply.send sin propagar error de email; try/catch + warn"}, {"item": "AC: webhook duplicado -> un solo email", "ok": true, "evidence": "gate outcome.kind==='processed'"}, {"item": "AC: email falla/no-config -> orden PAID + 2xx + warn (no excepción)", "ok": true, "evidence": "try/catch en payments.routes -> request.log.warn (o console.warn)"}, {"item": "Regresión: verify.sh/tsc/prettier/eslint/boundaries/tests", "ok": true, "evidence": "verify.sh exit 0; tsc 0; prettier+eslint clean; 197 tests pass; git diff --check clean"}, {"item": "Regresión: password-reset + admin dispatch intactos", "ok": true, "evidence": "solo se añade account_created; no se removen plantillas ni routes"} ], "issues": [], "notes": "No hay endpoint HTTP nuevo para validar en vivo más allá de los tests unitarios; la cobertura email está cubierta por SettingsWelcomeMailer.test.ts (nodemailer mockeado) y el gate de dedup outcome.kind. El order-confirmation wiring es difícil de unit-testear sin app full; se valida por tipo + gate lógico + transición PAID preexistente.", "timestamp": "2026-08-22T05:05:50Z" }