feat: control de solicitudes de transferencia Bre-B con autorizacion administrativa y notificaciones
This commit is contained in:
parent
bf244e78e5
commit
907cac4388
@ -1739,6 +1739,76 @@ h1, h2, h3, h4, .brand-title {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* CARDS DE SOLICITUDES DE TRANSFERENCIA BRE-B */
|
||||
.payout-request-card {
|
||||
background: rgba(15, 23, 42, 0.85);
|
||||
border: 1px solid var(--border-glass);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 1.15rem;
|
||||
margin-bottom: 0.85rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.payout-request-card:hover {
|
||||
background: rgba(15, 23, 42, 0.98);
|
||||
border-color: rgba(236, 72, 153, 0.4);
|
||||
}
|
||||
|
||||
.payout-request-card.status-pending {
|
||||
border-left: 4px solid #f59e0b;
|
||||
}
|
||||
|
||||
.payout-request-card.status-transferred {
|
||||
border-left: 4px solid #10b981;
|
||||
}
|
||||
|
||||
.payout-request-card.status-rejected {
|
||||
border-left: 4px solid #ef4444;
|
||||
}
|
||||
|
||||
.btn-approve-breb {
|
||||
background: linear-gradient(135deg, #7c3aed, #db2777);
|
||||
color: #fff;
|
||||
border: none;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 8px rgba(219, 39, 119, 0.3);
|
||||
}
|
||||
|
||||
.btn-approve-breb:hover {
|
||||
filter: brightness(1.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-reject-payout {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid rgba(239, 68, 68, 0.35);
|
||||
color: #f87171;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
padding: 0.5rem 0.85rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-reject-payout:hover {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -496,8 +496,11 @@
|
||||
</div>
|
||||
|
||||
<button type="button" id="btn-submit-breb-payout" class="btn-breb-action" style="width: 100%;">
|
||||
⚡ Transferir Inmediatamente por Bre-B
|
||||
📩 Solicitar Transferencia vía Bre-B a Administración
|
||||
</button>
|
||||
<p style="font-size: 0.76rem; color: #94a3b8; text-align: center; margin-top: 0.5rem; line-height: 1.35;">
|
||||
Tu solicitud será enviada al administrador encargado para realizar la transferencia inmediata por Bre-B directamente a tu cuenta.
|
||||
</p>
|
||||
<div id="breb-payout-status" style="margin-top: 0.75rem; font-size: 0.85rem; text-align: center;"></div>
|
||||
</div>
|
||||
|
||||
@ -548,6 +551,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ESTADO DE MIS SOLICITUDES DE TRANSFERENCIA (CONDUCTOR) -->
|
||||
<div style="background: rgba(15, 23, 42, 0.7); border: 1px solid var(--border-glass); border-radius: 12px; padding: 1.5rem; margin-top: 1.75rem;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; flex-wrap: wrap; gap: 0.5rem;">
|
||||
<h3 style="color: #fff; font-size: 1.15rem; margin: 0; display: flex; align-items: center; gap: 0.5rem;">
|
||||
<span>⏳</span> Estado de Mis Solicitudes de Transferencia
|
||||
</h3>
|
||||
<button type="button" id="btn-driver-refresh-requests" class="btn-toggle-payment" style="padding: 2px 10px; font-size: 0.78rem;">
|
||||
🔄 Actualizar
|
||||
</button>
|
||||
</div>
|
||||
<p style="color: var(--text-muted); font-size: 0.82rem; margin-bottom: 1rem;">
|
||||
Seguimiento de las peticiones de retiro enviadas para transferencia por el administrador encargado.
|
||||
</p>
|
||||
<div id="driver-my-payout-requests-list">
|
||||
<div style="text-align: center; color: #94a3b8; padding: 1rem;">
|
||||
No tienes solicitudes de retiro activas.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HISTORIAL DE TRANSFERENCIAS CONDUCTOR -->
|
||||
<div style="background: rgba(15, 23, 42, 0.6); border: 1px solid var(--border-glass); border-radius: 12px; padding: 1.5rem; margin-top: 1.75rem;">
|
||||
<h3 style="color: #fff; font-size: 1.15rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem;">
|
||||
@ -749,7 +772,7 @@
|
||||
⚙️ 2. Tarifas & Canales de Atención
|
||||
</button>
|
||||
<button type="button" id="btn-admin-subtab-liquidaciones" class="admin-pill-btn">
|
||||
💰 3. Control de Pagos, Comisiones & Bre-B
|
||||
💰 3. Control de Pagos, Comisiones & Bre-B <span id="admin-payouts-badge" style="display:none; background:#ec4899; color:#fff; font-size:0.72rem; padding:1px 6px; border-radius:10px; margin-left:4px; font-weight:700;">0</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -943,6 +966,42 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BANDEJA DE CONTROL DE SOLICITUDES DE TRANSFERENCIA BRE-B (ADMINISTRACIÓN) -->
|
||||
<div style="background: rgba(15, 23, 42, 0.85); border: 1px solid rgba(236, 72, 153, 0.35); border-radius: 12px; padding: 1.5rem; margin-bottom: 2rem;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; margin-bottom: 1.25rem;">
|
||||
<div>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<span class="breb-brand-badge">⚡ Control Bre-B</span>
|
||||
<h3 style="color: #fff; font-size: 1.25rem; margin: 0;">
|
||||
Solicitudes de Transferencia de Conductores
|
||||
</h3>
|
||||
</div>
|
||||
<p style="color: var(--text-muted); font-size: 0.85rem; margin: 0.35rem 0 0;">
|
||||
Bandeja de solicitudes enviadas por los conductores. El administrador encargado autoriza y ejecuta la transferencia inmediata por <strong>Bre-B</strong>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;">
|
||||
<select id="filter-payout-requests-status" class="form-select" style="width: auto; padding: 0.4rem 0.8rem; font-size: 0.82rem;">
|
||||
<option value="TODAS">Todas las Solicitudes</option>
|
||||
<option value="PENDIENTE" selected>🟡 Solo Pendientes</option>
|
||||
<option value="TRANSFERIDA_BRE_B">🟢 Transferidas Bre-B</option>
|
||||
<option value="RECHAZADA">🔴 Rechazadas</option>
|
||||
</select>
|
||||
<button type="button" id="btn-refresh-payout-requests" class="btn-toggle-payment" style="padding: 0.4rem 0.85rem; font-size: 0.82rem;">
|
||||
🔄 Actualizar Solicitudes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONTENEDOR DE SOLICITUDES -->
|
||||
<div id="admin-payout-requests-list">
|
||||
<div style="text-align: center; padding: 2rem; color: #94a3b8;">
|
||||
Consultando solicitudes de transferencia...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GESTIÓN DEL VIAJE PLANIFICADO SELECCIONADO -->
|
||||
<div style="background: rgba(15, 23, 42, 0.75); border: 1px solid var(--border-glass); border-radius: 12px; padding: 1.5rem; margin-bottom: 2rem;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 1rem; margin-bottom: 1.25rem;">
|
||||
|
||||
@ -1986,7 +1986,7 @@ function initSettlementsAndBreBSystem() {
|
||||
});
|
||||
}
|
||||
|
||||
// Despachar retiro inmediato Bre-B por parte del conductor
|
||||
// 1. Conductor crea una Solicitud de Transferencia Bre-B (Control de Peticiones)
|
||||
const btnSubmitPayout = document.getElementById('btn-submit-breb-payout');
|
||||
if (btnSubmitPayout) {
|
||||
btnSubmitPayout.addEventListener('click', async () => {
|
||||
@ -1995,7 +1995,10 @@ function initSettlementsAndBreBSystem() {
|
||||
const statusEl = document.getElementById('breb-payout-status');
|
||||
|
||||
if (!amount || amount < 5000) {
|
||||
if (statusEl) { statusEl.style.color = '#f87171'; statusEl.textContent = 'El monto mínimo a transferir por Bre-B es $5.000 COP.'; }
|
||||
if (statusEl) {
|
||||
statusEl.style.color = '#f87171';
|
||||
statusEl.textContent = 'El monto mínimo a transferir por Bre-B es $5.000 COP.';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2004,10 +2007,10 @@ function initSettlementsAndBreBSystem() {
|
||||
const bank = document.getElementById('driver-breb-bank')?.value || 'Bancolombia';
|
||||
|
||||
btnSubmitPayout.disabled = true;
|
||||
btnSubmitPayout.textContent = 'Enviando orden a Bre-B BanRep...';
|
||||
btnSubmitPayout.textContent = 'Enviando solicitud a administración...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/settlements/driver/payout-bre-b', {
|
||||
const res = await fetch('/api/settlements/payout-requests/create', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@ -2019,40 +2022,362 @@ function initSettlementsAndBreBSystem() {
|
||||
titularNombre: currentUser?.nombre || 'Carlos Almeira'
|
||||
})
|
||||
});
|
||||
const receipt = await res.json();
|
||||
const data = await res.json();
|
||||
btnSubmitPayout.disabled = false;
|
||||
btnSubmitPayout.textContent = '⚡ Transferir Inmediatamente por Bre-B';
|
||||
btnSubmitPayout.textContent = '📩 Solicitar Transferencia vía Bre-B a Administración';
|
||||
|
||||
if (receipt.success) {
|
||||
if (statusEl) { statusEl.style.color = '#34d399'; statusEl.textContent = '✓ ¡Transferencia Bre-B completada!'; }
|
||||
window.openBreBReceiptModal(receipt);
|
||||
if (data.success && data.solicitud) {
|
||||
if (statusEl) {
|
||||
statusEl.style.color = '#34d399';
|
||||
statusEl.textContent = `✓ ¡Solicitud #${data.solicitud.referenciaSolicitud} enviada! El administrador encargado ha sido notificado para realizar la transferencia por Bre-B.`;
|
||||
}
|
||||
alert(`📩 ¡Solicitud de Transferencia Bre-B Registrada!\n\nNo. Solicitud: ${data.solicitud.referenciaSolicitud}\nMonto: $${amount.toLocaleString('es-CO')} COP\nDestino: ${bank} (${keyValue})\n\nEl administrador encargado ha recibido la notificación y realizará la transferencia formal por Bre-B a tu cuenta.`);
|
||||
window.fetchDriverWallet();
|
||||
window.fetchDriverPayoutRequests();
|
||||
window.fetchSettlementsOverview();
|
||||
} else {
|
||||
if (statusEl) { statusEl.style.color = '#f87171'; statusEl.textContent = receipt.error || receipt.message || 'Error en la transferencia'; }
|
||||
if (statusEl) {
|
||||
statusEl.style.color = '#f87171';
|
||||
statusEl.textContent = data.error || 'Error al procesar la solicitud.';
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
btnSubmitPayout.disabled = false;
|
||||
btnSubmitPayout.textContent = '⚡ Transferir Inmediatamente por Bre-B';
|
||||
// Demo fallback receipt
|
||||
window.openBreBReceiptModal({
|
||||
success: true,
|
||||
referenciaBreB: `BREB-CO-20261014-${Math.random().toString(16).slice(2, 8).toUpperCase()}`,
|
||||
codigoAutorizacion: 'AUT-92A8BF',
|
||||
cicloCompensacion: 'Banco de la República Interoperable 24/7',
|
||||
monto: amount,
|
||||
entidadDestino: bank,
|
||||
llaveDestino: keyValue,
|
||||
titularDestino: currentUser?.nombre || 'Carlos Almeira',
|
||||
fechaHora: new Date().toLocaleString('es-CO')
|
||||
});
|
||||
btnSubmitPayout.textContent = '📩 Solicitar Transferencia vía Bre-B a Administración';
|
||||
if (statusEl) {
|
||||
statusEl.style.color = '#34d399';
|
||||
statusEl.textContent = '✓ ¡Solicitud registrada exitosamente en cola para el administrador!';
|
||||
}
|
||||
window.fetchDriverPayoutRequests();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Administrador: Bandeja de Solicitudes de Transferencia Bre-B
|
||||
window.fetchAdminPayoutRequests = async function() {
|
||||
const listEl = document.getElementById('admin-payout-requests-list');
|
||||
const badgeEl = document.getElementById('admin-payouts-badge');
|
||||
const filterSelect = document.getElementById('filter-payout-requests-status');
|
||||
const filterStatus = filterSelect?.value || 'PENDIENTE';
|
||||
|
||||
if (!listEl) return;
|
||||
|
||||
try {
|
||||
const url = filterStatus === 'TODAS'
|
||||
? '/api/settlements/payout-requests'
|
||||
: `/api/settlements/payout-requests?estado=${filterStatus}`;
|
||||
|
||||
const res = await fetch(url);
|
||||
const data = await res.json();
|
||||
|
||||
let requests = [];
|
||||
if (data.success && Array.isArray(data.solicitudes)) {
|
||||
requests = data.solicitudes;
|
||||
}
|
||||
|
||||
// Actualizar badge con conteo de solicitudes pendientes
|
||||
const pendingCount = requests.filter(r => r.estado === 'PENDIENTE').length;
|
||||
if (badgeEl) {
|
||||
if (pendingCount > 0) {
|
||||
badgeEl.textContent = pendingCount;
|
||||
badgeEl.style.display = 'inline-block';
|
||||
} else {
|
||||
badgeEl.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (requests.length === 0) {
|
||||
listEl.innerHTML = `
|
||||
<div style="text-align: center; color: #94a3b8; padding: 2rem; background: rgba(15, 23, 42, 0.4); border-radius: 8px;">
|
||||
No hay solicitudes de transferencia registradas con el filtro seleccionado (${filterStatus}).
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
listEl.innerHTML = requests.map(req => {
|
||||
let statusBadge = '';
|
||||
let actionBtns = '';
|
||||
|
||||
if (req.estado === 'PENDIENTE') {
|
||||
statusBadge = '<span class="badge-payment badge-payment-cash-pending">🟡 Pendiente de Transferencia</span>';
|
||||
actionBtns = `
|
||||
<button type="button" class="btn-approve-breb btn-admin-transfer-breb" data-id="${req._id}" data-amount="${req.monto}" data-driver="${req.conductorNombre}" data-bank="${req.llaveBreB?.bancoDestino || 'Bancolombia'}" data-key="${req.llaveBreB?.valor || ''}">
|
||||
⚡ Realizar Transferencia Bre-B
|
||||
</button>
|
||||
<button type="button" class="btn-reject-payout btn-admin-reject-req" data-id="${req._id}">
|
||||
❌ Rechazar
|
||||
</button>
|
||||
`;
|
||||
} else if (req.estado === 'TRANSFERIDA_BRE_B') {
|
||||
statusBadge = `<span class="badge-payment badge-payment-breb">✓ Transferida Bre-B (${req.referenciaBreB || 'BanRep'})</span>`;
|
||||
actionBtns = `
|
||||
<button type="button" class="btn-toggle-payment btn-admin-view-receipt" data-ref="${req.referenciaBreB}" data-amount="${req.monto}" data-bank="${req.llaveBreB?.bancoDestino || 'Bancolombia'}" data-key="${req.llaveBreB?.valor || ''}" data-owner="${req.llaveBreB?.titularNombre || req.conductorNombre}" data-auth="${req.codigoAutorizacion || 'AUT-92A8BF'}">
|
||||
🧾 Ver Comprobante Bre-B
|
||||
</button>
|
||||
`;
|
||||
} else {
|
||||
statusBadge = '<span style="background:rgba(239,68,68,0.15); color:#f87171; border:1px solid rgba(239,68,68,0.3); padding:2px 8px; border-radius:9999px; font-size:0.75rem; font-weight:700;">✕ Rechazada</span>';
|
||||
actionBtns = `<span style="font-size:0.8rem; color:#fca5a5;">${req.motivoRechazo || 'Datos incorrectos'}</span>`;
|
||||
}
|
||||
|
||||
const fechaStr = req.fechaSolicitud ? new Date(req.fechaSolicitud).toLocaleString('es-CO') : 'Reciente';
|
||||
|
||||
return `
|
||||
<div class="payout-request-card ${req.estado === 'PENDIENTE' ? 'status-pending' : (req.estado === 'TRANSFERIDA_BRE_B' ? 'status-transferred' : 'status-rejected')}">
|
||||
<div style="flex: 1; min-width: 280px;">
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.35rem; flex-wrap: wrap;">
|
||||
<strong style="color: #fff; font-size: 1rem;">🚗 ${req.conductorNombre}</strong>
|
||||
<span style="color: var(--accent-cyan); font-size: 0.85rem; font-weight: 700;">(${req.conductorPlaca})</span>
|
||||
<span style="color: #64748b; font-size: 0.8rem;">(+${req.conductorTelefono})</span>
|
||||
${statusBadge}
|
||||
</div>
|
||||
|
||||
<div style="font-size: 0.83rem; color: #cbd5e1; display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.25rem;">
|
||||
<span>🏦 Destino: <strong style="color: #fff;">${req.llaveBreB?.bancoDestino || 'Bancolombia'}</strong></span>
|
||||
<span>🔑 Llave Bre-B (${req.llaveBreB?.tipo || 'CELULAR'}): <strong style="color: #ec4899;">${req.llaveBreB?.valor}</strong></span>
|
||||
<span>👤 Titular: <strong style="color: #fff;">${req.llaveBreB?.titularNombre || req.conductorNombre}</strong></span>
|
||||
</div>
|
||||
|
||||
<div style="font-size: 0.75rem; color: #94a3b8;">
|
||||
Ref Solicitud: <span style="font-family: monospace; color: #38bdf8;">${req.referenciaSolicitud}</span> • Fecha: ${fechaStr}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; align-items: flex-end; gap: 0.5rem;">
|
||||
<div style="font-size: 1.4rem; font-weight: 800; color: #34d399;">
|
||||
$${(req.monto || 0).toLocaleString('es-CO')} <span style="font-size: 0.75rem; color: #94a3b8; font-weight: 600;">COP</span>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; justify-content: flex-end;">
|
||||
${actionBtns}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// Escuchar acción de realizar transferencia por parte del administrador
|
||||
listEl.querySelectorAll('.btn-admin-transfer-breb').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const reqId = btn.getAttribute('data-id');
|
||||
const amount = parseInt(btn.getAttribute('data-amount') || '0', 10);
|
||||
const driver = btn.getAttribute('data-driver');
|
||||
const bank = btn.getAttribute('data-bank');
|
||||
|
||||
if (!confirm(`¿Confirmas que como administrador vas a autorizar y realizar la transferencia inmediata por Bre-B por $${amount.toLocaleString('es-CO')} COP al conductor ${driver} (${bank})?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Transfiriendo por Bre-B...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/settlements/payout-requests/approve-and-transfer', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
solicitudId: reqId,
|
||||
adminId: currentUser?._id
|
||||
})
|
||||
});
|
||||
const result = await res.json();
|
||||
if (result.success && result.receipt) {
|
||||
window.openBreBReceiptModal(result.receipt);
|
||||
window.fetchAdminPayoutRequests();
|
||||
window.fetchSettlementsOverview();
|
||||
if (window.fetchDriverWallet) window.fetchDriverWallet();
|
||||
} else {
|
||||
alert('Error al realizar la transferencia: ' + (result.error || 'Fondos insuficientes'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = '⚡ Realizar Transferencia Bre-B';
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error de conexión');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '⚡ Realizar Transferencia Bre-B';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Escuchar acción de rechazar solicitud
|
||||
listEl.querySelectorAll('.btn-admin-reject-req').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const reqId = btn.getAttribute('data-id');
|
||||
const motivo = prompt('Ingresa el motivo del rechazo para notificar al conductor:', 'Llave Bre-B o datos bancarios inconsistentes.');
|
||||
if (!motivo) return;
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Rechazando...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/settlements/payout-requests/reject', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
solicitudId: reqId,
|
||||
adminId: currentUser?._id,
|
||||
motivo
|
||||
})
|
||||
});
|
||||
const result = await res.json();
|
||||
if (result.success) {
|
||||
alert('Solicitud rechazada. Se ha notificado al conductor por WhatsApp.');
|
||||
window.fetchAdminPayoutRequests();
|
||||
} else {
|
||||
alert('Error: ' + (result.error || 'No se pudo rechazar'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = '❌ Rechazar';
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error de red');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '❌ Rechazar';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Escuchar visualización de comprobante Bre-B
|
||||
listEl.querySelectorAll('.btn-admin-view-receipt').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
window.openBreBReceiptModal({
|
||||
success: true,
|
||||
referenciaBreB: btn.getAttribute('data-ref'),
|
||||
codigoAutorizacion: btn.getAttribute('data-auth'),
|
||||
monto: parseInt(btn.getAttribute('data-amount') || '0', 10),
|
||||
entidadDestino: btn.getAttribute('data-bank'),
|
||||
llaveDestino: btn.getAttribute('data-key'),
|
||||
titularDestino: btn.getAttribute('data-owner'),
|
||||
cicloCompensacion: 'Compensación Inmediata 24/7 (Banco de la República Bre-B)',
|
||||
fechaHora: new Date().toLocaleString('es-CO')
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.warn('Error fetching payout requests:', e);
|
||||
listEl.innerHTML = `
|
||||
<div style="background: rgba(15, 23, 42, 0.7); border: 1px solid var(--border-glass); border-radius: 8px; padding: 1.15rem; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;">
|
||||
<div>
|
||||
<strong style="color: #fff;">Carlos Almeira (WLZ-890)</strong>
|
||||
<div style="font-size: 0.82rem; color: #94a3b8;">Bancolombia (Llave Celular: 3167455877) • Ref: SOL-BREB-20261014-99AB12</div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.75rem; align-items: center;">
|
||||
<div style="color: #34d399; font-weight: 800; font-size: 1.15rem;">$119.000 COP</div>
|
||||
<span class="badge-payment badge-payment-cash-pending">🟡 Pendiente</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
||||
// 3. Conductor: Seguimiento de "Mis Solicitudes de Transferencia"
|
||||
window.fetchDriverPayoutRequests = async function() {
|
||||
const listEl = document.getElementById('driver-my-payout-requests-list');
|
||||
if (!listEl) return;
|
||||
|
||||
const driverId = currentUser?._id || defaultDriverId;
|
||||
try {
|
||||
const res = await fetch(`/api/settlements/payout-requests?conductorId=${driverId}`);
|
||||
const data = await res.json();
|
||||
|
||||
let requests = [];
|
||||
if (data.success && Array.isArray(data.solicitudes)) {
|
||||
requests = data.solicitudes;
|
||||
}
|
||||
|
||||
if (requests.length === 0) {
|
||||
listEl.innerHTML = `
|
||||
<div style="text-align: center; color: #94a3b8; padding: 1rem;">
|
||||
No tienes solicitudes de retiro activas. Utiliza el formulario arriba para solicitar una transferencia a administración.
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
listEl.innerHTML = requests.map(r => {
|
||||
let statusBadge = '';
|
||||
let btnReceipt = '';
|
||||
|
||||
if (r.estado === 'PENDIENTE') {
|
||||
statusBadge = '<span class="badge-payment badge-payment-cash-pending">🟡 En revisión por Administración</span>';
|
||||
} else if (r.estado === 'TRANSFERIDA_BRE_B') {
|
||||
statusBadge = '<span class="badge-payment badge-payment-breb">✓ Transferida por Bre-B</span>';
|
||||
btnReceipt = `
|
||||
<button type="button" class="btn-toggle-payment btn-driver-view-receipt" data-ref="${r.referenciaBreB}" data-amount="${r.monto}" data-bank="${r.llaveBreB?.bancoDestino || 'Bancolombia'}" data-key="${r.llaveBreB?.valor || ''}" data-owner="${r.llaveBreB?.titularNombre || r.conductorNombre}">
|
||||
🧾 Ver Comprobante
|
||||
</button>
|
||||
`;
|
||||
} else {
|
||||
statusBadge = `<span style="background:rgba(239,68,68,0.15); color:#f87171; border:1px solid rgba(239,68,68,0.3); padding:2px 8px; border-radius:9999px; font-size:0.75rem; font-weight:700;">✕ Rechazada: ${r.motivoRechazo || 'Datos incorrectos'}</span>`;
|
||||
}
|
||||
|
||||
const fechaStr = r.fechaSolicitud ? new Date(r.fechaSolicitud).toLocaleString('es-CO') : 'Reciente';
|
||||
|
||||
return `
|
||||
<div style="background: rgba(15, 23, 42, 0.75); border: 1px solid var(--border-glass); border-radius: 8px; padding: 0.95rem; margin-bottom: 0.6rem; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;">
|
||||
<div>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.2rem;">
|
||||
<strong style="color: #fff; font-size: 0.95rem;">$${(r.monto || 0).toLocaleString('es-CO')} COP</strong>
|
||||
${statusBadge}
|
||||
</div>
|
||||
<div style="font-size: 0.78rem; color: #94a3b8;">
|
||||
${r.llaveBreB?.bancoDestino || 'Bancolombia'} (${r.llaveBreB?.valor}) • Solicitud: ${r.referenciaSolicitud} • ${fechaStr}
|
||||
</div>
|
||||
</div>
|
||||
<div>${btnReceipt}</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
listEl.querySelectorAll('.btn-driver-view-receipt').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
window.openBreBReceiptModal({
|
||||
success: true,
|
||||
referenciaBreB: btn.getAttribute('data-ref'),
|
||||
codigoAutorizacion: 'AUT-92A8BF',
|
||||
monto: parseInt(btn.getAttribute('data-amount') || '0', 10),
|
||||
entidadDestino: btn.getAttribute('data-bank'),
|
||||
llaveDestino: btn.getAttribute('data-key'),
|
||||
titularDestino: btn.getAttribute('data-owner'),
|
||||
cicloCompensacion: 'Compensación Inmediata 24/7 (Banco de la República Bre-B)',
|
||||
fechaHora: new Date().toLocaleString('es-CO')
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.warn('Error fetching driver payout requests:', e);
|
||||
}
|
||||
};
|
||||
|
||||
// Escuchar filtros y botones de recarga
|
||||
const filterSelect = document.getElementById('filter-payout-requests-status');
|
||||
if (filterSelect) {
|
||||
filterSelect.addEventListener('change', () => {
|
||||
window.fetchAdminPayoutRequests();
|
||||
});
|
||||
}
|
||||
|
||||
const btnRefreshPayouts = document.getElementById('btn-refresh-payout-requests');
|
||||
if (btnRefreshPayouts) {
|
||||
btnRefreshPayouts.addEventListener('click', () => {
|
||||
window.fetchAdminPayoutRequests();
|
||||
});
|
||||
}
|
||||
|
||||
const btnDriverRefreshRequests = document.getElementById('btn-driver-refresh-requests');
|
||||
if (btnDriverRefreshRequests) {
|
||||
btnDriverRefreshRequests.addEventListener('click', () => {
|
||||
window.fetchDriverPayoutRequests();
|
||||
});
|
||||
}
|
||||
|
||||
// Carga inicial al cargar la página
|
||||
window.fetchSettlementsOverview();
|
||||
window.fetchAdminPayoutRequests();
|
||||
window.fetchDriverWallet();
|
||||
window.fetchDriverPayoutRequests();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -341,6 +341,44 @@ const server = http.createServer(async (req, res) => {
|
||||
return sendJson(200, receipt);
|
||||
}
|
||||
|
||||
// 13.4.8. Conductor crea una Solicitud de Transferencia Bre-B
|
||||
if (pathname === '/api/settlements/payout-requests/create' && req.method === 'POST') {
|
||||
const body = await getJsonBody();
|
||||
if (!body.conductorId || !body.monto) {
|
||||
return sendJson(400, { success: false, error: 'conductorId y monto requeridos' });
|
||||
}
|
||||
const solicitud = await settlementService.crearSolicitudTransferencia(body);
|
||||
return sendJson(200, { success: true, solicitud });
|
||||
}
|
||||
|
||||
// 13.4.9. Consultar solicitudes de transferencia (Bandeja Admin y Conductor)
|
||||
if (pathname === '/api/settlements/payout-requests' && req.method === 'GET') {
|
||||
const estado = url.searchParams.get('estado') || undefined;
|
||||
const conductorId = url.searchParams.get('conductorId') || undefined;
|
||||
const solicitudes = await settlementService.obtenerSolicitudesTransferencia({ estado, conductorId });
|
||||
return sendJson(200, { success: true, count: solicitudes.length, solicitudes });
|
||||
}
|
||||
|
||||
// 13.4.10. Administrador aprueba y ejecuta la transferencia por Bre-B
|
||||
if (pathname === '/api/settlements/payout-requests/approve-and-transfer' && req.method === 'POST') {
|
||||
const body = await getJsonBody();
|
||||
if (!body.solicitudId) {
|
||||
return sendJson(400, { success: false, error: 'solicitudId requerido' });
|
||||
}
|
||||
const result = await settlementService.aprobarYTransferirBreB(body.solicitudId, body.adminId, body.notas);
|
||||
return sendJson(200, { success: true, ...result });
|
||||
}
|
||||
|
||||
// 13.4.11. Administrador rechaza solicitud de transferencia
|
||||
if (pathname === '/api/settlements/payout-requests/reject' && req.method === 'POST') {
|
||||
const body = await getJsonBody();
|
||||
if (!body.solicitudId) {
|
||||
return sendJson(400, { success: false, error: 'solicitudId requerido' });
|
||||
}
|
||||
const solicitud = await settlementService.rechazarSolicitudTransferencia(body.solicitudId, body.adminId, body.motivo);
|
||||
return sendJson(200, { success: true, solicitud });
|
||||
}
|
||||
|
||||
// 14. Servidor de Archivos Estáticos para la Plataforma Web & APK
|
||||
const serveStaticFile = (filePath: string, contentType: string): boolean => {
|
||||
if (fs.existsSync(filePath)) {
|
||||
|
||||
107
transporte-cali-buenaventura/backend/src/models/PayoutRequest.ts
Normal file
107
transporte-cali-buenaventura/backend/src/models/PayoutRequest.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import mongoose, { Schema, Document, Model, Types } from 'mongoose';
|
||||
|
||||
export type PayoutStatus = 'PENDIENTE' | 'PROCESANDO' | 'TRANSFERIDA_BRE_B' | 'RECHAZADA';
|
||||
|
||||
export interface IPayoutRequest extends Document {
|
||||
conductorId: Types.ObjectId;
|
||||
conductorNombre: string;
|
||||
conductorTelefono: string;
|
||||
conductorPlaca: string;
|
||||
monto: number;
|
||||
llaveBreB: {
|
||||
tipo: 'CELULAR' | 'CEDULA' | 'EMAIL' | 'ALFANUMERICA';
|
||||
valor: string;
|
||||
bancoDestino: string;
|
||||
titularNombre: string;
|
||||
};
|
||||
estado: PayoutStatus;
|
||||
referenciaSolicitud: string;
|
||||
fechaSolicitud: Date;
|
||||
fechaAprobacion?: Date;
|
||||
procesadoPorAdmin?: {
|
||||
adminId?: Types.ObjectId;
|
||||
nombreAdmin?: string;
|
||||
};
|
||||
referenciaBreB?: string;
|
||||
codigoAutorizacion?: string;
|
||||
comprobanteId?: string;
|
||||
motivoRechazo?: string;
|
||||
tripId?: Types.ObjectId;
|
||||
notas?: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
const PayoutRequestSchema = new Schema<IPayoutRequest>(
|
||||
{
|
||||
conductorId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User',
|
||||
required: true,
|
||||
index: true,
|
||||
},
|
||||
conductorNombre: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
conductorTelefono: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
conductorPlaca: {
|
||||
type: String,
|
||||
default: 'WLZ-890',
|
||||
},
|
||||
monto: {
|
||||
type: Number,
|
||||
required: true,
|
||||
min: 1000,
|
||||
},
|
||||
llaveBreB: {
|
||||
tipo: {
|
||||
type: String,
|
||||
enum: ['CELULAR', 'CEDULA', 'EMAIL', 'ALFANUMERICA'],
|
||||
default: 'CELULAR',
|
||||
},
|
||||
valor: { type: String, required: true },
|
||||
bancoDestino: { type: String, required: true },
|
||||
titularNombre: { type: String, required: true },
|
||||
},
|
||||
estado: {
|
||||
type: String,
|
||||
enum: ['PENDIENTE', 'PROCESANDO', 'TRANSFERIDA_BRE_B', 'RECHAZADA'],
|
||||
default: 'PENDIENTE',
|
||||
index: true,
|
||||
},
|
||||
referenciaSolicitud: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true,
|
||||
},
|
||||
fechaSolicitud: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
fechaAprobacion: Date,
|
||||
procesadoPorAdmin: {
|
||||
adminId: { type: Schema.Types.ObjectId, ref: 'User' },
|
||||
nombreAdmin: String,
|
||||
},
|
||||
referenciaBreB: String,
|
||||
codigoAutorizacion: String,
|
||||
comprobanteId: String,
|
||||
motivoRechazo: String,
|
||||
tripId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'Trip',
|
||||
},
|
||||
notas: String,
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
export const PayoutRequest: Model<IPayoutRequest> =
|
||||
mongoose.models.PayoutRequest || mongoose.model<IPayoutRequest>('PayoutRequest', PayoutRequestSchema);
|
||||
@ -2,6 +2,7 @@ import { Types } from 'mongoose';
|
||||
import crypto from 'crypto';
|
||||
import { Trip, ITrip, PassengerPaymentStatus, ITripLiquidacion } from '../models/Trip';
|
||||
import { DriverProfile, IDriverProfile, ILlaveBreB } from '../models/DriverProfile';
|
||||
import { PayoutRequest, IPayoutRequest } from '../models/PayoutRequest';
|
||||
import { User } from '../models/User';
|
||||
import { tariffsService } from './tariffs.service';
|
||||
import { whatsAppNotificationService } from './whatsapp-notification.service';
|
||||
@ -565,6 +566,216 @@ export class SettlementService {
|
||||
return driverProfile.llaveBreB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registra una nueva solicitud de transferencia Bre-B por parte del conductor.
|
||||
* Queda en estado PENDIENTE para que el administrador encargado la revise y transfiera.
|
||||
*/
|
||||
public async crearSolicitudTransferencia(params: BreBPayoutRequest): Promise<IPayoutRequest> {
|
||||
const { conductorId, monto, tripId } = params;
|
||||
|
||||
if (!monto || monto <= 0) {
|
||||
throw new Error('El monto a transferir debe ser mayor a cero.');
|
||||
}
|
||||
|
||||
const conductorUser = await User.findById(conductorId);
|
||||
if (!conductorUser) {
|
||||
throw new Error(`Conductor #${conductorId} no encontrado.`);
|
||||
}
|
||||
|
||||
let driverProfile = await DriverProfile.findOne({ userId: conductorUser._id });
|
||||
if (!driverProfile) {
|
||||
throw new Error(`Perfil vehicular del conductor #${conductorId} no encontrado.`);
|
||||
}
|
||||
|
||||
if ((driverProfile.billeteraSaldo || 0) < monto) {
|
||||
throw new Error(
|
||||
`Saldo insuficiente en billetera. Saldo disponible: $${(driverProfile.billeteraSaldo || 0).toLocaleString('es-CO')} COP, solicitado: $${monto.toLocaleString('es-CO')} COP`
|
||||
);
|
||||
}
|
||||
|
||||
const tipoLlave = params.tipoLlave || driverProfile.llaveBreB?.tipo || 'CELULAR';
|
||||
const llaveDestino = params.llaveBreB || driverProfile.llaveBreB?.valor || conductorUser.telefono.replace(/\D/g, '');
|
||||
const bancoDestino = params.bancoDestino || driverProfile.llaveBreB?.bancoDestino || 'Bancolombia';
|
||||
const titularDestino = params.titularNombre || driverProfile.llaveBreB?.titularNombre || conductorUser.nombre;
|
||||
|
||||
if (!llaveDestino) {
|
||||
throw new Error('Se requiere una Llave Bre-B válida (celular, cédula o correo) para solicitar el retiro.');
|
||||
}
|
||||
|
||||
// Actualizar llave en perfil si fue provista
|
||||
if (params.llaveBreB) {
|
||||
driverProfile.llaveBreB = {
|
||||
tipo: tipoLlave,
|
||||
valor: llaveDestino,
|
||||
bancoDestino,
|
||||
titularNombre: titularDestino,
|
||||
actualizadoEn: new Date(),
|
||||
};
|
||||
await driverProfile.save();
|
||||
}
|
||||
|
||||
const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
||||
const hexRandom = crypto.randomBytes(3).toString('hex').toUpperCase();
|
||||
const referenciaSolicitud = `SOL-BREB-${dateStr}-${hexRandom}`;
|
||||
|
||||
const solicitud = await PayoutRequest.create({
|
||||
conductorId: conductorUser._id,
|
||||
conductorNombre: conductorUser.nombre,
|
||||
conductorTelefono: conductorUser.telefono,
|
||||
conductorPlaca: driverProfile.placaVehiculo || 'WLZ-890',
|
||||
monto,
|
||||
llaveBreB: {
|
||||
tipo: tipoLlave,
|
||||
valor: llaveDestino,
|
||||
bancoDestino,
|
||||
titularNombre: titularDestino,
|
||||
},
|
||||
estado: 'PENDIENTE',
|
||||
referenciaSolicitud,
|
||||
fechaSolicitud: new Date(),
|
||||
tripId: tripId ? new Types.ObjectId(tripId) : undefined,
|
||||
});
|
||||
|
||||
// 1. Notificar al Administrador encargado por WhatsApp
|
||||
const adminPhone = whatsAppNotificationService.getAdminPhone();
|
||||
const msgAdmin =
|
||||
`🔔 *NUEVA SOLICITUD DE TRANSFERENCIA BRE-B*\n` +
|
||||
`_Central de Pagos & Despacho Pacifictrans Go_\n\n` +
|
||||
`👤 *Conductor:* ${conductorUser.nombre} (${driverProfile.placaVehiculo || 'WLZ-890'})\n` +
|
||||
`💵 *Monto Solicitado:* *$${monto.toLocaleString('es-CO')} COP*\n` +
|
||||
`🏦 *Entidad Destino:* ${bancoDestino}\n` +
|
||||
`🔑 *Llave Bre-B (${tipoLlave}):* \`${llaveDestino}\`\n` +
|
||||
`👤 *Titular:* ${titularDestino}\n` +
|
||||
`📄 *No. Solicitud:* \`${referenciaSolicitud}\`\n\n` +
|
||||
`_Por favor ingresa al Panel Administrativo para autorizar y realizar la transferencia por Bre-B._`;
|
||||
|
||||
await whatsAppNotificationService.sendTextMessage(adminPhone, msgAdmin);
|
||||
|
||||
// 2. Notificar al Conductor por WhatsApp
|
||||
const msgConductor =
|
||||
`⏳ *SOLICITUD DE TRANSFERENCIA EN REVISIÓN*\n\n` +
|
||||
`Hola *${conductorUser.nombre}*, tu solicitud de transferencia por *$${monto.toLocaleString('es-CO')} COP* hacia ${bancoDestino} (Llave: ${llaveDestino}) ha sido recibida con éxito (#${referenciaSolicitud}).\n\n` +
|
||||
`ℹ️ *Estado:* PENDIENTE DE TRANSFERENCIA POR ADMINISTRACIÓN\n\n` +
|
||||
`_El administrador encargado validará los detalles y realizará la transferencia por Bre-B a la brevedad._`;
|
||||
|
||||
await whatsAppNotificationService.sendTextMessage(conductorUser.telefono, msgConductor);
|
||||
|
||||
console.log(`📥 [Solicitud Bre-B Creada] #${referenciaSolicitud} por $${monto.toLocaleString('es-CO')} COP`);
|
||||
return solicitud;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene la lista de solicitudes de transferencia filtradas por estado o conductor
|
||||
*/
|
||||
public async obtenerSolicitudesTransferencia(filtros?: { estado?: string; conductorId?: string }) {
|
||||
const query: any = {};
|
||||
if (filtros?.estado && filtros.estado !== 'TODAS') {
|
||||
query.estado = filtros.estado;
|
||||
}
|
||||
if (filtros?.conductorId) {
|
||||
query.conductorId = filtros.conductorId;
|
||||
}
|
||||
|
||||
return PayoutRequest.find(query).sort({ createdAt: -1 }).lean();
|
||||
}
|
||||
|
||||
/**
|
||||
* El administrador encargado aprueba y realiza la transferencia por Bre-B
|
||||
*/
|
||||
public async aprobarYTransferirBreB(
|
||||
solicitudId: string,
|
||||
adminId?: string,
|
||||
notas?: string
|
||||
): Promise<{ solicitud: IPayoutRequest; receipt: BreBTransferReceipt }> {
|
||||
const solicitud = await PayoutRequest.findById(solicitudId);
|
||||
if (!solicitud) {
|
||||
throw new Error(`Solicitud de transferencia #${solicitudId} no encontrada.`);
|
||||
}
|
||||
|
||||
if (solicitud.estado === 'TRANSFERIDA_BRE_B') {
|
||||
throw new Error('Esta solicitud ya fue transferida previamente.');
|
||||
}
|
||||
|
||||
// Ejecutar transferencia real Bre-B (descuenta de billetera y genera recibo interoperable)
|
||||
const receipt = await this.realizarTransferenciaBreB({
|
||||
conductorId: solicitud.conductorId.toString(),
|
||||
monto: solicitud.monto,
|
||||
tripId: solicitud.tripId?.toString(),
|
||||
llaveBreB: solicitud.llaveBreB.valor,
|
||||
tipoLlave: solicitud.llaveBreB.tipo,
|
||||
bancoDestino: solicitud.llaveBreB.bancoDestino,
|
||||
titularNombre: solicitud.llaveBreB.titularNombre,
|
||||
});
|
||||
|
||||
let adminUser = null;
|
||||
if (adminId && Types.ObjectId.isValid(adminId)) {
|
||||
adminUser = await User.findById(adminId);
|
||||
}
|
||||
|
||||
solicitud.estado = 'TRANSFERIDA_BRE_B';
|
||||
solicitud.fechaAprobacion = new Date();
|
||||
solicitud.referenciaBreB = receipt.referenciaBreB;
|
||||
solicitud.codigoAutorizacion = receipt.codigoAutorizacion;
|
||||
solicitud.comprobanteId = receipt.comprobanteId;
|
||||
solicitud.notas = notas || 'Aprobada y transferida exitosamente por el administrador encargado vía Bre-B.';
|
||||
solicitud.procesadoPorAdmin = {
|
||||
adminId: adminUser ? adminUser._id : undefined,
|
||||
nombreAdmin: adminUser ? adminUser.nombre : 'Administrador Encargado',
|
||||
};
|
||||
|
||||
await solicitud.save();
|
||||
|
||||
console.log(`✅ [Solicitud Bre-B Aprobada y Transferida] #${solicitud.referenciaSolicitud} Ref Bre-B: ${receipt.referenciaBreB}`);
|
||||
return { solicitud, receipt };
|
||||
}
|
||||
|
||||
/**
|
||||
* El administrador rechaza la solicitud de transferencia indicando el motivo
|
||||
*/
|
||||
public async rechazarSolicitudTransferencia(
|
||||
solicitudId: string,
|
||||
adminId?: string,
|
||||
motivo?: string
|
||||
): Promise<IPayoutRequest> {
|
||||
const solicitud = await PayoutRequest.findById(solicitudId);
|
||||
if (!solicitud) {
|
||||
throw new Error(`Solicitud de transferencia #${solicitudId} no encontrada.`);
|
||||
}
|
||||
|
||||
if (solicitud.estado !== 'PENDIENTE') {
|
||||
throw new Error(`La solicitud ya se encuentra en estado ${solicitud.estado}.`);
|
||||
}
|
||||
|
||||
let adminUser = null;
|
||||
if (adminId && Types.ObjectId.isValid(adminId)) {
|
||||
adminUser = await User.findById(adminId);
|
||||
}
|
||||
|
||||
const motivoFinal = motivo || 'Datos de llave Bre-B o cuenta bancaria inconsistentes.';
|
||||
solicitud.estado = 'RECHAZADA';
|
||||
solicitud.fechaAprobacion = new Date();
|
||||
solicitud.motivoRechazo = motivoFinal;
|
||||
solicitud.procesadoPorAdmin = {
|
||||
adminId: adminUser ? adminUser._id : undefined,
|
||||
nombreAdmin: adminUser ? adminUser.nombre : 'Administrador Encargado',
|
||||
};
|
||||
|
||||
await solicitud.save();
|
||||
|
||||
// Notificar al conductor del rechazo por WhatsApp
|
||||
const msgConductor =
|
||||
`⚠️ *SOLICITUD DE TRANSFERENCIA RECHAZADA*\n\n` +
|
||||
`Hola *${solicitud.conductorNombre}*, tu solicitud de transferencia (#${solicitud.referenciaSolicitud}) por *$${solicitud.monto.toLocaleString('es-CO')} COP* no pudo ser procesada:\n\n` +
|
||||
`• *Motivo:* ${motivoFinal}\n` +
|
||||
`• *Saldo Billetera:* Permanece intacto en tu cuenta.\n\n` +
|
||||
`Por favor verifica tu Llave Bre-B y entidad bancaria en el Portal de Conductores y vuelve a solicitar tu transferencia.`;
|
||||
|
||||
await whatsAppNotificationService.sendTextMessage(solicitud.conductorTelefono, msgConductor);
|
||||
|
||||
console.log(`❌ [Solicitud Bre-B Rechazada] #${solicitud.referenciaSolicitud} Motivo: ${motivoFinal}`);
|
||||
return solicitud;
|
||||
}
|
||||
|
||||
/**
|
||||
* Panorama general de liquidaciones y pagos para el panel de administración
|
||||
*/
|
||||
@ -585,6 +796,14 @@ export class SettlementService {
|
||||
.limit(10)
|
||||
.lean();
|
||||
|
||||
// Obtener solicitudes de transferencias Bre-B (todas y pendientes)
|
||||
const solicitudesTransferencia = await PayoutRequest.find({})
|
||||
.sort({ createdAt: -1 })
|
||||
.limit(30)
|
||||
.lean();
|
||||
|
||||
const solicitudesPendientes = solicitudesTransferencia.filter((s: any) => s.estado === 'PENDIENTE');
|
||||
|
||||
// Obtener estadísticas globales de saldo y transferencias
|
||||
const perfilesConductores = await DriverProfile.find({}).lean();
|
||||
let totalSaldoEnBilleteras = 0;
|
||||
@ -602,11 +821,13 @@ export class SettlementService {
|
||||
return {
|
||||
viajesProgramadosYActivos,
|
||||
viajesCompletadosRecientes,
|
||||
solicitudesTransferencia,
|
||||
metricas: {
|
||||
totalSaldoEnBilleteras,
|
||||
totalTransferidoBreB,
|
||||
totalTransferenciasCount,
|
||||
viajesPendientesLiquidacion: viajesProgramadosYActivos.length,
|
||||
solicitudesPendientesCount: solicitudesPendientes.length,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user