Apariencia
Integraciones externas
Casos para validar que Stripe (único SaaS), el storage local y el SMTP local (Postfix) funcionen end-to-end. Los tests de Stripe tocan la sandbox real; los de storage y SMTP tocan el propio servidor.
Duración estimada: 30 minutos.
Stripe — pagos
TC-INT-001 · Checkout Session completa exitosamente
Severidad: 🔴 Crítica
Precondiciones:
- Stripe en test mode (
STRIPE_SECRETempieza consk_test_) - Candidato logueado sin membresía
Pasos:
- Dashboard candidato → "Contratar membresía"
- En Stripe Checkout: tarjeta
4242 4242 4242 4242, fecha futura, CVC123 - Completar pago
Resultado esperado:
Payment.status = succeededMembership.status = activeconexpires_at = now + 180 days- Correo "Membresía activa" en MailHog (dev) o inbox real (staging/prod)
Validación en Stripe Dashboard:
- Stripe → Payments → última tx con status
succeeded - Amount = 499 MXN
- Metadata incluye
user_id,membership_plan_id
TC-INT-002 · Webhook con firma inválida
Severidad: 🔴 Crítica
Pasos:
bash
curl -X POST http://localhost:8000/api/webhooks/stripe \
-H "Content-Type: application/json" \
-H "Stripe-Signature: t=123,v1=invalid" \
-d '{"type":"checkout.session.completed","data":{"object":{"id":"cs_fake"}}}'Resultado esperado:
- Response 400 "Invalid signature"
- Log Laravel:
[StripeWebhook] SignatureVerificationException - NO se crea Payment ni Membership
TC-INT-003 · Stripe CLI — disparar webhook de prueba
Severidad: 🟠 Alta
Precondiciones: Stripe CLI instalado, stripe listen corriendo
Pasos:
bash
# Terminal 1
stripe listen --forward-to localhost:8000/api/webhooks/stripe
# Terminal 2
stripe trigger checkout.session.completedResultado esperado:
- Stripe CLI muestra:
<-- [200] POST ... - Backend loguea:
[StripeWebhook] checkout.session.completed processed
Variaciones:
- Replay del mismo evento → 200 idempotente, no duplica membership
TC-INT-004 · Webhook retry automático (fallar y recuperar)
Severidad: 🟠 Alta
Precondiciones: Ambiente donde puedas apagar el backend temporalmente
Pasos:
- Candidato inicia checkout
- Apagar el backend antes de que Stripe envíe el webhook
- Completar pago en Stripe
- Stripe intentará el webhook → falla (backend caído)
- Encender el backend
- Stripe reintenta automáticamente (3 retries con backoff exponencial)
Resultado esperado:
- Eventualmente webhook llega y procesa
- Membresía activa
- Dashboard Stripe → Webhooks → evento muestra retry history
Nota: Stripe reintenta hasta por 3 días. Si el webhook nunca llega a completar, el equipo recibe alerta.
Storage local — archivos
TC-INT-005 · Upload de avatar
Severidad: 🟠 Alta
Pasos:
- Candidato logueado → perfil → "Cambiar foto"
- Imagen JPG 2MB válida → upload
Resultado esperado:
- URL del tipo
{APP_URL}/storage/avatars/{user_id}/{hash}.webp User.avatar_urlapunta a esa URLUser.avatar_pathguardaavatars/{user_id}/{hash}.webp- Archivo físico existe en
humae_backend/storage/app/public/avatars/{user_id}/ GET {avatar_url}devuelve 200 conContent-Type: image/webp- Dimensiones: 400×400 (recortadas con cover)
Variaciones:
- Subir archivo > 4MB → 422 "Archivo demasiado grande" (rechazado en el FormRequest)
- Subir PDF como avatar → 422 "Formato inválido"
- Cambiar avatar una segunda vez → el archivo anterior se borra del disco (verificar con
ls); queda sólo el nuevo
TC-INT-006 · Upload de documento grande (PDF 9MB)
Severidad: 🟡 Media
Pasos:
- Perfil → "Documentos" → subir PDF de ~9MB
Resultado esperado:
- Upload exitoso (≤ 10MB permitido)
CandidateDocument.file_provider = 'local'CandidateDocument.file_public_id = 'documents/{profile_id}/{hash}.pdf'CandidateDocument.file_url = '{APP_URL}/api/v1/me/profile/documents/{id}/download'- Archivo físico existe en
storage/app/private/documents/{profile_id}/ - Documento visible en la lista; botón de descarga funciona
Variaciones:
- PDF de 11MB → 422
- Nginx devuelve 413 en vez de 422 → ampliar
client_max_body_sizea12M
TC-INT-007 · Descarga de documento privado requiere auth
Severidad: 🔴 Crítica
Pasos:
- Crear documento con un candidato
- Sin sesión,
curl -I {APP_URL}/api/v1/me/profile/documents/{id}/download - Con sesión del mismo candidato, repetir
- Con sesión de otro candidato, repetir
Resultado esperado:
- Sin sesión →
401 Unauthorized - Mismo candidato →
200con stream del archivo - Otro candidato →
404(no revela existencia del recurso ajeno)
TC-INT-008 · Symlink /storage/... sirve archivos estáticos
Severidad: 🟡 Media
Pasos:
bash
cd humae_backend
php artisan tinker --execute="Storage::disk('public')->put('probe.txt','ok');"
curl -I {APP_URL}/storage/probe.txtResultado esperado:
HTTP/2 200conContent-Type: text/plain- Si devuelve 404: falta ejecutar
php artisan storage:linko Nginx no está sirviendo/storage/*
SMTP local — correos
TC-INT-009 · Email transaccional llega al inbox con headers auth OK
Severidad: 🔴 Crítica
Precondiciones:
- Ambiente con Postfix real (no MailHog)
- DNS SPF/DKIM/DMARC/PTR ya configurados y propagados
Pasos:
bash
php artisan tinker --execute="\Mail::raw('Test QA '.now(), fn(\$m) => \$m->to('tu-email-real@gmail.com')->subject('HUMAE QA test'));"Resultado esperado:
- Email llega al inbox (no spam) en ≤ 2 minutos
- Sender:
HUMAE <no-reply@humae.com.mx> - Reply-To:
soporte@humae.com.mx - Header
Authentication-Resultsdel destinatario muestradkim=pass,spf=pass,dmarc=pass /var/log/mail.logmuestrastatus=sent
Variaciones:
- Email cae en spam → revisar SPF/DKIM/DMARC/PTR (ver SMTP local)
connect to 127.0.0.1:25: connection refused→ Postfix apagado (sudo systemctl start postfix)status=deferred (Network is unreachable)→ provider bloquea 25 saliente
TC-INT-010 · Email de verificación con link válido llegue
Severidad: 🔴 Crítica
Pasos: Ejecutar TC-CAND-001 con un email real
Resultado esperado:
- Email con subject "Verifica tu correo…"
- HTML se renderiza bien en Gmail, Outlook, Apple Mail
- Logo HUMAE visible
- Botón CTA con color
#314259y texto legible - Link funciona al click
Verificación rápida de integraciones
Una sola pasada combinada si tienes poco tiempo:
bash
# 1. Health endpoint - todo verde
curl https://api.humae.com.mx/api/v1/health
# 2. Stripe listener activo
stripe listen --forward-to localhost:8000/api/webhooks/stripe
# En otra terminal:
stripe trigger checkout.session.completed
# 3. Storage local - escribir, leer HTTP, borrar
php artisan tinker --execute="Storage::disk('public')->put('probe.txt','ok');"
curl -I {APP_URL}/storage/probe.txt
php artisan tinker --execute="Storage::disk('public')->delete('probe.txt');"
# 4. SMTP local - enviar correo de prueba
php artisan tinker --execute="\Mail::raw('test', fn(\$m) => \$m->to('tu@gmail.com')->subject('HUMAE test'));"
sudo tail -n 20 /var/log/mail.logLos 4 deben completar sin errores.

