@php $serviceStatusOptions = ['Pendiente', 'Confirmada', 'Realizada', 'Cancelada']; $normalizeServiceStatus = function ($status) { return match ((string) $status) { 'Confirmado', 'Confirmada' => 'Confirmada', 'Completada', 'Realizada' => 'Realizada', 'Cancelado', 'Cancelada' => 'Cancelada', 'Cedida', 'Pendiente' => 'Pendiente', default => 'Pendiente', }; }; $serviceStatusLabel = $normalizeServiceStatus($serenata->service_status); $paymentBadge = match ((string) $serenata->payment_status) { 'Pagado' => 'bg-label-success', 'Abono parcial' => 'bg-label-info', 'Cancelada sin cobro' => 'bg-label-danger', default => 'bg-label-warning', }; $serviceBadge = match($serviceStatusLabel) { 'Realizada' => 'bg-label-success', 'Confirmada' => 'bg-label-success', 'Pendiente' => 'bg-label-warning', 'Cancelada' => 'bg-label-danger', default => 'bg-label-info', }; $isEmbedded = $embedded ?? false; $effectiveCommission = (float) $serenata->commission_amount; if ($effectiveCommission <= 0) { $effectiveCommission = max(0, (float) $serenata->profit_amount); } $effectiveProfit = (float) $serenata->profit_amount; $formatCop = fn ($value) => '$' . number_format((float) $value, 0, ',', '.'); $plannedPrefix = '[PLAN]'; $actualCustomerCharge = $serenata->actual_customer_charge !== null ? (float) $serenata->actual_customer_charge : null; $effectiveCustomerCharge = $actualCustomerCharge ?? (float) $serenata->total_charged; $paymentRows = collect($payments ?? [])->map(function ($payment) use ($plannedPrefix) { $note = trim((string) ($payment->note ?? '')); $isRefund = str_contains($note, '[REFUND]'); $isPlannedPayment = !$isRefund && str_starts_with($note, $plannedPrefix); return [ 'model' => $payment, 'is_refund' => $isRefund, 'is_planned' => $isPlannedPayment, 'is_applied' => !$isRefund && !$isPlannedPayment, ]; })->values(); $totalPaid = (float) collect($payments ?? [])->filter(function ($payment) use ($plannedPrefix) { $note = trim((string) ($payment->note ?? '')); $isRefund = str_contains($note, '[REFUND]'); return $note === '' || !str_starts_with($note, $plannedPrefix) || $isRefund; })->sum('amount'); $appliedPaymentsCount = $paymentRows->where('is_applied', true)->count(); $refundPaymentsCount = $paymentRows->where('is_refund', true)->count(); $plannedPaymentsCount = $paymentRows->where('is_planned', true)->count(); $pendingAmount = max(0, $effectiveCustomerCharge - $totalPaid); $paymentDiff = round($totalPaid - $effectiveCustomerCharge, 2); $hasPaymentMismatch = abs($paymentDiff) >= 1; $cededOverview = $cededOverview ?? [ 'status' => 'equilibrado', 'label' => 'Sin excedente', 'amount' => 0, 'signed_amount' => 0, ]; $externalPartnerMovements = collect($externalPartnerMovements ?? []); $externalMovementCount = $externalPartnerMovements->count(); $externalPartnerBalanceSummary = $externalPartnerBalanceSummary ?? []; $externalPartnerMovementTypeOptions = $externalPartnerMovementTypeOptions ?? []; $externalPartnerMovementDirectionOptions = $externalPartnerMovementDirectionOptions ?? []; $externalPartnerMovementSettlementOptions = $externalPartnerMovementSettlementOptions ?? []; $externalPartnerNameLookup = collect($externalPartnerOptions ?? []) ->mapWithKeys(fn ($item) => [(int) ($item['id'] ?? 0) => (string) ($item['name'] ?? '')]) ->merge( $externalPartnerMovements->mapWithKeys(function ($movement) { $partnerId = (int) ($movement->external_partner_id ?? 0); if ($partnerId <= 0) { return []; } return [ $partnerId => (string) ($movement->externalPartner->name ?? ('Externo #' . $partnerId)), ]; }) ) ->all(); $savedCededBreakdown = collect(old('ceded_cost_breakdown') ? (json_decode((string) old('ceded_cost_breakdown'), true) ?: []) : ($serenata->ceded_cost_breakdown ?? [])); $savedCededBreakdownLookup = $savedCededBreakdown ->filter(fn ($item) => is_array($item)) ->mapWithKeys(function ($item) { $source = (string) ($item['source'] ?? ''); $index = array_key_exists('index', $item) ? (string) $item['index'] : 'x'; if ($source === '') { return []; } return [$source . ':' . $index => $item]; }); $giftItems = collect($serenata->gift_items ?? [])->filter(function ($item) { return !(bool) data_get($item, 'is_deleted', false); }); $giftIncluded = $giftItems->filter(function ($item) { $type = strtolower((string) data_get($item, 'type', 'included')); return $type !== 'additional'; })->values(); $giftAdditional = $giftItems->filter(function ($item) { $type = strtolower((string) data_get($item, 'type', 'included')); return $type === 'additional'; })->values(); $formatGiftLine = function ($item): string { $name = (string) data_get($item, 'name', 'Obsequio'); $qty = max(1, (int) data_get($item, 'quantity', 1)); $unit = (float) data_get($item, 'unit_price', 0); return $name . " x{$qty} ($" . number_format($unit, 0, ',', '.') . ')'; }; $activeLineItems = collect($lineItems ?? [])->filter(fn ($item) => empty($item['is_deleted']))->values(); $deletedLineItemsCount = collect($lineItems ?? [])->filter(fn ($item) => !empty($item['is_deleted']))->count(); $costCategorySummary = collect([ [ 'key' => 'artist', 'label' => 'Artistas', 'icon' => 'tabler-music', 'tone' => 'primary', 'items' => $activeLineItems->where('source', 'artist'), ], [ 'key' => 'gift', 'label' => 'Obsequios', 'icon' => 'tabler-gift', 'tone' => 'warning', 'items' => $activeLineItems->where('source', 'gift'), ], [ 'key' => 'other_cost', 'label' => 'Otros costos', 'icon' => 'tabler-tool', 'tone' => 'info', 'items' => $activeLineItems->where('source', 'other_cost'), ], ])->map(function ($group) { $items = collect($group['items'] ?? []); return [ 'key' => $group['key'], 'label' => $group['label'], 'icon' => $group['icon'], 'tone' => $group['tone'], 'count' => $items->count(), 'total' => (float) $items->sum(fn ($item) => (float) ($item['total'] ?? 0)), ]; })->values(); $includedText = $giftIncluded->isNotEmpty() ? $giftIncluded->map($formatGiftLine)->implode(', ') : 'Ninguno'; $additionalText = $giftAdditional->isNotEmpty() ? $giftAdditional->map($formatGiftLine)->implode(', ') : 'Ninguno'; $shareLines = [ "SERVICIO {$serenata->code}", 'Grupo: ' . ($serenata->mariachiGroup?->name ?: 'Sin grupo'), "Cliente: {$serenata->payer_name}", 'Correo: ' . ($serenata->payer_email ?: '-'), "Teléfono 1: {$serenata->formatted_contact_phone_1}", 'Teléfono 2: ' . ($serenata->formatted_contact_phone_2 ?: '-'), "Dirección: {$serenata->service_address}", 'País: ' . ($serenata->service_country ?: 'Colombia'), 'Departamento: ' . ($serenata->service_department ?: '-'), "Ciudad/Municipio: {$serenata->locality}", 'Localidad: ' . ($serenata->service_locality ?: '-'), 'Sector/UPZ: ' . ($serenata->service_sector ?: '-'), "Barrio: {$serenata->neighborhood}", 'Fecha: ' . optional($serenata->event_date)->format('d/m/Y'), 'Horario estimado: ' . $serenata->eventWindowLabel(), "De parte de: {$serenata->from_name}", "Para quién: {$serenata->to_name}", 'Motivo: ' . ($serenata->reason ?: '-'), "Obsequios incluidos: {$includedText}", "Obsequios adicionales: {$additionalText}", 'Precio total: $' . number_format((float) ($serenata->total_charged ?? 0), 0, ',', '.'), ]; if (!empty($serenata->address_additional)) { $shareLines[] = "Dirección adicional: {$serenata->address_additional}"; } if (!empty($serenata->service_private_note)) { $shareLines[] = "Nota para el servicio: {$serenata->service_private_note}"; } $serenataShareText = implode("\n", $shareLines); $phoneDigits = $serenata->contact_phone_1_whatsapp_digits; $confirmMessage = implode("\n", [ "Hola {$serenata->payer_name}, te habla Camila Serenatas.", 'Queremos reconfirmar tu servicio:', 'Fecha: ' . optional($serenata->event_date)->format('d/m/Y'), 'Horario estimado: ' . $serenata->eventWindowLabel(), "Dirección: {$serenata->service_address}", 'Localidad: ' . ($serenata->service_locality ?: '-'), 'Sector: ' . ($serenata->service_sector ?: '-'), "Barrio: {$serenata->neighborhood}", "Ciudad/Municipio: {$serenata->locality}", '', '¿Todo sigue en pie? Muchas gracias.' ]); $waConfirmUrl = $phoneDigits !== '' ? 'https://wa.me/' . $phoneDigits . '?text=' . rawurlencode($confirmMessage) : null; $googleMapsQuery = implode(', ', array_filter([ trim((string) ($serenata->service_address ?? '')), trim((string) ($serenata->address_additional ?? '')), trim((string) ($serenata->service_sector ?? '')), trim((string) ($serenata->service_locality ?? '')), trim((string) ($serenata->neighborhood ?? '')), trim((string) ($serenata->service_department ?? '')), trim((string) ($serenata->locality ?? '')), trim((string) ($serenata->service_country ?? 'Colombia')), ])); $googleMapsFallbackUrl = $googleMapsQuery !== '' ? 'https://www.google.com/maps/search/?api=1&query=' . rawurlencode($googleMapsQuery) : null; $googleMapsAddressUrl = !empty($googleMapsAddressUrl ?? '') ? $googleMapsAddressUrl : $googleMapsFallbackUrl; $addressShareText = $googleMapsAddressUrl ?: trim((string) ($serenata->service_address ?? '')); $currentExternalPartnerName = $serenata->externalPartner?->name ?: 'Sin externo asignado'; $cededOverviewStatus = (string) ($cededOverview['status'] ?? 'equilibrado'); $cededOverviewBadge = match ($cededOverviewStatus) { 'por_cobrar' => 'bg-label-success', 'yo_debo' => 'bg-label-warning', default => 'bg-label-secondary', }; $eventDateHeadline = '-'; if ($serenata->event_date) { $eventDateValue = $serenata->event_date->copy(); $eventDateLabel = mb_strtoupper($eventDateValue->locale('es')->translatedFormat('d F'), 'UTF-8'); $eventTimeLabel = $serenata->eventWindowLabel(); if ((int) $eventDateValue->year !== (int) now()->year) { $eventDateLabel .= ' ' . $eventDateValue->format('Y'); } $eventDateHeadline = trim($eventDateLabel . ($eventTimeLabel !== '' ? (((int) $eventDateValue->year !== (int) now()->year ? ' | ' : ' ') . $eventTimeLabel) : '')); } $cededBreakdownItems = collect($lineItems ?? []) ->filter(fn ($item) => empty($item['is_deleted'])) ->values() ->map(function ($item) use ($savedCededBreakdownLookup) { $lookupKey = (string) ($item['source'] ?? '') . ':' . (string) ($item['index'] ?? '0'); $saved = $savedCededBreakdownLookup->get($lookupKey, []); $quantity = max(1, (int) ($saved['quantity'] ?? $item['quantity'] ?? 1)); $unitPrice = (float) ($saved['unit_price'] ?? $item['unit_price'] ?? 0); $included = array_key_exists('included', $saved) ? (bool) $saved['included'] : true; return [ 'source' => (string) ($item['source'] ?? ''), 'index' => (int) ($item['index'] ?? 0), 'name' => (string) ($saved['name'] ?? $item['name'] ?? 'Ítem'), 'category' => (string) ($item['category'] ?? ''), 'quantity' => $quantity, 'unit_price' => $unitPrice, 'included' => $included, 'total' => $included ? ($quantity * $unitPrice) : 0, ]; }) ->values(); $savedTransportItem = $savedCededBreakdown->first(fn ($item) => is_array($item) && (($item['source'] ?? '') === 'transport')); $cededTransportInitial = (float) old('ceded_transport_amount', $savedTransportItem['unit_price'] ?? $serenata->ceded_transport_amount); $cededBreakdownJson = $cededBreakdownItems ->push([ 'source' => 'transport', 'index' => 0, 'name' => 'Transporte manual', 'category' => 'Transporte', 'quantity' => 1, 'unit_price' => $cededTransportInitial, 'included' => $cededTransportInitial > 0, 'total' => $cededTransportInitial > 0 ? $cededTransportInitial : 0, ]) ->values() ->toJson(JSON_UNESCAPED_UNICODE); @endphp
Actualiza aquí el seguimiento operativo de la serenata.
| Categoría | Ítem | Precio | Cant. | Total | Acciones |
|---|---|---|---|---|---|
| {{ $item['category'] }} |
@if(!empty($item['is_deleted']))
{{ $item['name'] }}
Eliminado
@if(!empty($item['delete_reason']))
Motivo: {{ $item['delete_reason'] }}
@endif
@else
{{ $item['name'] }}
@endif
|
${{ number_format((float) $item['unit_price'], 0, ',', '.') }} | {{ $item['quantity'] }} | ${{ number_format((float) $item['total'], 0, ',', '.') }} | @if(empty($item['is_deleted'])) @else @endif |
| Sin ítems seleccionados en esta serenata. | |||||
Obsequios que pueden ir incluidos en el servicio.
Obsequios que se suman como adicional al servicio.
Selecciona artistas para esta serenata y ajusta precio cuando aplique.
Agrega otros costos del servicio.
| Nombre | Valor |
|---|---|
| Descuento aplicado | - ${{ number_format((float) $serenata->discount_amount, 0, ',', '.') }} |
| Cambio | Usuario | Fecha |
|---|---|---|
| {{ $history->to_service_status ? $normalizeServiceStatus($history->to_service_status) : '-' }} | {{ $history->changedByUser->name ?? 'Sistema' }} | {{ $history->created_at?->format('d/m/Y H:i:s') }} |
| {{ $serviceStatusLabel }} | Sistema | {{ $serenata->created_at?->format('d/m/Y H:i:s') }} |
Úsalo solo si el cliente terminó pagando distinto a lo pactado.
Añade aquí un pago real que sí entró o quedó registrado.
Resumen de pagos aplicados, pendientes planeados y reembolsos.
| Fecha | Método | Estado | Referencia | Importe | Registrado por |
|---|---|---|---|---|---|
| {{ $payment->paid_at?->format('d/m/Y H:i') }} | {{ $payment->payment_method_name }} | @if($isRefund) Reembolso @elseif($isPlannedPayment) Pendiente @else Aplicado @endif | {{ $payment->transaction_reference ?: '-' }} | {{ $formatCop($payment->amount) }} | {{ $payment->registeredBy->name ?? 'Sistema' }} |
| Aún no hay pagos registrados. | |||||
Registro inicial en el sistema.
Motivo: {{ $serenata->reason }}
Motivo cancelación: {{ $serenata->cancellation_reason }}
@elseif($serenata->is_ceded){{ $cededOverview['label'] ?? 'Sin excedente' }}: ${{ number_format((float) ($cededOverview['amount'] ?? 0), 0, ',', '.') }}
@endif| Movimiento | Usuario | Fecha |
|---|---|---|
| {{ $history->note ?: 'Cambio de estado' }} | {{ $history->changedByUser->name ?? 'Sistema' }} | {{ $history->created_at?->format('d/m/Y H:i:s') }} |
| Sin movimientos registrados. | ||
Aquí registras cesiones, cambios pendientes, compensaciones con serenata, ajustes en efectivo y penalidades.
{{ $externalMovementCount }} registros vinculados a esta serenata.
| Fecha | Externo | Movimiento | Dirección | Valor | Compensación | Promesa | Referencia | Nota |
|---|---|---|---|---|---|---|---|---|
| {{ $movement->created_at?->format('d/m/Y H:i') }} | {{ $movement->externalPartner->name ?? 'Sin externo' }} | {{ $externalPartnerMovementTypeOptions[$movement->movement_type] ?? ucfirst(str_replace('_', ' ', (string) $movement->movement_type)) }} | {{ $externalPartnerMovementDirectionOptions[$movement->direction] ?? $movement->direction }} | ${{ number_format((float) $movement->amount, 0, ',', '.') }} | {{ $externalPartnerMovementSettlementOptions[$movement->settlement_kind] ?? ucfirst((string) $movement->settlement_kind) }} | {{ $movement->due_date?->format('d/m/Y') ?: '-' }} | {{ $movement->counterpart_reference ?: '-' }} | {{ $movement->notes ?: '-' }} |
| Todavía no hay movimientos registrados para esta serenata. | ||||||||