@extends('layouts.app') @push('styles') @endpush @section('content') @php $counts = $preflight['counts']; $issues = $preflight['issues']; $previewContact = $preview['contact']; $bridge = $launchBridge; $bridgePayload = $bridge['payload']; $bridgeAvailability = $bridge['availability']; $bridgeIsLaunchMode = in_array($bridge['mode'], ['legacy_dispatch', 'laravel'], true); $bridgeLaunchChannel = $bridge['mode'] === 'laravel' ? 'Laravel' : 'legacy'; $bridgeWarnings = $bridge['warnings'] ?? []; $testSendGuard = $bridge['test_send_guard']; $previewAvatar = strtoupper(substr(trim((string) ($campaign->from_name ?? 'S')), 0, 1)) ?: 'S'; $previewContactName = trim((string) ($previewContact['full_name'] ?? '')) !== '' ? (string) $previewContact['full_name'] : 'Sin nombre'; $previewContactMeta = (string) ($previewContact['email'] ?? ''); if (!empty($previewContact['is_demo'])) { $previewContactMeta .= ' | demo'; } $previewTimestampLabel = $bridgePayload['scheduled_at_utc'] ? 'Programada' : 'Ahora'; @endphp

{{ $campaign->name }}

{{ $preview['subject'] ?: $campaign->subject }}

Volver al builder Volver a campañas
Enviables {{ number_format((int) $counts['sendable']) }}
Audiencia útil {{ number_format((int) $counts['audience_total']) }}
Bloqueados {{ number_format((int) $counts['blocked_total']) }}
Issues {{ number_format(count($issues)) }}
@if (false) @endif
Simulación de email recibido

Vista previa

Render aislado del `html_body` con subject, preheader y placeholders resueltos usando un contacto enviable cuando existe.

{{ $previewAvatar }}
{{ (string) ($campaign->from_name ?? '') }} <{{ (string) ($campaign->from_email ?? '') }}>
Para: {{ $previewContact['email'] ?? '' }}

{{ $preview['subject'] ?: '-' }}

{{ $previewTimestampLabel }}
Contacto de preview {{ $previewContactName }} {{ $previewContactMeta }}
Subject renderizado {{ $preview['subject'] ?: '-' }} Versión final con placeholders resueltos.
Preheader renderizado {{ $preview['preheader'] ?: '-' }} {{ $launchPlan['scheduled_label'] }}
Desktop preview
@if (trim((string) ($preview['text'] ?? '')) !== '')
Texto plano
{{ $preview['text'] }}
@endif

Resumen de audiencia

Preflight y conteos antes de tocar el sender real.

{{ number_format((int) $counts['sendable']) }} enviables
@foreach ($counts['include_names'] as $name) INCLUDE | {{ $name }} @endforeach @forelse ($counts['exclude_names'] as $name) EXCLUDE | {{ $name }} @empty Sin excludes @endforelse
Incluidos activos {{ number_format((int) $counts['included_contacts']) }} Base inicial antes de excludes.
Excluidos por listas {{ number_format((int) $counts['excluded_contacts']) }} Contactos retirados por rules exclude.
Audiencia útil {{ number_format((int) $counts['audience_total']) }} Destinatarios después del cruce include/exclude.
Bloqueados por suppression {{ number_format((int) $counts['suppressed_contacts']) }} Protegidos por bajas, rebotes o quejas.
No enviables por status {{ number_format((int) $counts['non_subscribed_contacts']) }} Contactos fuera de `subscribed`.
Enviables {{ number_format((int) $counts['sendable']) }} Volumen listo para una prueba real.

Plan de lanzamiento

El draft ya guarda schedule y rate sin crear `campaign_sends`.

{{ $launchPlan['scheduled_label'] }}
Programación guardada {{ $launchPlan['scheduled_label'] }}
Zona horaria {{ $launchPlan['scheduled_timezone'] }}
Velocidad guardada {{ (int) $campaign->send_rate_per_minute }} emails/seg

@if ($bridgeIsLaunchMode) Laravel ya puede abrir un launch controlado hacia legacy desde esta misma pantalla. @else Esto solo deja listo el draft. Todavía no se agenda un envío real desde Laravel. @endif

Puente legacy / Laravel

@if ($bridgeIsLaunchMode) Laravel ya puede preparar el payload, validar la paridad y lanzar un envío real controlado vía {{ $bridgeLaunchChannel }}. @else Laravel ya puede preparar y validar el payload que usaría el flujo legacy sin crear `campaign_sends`. @endif

{{ $bridge['mode'] }} {{ $bridge['ready'] ? 'Bridge listo' : 'Con blockers' }}
Modo {{ $bridge['mode'] }}
Bridge listo {{ $bridge['ready'] ? 'Sí' : 'Con blockers' }}
config.php legacy {{ $bridgeAvailability['legacy_config_exists'] ? 'Disponible' : 'No encontrado' }}
campaigns_lib.php {{ $bridgeAvailability['campaigns_lib_exists'] ? 'Disponible' : 'No encontrado' }}
Dispatch worker {{ $bridgeAvailability['dispatch_worker'] ? 'Activo' : 'Off por config' }}
Prueba reciente requerida {{ $testSendGuard['required'] ? 'Si' : 'No' }}
Última prueba exitosa {{ $testSendGuard['last_success_at'] ? $testSendGuard['last_success_at'] . ($testSendGuard['last_success_to'] ? ' | ' . $testSendGuard['last_success_to'] : '') : 'Sin prueba exitosa' }}
Ventana válida {{ (int) $testSendGuard['max_age_minutes'] }} min
Cap max sendable {{ (int) $bridgeAvailability['max_sendable'] > 0 ? number_format((int) $bridgeAvailability['max_sendable']) : 'Sin límite' }}
Launch type {{ $bridgePayload['launch_type'] }}
Schedule UTC {{ $bridgePayload['scheduled_at_utc'] ?: 'Enviar ahora' }}
Fallback contact_list_id {{ $bridgePayload['contact_list_id'] !== null ? $bridgePayload['contact_list_id'] : 'null' }}
Rate legado {{ (int) $bridgePayload['rate_per_minute'] }} emails/seg
Payload sombra
Includes {{ $bridgePayload['include_ids'] ? implode(', ', $bridgePayload['include_ids']) : 'Sin include' }}
Excludes {{ $bridgePayload['exclude_ids'] ? implode(', ', $bridgePayload['exclude_ids']) : 'Sin exclude' }}
Opciones legacy @if ($bridgePayload['options']['scheduled_at']) scheduled_at={{ $bridgePayload['options']['scheduled_at'] }} | scheduled_timezone={{ $bridgePayload['options']['scheduled_timezone'] }} @else Sin schedule extra @endif
    @foreach ($bridge['parity_checks'] as $check)
  • {{ $check['label'] }}
    {{ $check['detail'] }}
    {{ $check['ok'] ? 'ok' : 'revisar' }}
  • @endforeach
@if ($bridge['blockers'])

Blockers del bridge

    @foreach ($bridge['blockers'] as $blocker)
  • {{ $blocker }}
  • @endforeach
@else

@if ($bridgeIsLaunchMode) La paridad base ya está lista y el bridge puede abrir un envío real controlado vía {{ $bridgeLaunchChannel }}. @else La paridad base ya está lista para un launch controlado cuando abramos el siguiente corte. @endif

@endif @if ($bridgeWarnings)

Warnings operativos

    @foreach ($bridgeWarnings as $warning)
  • {{ $warning }}
  • @endforeach
@endif @if ($bridgeIsLaunchMode)

Guardas antes del launch real

Prueba reciente {{ $testSendGuard['ok'] ? 'Lista' : 'Pendiente' }}
Edad de última prueba {{ $testSendGuard['age_minutes'] !== null ? (int) $testSendGuard['age_minutes'] . ' min' : 'Sin prueba exitosa' }}
Worker inicial {{ $bridgeAvailability['dispatch_worker'] ? 'Se intentará disparar' : 'Queda apagado por config' }}
Estado para launch {{ $bridge['ready'] ? 'Listo' : 'Bloqueado' }}
@endif
@csrf

@if ($bridgeIsLaunchMode) Este POST crea el `campaign_send` real vía {{ $bridgeLaunchChannel }}. El worker masivo sigue respetando schedule y rate desde la cola. @else Este POST solo registra la validación y confirma blockers. No dispara envío real ni toca el worker masivo. @endif

Enviar prueba

Manda esta versión de la campaña a un inbox puntual sin crear envío masivo ni tocar el worker real.

{{ optional($campaign->last_test_sent_at)->format('Y-m-d H:i:s') ? 'Con test reciente' : 'Sin test exitoso' }}
@csrf
Última prueba {{ optional($campaign->last_test_sent_at)->format('Y-m-d H:i:s') ?: 'Sin pruebas exitosas todavía' }}

Pruebas recientes

Historial inmediato de disparos de prueba desde el paralelo.

    @forelse ($recentTestSends as $testSend)
  • {{ $testSend->to_email }}
    {{ optional($testSend->created_at)->format('Y-m-d H:i:s') }}
    @if ($testSend->error_message)
    {{ $testSend->error_message }}
    @endif
    {{ $testSend->status }}
  • @empty
  • Sin pruebas registradas todavía.
  • @endforelse

Preflight

Checklist rápido del draft antes de cualquier launch.

{{ $issues ? count($issues) . ' issues' : 'Sin issues' }}
@if ($issues)
    @foreach ($issues as $issue)
  • {{ $issue }}
  • @endforeach
@else

@if ($bridgeIsLaunchMode) El preflight base quedó sano. Si también pasan las guardas del bridge, ya puedes abrir un launch real controlado. @else Todo listo para seguir al siguiente corte. Todavía no se dispara ningún envío real desde Laravel. @endif

@endif

Compatibilidad legacy

Se mantiene la convivencia segura con el flujo productivo actual.

  • `contact_list_id` sigue alineado con el primer include.
  • El preview por sí solo no crea `campaign_sends`; el launch real puede crearlos cuando el modo es `legacy_dispatch` o `laravel`.
  • El test send usa un adapter temporal y sigue sin mover el worker masivo.
@endsection @push('scripts') @endpush